/* * Copyright 2006-2010 Novell * Copyright 2011 Xamarin Inc * Licensed under the MIT license. See LICENSE file in the project root for full license information. */ #include #include #include "config.h" #include "mono-counters.h" #include "mono-proclib.h" #include "mono-os-mutex.h" #ifdef HAVE_UNISTD_H #include #endif struct _MonoCounter { MonoCounter *next; const char *name; void *addr; int type; size_t size; }; static MonoCounter *counters = NULL; static mono_mutex_t counters_mutex; static volatile gboolean initialized = FALSE; static int valid_mask = 0; static int set_mask = 0; static GSList *register_callbacks = NULL; static void initialize_system_counters (void); /** * mono_counter_get_variance: * @counter: counter to get the variance * * Variance specifies how the counter value is expected to behave between any two samplings. * * Returns: the monotonicity of the counter. */ int mono_counter_get_variance (MonoCounter *counter) { return counter->type & MONO_COUNTER_VARIANCE_MASK; } /** * mono_counter_get_unit: * @counter: counter to get the unit * * The unit gives a high level view of the unit that the counter is measuring. * * Returns: the unit of the counter. */ int mono_counter_get_unit (MonoCounter *counter) { return counter->type & MONO_COUNTER_UNIT_MASK; } /** * mono_counter_get_section: * @counter: counter to get the section * * Sections are the unit of organization between all counters. * * Returns: the section of the counter. */ int mono_counter_get_section (MonoCounter *counter) { return counter->type & MONO_COUNTER_SECTION_MASK; } /** * mono_counter_get_type: * @counter: counter to get the type * * Returns the type used to strong the value of the counter. * * Returns:the type of the counter. */ int mono_counter_get_type (MonoCounter *counter) { return counter->type & MONO_COUNTER_TYPE_MASK; } /** * mono_counter_get_name: * @counter: counter to get the name * * Returns the counter name. The string should not be freed. * * Returns the name of the counter. */ const char* mono_counter_get_name (MonoCounter *counter) { return counter->name; } /** * mono_counter_get_size: * @counter: counter to get the max size of the counter * * Use the returned size to create the buffer used with mono_counters_sample * * Returns: the max size of the counter data. */ size_t mono_counter_get_size (MonoCounter *counter) { return counter->size; } /** * mono_counters_enable: * @section_mask: a mask listing the sections that will be displayed * * This is used to track which counters will be displayed. */ void mono_counters_enable (int section_mask) { valid_mask = section_mask & MONO_COUNTER_SECTION_MASK; } void mono_counters_init (void) { if (initialized) return; mono_os_mutex_init (&counters_mutex); initialize_system_counters (); initialized = TRUE; } static void register_internal (const char *name, int type, void *addr, int size) { MonoCounter *counter; GSList *register_callback; g_assert (size >= 0); if ((type & MONO_COUNTER_VARIANCE_MASK) == 0) type |= MONO_COUNTER_MONOTONIC; mono_os_mutex_lock (&counters_mutex); for (counter = counters; counter; counter = counter->next) { if (counter->addr == addr) { g_warning ("you are registering twice the same counter address"); mono_os_mutex_unlock (&counters_mutex); return; } } counter = (MonoCounter *) malloc (sizeof (MonoCounter)); if (!counter) { mono_os_mutex_unlock (&counters_mutex); return; } counter->name = g_strdup (name); counter->type = type; counter->addr = addr; counter->next = NULL; counter->size = size; set_mask |= type; /* Append */ if (counters) { MonoCounter *item = counters; while (item->next) item = item->next; item->next = counter; } else { counters = counter; } for (register_callback = register_callbacks; register_callback; register_callback = register_callback->next) ((MonoCounterRegisterCallback)register_callback->data) (counter); mono_os_mutex_unlock (&counters_mutex); } /** * mono_counters_register: * @name: The name for this counters. * @type: One of the possible MONO_COUNTER types, or MONO_COUNTER_CALLBACK for a function pointer. * @addr: The address to register. * * Register addr as the address of a counter of type type. * Note that @name must be a valid string at all times until * mono_counters_dump () is called. * * This function should not be used with counter types that require an explicit size such as string * as the counter size will be set to zero making them effectively useless. * * * It may be a function pointer if MONO_COUNTER_CALLBACK is specified: * the function should return the value and take no arguments. */ void mono_counters_register (const char* name, int type, void *addr) { int size; switch (type & MONO_COUNTER_TYPE_MASK) { case MONO_COUNTER_INT: size = sizeof (int); break; case MONO_COUNTER_UINT: size = sizeof (guint); break; case MONO_COUNTER_LONG: case MONO_COUNTER_TIME_INTERVAL: size = sizeof (gint64); break; case MONO_COUNTER_ULONG: size = sizeof (guint64); break; case MONO_COUNTER_WORD: size = sizeof (gssize); break; case MONO_COUNTER_DOUBLE: size = sizeof (double); break; case MONO_COUNTER_STRING: size = 0; break; default: g_assert_not_reached (); } if (!initialized) g_debug ("counters not enabled"); else register_internal (name, type, addr, size); } /** * mono_counters_register_with_size: * @name: The name for this counters. * @type: One of the possible MONO_COUNTER types, or MONO_COUNTER_CALLBACK for a function pointer. * @addr: The address to register. * @size: Max size of the counter data. * * Register addr as the address of a counter of type @type. * Note that @name must be a valid string at all times until * mono_counters_dump () is called. * * It may be a function pointer if MONO_COUNTER_CALLBACK is specified: * the function should return the value and take no arguments. * * The value of @size is ignored for types with fixed size such as int and long. * * Use @size for types that can have dynamic size such as string. * * If @size is negative, it's silently converted to zero. */ void mono_counters_register_with_size (const char *name, int type, void *addr, int size) { if (!initialized) g_debug ("counters not enabled"); else register_internal (name, type, addr, size); } /** * mono_counters_on_register * @callback : function to callback when a counter is registered * * Add a callback that is going to be called when a counter is registered */ void mono_counters_on_register (MonoCounterRegisterCallback callback) { if (!initialized) { g_debug ("counters not enabled"); return; } mono_os_mutex_lock (&counters_mutex); register_callbacks = g_slist_append (register_callbacks, (gpointer) callback); mono_os_mutex_unlock (&counters_mutex); } typedef int (*IntFunc) (void); typedef guint (*UIntFunc) (void); typedef gint64 (*LongFunc) (void); typedef guint64 (*ULongFunc) (void); typedef gssize (*PtrFunc) (void); typedef double (*DoubleFunc) (void); typedef char* (*StrFunc) (void); static gint64 user_time (void) { return mono_process_get_data (GINT_TO_POINTER (mono_process_current_pid ()), MONO_PROCESS_USER_TIME); } static gint64 system_time (void) { return mono_process_get_data (GINT_TO_POINTER (mono_process_current_pid ()), MONO_PROCESS_SYSTEM_TIME); } static gint64 total_time (void) { return mono_process_get_data (GINT_TO_POINTER (mono_process_current_pid ()), MONO_PROCESS_TOTAL_TIME); } static gint64 working_set (void) { return mono_process_get_data (GINT_TO_POINTER (mono_process_current_pid ()), MONO_PROCESS_WORKING_SET); } static gint64 private_bytes (void) { return mono_process_get_data (GINT_TO_POINTER (mono_process_current_pid ()), MONO_PROCESS_PRIVATE_BYTES); } static gint64 virtual_bytes (void) { return mono_process_get_data (GINT_TO_POINTER (mono_process_current_pid ()), MONO_PROCESS_VIRTUAL_BYTES); } static gint64 page_faults (void) { return mono_process_get_data (GINT_TO_POINTER (mono_process_current_pid ()), MONO_PROCESS_FAULTS); } static double cpu_load (int kind) { #if defined(TARGET_WIN32) #elif defined(TARGET_MACH) double load [3]; if (getloadavg (load, 3) > 0) return load [kind]; #else char buffer[512], *b; int len, i; FILE *f = fopen ("/proc/loadavg", "r"); if (f) { len = fread (buffer, 1, sizeof (buffer) - 1, f); fclose (f); if (len > 0) { buffer [len < 511 ? len : 511] = 0; b = buffer; for (i = 0; i < 3; i++) { if (kind == i) return strtod (b, NULL); if (i < 2) { b = strchr (b, ' '); if (!b) return 0; b += 1; } } } } #endif return 0; } static double cpu_load_1min (void) { return cpu_load (0); } static double cpu_load_5min (void) { return cpu_load (1); } static double cpu_load_15min (void) { return cpu_load (2); } #define SYSCOUNTER_TIME (MONO_COUNTER_SYSTEM | MONO_COUNTER_LONG | MONO_COUNTER_TIME | MONO_COUNTER_MONOTONIC | MONO_COUNTER_CALLBACK) #define SYSCOUNTER_BYTES (MONO_COUNTER_SYSTEM | MONO_COUNTER_LONG | MONO_COUNTER_BYTES | MONO_COUNTER_VARIABLE | MONO_COUNTER_CALLBACK) #define SYSCOUNTER_COUNT (MONO_COUNTER_SYSTEM | MONO_COUNTER_LONG | MONO_COUNTER_COUNT | MONO_COUNTER_MONOTONIC | MONO_COUNTER_CALLBACK) #define SYSCOUNTER_LOAD (MONO_COUNTER_SYSTEM | MONO_COUNTER_DOUBLE | MONO_COUNTER_PERCENTAGE | MONO_COUNTER_VARIABLE | MONO_COUNTER_CALLBACK) static void initialize_system_counters (void) { register_internal ("User Time", SYSCOUNTER_TIME, (gpointer) &user_time, sizeof (gint64)); register_internal ("System Time", SYSCOUNTER_TIME, (gpointer) &system_time, sizeof (gint64)); register_internal ("Total Time", SYSCOUNTER_TIME, (gpointer) &total_time, sizeof (gint64)); register_internal ("Working Set", SYSCOUNTER_BYTES, (gpointer) &working_set, sizeof (gint64)); register_internal ("Private Bytes", SYSCOUNTER_BYTES, (gpointer) &private_bytes, sizeof (gint64)); register_internal ("Virtual Bytes", SYSCOUNTER_BYTES, (gpointer) &virtual_bytes, sizeof (gint64)); register_internal ("Page Faults", SYSCOUNTER_COUNT, (gpointer) &page_faults, sizeof (gint64)); register_internal ("CPU Load Average - 1min", SYSCOUNTER_LOAD, (gpointer) &cpu_load_1min, sizeof (double)); register_internal ("CPU Load Average - 5min", SYSCOUNTER_LOAD, (gpointer) &cpu_load_5min, sizeof (double)); register_internal ("CPU Load Average - 15min", SYSCOUNTER_LOAD, (gpointer) &cpu_load_15min, sizeof (double)); } /** * mono_counters_foreach: * @cb: The callback that will be called for each counter. * @user_data: Value passed as second argument of the callback. * * Iterate over all counters and call @cb for each one of them. Stop iterating if * the callback returns FALSE. * */ void mono_counters_foreach (CountersEnumCallback cb, gpointer user_data) { MonoCounter *counter; if (!initialized) { g_debug ("counters not enabled"); return; } mono_os_mutex_lock (&counters_mutex); for (counter = counters; counter; counter = counter->next) { if (!cb (counter, user_data)) { mono_os_mutex_unlock (&counters_mutex); return; } } mono_os_mutex_unlock (&counters_mutex); } #define COPY_COUNTER(type,functype) do { \ size = sizeof (type); \ if (buffer_size < size) \ size = -1; \ else \ *(type*)buffer = cb ? ((functype)counter->addr) () : *(type*)counter->addr; \ } while (0); /* lockless */ static int sample_internal (MonoCounter *counter, void *buffer, int buffer_size) { int cb = counter->type & MONO_COUNTER_CALLBACK; int size = -1; char *strval; switch (mono_counter_get_type (counter)) { case MONO_COUNTER_INT: COPY_COUNTER (int, IntFunc); break; case MONO_COUNTER_UINT: COPY_COUNTER (guint, UIntFunc); break; case MONO_COUNTER_LONG: case MONO_COUNTER_TIME_INTERVAL: COPY_COUNTER (gint64, LongFunc); break; case MONO_COUNTER_ULONG: COPY_COUNTER (guint64, ULongFunc); break; case MONO_COUNTER_WORD: COPY_COUNTER (gssize, PtrFunc); break; case MONO_COUNTER_DOUBLE: COPY_COUNTER (double, DoubleFunc); break; case MONO_COUNTER_STRING: if (buffer_size < counter->size) { size = -1; } else if (counter->size == 0) { size = 0; } else { strval = cb ? ((StrFunc)counter->addr) () : (char*)counter->addr; if (!strval) { size = 0; } else { size = counter->size; strncpy ((char *) buffer, strval, size - 1); ((char*)buffer)[size - 1] = '\0'; } } } return size; } int mono_counters_sample (MonoCounter *counter, void *buffer, int buffer_size) { if (!initialized) { g_debug ("counters not enabled"); return -1; } return sample_internal (counter, buffer, buffer_size); } #define ENTRY_FMT "%-36s: " static void dump_counter (MonoCounter *counter, FILE *outfile) { void *buffer = g_malloc0 (counter->size); int size = sample_internal (counter, buffer, counter->size); switch (counter->type & MONO_COUNTER_TYPE_MASK) { case MONO_COUNTER_INT: fprintf (outfile, ENTRY_FMT "%d\n", counter->name, *(int*)buffer); break; case MONO_COUNTER_UINT: fprintf (outfile, ENTRY_FMT "%u\n", counter->name, *(guint*)buffer); break; case MONO_COUNTER_LONG: if ((counter->type & MONO_COUNTER_UNIT_MASK) == MONO_COUNTER_TIME) fprintf (outfile, ENTRY_FMT "%.2f ms\n", counter->name, (double)(*(gint64*)buffer) / 10000.0); else fprintf (outfile, ENTRY_FMT "%lld\n", counter->name, *(long long *)buffer); break; case MONO_COUNTER_ULONG: if ((counter->type & MONO_COUNTER_UNIT_MASK) == MONO_COUNTER_TIME) fprintf (outfile, ENTRY_FMT "%.2f ms\n", counter->name, (double)(*(guint64*)buffer) / 10000.0); else fprintf (outfile, ENTRY_FMT "%llu\n", counter->name, *(unsigned long long *)buffer); break; case MONO_COUNTER_WORD: fprintf (outfile, ENTRY_FMT "%zd\n", counter->name, *(gssize*)buffer); break; case MONO_COUNTER_DOUBLE: fprintf (outfile, ENTRY_FMT "%.4f\n", counter->name, *(double*)buffer); break; case MONO_COUNTER_STRING: fprintf (outfile, ENTRY_FMT "%s\n", counter->name, (size == 0) ? "(null)" : (char*)buffer); break; case MONO_COUNTER_TIME_INTERVAL: fprintf (outfile, ENTRY_FMT "%.2f ms\n", counter->name, (double)(*(gint64*)buffer) / 1000.0); break; } g_free (buffer); } static const char section_names [][10] = { "JIT", "GC", "Metadata", "Generics", "Security", "Runtime", "System", }; static void mono_counters_dump_section (int section, int variance, FILE *outfile) { MonoCounter *counter = counters; while (counter) { if ((counter->type & section) && (mono_counter_get_variance (counter) & variance)) dump_counter (counter, outfile); counter = counter->next; } } /** * mono_counters_dump: * @section_mask: The sections to dump counters for * @outfile: a FILE to dump the results to * * Displays the counts of all the enabled counters registered. * To filter by variance, you can OR one or more variance with the specific section you want. * Use MONO_COUNTER_SECTION_MASK to dump all categories of a specific variance. */ void mono_counters_dump (int section_mask, FILE *outfile) { int i, j; int variance; section_mask &= valid_mask; if (!initialized) return; mono_os_mutex_lock (&counters_mutex); if (!counters) { mono_os_mutex_unlock (&counters_mutex); return; } variance = section_mask & MONO_COUNTER_VARIANCE_MASK; /* If no variance mask is supplied, we default to all kinds. */ if (!variance) variance = MONO_COUNTER_VARIANCE_MASK; section_mask &= ~MONO_COUNTER_VARIANCE_MASK; for (j = 0, i = MONO_COUNTER_JIT; i < MONO_COUNTER_LAST_SECTION; j++, i <<= 1) { if ((section_mask & i) && (set_mask & i)) { fprintf (outfile, "\n%s statistics\n", section_names [j]); mono_counters_dump_section (i, variance, outfile); } } fflush (outfile); mono_os_mutex_unlock (&counters_mutex); } /** * mono_counters_cleanup: * * Perform any needed cleanup at process exit. */ void mono_counters_cleanup (void) { MonoCounter *counter; if (!initialized) return; mono_os_mutex_lock (&counters_mutex); counter = counters; counters = NULL; while (counter) { MonoCounter *tmp = counter; counter = counter->next; free ((void*)tmp->name); free (tmp); } mono_os_mutex_unlock (&counters_mutex); } static MonoResourceCallback limit_reached = NULL; static uintptr_t resource_limits [MONO_RESOURCE_COUNT * 2]; /** * mono_runtime_resource_check_limit: * @resource_type: one of the #MonoResourceType enum values * @value: the current value of the resource usage * * Check if a runtime resource limit has been reached. This function * is intended to be used by the runtime only. */ void mono_runtime_resource_check_limit (int resource_type, uintptr_t value) { if (!limit_reached) return; /* check the hard limit first */ if (value > resource_limits [resource_type * 2 + 1]) { limit_reached (resource_type, value, 0); return; } if (value > resource_limits [resource_type * 2]) limit_reached (resource_type, value, 1); } /** * mono_runtime_resource_limit: * @resource_type: one of the #MonoResourceType enum values * @soft_limit: the soft limit value * @hard_limit: the hard limit value * * This function sets the soft and hard limit for runtime resources. When the limit * is reached, a user-specified callback is called. The callback runs in a restricted * environment, in which the world coult be stopped, so it can't take locks, perform * allocations etc. The callback may be called multiple times once a limit has been reached * if action is not taken to decrease the resource use. * * Returns: 0 on error or a positive integer otherwise. */ int mono_runtime_resource_limit (int resource_type, uintptr_t soft_limit, uintptr_t hard_limit) { if (resource_type >= MONO_RESOURCE_COUNT || resource_type < 0) return 0; if (soft_limit > hard_limit) return 0; resource_limits [resource_type * 2] = soft_limit; resource_limits [resource_type * 2 + 1] = hard_limit; return 1; } /** * mono_runtime_resource_set_callback: * @callback: a function pointer * * Set the callback to be invoked when a resource limit is reached. * The callback will receive the resource type, the resource amount in resource-specific * units and a flag indicating whether the soft or hard limit was reached. */ void mono_runtime_resource_set_callback (MonoResourceCallback callback) { limit_reached = callback; }