#include "ep-rt-config.h" #ifdef ENABLE_PERFTRACING #if !defined(EP_INCLUDE_SOURCE_FILES) || defined(EP_FORCE_INCLUDE_SOURCE_FILES) #define EP_IMPL_SAMPLE_PROFILER_GETTER_SETTER #include "ep.h" #include "ep-sample-profiler.h" #include "ep-event.h" #include "ep-provider-internals.h" #include "ep-rt.h" #define NUM_NANOSECONDS_IN_1_MS 1000000 static volatile uint32_t _profiling_enabled = (uint32_t)false; static EventPipeProvider *_sampling_provider = NULL; static EventPipeEvent *_thread_time_event = NULL; static ep_rt_wait_event_handle_t _thread_shutdown_event; static uint64_t _sampling_rate_in_ns = NUM_NANOSECONDS_IN_1_MS; // 1ms static bool _time_period_is_set = false; static volatile uint32_t _can_start_sampling = (uint32_t)false; static int32_t _ref_count = 0; #ifdef HOST_WIN32 #include static PVOID _time_begin_period_func = NULL; static PVOID _time_end_period_func = NULL; static HINSTANCE _multimedia_library_handle = NULL; typedef MMRESULT(WINAPI *time_period_func)(UINT uPeriod); #endif /* * Forward declares of all static functions. */ EP_RT_DEFINE_THREAD_FUNC (sampling_thread); static void sample_profiler_set_time_granularity (void); static void sample_profiler_reset_time_granularity (void); static bool sample_profiler_load_dependencies (void); static void sample_profiler_unload_dependencies (void); static void sample_profiler_enable (void); /* * EventPipeSampleProfiler. */ static inline bool sample_profiler_load_profiling_enabled (void) { return (ep_rt_volatile_load_uint32_t (&_profiling_enabled) != 0) ? true : false; } static inline void sample_profiler_store_profiling_enabled (bool enabled) { ep_rt_volatile_store_uint32_t (&_profiling_enabled, enabled ? 1 : 0); } static inline void sample_profiler_store_can_start_sampling (bool start_sampling) { ep_rt_volatile_store_uint32_t (&_can_start_sampling, start_sampling ? 1 : 0); } EP_RT_DEFINE_THREAD_FUNC (sampling_thread) { EP_ASSERT (data != NULL); if (data == NULL) return 1; ep_rt_thread_params_t *thread_params = (ep_rt_thread_params_t *)data; if (thread_params->thread && ep_rt_thread_has_started (thread_params->thread)) { EP_GCX_PREEMP_ENTER while (sample_profiler_load_profiling_enabled ()) { // Sample all threads. ep_rt_sample_profiler_write_sampling_event_for_threads (thread_params->thread, _thread_time_event); // Wait until it's time to sample again. ep_rt_thread_sleep (_sampling_rate_in_ns); } EP_GCX_PREEMP_EXIT } // Signal disable () that the thread has been destroyed. ep_rt_wait_event_set (&_thread_shutdown_event); return (ep_rt_thread_start_func_return_t)0; } static void sample_profiler_set_time_granularity (void) { #ifdef HOST_WIN32 // Attempt to set the systems minimum timer period to the sampling rate // If the sampling rate is lower than the current system setting (16ms by default), // this will cause the OS to wake more often for scheduling descsion, allowing us to take samples // Note that is effects a system-wide setting and when set low will increase the amount of time // the OS is on-CPU, decreasing overall system performance and increasing power consumption if (_time_begin_period_func != NULL) { if (((time_period_func)_time_begin_period_func)((uint32_t)(_sampling_rate_in_ns / NUM_NANOSECONDS_IN_1_MS)) == TIMERR_NOERROR) { _time_period_is_set = true; } } #endif //HOST_WIN32 } static void sample_profiler_reset_time_granularity (void) { #ifdef HOST_WIN32 // End the modifications we had to the timer period in enable. if (_time_end_period_func != NULL) { if (((time_period_func)_time_end_period_func)((uint32_t)(_sampling_rate_in_ns / NUM_NANOSECONDS_IN_1_MS)) == TIMERR_NOERROR) { _time_period_is_set = false; } } #endif //HOST_WIN32 } static bool sample_profiler_load_dependencies (void) { #ifdef HOST_WIN32 if (_ref_count > 0) return true; // Already loaded. #ifdef WszLoadLibrary _multimedia_library_handle = WszLoadLibrary (L"winmm.dll"); #else _multimedia_library_handle = LoadLibraryW (L"winmm.dll"); #endif if (_multimedia_library_handle != NULL) { _time_begin_period_func = (PVOID)GetProcAddress (_multimedia_library_handle, "timeBeginPeriod"); _time_end_period_func = (PVOID)GetProcAddress (_multimedia_library_handle, "timeEndPeriod"); } return _multimedia_library_handle != NULL && _time_begin_period_func != NULL && _time_end_period_func != NULL; #else return true; #endif //HOST_WIN32 } static void sample_profiler_unload_dependencies (void) { #ifdef HOST_WIN32 if (_multimedia_library_handle != NULL) { FreeLibrary (_multimedia_library_handle); _multimedia_library_handle = NULL; _time_begin_period_func = NULL; _time_end_period_func = NULL; } #endif //HOST_WIN32 } static void sample_profiler_enable (void) { EP_ASSERT (_sampling_provider != NULL); EP_ASSERT (_thread_time_event != NULL); ep_requires_lock_held (); if (!sample_profiler_load_profiling_enabled ()) { sample_profiler_store_profiling_enabled (true); EP_ASSERT (!ep_rt_wait_event_is_valid (&_thread_shutdown_event)); ep_rt_wait_event_alloc (&_thread_shutdown_event, true, false); if (!ep_rt_wait_event_is_valid (&_thread_shutdown_event)) EP_UNREACHABLE ("Unable to create sample profiler event."); ep_rt_thread_id_t thread_id = ep_rt_uint64_t_to_thread_id_t (0); if (!ep_rt_thread_create ((void *)sampling_thread, NULL, EP_THREAD_TYPE_SAMPLING, &thread_id)) EP_UNREACHABLE ("Unable to create sample profiler thread."); sample_profiler_set_time_granularity (); } } void ep_sample_profiler_init (EventPipeProviderCallbackDataQueue *provider_callback_data_queue) { ep_requires_lock_held (); if (!_sampling_provider) { _sampling_provider = provider_create_register (ep_config_get_sample_profiler_provider_name_utf8 (), NULL, NULL, NULL, provider_callback_data_queue); ep_raise_error_if_nok (_sampling_provider != NULL); _thread_time_event = provider_add_event ( _sampling_provider, 0, /* eventID */ 0, /* keywords */ 0, /* eventVersion */ EP_EVENT_LEVEL_INFORMATIONAL, false /* NeedStack */, NULL, 0); ep_raise_error_if_nok (_thread_time_event != NULL); } ep_on_exit: ep_requires_lock_held (); return; ep_on_error: ep_exit_error_handler (); } void ep_sample_profiler_shutdown (void) { ep_requires_lock_held (); EP_ASSERT (_ref_count == 0); provider_unregister_delete (_sampling_provider); _sampling_provider = NULL; _thread_time_event = NULL; _can_start_sampling = false; } void ep_sample_profiler_enable (void) { EP_ASSERT (_sampling_provider != NULL); EP_ASSERT (_thread_time_event != NULL); ep_requires_lock_held (); // Check to see if the sample profiler event is enabled. If it is not, do not spin up the sampling thread. if (!ep_event_is_enabled (_thread_time_event)) return; sample_profiler_load_dependencies (); if (_can_start_sampling) sample_profiler_enable (); ++_ref_count; EP_ASSERT (_ref_count > 0); } void ep_sample_profiler_disable (void) { EP_ASSERT (_ref_count > 0); ep_requires_lock_held (); // Bail early if profiling is not enabled. if (!sample_profiler_load_profiling_enabled ()) return; if (_ref_count == 1) { EP_ASSERT (!ep_rt_process_detach ()); // The sampling thread will watch this value and exit // when profiling is disabled. sample_profiler_store_profiling_enabled (false); // Wait for the sampling thread to clean itself up. ep_rt_wait_event_wait (&_thread_shutdown_event, EP_INFINITE_WAIT, false); ep_rt_wait_event_free (&_thread_shutdown_event); if (_time_period_is_set) sample_profiler_reset_time_granularity (); sample_profiler_unload_dependencies (); } --_ref_count; EP_ASSERT (_ref_count >= 0); } void ep_sample_profiler_can_start_sampling (void) { ep_requires_lock_held (); sample_profiler_store_can_start_sampling (true); if (_ref_count > 0) sample_profiler_enable (); } void ep_sample_profiler_set_sampling_rate (uint64_t nanoseconds) { // If the time period setting was modified by us, // make sure to change it back before changing our period // and losing track of what we set it to if (_time_period_is_set) sample_profiler_reset_time_granularity (); _sampling_rate_in_ns = nanoseconds; if (!_time_period_is_set) sample_profiler_set_time_granularity (); } uint64_t ep_sample_profiler_get_sampling_rate (void) { return _sampling_rate_in_ns; } #endif /* !defined(EP_INCLUDE_SOURCE_FILES) || defined(EP_FORCE_INCLUDE_SOURCE_FILES) */ #endif /* ENABLE_PERFTRACING */ #if !defined(ENABLE_PERFTRACING) || (defined(EP_INCLUDE_SOURCE_FILES) && !defined(EP_FORCE_INCLUDE_SOURCE_FILES)) extern const char quiet_linker_empty_file_warning_eventpipe_sample_profiler; const char quiet_linker_empty_file_warning_eventpipe_sample_profiler = 0; #endif