-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Profiler changes to enable better support for dynamic event flags changes #3546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
sgen commits look ok |
…rofiling.
This new MANAGED_ALLOCATOR_PROFILER works much like the regular managed
allocator, but has a check at the end before returning:
if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_ALLOCATIONS)) {
mono_profiler_allocation (p);
}
There are two main reasons why we need this:
1. Running with the slow path managed allocator as a means to ensure that all
allocations are reported to the profiler is overkill. It slows down
allocation-heavy programs massively. This matters a lot if a user is doing
sampling at the same time as allocation profiling.
2. We would like to be able to dynamically switch allocation profiling on or
off at runtime. To do so with the old managed allocator setup, we would
have to always use the slow path managed allocator when profiling at all,
even if the user never decides to enable allocation profiling.
Having a variant of the managed allocator that is instrumented with a profiling
check solves both of these issues. It won't be quite as fast as the regular
managed allocator, but the slowdown from the check is only about 2-3%.
It could previously fail to process a newly allocated buffer.
…llocators. Traces only show 6 frames by default, but when the new managed allocator is in use, we need 7 frames to see the full call chain. Also change check_alloc_traces () to skip over any amount of wrapper frames.
The runtime itself should access the mono_profiler_events variable directly.
This has not actually been necessary since allocation reporting was moved into the GC implementations.
* mono_gc_enable_events: This did nothing for SGen and we called it unconditionally for Boehm. So no point in having it around. * mono_gc_enable_alloc_events: The performance difference between checking a static alloc_events variable versus checking mono_profiler_events is marginal at best and doesn't justify the indirection, especially now that we support managed allocators while profiling.
The sampling thread is now always started when profiling, but sits idle if statistical sampling is not enabled.
…untime. This avoids doing calls in the sampling thread all the time.
It was never documented and is no longer functional.
Also refactor the heap walk code a bit.
…API + MONO_RT_EXTERNAL_ONLY. Also make it work with MSVC.
This new API has two goals: 1. Explicitly specify the profiler descriptor in all callback installation functions so that they can be changed at any point, even when multiple profilers are loaded. 2. Make sure that a profiler can change its own event flags at any point without interfering with other profilers that may be loaded. The old APIs have been marked with MONO_DEPRECATED.
…_LEAVE rather than MONO_PROFILE_EXCEPTIONS. This event isn't that useful at profiling exceptions. It's mainly meant to keep track of call depth (for enter/leave profiling purposes) when an exception is thrown.
…s instead of MONO_PROFILE_GC_ROOTS.
These have no value outside of heapshots, and in large apps, tend to contribute significantly to log file bloat.
This variable can be changed at any time by any thread. It makes no sense to assert that it has a particular flag.
|
Ignore the Ok above. This is github being terrible as usual. I was on the per commit view as the whole diff is just too big. |
| mono_native_thread_set_name (mono_native_thread_id_get (), "Main"); | ||
|
|
||
| if (enable_profile) { | ||
| if (mono_profiler_enabled ()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to enable it at arg parsing time?
Can't we simply let mono_profiler_load set it? This reduces the amount of state we expose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I like that way of doing it more.
mono/profiler/proflog.c
Outdated
| InterlockedIncrement (&thread_ends_ctr); | ||
|
|
||
| buffer = ensure_logbuf_inner (buffer, | ||
| thread->buffer = ensure_logbuf_inner (thread->buffer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we backport this one to 4.8?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This plus a few other crash/deadlock fixes in this PR are definitely worth backporting.
| } MonoCounterAgent; | ||
|
|
||
| static MonoCounterAgent* counters; | ||
| static gboolean counters_initialized = FALSE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this check is no longer needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because in the same commit, I moved helper thread initialization to just after counters initialization, and made sure only the helper thread samples counters. So counters will always be initialized when we sample them.
| } | ||
|
|
||
| if (G_UNLIKELY (alloc_events)) | ||
| if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_ALLOCATIONS)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should wrap this into a any macro that says whether a given profiler event it enabled. Including the G_UNLIKELY.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, good point. I completely forgot to apply the G_UNLIKELY to many of the checks.
| #endif | ||
|
|
||
| if (mono_profiler_events & MONO_PROFILE_STATISTICAL) | ||
| if (mono_profiler_enabled ()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a no go change. The products install profilers during regular operations. This would cause a background thread to be created. Worse, that thread always spins, which is a power disaster.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is a really good argument for why we should not be calling mono_profiler_enable in mono_profiler_new/mono_profiler_install, only in mono_profiler_load.
| InterlockedWrite (&sampling_thread_running, 0); | ||
|
|
||
| #ifdef HAVE_CLOCK_NANOSLEEP | ||
| #ifndef PLATFORM_MACOSX |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about iOS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For whatever strange reason, this macro is defined for all Apple platforms. It should be renamed at some point.
| #define MONO_LLVM_INTERNAL | ||
| #endif | ||
|
|
||
| #if HAVE_DEPRECATED |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should remove the check from configure.
| mono_profiler_enable (void) | ||
| { | ||
| if (!profiling_enabled) | ||
| mono_os_mutex_init_recursive (&profiler_mutex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it need to be recursive?
| mono_profiler_install_module (MonoProfileModuleFunc start_load, MonoProfileModuleResult end_load, | ||
| MonoProfileModuleFunc start_unload, MonoProfileModuleFunc end_unload) | ||
| void | ||
| mono_profiler_set_class_cb (MonoProfilerDesc *desc, MonoProfileClassFunc start_load, MonoProfileClassResult end_load, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good chance to document all those undocumented callbacks
| do_heap_walk = TRUE; | ||
|
|
||
| if (do_heap_shot && do_heap_walk) | ||
| mono_profiler_set_event_flags (profiler->desc, mono_profiler_get_event_flags (profiler->desc) | MONO_PROFILE_GC_ROOTS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about mono_profiler_enable_events / mono_profiler_disable_event macros?
| data->friendly_name = g_strdup (friendly_name); | ||
|
|
||
| mono_profiler_appdomain_name (data, data->friendly_name); | ||
| if (mono_profiler_events & MONO_PROFILE_APPDOMAIN_EVENTS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't like this change.
The profiler events are checked anyways on the calls. Plus we're not hinting the compiler that it's a non taken branch.
We should use something like this:
#define GEN_EVT2(EVENT, FUNC, ARG0, ARG1) do { \
if (G_UNLIKELY (mono_profiler_events & MONO_PROFILE_ ## EVENT)) \
mono_profiler_ ## FUNC (ARG0, ARG1); \
} while (0)
#define RAISE_APPDOMAIN_NAME(ARG0, ARG1) GEN_EVT2( APPDOMAIN_EVENTS, appdomain_name, ARG0, ARG1);
…read ID. This was introduced in 928b840.
| if (moved_objects_idx == MOVED_OBJECTS_NUM) { | ||
| mono_profiler_gc_moves (moved_objects, moved_objects_idx); | ||
| moved_objects_idx = 0; | ||
| /* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BrzVlad can you review this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vlad already OK'd the SGen changes here.
mono/profiler/mono-profiler-log.c
Outdated
| // Avoid calling this directly if possible. Use the functions below. | ||
| static void | ||
| safe_send (MonoProfiler *profiler, gboolean if_needed, gboolean threadless) | ||
| send_log_unsafe (MonoProfiler *profiler, gboolean lock, gboolean if_needed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More than one boolean per function is a code smell, It's better to use a single flags argument.
| */ | ||
| if (fd >= FD_SETSIZE) { | ||
| fprintf (stderr, "File descriptor is out of bounds for fd_set: %d\n", fd); | ||
| exit (1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that the profiler aborting the whole process is a good idea. We don't do that in other cases IIRC.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do. For example, if we can't set up the server socket for whatever reason, we exit (1). We also do it for command line syntax errors, e.g. log:sample=,alloc. Whether that's good or bad is arguable, but I'm just following precedent here.
I don't think there's much value in continuing to execute if the profiler is in an unusable state, though. Note that this particular check will only fail at early startup if either the pipe or server socket FD is larger than FD_SETSIZE for some reason.
| continue; | ||
|
|
||
| fprintf (stderr, "Error in mono-profiler-log server: %s", strerror (errno)); | ||
| exit (1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This used to be non fatal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
select can return these errors: EINTR (signal, we handle this one), EBADF (bad FD, indicates a programming error, because the server socket and pipes should not be closed at this point), EINVAL (bad arguments, programming error), ENOMEM (OOM, nothing we can do at that point).
| } | ||
| if ((opt = match_option (p, "heapshot", &val)) != p) { | ||
| events &= ~MONO_PROFILE_ALLOCATIONS; | ||
| events &= ~MONO_PROFILE_GC_MOVES; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple of things. I believe the commit message is wrong.. The serve no purpose when allocation profiling is disabled.
More importantly, have you discussed this change with the profiler team?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The serve no purpose when allocation profiling is disabled.
Oops.
More importantly, have you discussed this change with the profiler team?
AFAIK, they always enable allocation events anyway, so this shouldn't affect them. But I'll check with Moya to be sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We only enable allocations if the user loads the 'Allocations' instrument.
I guess this is about what @alexrp asked yesterday ("would it be a problem for you guys if the profiler stopped producing gc move events when allocation events are disabled?")? If so, yes, if we don't enable allocations, not having GC_MOVES is ok for us. In fact, getting GC_MOVES events when we are not tracking allocations just gives us some unneeded extra work (reading those events just to find out we know nothing about those allocations being moved).
|
Besides the cosmetic suggestions, the only issue is on always starting the sampler thread when a profiler module is installed. As mentioned, the SDKs install profilers and that will screw with power consumption. |
No description provided.