[Mono-dev] The future of Mono's profiler API

Alex Rønne Petersen alex at alexrp.com
Tue Jun 20 16:44:43 UTC 2017

(Re-sending since my last email didn't go to the list for some reason.)

Hey Greg,

One possibility is that we could use a new entry point name for the
new version of the profiler API. That way, if we detect that a
profiler module has the old entry point name, we could print an error
and refuse to load it, rather than relying on the dynamic linker to
throw errors when mono_profiler_install_* functions are invoked by the
profiler modules, Does this sound reasonable?

Regarding dynamic enter/leave hooking, I agree that it would be a
super cool feature to have. Unfortunately, it would require a
significant amount of work on the JIT side as re-JITing code is a hard
problem to solve reliably on most architectures. There are other
reasons I could see re-JITing being a useful feature to have (e.g.
incremental optimization based on profiling), but I can't really say
definitively whether we'll ever do it.


On Tue, Jun 20, 2017 at 11:04 AM, Greg Young <gregoryyoung1 at gmail.com> wrote:
> So the possible issue with option #2 that I see is in distribution for
> 3rd party profilers like privateeye. I don't see this as a huge issue
> but it might be useful to at least be able to load the old API still
> (not work) so the old version of the profiler could realize it is on a
> newer version and exit (or the runtime could recognize this and give a
> reasonable error message.
> Also a wonderful feature would be the ability to dynamically hook
> mono_profiler_install_enter_leave (pe_method_enter, pe_method_leave);
> As it is quite expensive. I imagine though this would be non-trivial.
> Greg
> On Tue, Jun 20, 2017 at 9:50 AM, Alex Rønne Petersen <alex at alexrp.com> wrote:
>> Hello everyone,
>> As part of our ongoing effort to make Mono's log profiler useful for
>> more scenarios, I'm planning to make it possible to interact with the
>> profiler at runtime - you can enable, disable, and tweak specific
>> profiler features in certain sections of your application, so you get
>> exactly the data that you're interested in. In order to do this, the
>> log profiler needs to be able to change its event flags and installed
>> callbacks dynamically at runtime.
>> # The Problem
>> It is currently impossible for any profiler to reliably change its
>> setup at runtime because Mono's profiler API (metadata/profiler.h)
>> only allows modifying the most recently installed profiler. Mono
>> supports having multiple profilers active at the same time, and we do
>> in fact use this feature in the Xamarin platform products. There's no
>> way around it: We need to rethink the profiler API. All functions must
>> take an explicit MonoProfiler* parameter.
>> This isn't the only problem with the current API.
>> Another issue is that multiple callbacks are installed through the
>> same function. For example, mono_profiler_install_exception installs
>> callbacks for thrown exceptions, exceptional method exits, and
>> exception clauses. When I had to add an extra parameter to the
>> exception clause callback recently, I introduced
>> mono_profiler_install_exception_clause for version 2 of that callback.
>> This means that new code will pass NULL to the third parameter of
>> mono_profiler_install_exception from now on. This just adds confusion.
>> It would be much clearer if the old function had been called
>> mono_profiler_install_exception_clause and I'd just been able to
>> introduce a mono_profiler_install_exception_v2 function. New users of
>> the API will likely wonder why mono_profiler_install_exception_clause
>> isn't part of mono_profiler_install_exception since the API has a
>> precedent of bundling related callbacks into the same installation
>> function.
>> There are also multiple callbacks in the API that aren't guarded by
>> event flags. For example, the code buffer callbacks should logically
>> be guarded by MONO_PROFILE_JIT_COMPILATION, but that's a change we
>> can't make now as it would be breaking. Another curiosity is that the
>> GC handle callbacks are guarded by MONO_PROFILE_GC_ROOTS even though
>> it's entirely likely that someone would be interested in GC handles
>> but not GC roots (see: Alan McGovern's GC handle profiler). It's also
>> odd that the exceptional method exit callback is guarded by
>> MONO_PROFILE_EXCEPTIONS when in fact most uses of this callback have
>> little to do with profiling exceptions and everything to do with
>> keeping track of method entries/exits as with the normal method
>> enter/exit callbacks (which are guarded by MONO_PROFILE_ENTER_LEAVE).
>> We also have callbacks that serve no actual purpose, and never will.
>> For example, the notion of a 'class unload' does not exist in the Mono
>> runtime. Never has, probably never will. Entire images are unloaded at
>> once, so this callback is literally never invoked. I'd actually say
>> having that callback there adds negative value to the API. The
>> managed/native transition callback was never implemented, either.
>> Finally, some features in the API have not been maintained or tested
>> for years. The call chain sampling API is a great example of this.
>> Another example: Did you know that the profiler API supports two
>> coverage modes which are mutually exclusive? You might think that
>> MONO_PROFILE_COVERAGE is the flag that you're supposed to be using.
>> Nope; it's MONO_PROFILE_INS_COVERAGE. The former is implemented in a
>> very platform-specific manner that has resulted in it not being
>> maintained, tested, or ported fully to new platforms.
>> In short, the current profiler API is pretty bad. We need a new API.
>> Of course, the elephant in the room is backwards compatibility. The
>> question is: Do we introduce a new profiler API and make the old one
>> 'simply' call the new one? Or do we just replace the old API entirely,
>> backwards compatibility be damned?
>> # The New Profiler API
>> The new API would not be all that different from the old one. The main
>> changes would be:
>> 1. All functions in the API take an explicit MonoProfiler* parameter.
>> 2. Callbacks can be changed safely at runtime.
>> 3. One installation function installs exactly one callback.
>> 4. You will no longer need to specify event flags.
>> 5. Unmaintained and unfinished features (see above) will be removed.
>> As an example, old code might look like this:
>> void
>> mono_profiler_startup (const char *args)
>> {
>>     MonoProfiler *prof = malloc (...);
>>     profiler_specific_setup (prof);
>>     mono_profiler_install (prof, my_shutdown_callback);
>>     mono_profiler_install_enter_leave (my_enter_callback, my_leave_callback);
>>     mono_profiler_set_events (MONO_PROFILE_ENTER_LEAVE);
>> }
>> New code would look like this:
>> void
>> mono_profiler_startup (const char *args)
>> {
>>     MonoProfiler *prof = malloc (...);
>>     profiler_specific_setup (prof);
>>     mono_profiler_install (prof);
>>     mono_profiler_set_shutdown_callback (prof, my_shutdown_callback);
>>     mono_profiler_set_enter_callback (prof, my_enter_callback);
>>     mono_profiler_set_leave_callback (prof, my_leave_callback);
>> }
>> We would still use flags internally so we don't slow the runtime down
>> with unnecessary profiler API calls, but that will be completely
>> hidden from users. All a user would have to worry about is (un)setting
>> callbacks, which can be done at any point during an app's lifetime.
>> Transitioning to the new API should be fairly painless. I'd estimate
>> it to take an hour or two at worst for e.g. the log profiler.
>> # Approach One: Backwards Compatibility
>> In this approach, we would introduce a new metadata/profiler-v2.h
>> header. This header would provide the new API and have no dependencies
>> on the old one. The old API would remain in metadata/profiler.h and
>> people's code would continue to compile and work. We would need to
>> bridge the old API to the new one and make sure that it's done in a
>> backwards-compatible way.
>> The advantage here is fairly obvious: Nobody likes having to rewrite
>> their code because the authors of a library decided to change the API,
>> especially if that change doesn't carry an obvious benefit to users,
>> which it could be argued this change wouldn't for most (all?) current
>> users of Mono's profiler API.
>> On the other hand, this is a significant maintenance burden, both in
>> the short and long term. Writing the code to bridge the nonsensical
>> aspects of the old API with the new one would be tricky to say the
>> least. In addition, there's the risk that any change to the new API in
>> the future could break the old API.
>> # Approach Two: Replacing the API
>> In this approach, we replace the old API in metadata/profiler.h with
>> the new one, with zero regard for backwards compatibility. People's
>> code would fail to compile, and old compiled profiler modules would
>> fail to run. In both cases, the failures should be fairly loud - a
>> compiler error, or a dynamic linker error.
>> The advantage of this approach is that it's significantly less effort
>> to implement and maintain. It also avoids any potential confusion for
>> new users of the API, in that there's only one set of functions to
>> use.
>> If we go down this route, all projects that use Mono's profiler API
>> would need to change their code slightly, and people would need to
>> compile separate versions of their profiler modules if they want to
>> support older Mono versions.
>> # My Opinion
>> I'm strongly in favor of the second approach. Frankly, as the person
>> who'll be implementing and maintaining the new API, I don't
>> particularly enjoy the idea of having to also maintain the old one in
>> a backwards compatible fashion. I think there are much better things I
>> could be working on in Mono's profiling infrastructure.
>> I also firmly believe that this is the only time we'll have to do such
>> a drastic breaking change to the profiler API. This isn't a proposal
>> to jump on some fancy new API design fad. Using a mutable global
>> variable as an implicit parameter to an entire API was pretty bad
>> design, even by 2002 standards. Just by passing an explicit
>> MonoProfiler* argument to all API functions, we open ourselves up to
>> much easier, backwards-compatible expansion of the API in the future.
>> Finally, as I mentioned earlier, transitioning to the new API would be
>> very easy, and users would have to do it sooner or later anyway, as we
>> wouldn't want to keep the old API around forever, even in the first
>> approach. Also, in the grand scheme of things, this probably won't
>> affect that many people, unlike breaking changes to the core embedding
>> API.
>> What's everyone's thoughts on this?
>> Regards,
>> Alex
>> _______________________________________________
>> Mono-devel-list mailing list
>> Mono-devel-list at lists.dot.net
>> http://lists.dot.net/mailman/listinfo/mono-devel-list
> --
> Studying for the Turing test

More information about the Mono-devel-list mailing list