Skip to content

Commit

Permalink
[MonoVM] Add the ability for profilers to listen to EventPipe events.
Browse files Browse the repository at this point in the history
Mono port of CoreCLR PR: dotnet#37002.
  • Loading branch information
lateralusX committed Aug 21, 2020
1 parent 960a175 commit 71a1dca
Show file tree
Hide file tree
Showing 17 changed files with 342 additions and 178 deletions.
93 changes: 9 additions & 84 deletions src/mono/mono/eventpipe/ep-buffer-manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -423,13 +423,6 @@ buffer_manager_allocate_buffer_for_thread (

// Allocating a buffer requires us to take the lock.
EP_SPIN_LOCK_ENTER (&buffer_manager->rt_lock, section1)

// if we are deallocating then give up, see the comments in ep_buffer_manager_suspend_write_event () for why this is important.
if ((bool)ep_rt_volatile_load_uint32_t (&buffer_manager->write_event_suspending)) {
*write_suspended = true;
ep_raise_error_holding_spin_lock (section1);
}

thread_buffer_list = ep_thread_session_state_get_buffer_list (thread_session_state);
if (thread_buffer_list == NULL) {
thread_buffer_list = ep_buffer_list_alloc (buffer_manager, ep_thread_session_state_get_thread (thread_session_state));
Expand Down Expand Up @@ -797,8 +790,6 @@ ep_buffer_manager_alloc (
instance->session = session;
instance->size_of_all_buffers = 0;

ep_rt_volatile_store_uint32_t (&instance->write_event_suspending, (uint32_t)false);

#ifdef EP_CHECKED_BUILD
instance->num_buffers_allocated = 0;
instance->num_buffers_stolen = 0;
Expand Down Expand Up @@ -837,8 +828,6 @@ ep_buffer_manager_free (EventPipeBufferManager * buffer_manager)
{
ep_return_void_if_nok (buffer_manager != NULL);

ep_rt_volatile_store_uint32_t (&buffer_manager->write_event_suspending, (uint32_t)true);

ep_buffer_manager_deallocate_buffers (buffer_manager);

ep_rt_wait_event_free (&buffer_manager->rt_wait_event);
Expand Down Expand Up @@ -931,10 +920,6 @@ ep_buffer_manager_write_event (
thread_lock = ep_thread_get_rt_lock_ref (current_thread);

EP_SPIN_LOCK_ENTER (thread_lock, section1)
if (ep_rt_volatile_load_uint32_t_without_barrier (&buffer_manager->write_event_suspending) != (uint32_t)false)
// This session is suspending, we need to avoid initializing any session state and exit
ep_raise_error_holding_spin_lock (section1);

session_state = ep_thread_get_or_create_session_state (current_thread, session);
ep_raise_error_if_nok_holding_spin_lock (session_state != NULL, section1);

Expand Down Expand Up @@ -977,24 +962,13 @@ ep_buffer_manager_write_event (

thread_lock = ep_thread_get_rt_lock_ref (current_thread);
EP_SPIN_LOCK_ENTER (thread_lock, section3)
if (ep_rt_volatile_load_uint32_t_without_barrier (&buffer_manager->write_event_suspending) != (uint32_t)false) {
// After leaving the manager's lock in buffer_manager_allocated_buffer_for_thread some other thread decided to suspend writes.
// We need to immediately return the buffer we just took without storing it or writing to it.
// suspend_write_event () is spinning waiting for this buffer to be relinquished.
ep_buffer_convert_to_read_only (buffer);

// We treat this as the write_event() call occurring after this session stopped listening for events, effectively the
// same as if ep_event_is_enabled returned false.
ep_raise_error_holding_spin_lock (section3);
} else {
ep_thread_session_state_set_write_buffer (session_state, buffer);
// Try to write the event after we allocated a buffer.
// This is the first time if the thread had no buffers before the call to this function.
// This is the second time if this thread did have one or more buffers, but they were full.
alloc_new_buffer = !ep_buffer_write_event (buffer, event_thread, session, ep_event, payload, activity_id, related_activity_id, stack);
EP_ASSERT(!alloc_new_buffer);
ep_thread_session_state_increment_sequence_number (session_state);
}
EP_SPIN_LOCK_EXIT (thread_lock, section3)
}
}
Expand Down Expand Up @@ -1034,73 +1008,33 @@ ep_buffer_manager_suspend_write_event (
ep_rt_thread_array_alloc (&thread_array);
EP_SPIN_LOCK_ENTER (&buffer_manager->rt_lock, section1);
EP_ASSERT (ep_buffer_manager_ensure_consistency (buffer_manager) == true);
ep_rt_volatile_store_uint32_t (&buffer_manager->write_event_suspending, (uint32_t)true);

// From this point until write_event_suspending is reset to false it is impossible
// for new EventPipeThreadSessionStates to be added to the thread_session_state_list or
// for new EventBuffers to be added to an existing EventPipeBufferList. The only
// way ep_buffer_manager_allocate_buffer_for_thread is allowed to add one is by:
// 1) take rt_lock - ep_buffer_manager_allocate_buffer_for_thread can't own it now because this thread owns it,
// but after this thread gives it up lower in this function it could be acquired.
// 2) observe write_event_suspending = false - that won't happen, acquiring rt_lock
// guarantees ep_buffer_manager_allocate_buffer_for_thread will observe all the memory changes this
// thread made prior to releasing m_lock and we've already set it true.
// This ensures that we iterate over the list of threads below we've got the complete list.
// Find all threads that have used this buffer manager.
ep_rt_thread_session_state_list_iterator_t thread_session_state_list_iterator;
ep_rt_thread_session_state_list_iterator_begin (&buffer_manager->thread_session_state_list, &thread_session_state_list_iterator);
while (!ep_rt_thread_session_state_list_iterator_end (&buffer_manager->thread_session_state_list, &thread_session_state_list_iterator)) {
ep_rt_thread_array_append (&thread_array, ep_thread_session_state_get_thread (ep_rt_thread_session_state_list_iterator_value (&thread_session_state_list_iterator)));
EventPipeThread *thread = ep_thread_session_state_get_thread (ep_rt_thread_session_state_list_iterator_value (&thread_session_state_list_iterator));
ep_rt_thread_array_append (&thread_array, thread);
ep_rt_thread_session_state_list_iterator_next (&buffer_manager->thread_session_state_list, &thread_session_state_list_iterator);

// Once EventPipeSession::SuspendWriteEvent completes, we shouldn't have any
// in progress writes left.
EP_ASSERT (ep_thread_get_session_write_in_progress (thread) != session_index);
}
EP_SPIN_LOCK_EXIT (&buffer_manager->rt_lock, section1);

// Iterate through all the threads, forcing them to finish writes in progress inside EventPipeThread::m_lock,
// relinquish any buffers stored in EventPipeThread::m_pWriteBuffer and prevent storing new ones.
// Iterate through all the threads, forcing them to relinquish any buffers stored in
// EventPipeThread's write buffer and prevent storing new ones.
ep_rt_thread_array_iterator_t thread_array_iterator;
ep_rt_thread_array_iterator_begin (&thread_array, &thread_array_iterator);
while (!ep_rt_thread_array_iterator_end (&thread_array, &thread_array_iterator)) {
EventPipeThread *thread = ep_rt_thread_array_iterator_value (&thread_array_iterator);
EP_SPIN_LOCK_ENTER (ep_thread_get_rt_lock_ref (thread), section2)
EventPipeThreadSessionState *thread_session_state = ep_thread_get_session_state (thread, buffer_manager->session);
ep_thread_session_state_set_write_buffer (thread_session_state, NULL);
// From this point until write_event_suspending is reset to false it is impossible
// for this thread to set the write buffer to a non-null value which in turn means
// it can't write events into any buffer. To do this it would need to both:
// 1) Acquire the thread lock - it can't right now but it will be able to do so after
// we release the lock below
// 2) Observe write_event_suspending = false - that won't happen, acquiring the thread
// lock guarantees ep_buffer_manager_write_event will observe all the memory
// changes this thread made prior to releasing the thread
// lock and we already set it true.
EP_SPIN_LOCK_EXIT (ep_thread_get_rt_lock_ref (thread), section2)
ep_rt_thread_array_iterator_next (&thread_array, &thread_array_iterator);
}

// Wait for any straggler ep_buffer_manager_write_event threads that may have already allocated a buffer but
// hadn't yet relinquished it.
ep_rt_thread_session_state_list_iterator_t thread_session_state_list_iterator;
EP_SPIN_LOCK_ENTER (&buffer_manager->rt_lock, section3)
ep_rt_thread_session_state_list_iterator_begin (&buffer_manager->thread_session_state_list, &thread_session_state_list_iterator);
while (!ep_rt_thread_session_state_list_iterator_end (&buffer_manager->thread_session_state_list, &thread_session_state_list_iterator)) {
EventPipeBufferList *buffer_list = ep_thread_session_state_get_buffer_list (ep_rt_thread_session_state_list_iterator_value (&thread_session_state_list_iterator));
if (buffer_list) {
EventPipeThread *const event_pipe_thread = ep_buffer_list_get_thread (buffer_list);
if (event_pipe_thread) {
EP_YIELD_WHILE (ep_thread_get_session_write_in_progress (event_pipe_thread) == session_index);
// It still guarantees that the thread has returned its buffer, but it also now guarantees that
// that the thread has returned from ep_session_write_event () and has relinquished the session pointer
// This yield is guaranteed to eventually finish because threads will eventually exit write_event ()
// setting the flag back to -1. If the thread could quickly re-enter WriteEvent and set the flag
// back to this_session_id we could theoretically get unlucky and never observe the gap, but
// setting s_pSessions[this_session_id] = NULL above guaranteed that can't happen indefinately.
// Sooner or later the thread is going to see the NULL value and once it does it won't store
// this_session_id into the flag again.
}
}
ep_rt_thread_session_state_list_iterator_next (&buffer_manager->thread_session_state_list, &thread_session_state_list_iterator);
}
EP_SPIN_LOCK_EXIT (&buffer_manager->rt_lock, section3)

ep_on_exit:
ep_requires_lock_held ();
ep_rt_thread_array_free (&thread_array);
Expand Down Expand Up @@ -1338,15 +1272,6 @@ ep_buffer_manager_deallocate_buffers (EventPipeBufferManager *buffer_manager)
// Take the buffer manager manipulation lock
EP_SPIN_LOCK_ENTER (&buffer_manager->rt_lock, section1)
EP_ASSERT (ep_buffer_manager_ensure_consistency (buffer_manager));
EP_ASSERT ((bool)ep_rt_volatile_load_uint32_t (&buffer_manager->write_event_suspending));

// This m_writeEventSuspending flag + locks ensures that no thread will touch any of the
// state we are dismantling here. This includes:
// a) EventPipeThread m_sessions[session_id]
// b) EventPipeThreadSessionState
// c) EventPipeBufferList
// d) EventPipeBuffer
// e) EventPipeBufferManager.m_pThreadSessionStateList

ep_rt_thread_session_state_list_iterator_t thread_session_state_list_iterator;
ep_rt_thread_session_state_list_iterator_begin (&buffer_manager->thread_session_state_list, &thread_session_state_list_iterator);
Expand Down
1 change: 0 additions & 1 deletion src/mono/mono/eventpipe/ep-buffer-manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ struct _EventPipeBufferManager_Internal {
uint32_t num_buffers_stolen;
uint32_t num_buffers_leaked;
#endif
volatile uint32_t write_event_suspending;
};

#if !defined(EP_INLINE_GETTER_SETTER) && !defined(EP_IMPL_BUFFER_MANAGER_GETTER_SETTER)
Expand Down
2 changes: 2 additions & 0 deletions src/mono/mono/eventpipe/ep-event-source.c
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ ep_event_source_enable (
EP_ASSERT (event_source != NULL);
EP_ASSERT (session != NULL);

ep_requires_lock_held ();

EventPipeSessionProvider *session_provider = ep_session_provider_alloc (event_source->provider_name, (uint64_t)-1, EP_EVENT_LEVEL_LOG_ALWAYS, NULL);
if (session_provider != NULL)
ep_session_add_session_provider (session, session_provider);
Expand Down
3 changes: 3 additions & 0 deletions src/mono/mono/eventpipe/ep-event.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,11 @@ struct _EventPipeEvent {
EP_DEFINE_GETTER(EventPipeEvent *, event, uint64_t, keywords)
EP_DEFINE_GETTER_REF(EventPipeEvent *, event, volatile int64_t *, enabled_mask)
EP_DEFINE_SETTER(EventPipeEvent *, event, int64_t, enabled_mask)
EP_DEFINE_GETTER(EventPipeEvent *, event, uint8_t *, metadata)
EP_DEFINE_GETTER(EventPipeEvent *, event, EventPipeProvider *, provider)
EP_DEFINE_GETTER(EventPipeEvent *, event, uint32_t, event_id)
EP_DEFINE_GETTER(EventPipeEvent *, event, uint32_t, event_version)
EP_DEFINE_GETTER(EventPipeEvent *, event, uint32_t, metadata_len)
EP_DEFINE_GETTER(EventPipeEvent *, event, EventPipeEventLevel, level)
EP_DEFINE_GETTER(EventPipeEvent *, event, bool, need_stack)

Expand Down
25 changes: 24 additions & 1 deletion src/mono/mono/eventpipe/ep-rt-mono.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@
static inline void ep_rt_ ## array_name ## _free (array_type *ep_array) { g_array_free (ep_array->array, TRUE); } \
static inline void ep_rt_ ## array_name ## _clear (array_type *ep_array) { g_array_set_size (ep_array->array, 0); } \
static inline void ep_rt_ ## array_name ## _append (array_type *ep_array, item_type item) { g_array_append_val (ep_array->array, item); } \
static inline bool ep_rt_ ## array_name ## _remove (array_type *ep_array, const item_type item) { \
for (gint i = 0; i < ep_array->array->len; ++i ) { \
if (g_array_index (ep_array->array, item_type, i) == item) { \
ep_array->array = g_array_remove_index_fast (ep_array->array, i); \
return true; \
} \
} \
return false; \
} \
static inline size_t ep_rt_ ## array_name ## _size (const array_type *ep_array) { return ep_array->array->len; }

#define EP_RT_DEFINE_ARRAY_ITERATOR(array_name, array_type, iterator_type, item_type) \
Expand Down Expand Up @@ -621,7 +630,21 @@ ep_rt_sample_profiler_get_sampling_rate (void)
static
inline
void
ep_rt_sample_set_sampling_rate (uint32_t nanoseconds)
ep_rt_sample_profiler_set_sampling_rate (uint32_t nanoseconds)
{
//TODO: Not supported.
}

static
void
ep_rt_sample_profiler_can_start_sampling (void)
{
//TODO: Not supported.
}

static
void
ep_rt_notify_profiler_provider_created (EventPipeProvider *provider)
{
//TODO: Not supported.
}
Expand Down
11 changes: 10 additions & 1 deletion src/mono/mono/eventpipe/ep-rt.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
static void ep_rt_ ## array_name ## _free (array_type *ep_array); \
static void ep_rt_ ## array_name ## _clear (array_type *ep_array); \
static void ep_rt_ ## array_name ## _append (array_type *ep_array, item_type item); \
static bool ep_rt_ ## array_name ## _remove (array_type *ep_array, const item_type item); \
static size_t ep_rt_ ## array_name ## _size (const array_type *ep_array);

#define EP_RT_DECLARE_ARRAY_ITERATOR(array_name, array_type, iterator_type, item_type) \
Expand Down Expand Up @@ -231,7 +232,15 @@ ep_rt_sample_profiler_get_sampling_rate (void);

static
void
ep_rt_sample_set_sampling_rate (uint32_t nanoseconds);
ep_rt_sample_profiler_set_sampling_rate (uint32_t nanoseconds);

static
void
ep_rt_sample_profiler_can_start_sampling (void);

static
void
ep_rt_notify_profiler_provider_created (EventPipeProvider *provider);

/*
* EventPipeSessionProvider.
Expand Down
Loading

0 comments on commit 71a1dca

Please sign in to comment.