/*
|
* Licensed to the .NET Foundation under one or more agreements.
|
* The .NET Foundation licenses this file to you under the MIT license.
|
* See the LICENSE file in the project root for more information.
|
*/
|
|
#include <config.h>
|
#include <mono/metadata/assembly.h>
|
#include <mono/metadata/gc-internals.h>
|
#include <mono/metadata/mono-config-dirs.h>
|
#include <mono/metadata/mono-debug.h>
|
#include <mono/metadata/profiler-private.h>
|
#include <mono/metadata/debug-internals.h>
|
#include <mono/utils/mono-dl.h>
|
#include <mono/utils/mono-error-internals.h>
|
#include <mono/utils/mono-logger-internals.h>
|
|
MonoProfilerState mono_profiler_state;
|
|
typedef void (*MonoProfilerInitializer) (const char *);
|
|
#define OLD_INITIALIZER_NAME "mono_profiler_startup"
|
#define NEW_INITIALIZER_NAME "mono_profiler_init"
|
|
static gboolean
|
load_profiler (MonoDl *module, const char *name, const char *desc)
|
{
|
if (!module)
|
return FALSE;
|
|
char *err, *old_name = g_strdup_printf (OLD_INITIALIZER_NAME);
|
MonoProfilerInitializer func;
|
|
if (!(err = mono_dl_symbol (module, old_name, (gpointer) &func))) {
|
mono_profiler_printf_err ("Found old-style startup symbol '%s' for the '%s' profiler; it has not been migrated to the new API.", old_name, name);
|
g_free (old_name);
|
return FALSE;
|
}
|
|
g_free (err);
|
g_free (old_name);
|
|
char *new_name = g_strdup_printf (NEW_INITIALIZER_NAME "_%s", name);
|
|
if ((err = mono_dl_symbol (module, new_name, (gpointer *) &func))) {
|
g_free (err);
|
g_free (new_name);
|
return FALSE;
|
}
|
|
g_free (new_name);
|
|
func (desc);
|
|
return TRUE;
|
}
|
|
static gboolean
|
load_profiler_from_executable (const char *name, const char *desc)
|
{
|
char *err;
|
|
/*
|
* Some profilers (such as ours) may need to call back into the runtime
|
* from their sampling callback (which is called in async-signal context).
|
* They need to be able to know that all references back to the runtime
|
* have been resolved; otherwise, calling runtime functions may result in
|
* invoking the dynamic linker which is not async-signal-safe. Passing
|
* MONO_DL_EAGER will ask the dynamic linker to resolve everything upfront.
|
*/
|
MonoDl *module = mono_dl_open (NULL, MONO_DL_EAGER, &err);
|
|
if (!module) {
|
mono_profiler_printf_err ("Could not open main executable: %s", err);
|
g_free (err);
|
return FALSE;
|
}
|
|
return load_profiler (module, name, desc);
|
}
|
|
static gboolean
|
load_profiler_from_directory (const char *directory, const char *libname, const char *name, const char *desc)
|
{
|
char* path;
|
void *iter = NULL;
|
|
while ((path = mono_dl_build_path (directory, libname, &iter))) {
|
// See the comment in load_embedded_profiler ().
|
MonoDl *module = mono_dl_open (path, MONO_DL_EAGER, NULL);
|
|
g_free (path);
|
|
if (module)
|
return load_profiler (module, name, desc);
|
}
|
|
return FALSE;
|
}
|
|
static gboolean
|
load_profiler_from_installation (const char *libname, const char *name, const char *desc)
|
{
|
char *err;
|
MonoDl *module = mono_dl_open_runtime_lib (libname, MONO_DL_EAGER, &err);
|
|
g_free (err);
|
|
if (module)
|
return load_profiler (module, name, desc);
|
|
return FALSE;
|
}
|
|
/**
|
* mono_profiler_load:
|
*
|
* Loads a profiler module based on the specified description. \p desc can be
|
* of the form \c name:args or just \c name. For example, \c log:sample and
|
* \c log will both load \c libmono-profiler-log.so. The description is passed
|
* to the module after it has been loaded. If the specified module has already
|
* been loaded, this function has no effect.
|
*
|
* A module called \c foo should declare an entry point like so:
|
*
|
* \code
|
* void mono_profiler_init_foo (const char *desc)
|
* {
|
* }
|
* \endcode
|
*
|
* This function is \b not async safe.
|
*
|
* This function may \b only be called by embedders prior to running managed
|
* code.
|
*/
|
#ifndef RUNTIME_IL2CPP
|
void
|
mono_profiler_load (const char *desc)
|
{
|
if (!desc || !strcmp ("default", desc))
|
desc = "log:report";
|
|
const char *col = strchr (desc, ':');
|
char *mname;
|
|
if (col != NULL) {
|
mname = (char *) g_memdup (desc, col - desc + 1);
|
mname [col - desc] = 0;
|
} else
|
mname = g_strdup (desc);
|
|
if (!load_profiler_from_executable (mname, desc)) {
|
char *libname = g_strdup_printf ("mono-profiler-%s", mname);
|
gboolean res = load_profiler_from_installation (libname, mname, desc);
|
|
if (!res && mono_config_get_assemblies_dir ())
|
res = load_profiler_from_directory (mono_assembly_getrootdir (), libname, mname, desc);
|
|
if (!res)
|
res = load_profiler_from_directory (NULL, libname, mname, desc);
|
|
if (!res)
|
mono_profiler_printf_err ("The '%s' profiler wasn't found in the main executable nor could it be loaded from '%s'.", mname, libname);
|
|
g_free (libname);
|
}
|
|
g_free (mname);
|
}
|
#endif
|
|
/**
|
* mono_profiler_create:
|
*
|
* Installs a profiler and returns a handle for it. The handle is used with the
|
* other functions in the profiler API (e.g. for setting up callbacks). The
|
* given structure pointer, \p prof, will be passed to all callbacks from the
|
* profiler API. It can be \c NULL.
|
*
|
* Example usage:
|
*
|
* \code
|
* struct _MonoProfiler {
|
* int my_stuff;
|
* // ...
|
* };
|
*
|
* MonoProfiler *prof = malloc (sizeof (MonoProfiler));
|
* prof->my_stuff = 42;
|
* MonoProfilerHandle handle = mono_profiler_create (prof);
|
* mono_profiler_set_shutdown_callback (handle, my_shutdown_cb);
|
* \endcode
|
*
|
* This function is \b not async safe.
|
*
|
* This function may \b only be called from a profiler's init function or prior
|
* to running managed code.
|
*/
|
MonoProfilerHandle
|
mono_profiler_create (MonoProfiler *prof)
|
{
|
MonoProfilerHandle handle = g_new0 (struct _MonoProfilerDesc, 1);
|
|
handle->prof = prof;
|
handle->next = mono_profiler_state.profilers;
|
|
mono_profiler_state.profilers = handle;
|
|
return handle;
|
}
|
|
/**
|
* mono_profiler_set_cleanup_callback:
|
*
|
* Sets a profiler cleanup function. This function will be invoked at shutdown
|
* when the profiler API is cleaning up its internal structures. It's mainly
|
* intended for a profiler to free the structure pointer that was passed to
|
* \c mono_profiler_create, if necessary.
|
*
|
* This function is async safe.
|
*/
|
void
|
mono_profiler_set_cleanup_callback (MonoProfilerHandle handle, MonoProfilerCleanupCallback cb)
|
{
|
mono_atomic_store_ptr (&handle->cleanup_callback, (gpointer) cb);
|
}
|
|
/**
|
* mono_profiler_enable_coverage:
|
*
|
* Enables support for code coverage instrumentation. At the moment, this means
|
* enabling the debug info subsystem. If this function is not called, it will
|
* not be possible to use \c mono_profiler_get_coverage_data. Returns \c TRUE
|
* if code coverage support was enabled, or \c FALSE if the function was called
|
* too late for this to be possible.
|
*
|
* This function is \b not async safe.
|
*
|
* This function may \b only be called from a profiler's init function or prior
|
* to running managed code.
|
*/
|
#ifndef RUNTIME_IL2CPP
|
mono_bool
|
mono_profiler_enable_coverage (void)
|
{
|
if (mono_profiler_state.startup_done)
|
return FALSE;
|
|
mono_os_mutex_init (&mono_profiler_state.coverage_mutex);
|
mono_profiler_state.coverage_hash = g_hash_table_new (NULL, NULL);
|
|
if (!mono_debug_enabled ())
|
mono_debug_init (MONO_DEBUG_FORMAT_MONO);
|
|
return mono_profiler_state.code_coverage = TRUE;
|
}
|
#endif
|
|
/**
|
* mono_profiler_set_coverage_filter_callback:
|
*
|
* Sets a code coverage filter function. The profiler API will invoke filter
|
* functions from all installed profilers. If any of them return \c TRUE, then
|
* the given method will be instrumented for coverage analysis. All filters are
|
* guaranteed to be called at least once per method, even if an earlier filter
|
* has already returned \c TRUE.
|
*
|
* Note that filter functions must be installed before a method is compiled in
|
* order to have any effect, i.e. a filter should be registered in a profiler's
|
* init function or prior to running managed code (if embedding).
|
*
|
* This function is async safe.
|
*/
|
void
|
mono_profiler_set_coverage_filter_callback (MonoProfilerHandle handle, MonoProfilerCoverageFilterCallback cb)
|
{
|
mono_atomic_store_ptr (&handle->coverage_filter, (gpointer) cb);
|
}
|
|
static void
|
coverage_lock (void)
|
{
|
mono_os_mutex_lock (&mono_profiler_state.coverage_mutex);
|
}
|
|
static void
|
coverage_unlock (void)
|
{
|
mono_os_mutex_unlock (&mono_profiler_state.coverage_mutex);
|
}
|
|
/**
|
* mono_profiler_get_coverage_data:
|
*
|
* Retrieves all coverage data for \p method and invokes \p cb for each entry.
|
* Source location information will only be filled out if \p method has debug
|
* info available. Returns \c TRUE if \p method was instrumented for code
|
* coverage; otherwise, \c FALSE.
|
*
|
* Please note that the structure passed to \p cb is only valid for the
|
* duration of the callback.
|
*
|
* This function is \b not async safe.
|
*/
|
#ifndef RUNTIME_IL2CPP
|
mono_bool
|
mono_profiler_get_coverage_data (MonoProfilerHandle handle, MonoMethod *method, MonoProfilerCoverageCallback cb)
|
{
|
if (!mono_profiler_state.code_coverage)
|
return FALSE;
|
|
if ((method->flags & METHOD_ATTRIBUTE_ABSTRACT) || (method->iflags & METHOD_IMPL_ATTRIBUTE_RUNTIME) || (method->iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) || (method->flags & METHOD_ATTRIBUTE_PINVOKE_IMPL))
|
return FALSE;
|
|
coverage_lock ();
|
|
MonoProfilerCoverageInfo *info = g_hash_table_lookup (mono_profiler_state.coverage_hash, method);
|
|
coverage_unlock ();
|
|
MonoError error;
|
MonoMethodHeader *header = mono_method_get_header_checked (method, &error);
|
mono_error_assert_ok (&error);
|
|
guint32 size;
|
|
const unsigned char *start = mono_method_header_get_code (header, &size, NULL);
|
const unsigned char *end = start + size;
|
MonoDebugMethodInfo *minfo = mono_debug_lookup_method (method);
|
|
if (!info) {
|
char *source_file;
|
int i, n_il_offsets;
|
int *source_files;
|
GPtrArray *source_file_list;
|
MonoSymSeqPoint *sym_seq_points;
|
|
if (!minfo)
|
return TRUE;
|
|
/* Return 0 counts for all locations */
|
|
mono_debug_get_seq_points (minfo, &source_file, &source_file_list, &source_files, &sym_seq_points, &n_il_offsets);
|
for (i = 0; i < n_il_offsets; ++i) {
|
MonoSymSeqPoint *sp = &sym_seq_points [i];
|
const char *srcfile = "";
|
|
if (source_files [i] != -1) {
|
MonoDebugSourceInfo *sinfo = (MonoDebugSourceInfo *)g_ptr_array_index (source_file_list, source_files [i]);
|
srcfile = sinfo->source_file;
|
}
|
|
MonoProfilerCoverageData data = {
|
.method = method,
|
.il_offset = sp->il_offset,
|
.counter = 0,
|
.file_name = srcfile,
|
.line = sp->line,
|
.column = 0,
|
};
|
|
cb (handle->prof, &data);
|
}
|
|
g_free (source_files);
|
g_free (sym_seq_points);
|
g_ptr_array_free (source_file_list, TRUE);
|
|
mono_metadata_free_mh (header);
|
return TRUE;
|
}
|
|
for (guint32 i = 0; i < info->entries; i++) {
|
guchar *cil_code = info->data [i].cil_code;
|
|
if (cil_code && cil_code >= start && cil_code < end) {
|
guint32 offset = cil_code - start;
|
|
MonoProfilerCoverageData data = {
|
.method = method,
|
.il_offset = offset,
|
.counter = info->data [i].count,
|
.line = 1,
|
.column = 1,
|
};
|
|
if (minfo) {
|
MonoDebugSourceLocation *loc = mono_debug_method_lookup_location (minfo, offset);
|
|
if (loc) {
|
data.file_name = g_strdup (loc->source_file);
|
data.line = loc->row;
|
data.column = loc->column;
|
|
mono_debug_free_source_location (loc);
|
}
|
}
|
|
cb (handle->prof, &data);
|
|
g_free ((char *) data.file_name);
|
}
|
}
|
|
mono_metadata_free_mh (header);
|
|
return TRUE;
|
}
|
#endif
|
|
MonoProfilerCoverageInfo *
|
mono_profiler_coverage_alloc (MonoMethod *method, guint32 entries)
|
{
|
if (!mono_profiler_state.code_coverage)
|
return FALSE;
|
|
if (method->wrapper_type)
|
return FALSE;
|
|
gboolean cover = FALSE;
|
|
for (MonoProfilerHandle handle = mono_profiler_state.profilers; handle; handle = handle->next) {
|
MonoProfilerCoverageFilterCallback cb = handle->coverage_filter;
|
|
if (cb)
|
cover |= cb (handle->prof, method);
|
}
|
|
if (!cover)
|
return NULL;
|
|
coverage_lock ();
|
|
MonoProfilerCoverageInfo *info = g_malloc0 (sizeof (MonoProfilerCoverageInfo) + SIZEOF_VOID_P * 2 * entries);
|
|
info->entries = entries;
|
|
g_hash_table_insert (mono_profiler_state.coverage_hash, method, info);
|
|
coverage_unlock ();
|
|
return info;
|
}
|
|
/**
|
* mono_profiler_enable_sampling:
|
*
|
* Enables the sampling thread. Users must call this function if they intend
|
* to use statistical sampling; \c mono_profiler_set_sample_mode will have no
|
* effect if this function has not been called. The first profiler to call this
|
* function will get ownership over sampling settings (mode and frequency) so
|
* that no other profiler can change those settings. Returns \c TRUE if the
|
* sampling thread was enabled, or \c FALSE if the function was called too late
|
* for this to be possible.
|
*
|
* Note that \c mono_profiler_set_sample_mode must still be called with a mode
|
* other than \c MONO_PROFILER_SAMPLE_MODE_NONE to actually start sampling.
|
*
|
* This function is \b not async safe.
|
*
|
* This function may \b only be called from a profiler's init function or prior
|
* to running managed code.
|
*/
|
mono_bool
|
mono_profiler_enable_sampling (MonoProfilerHandle handle)
|
{
|
if (mono_profiler_state.startup_done)
|
return FALSE;
|
|
if (mono_profiler_state.sampling_owner)
|
return TRUE;
|
|
mono_profiler_state.sampling_owner = handle;
|
mono_profiler_state.sample_mode = MONO_PROFILER_SAMPLE_MODE_NONE;
|
mono_profiler_state.sample_freq = 100;
|
mono_os_sem_init (&mono_profiler_state.sampling_semaphore, 0);
|
|
return TRUE;
|
}
|
|
/**
|
* mono_profiler_set_sample_mode:
|
*
|
* Sets the sampling mode and frequency (in Hz). \p freq must be a positive
|
* number. If the calling profiler has ownership over sampling settings, the
|
* settings will be changed and this function will return \c TRUE; otherwise,
|
* it returns \c FALSE without changing any settings.
|
*
|
* This function is async safe.
|
*/
|
mono_bool
|
mono_profiler_set_sample_mode (MonoProfilerHandle handle, MonoProfilerSampleMode mode, uint32_t freq)
|
{
|
if (handle != mono_profiler_state.sampling_owner)
|
return FALSE;
|
|
mono_profiler_state.sample_mode = mode;
|
mono_profiler_state.sample_freq = freq;
|
|
mono_profiler_sampling_thread_post ();
|
|
return TRUE;
|
}
|
|
/**
|
* mono_profiler_get_sample_mode:
|
*
|
* Retrieves the current sampling mode and/or frequency (in Hz). Returns
|
* \c TRUE if the calling profiler is allowed to change the sampling settings;
|
* otherwise, \c FALSE.
|
*
|
* This function is async safe.
|
*/
|
mono_bool
|
mono_profiler_get_sample_mode (MonoProfilerHandle handle, MonoProfilerSampleMode *mode, uint32_t *freq)
|
{
|
if (mode)
|
*mode = mono_profiler_state.sample_mode;
|
|
if (freq)
|
*freq = mono_profiler_state.sample_freq;
|
|
return handle == mono_profiler_state.sampling_owner;
|
}
|
|
gboolean
|
mono_profiler_sampling_enabled (void)
|
{
|
return !!mono_profiler_state.sampling_owner;
|
}
|
|
void
|
mono_profiler_sampling_thread_post (void)
|
{
|
mono_os_sem_post (&mono_profiler_state.sampling_semaphore);
|
}
|
|
void
|
mono_profiler_sampling_thread_wait (void)
|
{
|
mono_os_sem_wait (&mono_profiler_state.sampling_semaphore, MONO_SEM_FLAGS_NONE);
|
}
|
|
/**
|
* mono_profiler_enable_allocations:
|
*
|
* Enables instrumentation of GC allocations. This is necessary so that managed
|
* allocators can be instrumented with a call into the profiler API.
|
* Allocations will not be reported unless this function is called. Returns
|
* \c TRUE if allocation instrumentation was enabled, or \c FALSE if the
|
* function was called too late for this to be possible.
|
*
|
* This function is \b not async safe.
|
*
|
* This function may \b only be called from a profiler's init function or prior
|
* to running managed code.
|
*/
|
mono_bool
|
mono_profiler_enable_allocations (void)
|
{
|
#ifndef RUNTIME_IL2CPP
|
if (mono_gc_get_managed_allocator_types () > 0 && mono_profiler_state.startup_done)
|
return FALSE;
|
#endif
|
|
return mono_profiler_state.allocations = TRUE;
|
}
|
|
mono_bool
|
mono_profiler_enable_fileio (void)
|
{
|
return mono_profiler_state.fileio = TRUE;
|
}
|
|
/**
|
* mono_profiler_set_call_instrumentation_filter_callback:
|
*
|
* Sets a call instrumentation filter function. The profiler API will invoke
|
* filter functions from all installed profilers. If any of them return flags
|
* other than \c MONO_PROFILER_CALL_INSTRUMENTATION_NONE, then the given method
|
* will be instrumented as requested. All filters are guaranteed to be called
|
* exactly once per method, even if earlier filters have already specified all
|
* flags.
|
*
|
* Note that filter functions must be installed before a method is compiled in
|
* order to have any effect, i.e. a filter should be registered in a profiler's
|
* init function or prior to running managed code (if embedding). Also, to
|
* instrument a method that's going to be AOT-compiled, a filter must be
|
* installed at AOT time. This can be done in exactly the same way as one would
|
* normally, i.e. by passing the \c --profile option on the command line, by
|
* calling \c mono_profiler_load, or simply by using the profiler API as an
|
* embedder.
|
*
|
* Indiscriminate method instrumentation is extremely heavy and will slow down
|
* most applications to a crawl. Users should consider sampling as a possible
|
* alternative to such heavy-handed instrumentation.
|
*
|
* This function is async safe.
|
*/
|
void
|
mono_profiler_set_call_instrumentation_filter_callback (MonoProfilerHandle handle, MonoProfilerCallInstrumentationFilterCallback cb)
|
{
|
mono_atomic_store_ptr (&handle->call_instrumentation_filter, (gpointer) cb);
|
}
|
|
/**
|
* mono_profiler_enable_call_context_introspection:
|
*
|
* Enables support for retrieving stack frame data from a call context. At the
|
* moment, this means enabling the debug info subsystem. If this function is not
|
* called, it will not be possible to use the call context introspection
|
* functions (they will simply return \c NULL). Returns \c TRUE if call context
|
* introspection was enabled, or \c FALSE if the function was called too late for
|
* this to be possible.
|
*
|
* Please note: Mono's LLVM backend does not support this feature. This means
|
* that methods with call context instrumentation will be handled by Mono's
|
* JIT even in LLVM mode. There is also a special case when Mono is compiling
|
* in LLVM-only mode: Since LLVM does not provide a way to implement call
|
* contexts, a \c NULL context will always be passed to enter/leave events even
|
* though this method returns \c TRUE.
|
*
|
* This function is \b not async safe.
|
*
|
* This function may \b only be called from a profiler's init function or prior
|
* to running managed code.
|
*/
|
mono_bool
|
mono_profiler_enable_call_context_introspection (void)
|
{
|
if (mono_profiler_state.startup_done)
|
return FALSE;
|
|
mono_profiler_state.context_enable ();
|
|
return mono_profiler_state.call_contexts = TRUE;
|
}
|
|
/**
|
* mono_profiler_call_context_get_this:
|
*
|
* Given a valid call context from an enter/leave event, retrieves a pointer to
|
* the \c this reference for the method. Returns \c NULL if none exists (i.e.
|
* it's a static method) or if call context introspection was not enabled.
|
*
|
* The buffer returned by this function must be freed with
|
* \c mono_profiler_call_context_free_buffer.
|
*
|
* Please note that a call context is only valid for the duration of the
|
* enter/leave callback it was passed to.
|
*
|
* This function is \b not async safe.
|
*/
|
void *
|
mono_profiler_call_context_get_this (MonoProfilerCallContext *context)
|
{
|
if (!mono_profiler_state.call_contexts)
|
return NULL;
|
|
return mono_profiler_state.context_get_this (context);
|
}
|
|
/**
|
* mono_profiler_call_context_get_argument:
|
*
|
* Given a valid call context from an enter/leave event, retrieves a pointer to
|
* the method argument at the given position. Returns \c NULL if \p position is
|
* out of bounds or if call context introspection was not enabled.
|
*
|
* The buffer returned by this function must be freed with
|
* \c mono_profiler_call_context_free_buffer.
|
*
|
* Please note that a call context is only valid for the duration of the
|
* enter/leave callback it was passed to.
|
*
|
* This function is \b not async safe.
|
*/
|
void *
|
mono_profiler_call_context_get_argument (MonoProfilerCallContext *context, uint32_t position)
|
{
|
if (!mono_profiler_state.call_contexts)
|
return NULL;
|
|
return mono_profiler_state.context_get_argument (context, position);
|
}
|
|
/**
|
* mono_profiler_call_context_get_local:
|
*
|
* Given a valid call context from an enter/leave event, retrieves a pointer to
|
* the local variable at the given position. Returns \c NULL if \p position is
|
* out of bounds or if call context introspection was not enabled.
|
*
|
* The buffer returned by this function must be freed with
|
* \c mono_profiler_call_context_free_buffer.
|
*
|
* Please note that a call context is only valid for the duration of the
|
* enter/leave callback it was passed to.
|
*
|
* This function is \b not async safe.
|
*/
|
void *
|
mono_profiler_call_context_get_local (MonoProfilerCallContext *context, uint32_t position)
|
{
|
if (!mono_profiler_state.call_contexts)
|
return NULL;
|
|
return mono_profiler_state.context_get_local (context, position);
|
}
|
|
/**
|
* mono_profiler_call_context_get_result:
|
*
|
* Given a valid call context from an enter/leave event, retrieves a pointer to
|
* return value of a method. Returns \c NULL if the method has no return value
|
* (i.e. it returns \c void), if the leave event was the result of a tail call,
|
* if the function is called on a context from an enter event, or if call
|
* context introspection was not enabled.
|
*
|
* The buffer returned by this function must be freed with
|
* \c mono_profiler_call_context_free_buffer.
|
*
|
* Please note that a call context is only valid for the duration of the
|
* enter/leave callback it was passed to.
|
*
|
* This function is \b not async safe.
|
*/
|
void *
|
mono_profiler_call_context_get_result (MonoProfilerCallContext *context)
|
{
|
if (!mono_profiler_state.call_contexts)
|
return NULL;
|
|
return mono_profiler_state.context_get_result (context);
|
}
|
|
/**
|
* mono_profiler_call_context_free_buffer:
|
*
|
* Frees a buffer returned by one of the call context introspection functions.
|
* Passing a \c NULL value for \p buffer is allowed, which makes this function
|
* a no-op.
|
*
|
* This function is \b not async safe.
|
*/
|
void
|
mono_profiler_call_context_free_buffer (void *buffer)
|
{
|
mono_profiler_state.context_free_buffer (buffer);
|
}
|
|
MonoProfilerCallInstrumentationFlags
|
mono_profiler_get_call_instrumentation_flags (MonoMethod *method)
|
{
|
MonoProfilerCallInstrumentationFlags flags = MONO_PROFILER_CALL_INSTRUMENTATION_NONE;
|
|
for (MonoProfilerHandle handle = mono_profiler_state.profilers; handle; handle = handle->next) {
|
MonoProfilerCallInstrumentationFilterCallback cb = handle->call_instrumentation_filter;
|
|
if (cb)
|
flags |= cb (handle->prof, method);
|
}
|
|
return flags;
|
}
|
|
void
|
mono_profiler_started (void)
|
{
|
mono_profiler_state.startup_done = TRUE;
|
}
|
|
void
|
mono_profiler_cleanup (void)
|
{
|
for (MonoProfilerHandle handle = mono_profiler_state.profilers; handle; handle = handle->next) {
|
#define _MONO_PROFILER_EVENT(name) \
|
mono_profiler_set_ ## name ## _callback (handle, NULL); \
|
g_assert (!handle->name ## _cb);
|
#define MONO_PROFILER_EVENT_0(name, type) \
|
_MONO_PROFILER_EVENT(name)
|
#define MONO_PROFILER_EVENT_1(name, type, arg1_type, arg1_name) \
|
_MONO_PROFILER_EVENT(name)
|
#define MONO_PROFILER_EVENT_2(name, type, arg1_type, arg1_name, arg2_type, arg2_name) \
|
_MONO_PROFILER_EVENT(name)
|
#define MONO_PROFILER_EVENT_3(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name) \
|
_MONO_PROFILER_EVENT(name)
|
#define MONO_PROFILER_EVENT_4(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name, arg4_type, arg4_name) \
|
_MONO_PROFILER_EVENT(name)
|
#define MONO_PROFILER_EVENT_5(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name, arg4_type, arg4_name, arg5_type, arg5_name) \
|
_MONO_PROFILER_EVENT(name)
|
#include <mono/metadata/profiler-events.h>
|
#undef MONO_PROFILER_EVENT_0
|
#undef MONO_PROFILER_EVENT_1
|
#undef MONO_PROFILER_EVENT_2
|
#undef MONO_PROFILER_EVENT_3
|
#undef MONO_PROFILER_EVENT_4
|
#undef MONO_PROFILER_EVENT_5
|
#undef _MONO_PROFILER_EVENT
|
}
|
|
#define _MONO_PROFILER_EVENT(name, type) \
|
g_assert (!mono_profiler_state.name ## _count);
|
#define MONO_PROFILER_EVENT_0(name, type) \
|
_MONO_PROFILER_EVENT(name, type)
|
#define MONO_PROFILER_EVENT_1(name, type, arg1_type, arg1_name) \
|
_MONO_PROFILER_EVENT(name, type)
|
#define MONO_PROFILER_EVENT_2(name, type, arg1_type, arg1_name, arg2_type, arg2_name) \
|
_MONO_PROFILER_EVENT(name, type)
|
#define MONO_PROFILER_EVENT_3(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name) \
|
_MONO_PROFILER_EVENT(name, type)
|
#define MONO_PROFILER_EVENT_4(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name, arg4_type, arg4_name) \
|
_MONO_PROFILER_EVENT(name, type)
|
#define MONO_PROFILER_EVENT_5(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name, arg4_type, arg4_name, arg5_type, arg5_name) \
|
_MONO_PROFILER_EVENT(name, type)
|
#include <mono/metadata/profiler-events.h>
|
#undef MONO_PROFILER_EVENT_0
|
#undef MONO_PROFILER_EVENT_1
|
#undef MONO_PROFILER_EVENT_2
|
#undef MONO_PROFILER_EVENT_3
|
#undef MONO_PROFILER_EVENT_4
|
#undef MONO_PROFILER_EVENT_5
|
#undef _MONO_PROFILER_EVENT
|
|
MonoProfilerHandle head = mono_profiler_state.profilers;
|
|
while (head) {
|
MonoProfilerCleanupCallback cb = head->cleanup_callback;
|
|
if (cb)
|
cb (head->prof);
|
|
MonoProfilerHandle cur = head;
|
head = head->next;
|
|
g_free (cur);
|
}
|
|
if (mono_profiler_state.code_coverage) {
|
mono_os_mutex_destroy (&mono_profiler_state.coverage_mutex);
|
|
GHashTableIter iter;
|
|
g_hash_table_iter_init (&iter, mono_profiler_state.coverage_hash);
|
|
MonoProfilerCoverageInfo *info;
|
|
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
|
g_free (info);
|
|
g_hash_table_destroy (mono_profiler_state.coverage_hash);
|
}
|
|
if (mono_profiler_state.sampling_owner)
|
mono_os_sem_destroy (&mono_profiler_state.sampling_semaphore);
|
}
|
|
static void
|
update_callback (volatile gpointer *location, gpointer new_, volatile gint32 *counter)
|
{
|
gpointer old;
|
|
do {
|
old = mono_atomic_load_ptr (location);
|
} while (mono_atomic_cas_ptr (location, new_, old) != old);
|
|
/*
|
* At this point, we could have installed a NULL callback while the counter
|
* is still non-zero, i.e. setting the callback and modifying the counter
|
* is not a single atomic operation. This is fine as we make sure callbacks
|
* are non-NULL before invoking them (see the code below that generates the
|
* raise functions), and besides, updating callbacks at runtime is an
|
* inherently racy operation.
|
*/
|
|
if (old)
|
mono_atomic_dec_i32 (counter);
|
|
if (new_)
|
mono_atomic_inc_i32 (counter);
|
}
|
|
#define _MONO_PROFILER_EVENT(name, type) \
|
void \
|
mono_profiler_set_ ## name ## _callback (MonoProfilerHandle handle, MonoProfiler ## type ## Callback cb) \
|
{ \
|
update_callback (&handle->name ## _cb, (gpointer) cb, &mono_profiler_state.name ## _count); \
|
}
|
#define MONO_PROFILER_EVENT_0(name, type) \
|
_MONO_PROFILER_EVENT(name, type)
|
#define MONO_PROFILER_EVENT_1(name, type, arg1_type, arg1_name) \
|
_MONO_PROFILER_EVENT(name, type)
|
#define MONO_PROFILER_EVENT_2(name, type, arg1_type, arg1_name, arg2_type, arg2_name) \
|
_MONO_PROFILER_EVENT(name, type)
|
#define MONO_PROFILER_EVENT_3(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name) \
|
_MONO_PROFILER_EVENT(name, type)
|
#define MONO_PROFILER_EVENT_4(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name, arg4_type, arg4_name) \
|
_MONO_PROFILER_EVENT(name, type)
|
#define MONO_PROFILER_EVENT_5(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name, arg4_type, arg4_name, arg5_type, arg5_name) \
|
_MONO_PROFILER_EVENT(name, type)
|
#include <mono/metadata/profiler-events.h>
|
#undef MONO_PROFILER_EVENT_0
|
#undef MONO_PROFILER_EVENT_1
|
#undef MONO_PROFILER_EVENT_2
|
#undef MONO_PROFILER_EVENT_3
|
#undef MONO_PROFILER_EVENT_4
|
#undef MONO_PROFILER_EVENT_5
|
#undef _MONO_PROFILER_EVENT
|
|
#define _MONO_PROFILER_EVENT(name, type, params, args) \
|
void \
|
mono_profiler_raise_ ## name params \
|
{ \
|
for (MonoProfilerHandle h = mono_profiler_state.profilers; h; h = h->next) { \
|
MonoProfiler ## type ## Callback cb = h->name ## _cb; \
|
if (cb) \
|
cb args; \
|
} \
|
}
|
#define MONO_PROFILER_EVENT_0(name, type) \
|
_MONO_PROFILER_EVENT(name, type, (void), (h->prof))
|
#define MONO_PROFILER_EVENT_1(name, type, arg1_type, arg1_name) \
|
_MONO_PROFILER_EVENT(name, type, (arg1_type arg1_name), (h->prof, arg1_name))
|
#define MONO_PROFILER_EVENT_2(name, type, arg1_type, arg1_name, arg2_type, arg2_name) \
|
_MONO_PROFILER_EVENT(name, type, (arg1_type arg1_name, arg2_type arg2_name), (h->prof, arg1_name, arg2_name))
|
#define MONO_PROFILER_EVENT_3(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name) \
|
_MONO_PROFILER_EVENT(name, type, (arg1_type arg1_name, arg2_type arg2_name, arg3_type arg3_name), (h->prof, arg1_name, arg2_name, arg3_name))
|
#define MONO_PROFILER_EVENT_4(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name, arg4_type, arg4_name) \
|
_MONO_PROFILER_EVENT(name, type, (arg1_type arg1_name, arg2_type arg2_name, arg3_type arg3_name, arg4_type arg4_name), (h->prof, arg1_name, arg2_name, arg3_name, arg4_name))
|
#define MONO_PROFILER_EVENT_5(name, type, arg1_type, arg1_name, arg2_type, arg2_name, arg3_type, arg3_name, arg4_type, arg4_name, arg5_type, arg5_name) \
|
_MONO_PROFILER_EVENT(name, type, (arg1_type arg1_name, arg2_type arg2_name, arg3_type arg3_name, arg4_type arg4_name, arg5_type arg5_name), (h->prof, arg1_name, arg2_name, arg3_name, arg4_name, arg5_name))
|
#include <mono/metadata/profiler-events.h>
|
#undef MONO_PROFILER_EVENT_0
|
#undef MONO_PROFILER_EVENT_1
|
#undef MONO_PROFILER_EVENT_2
|
#undef MONO_PROFILER_EVENT_3
|
#undef MONO_PROFILER_EVENT_4
|
#undef MONO_PROFILER_EVENT_5
|
#undef _MONO_PROFILER_EVENT
|
|
/*
|
* The following code is here to maintain compatibility with a few profiler API
|
* functions used by Xamarin.{Android,iOS,Mac} so that they keep working
|
* regardless of which system Mono version is used.
|
*
|
* TODO: Remove this some day if we're OK with breaking compatibility.
|
*/
|
|
typedef void *MonoLegacyProfiler;
|
|
typedef void (*MonoLegacyProfileFunc) (MonoLegacyProfiler *prof);
|
typedef void (*MonoLegacyProfileThreadFunc) (MonoLegacyProfiler *prof, uintptr_t tid);
|
typedef void (*MonoLegacyProfileGCFunc) (MonoLegacyProfiler *prof, MonoProfilerGCEvent event, int generation);
|
typedef void (*MonoLegacyProfileGCResizeFunc) (MonoLegacyProfiler *prof, int64_t new_size);
|
typedef void (*MonoLegacyProfileJitResult) (MonoLegacyProfiler *prof, MonoMethod *method, MonoJitInfo *jinfo, int result);
|
typedef void (*MonoLegacyProfileAllocFunc) (MonoLegacyProfiler *prof, MonoObject *obj, MonoClass *klass);
|
typedef void (*MonoLegacyProfileMethodFunc) (MonoLegacyProfiler *prof, MonoMethod *method);
|
typedef void (*MonoLegacyProfileExceptionFunc) (MonoLegacyProfiler *prof, MonoObject *object);
|
typedef void (*MonoLegacyProfileExceptionClauseFunc) (MonoLegacyProfiler *prof, MonoMethod *method, int clause_type, int clause_num);
|
typedef void (*MonoLegacyProfileFileIOFunc) (MonoLegacyProfiler *prof, int kind, int size);
|
|
struct _MonoProfiler {
|
MonoProfilerHandle handle;
|
MonoLegacyProfiler *profiler;
|
MonoLegacyProfileFunc shutdown_callback;
|
MonoLegacyProfileThreadFunc thread_start, thread_end;
|
MonoLegacyProfileGCFunc gc_event;
|
MonoLegacyProfileGCResizeFunc gc_heap_resize;
|
MonoLegacyProfileJitResult jit_end2;
|
MonoLegacyProfileAllocFunc allocation;
|
MonoLegacyProfileFileIOFunc fileio;
|
MonoLegacyProfileMethodFunc enter;
|
MonoLegacyProfileMethodFunc leave;
|
MonoLegacyProfileExceptionFunc throw_callback;
|
MonoLegacyProfileMethodFunc exc_method_leave;
|
MonoLegacyProfileExceptionClauseFunc clause_callback;
|
};
|
|
static MonoProfiler *current;
|
|
MONO_API void mono_profiler_install (MonoLegacyProfiler *prof, MonoLegacyProfileFunc callback);
|
MONO_API void mono_profiler_install_thread (MonoLegacyProfileThreadFunc start, MonoLegacyProfileThreadFunc end);
|
MONO_API void mono_profiler_install_gc (MonoLegacyProfileGCFunc callback, MonoLegacyProfileGCResizeFunc heap_resize_callback);
|
MONO_API void mono_profiler_install_jit_end (MonoLegacyProfileJitResult end);
|
MONO_API void mono_profiler_set_events (int flags);
|
MONO_API void mono_profiler_install_allocation (MonoLegacyProfileAllocFunc callback);
|
MONO_API void mono_profiler_install_enter_leave (MonoLegacyProfileMethodFunc enter, MonoLegacyProfileMethodFunc fleave);
|
MONO_API void mono_profiler_install_fileio (MonoLegacyProfileFileIOFunc callback);
|
MONO_API void mono_profiler_install_exception (MonoLegacyProfileExceptionFunc throw_callback, MonoLegacyProfileMethodFunc exc_method_leave, MonoLegacyProfileExceptionClauseFunc clause_callback);
|
|
static void
|
shutdown_cb (MonoProfiler *prof)
|
{
|
prof->shutdown_callback (prof->profiler);
|
}
|
|
void
|
mono_profiler_install (MonoLegacyProfiler *prof, MonoLegacyProfileFunc callback)
|
{
|
current = g_new0 (MonoProfiler, 1);
|
current->handle = mono_profiler_create (current);
|
current->profiler = prof;
|
current->shutdown_callback = callback;
|
|
if (callback)
|
mono_profiler_set_runtime_shutdown_end_callback (current->handle, shutdown_cb);
|
}
|
|
static void
|
thread_start_cb (MonoProfiler *prof, uintptr_t tid)
|
{
|
prof->thread_start (prof->profiler, tid);
|
}
|
|
static void
|
thread_stop_cb (MonoProfiler *prof, uintptr_t tid)
|
{
|
prof->thread_end (prof->profiler, tid);
|
}
|
|
void
|
mono_profiler_install_thread (MonoLegacyProfileThreadFunc start, MonoLegacyProfileThreadFunc end)
|
{
|
current->thread_start = start;
|
current->thread_end = end;
|
|
if (start)
|
mono_profiler_set_thread_started_callback (current->handle, thread_start_cb);
|
|
if (end)
|
mono_profiler_set_thread_stopped_callback (current->handle, thread_stop_cb);
|
}
|
|
static void
|
gc_event_cb (MonoProfiler *prof, MonoProfilerGCEvent event, uint32_t generation)
|
{
|
prof->gc_event (prof->profiler, event, generation);
|
}
|
|
static void
|
gc_resize_cb (MonoProfiler *prof, uintptr_t size)
|
{
|
prof->gc_heap_resize (prof->profiler, size);
|
}
|
|
void
|
mono_profiler_install_gc (MonoLegacyProfileGCFunc callback, MonoLegacyProfileGCResizeFunc heap_resize_callback)
|
{
|
current->gc_event = callback;
|
current->gc_heap_resize = heap_resize_callback;
|
|
if (callback)
|
mono_profiler_set_gc_event_callback (current->handle, gc_event_cb);
|
|
if (heap_resize_callback)
|
mono_profiler_set_gc_resize_callback (current->handle, gc_resize_cb);
|
}
|
|
static void
|
jit_done_cb (MonoProfiler *prof, MonoMethod *method, MonoJitInfo *jinfo)
|
{
|
prof->jit_end2 (prof->profiler, method, jinfo, 0);
|
}
|
|
static void
|
jit_failed_cb (MonoProfiler *prof, MonoMethod *method)
|
{
|
prof->jit_end2 (prof->profiler, method, NULL, 1);
|
}
|
|
void
|
mono_profiler_install_jit_end (MonoLegacyProfileJitResult end)
|
{
|
current->jit_end2 = end;
|
|
if (end) {
|
mono_profiler_set_jit_done_callback (current->handle, jit_done_cb);
|
mono_profiler_set_jit_failed_callback (current->handle, jit_failed_cb);
|
}
|
}
|
|
|
static MonoProfilerCallInstrumentationFlags
|
call_instrumentation_filter_callback (MonoProfiler *prof, MonoMethod *method)
|
{
|
return MONO_PROFILER_CALL_INSTRUMENTATION_ENTER | MONO_PROFILER_CALL_INSTRUMENTATION_LEAVE | MONO_PROFILER_CALL_INSTRUMENTATION_TAIL_CALL | MONO_PROFILER_CALL_INSTRUMENTATION_EXCEPTION_LEAVE;
|
}
|
|
typedef enum
|
{
|
MONO_PROFILE_NONE = 0,
|
MONO_PROFILE_APPDOMAIN_EVENTS = 1 << 0,
|
MONO_PROFILE_ASSEMBLY_EVENTS = 1 << 1,
|
MONO_PROFILE_MODULE_EVENTS = 1 << 2,
|
MONO_PROFILE_CLASS_EVENTS = 1 << 3,
|
MONO_PROFILE_JIT_COMPILATION = 1 << 4,
|
MONO_PROFILE_INLINING = 1 << 5,
|
MONO_PROFILE_EXCEPTIONS = 1 << 6,
|
MONO_PROFILE_ALLOCATIONS = 1 << 7,
|
MONO_PROFILE_GC = 1 << 8,
|
MONO_PROFILE_THREADS = 1 << 9,
|
MONO_PROFILE_REMOTING = 1 << 10,
|
MONO_PROFILE_TRANSITIONS = 1 << 11,
|
MONO_PROFILE_ENTER_LEAVE = 1 << 12,
|
MONO_PROFILE_COVERAGE = 1 << 13,
|
MONO_PROFILE_INS_COVERAGE = 1 << 14,
|
MONO_PROFILE_STATISTICAL = 1 << 15,
|
MONO_PROFILE_FILEIO = 1 << 16
|
} LegacyMonoProfileFlags;
|
|
void
|
mono_profiler_set_events (int flags)
|
{
|
if (flags & MONO_PROFILE_ENTER_LEAVE)
|
mono_profiler_set_call_instrumentation_filter_callback (current->handle, call_instrumentation_filter_callback);
|
else
|
mono_profiler_set_call_instrumentation_filter_callback (current->handle, NULL);
|
/* Do nothing. */
|
|
if (flags & MONO_PROFILE_ALLOCATIONS)
|
mono_profiler_enable_allocations ();
|
|
if (flags & MONO_PROFILE_FILEIO)
|
mono_profiler_enable_fileio ();
|
}
|
|
static void
|
allocation_cb (MonoProfiler *prof, MonoObject* object)
|
{
|
prof->allocation (prof->profiler, object, object->vtable->klass);
|
}
|
|
void
|
mono_profiler_install_allocation (MonoLegacyProfileAllocFunc callback)
|
{
|
current->allocation = callback;
|
|
if (callback)
|
mono_profiler_set_gc_allocation_callback (current->handle, allocation_cb);
|
}
|
|
static void
|
fileio_cb (MonoProfiler *prof, uint64_t kind, uint64_t size)
|
{
|
prof->fileio (prof->profiler, kind, size);
|
}
|
|
void
|
mono_profiler_install_fileio (MonoLegacyProfileFileIOFunc callback)
|
{
|
current->fileio = callback;
|
|
if (callback)
|
mono_profiler_set_fileio_callback (current->handle, fileio_cb);
|
}
|
|
static void
|
enter_cb (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *context)
|
{
|
prof->enter (prof->profiler, method);
|
}
|
|
static void
|
leave_cb (MonoProfiler *prof, MonoMethod *method, MonoProfilerCallContext *context)
|
{
|
prof->leave (prof->profiler, method);
|
}
|
|
static void
|
tail_call_cb (MonoProfiler *prof, MonoMethod *method, MonoMethod *target)
|
{
|
prof->leave (prof->profiler, method);
|
}
|
|
void
|
mono_profiler_install_enter_leave (MonoLegacyProfileMethodFunc enter, MonoLegacyProfileMethodFunc fleave)
|
{
|
current->enter = enter;
|
current->leave = fleave;
|
|
if (enter)
|
mono_profiler_set_method_enter_callback (current->handle, enter_cb);
|
|
if (fleave) {
|
mono_profiler_set_method_leave_callback (current->handle, leave_cb);
|
mono_profiler_set_method_tail_call_callback (current->handle, tail_call_cb);
|
}
|
}
|
|
static void
|
throw_callback_cb (MonoProfiler *prof, MonoObject *exception)
|
{
|
prof->throw_callback (prof->profiler, exception);
|
}
|
|
static void
|
exc_method_leave_cb (MonoProfiler *prof, MonoMethod *method, MonoObject *exception)
|
{
|
prof->exc_method_leave (prof->profiler, method);
|
}
|
|
static void
|
clause_callback_cb (MonoProfiler *prof, MonoMethod *method, uint32_t index, MonoExceptionEnum type, MonoObject *exception)
|
{
|
prof->clause_callback (prof->profiler, method, type, index);
|
}
|
|
void
|
mono_profiler_install_exception (MonoLegacyProfileExceptionFunc throw_callback, MonoLegacyProfileMethodFunc exc_method_leave, MonoLegacyProfileExceptionClauseFunc clause_callback)
|
{
|
current->throw_callback = throw_callback;
|
current->exc_method_leave = exc_method_leave;
|
current->clause_callback = clause_callback;
|
|
if (throw_callback)
|
mono_profiler_set_exception_throw_callback (current->handle, throw_callback_cb);
|
|
if (exc_method_leave)
|
mono_profiler_set_method_exception_leave_callback (current->handle, exc_method_leave_cb);
|
|
if (clause_callback)
|
mono_profiler_set_exception_clause_callback (current->handle, clause_callback_cb);
|
}
|