#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/mono-perfcounters.h>
#include <mono/metadata/appdomain.h>
+#include <mono/metadata/assembly.h>
+#include <mono/metadata/tokentype.h>
+#include <mono/metadata/tabledefs.h>
#include <mono/utils/atomic.h>
#include <mono/utils/mono-membar.h>
#include <mono/utils/mono-counters.h>
#include <sys/syscall.h>
#include "perf_event.h"
-#ifndef DISABLE_PERF_EVENTS
+#ifdef ENABLE_PERF_EVENTS
#define USE_PERF_EVENTS 1
static int read_perf_mmap (MonoProfiler* prof, int cpu);
static int do_report = 0;
static int do_heap_shot = 0;
static int max_call_depth = 100;
-static int runtime_inited = 0;
+static volatile int runtime_inited = 0;
+static int need_helper_thread = 0;
static int command_port = 0;
static int heapshot_requested = 0;
static int sample_type = 0;
static int in_shutdown = 0;
static int do_debug = 0;
static int do_counters = 0;
+static int do_coverage = 0;
+static gboolean debug_coverage = FALSE;
static MonoProfileSamplingMode sampling_mode = MONO_PROFILER_STAT_MODE_PROCESS;
/* For linux compile with:
* else:
* [value: uleb128/sleb128/double] counter value, can be sleb128, uleb128 or double (determined by using type)
*
+ * type coverage format
+ * type: TYPE_COVERAGE
+ * exinfo: one of TYPE_COVERAGE_METHOD, TYPE_COVERAGE_STATEMENT, TYPE_COVERAGE_ASSEMBLY, TYPE_COVERAGE_CLASS
+ * if exinfo == TYPE_COVERAGE_METHOD
+ * [assembly: string] name of assembly
+ * [class: string] name of the class
+ * [name: string] name of the method
+ * [signature: string] the signature of the method
+ * [filename: string] the file path of the file that contains this method
+ * [token: uleb128] the method token
+ * [method_id: uleb128] an ID for this data to associate with the buffers of TYPE_COVERAGE_STATEMENTS
+ * [len: uleb128] the number of TYPE_COVERAGE_BUFFERS associated with this method
+ * if exinfo == TYPE_COVERAGE_STATEMENTS
+ * [method_id: uleb128] an the TYPE_COVERAGE_METHOD buffer to associate this with
+ * [offset: uleb128] the il offset relative to the previous offset
+ * [counter: uleb128] the counter for this instruction
+ * [line: uleb128] the line of filename containing this instruction
+ * [column: uleb128] the column containing this instruction
+ * if exinfo == TYPE_COVERAGE_ASSEMBLY
+ * [name: string] assembly name
+ * [guid: string] assembly GUID
+ * [filename: string] assembly filename
+ * [number_of_methods: uleb128] the number of methods in this assembly
+ * [fully_covered: uleb128] the number of fully covered methods
+ * [partially_covered: uleb128] the number of partially covered methods
+ * currently partially_covered will always be 0, and fully_covered is the
+ * number of methods that are fully and partially covered.
+ * if exinfo == TYPE_COVERAGE_CLASS
+ * [name: string] assembly name
+ * [class: string] class name
+ * [number_of_methods: uleb128] the number of methods in this class
+ * [fully_covered: uleb128] the number of fully covered methods
+ * [partially_covered: uleb128] the number of partially covered methods
+ * currently partially_covered will always be 0, and fully_covered is the
+ * number of methods that are fully and partially covered.
*/
+
struct _LogBuffer {
LogBuffer *next;
uint64_t time_base;
MonoConcurrentHashTable *method_table;
mono_mutex_t method_table_mutex;
BinaryObject *binary_objects;
+ GPtrArray *coverage_filters;
};
typedef struct _WriterQueueEntry WriterQueueEntry;
MonoJitInfo *ji;
};
+#ifdef TLS_INIT
+#undef TLS_INIT
+#endif
+
#ifdef HOST_WIN32
#define TLS_SET(x,y) (TlsSetValue (x, y))
#define TLS_GET(t,x) ((t *) TlsGetValue (x))
MonoJitInfo *ji = mono_get_jit_info_from_method (domain, search->method);
- // It could be AOT'd, so we need to force it to be loaded.
+ // It could be AOT'd, so we need to get it from the AOT runtime's cache.
if (!ji) {
- // Loads the method as a side effect.
- mono_aot_get_method (domain, search->method);
+ void *ip = mono_aot_get_method (domain, search->method);
- ji = mono_get_jit_info_from_method (domain, search->method);
+ // Avoid a slow path in mono_jit_info_table_find ().
+ if (ip)
+ ji = mono_jit_info_table_find (domain, ip);
}
if (ji)
static void counters_init (MonoProfiler *profiler);
static void counters_sample (MonoProfiler *profiler, uint64_t timestamp);
-static void
-runtime_initialized (MonoProfiler *profiler)
-{
- runtime_inited = 1;
-#ifndef DISABLE_HELPER_THREAD
- counters_init (profiler);
- counters_sample (profiler, 0);
-#endif
- /* ensure the main thread data and startup are available soon */
- safe_send (profiler, ensure_logbuf (0));
-}
-
/*
* Can be called only at safe callback locations.
*/
static void
safe_send (MonoProfiler *profiler, LogBuffer *logbuffer)
{
+ /* We need the runtime initialized so that we have threads and hazard
+ * pointers available. Otherwise, the lock free queue will not work and
+ * there won't be a thread to process the data.
+ *
+ * While the runtime isn't initialized, we just accumulate data in the
+ * thread local buffer list.
+ */
+ if (!InterlockedRead (&runtime_inited))
+ return;
+
int cd = logbuffer->call_depth;
send_buffer (profiler, TLS_GET (GPtrArray, tlsmethodlist), TLS_GET (LogBuffer, tlsbuffer));
{
uint64_t now;
uintptr_t len;
- int do_bt = (nocalls && runtime_inited && !notraces)? TYPE_ALLOC_BT: 0;
+ int do_bt = (nocalls && InterlockedRead (&runtime_inited) && !notraces)? TYPE_ALLOC_BT: 0;
FrameData data;
LogBuffer *logbuffer;
len = mono_object_get_size (obj);
LogBuffer *logbuffer;
if (result != MONO_PROFILE_OK)
return;
- if (runtime_inited)
+ if (InterlockedRead (&runtime_inited))
name = mono_type_get_name (mono_class_get_type (klass));
else
name = type_name (klass);
process_requests (prof);
}
+#ifndef DISABLE_HELPER_THREAD
+static void process_method_enter (MonoProfiler *prof, MonoMethod *method);
+#endif /* DISABLE_HELPER_THREAD */
+
static void
method_enter (MonoProfiler *prof, MonoMethod *method)
{
emit_time (logbuffer, now);
emit_method (prof, logbuffer, mono_domain_get (), method);
EXIT_LOG (logbuffer);
+
+#ifndef DISABLE_HELPER_THREAD
+ process_method_enter (prof, method);
+#endif /* DISABLE_HELPER_THREAD */
+
process_requests (prof);
}
static void
throw_exc (MonoProfiler *prof, MonoObject *object)
{
- int do_bt = (nocalls && runtime_inited && !notraces)? TYPE_EXCEPTION_BT: 0;
+ int do_bt = (nocalls && InterlockedRead (&runtime_inited) && !notraces)? TYPE_EXCEPTION_BT: 0;
uint64_t now;
FrameData data;
LogBuffer *logbuffer;
static void
monitor_event (MonoProfiler *profiler, MonoObject *object, MonoProfilerMonitorEvent event)
{
- int do_bt = (nocalls && runtime_inited && !notraces && event == MONO_PROFILER_MONITOR_CONTENTION)? TYPE_MONITOR_BT: 0;
+ int do_bt = (nocalls && InterlockedRead (&runtime_inited) && !notraces && event == MONO_PROFILER_MONITOR_CONTENTION)? TYPE_MONITOR_BT: 0;
uint64_t now;
FrameData data;
LogBuffer *logbuffer;
perfcounters_sample (prof, (now - start) / 1000/ 1000);
}
+#define COVERAGE_DEBUG(x) if (debug_coverage) {x}
+static MonoConcurrentHashTable *coverage_methods = NULL;
+static mono_mutex_t coverage_methods_mutex;
+static MonoConcurrentHashTable *coverage_assemblies = NULL;
+static mono_mutex_t coverage_assemblies_mutex;
+static MonoConcurrentHashTable *coverage_classes = NULL;
+static mono_mutex_t coverage_classes_mutex;
+static MonoConcurrentHashTable *filtered_classes = NULL;
+static mono_mutex_t filtered_classes_mutex;
+static MonoConcurrentHashTable *entered_methods = NULL;
+static mono_mutex_t entered_methods_mutex;
+static MonoConcurrentHashTable *image_to_methods = NULL;
+static mono_mutex_t image_to_methods_mutex;
+static MonoConcurrentHashTable *suppressed_assemblies = NULL;
+static mono_mutex_t suppressed_assemblies_mutex;
+static gboolean coverage_initialized = FALSE;
+
+static GPtrArray *coverage_data = NULL;
+static int previous_offset = 0;
+
+typedef struct _MethodNode MethodNode;
+struct _MethodNode {
+ MonoLockFreeQueueNode node;
+ MonoMethod *method;
+};
+
+typedef struct _CoverageEntry CoverageEntry;
+struct _CoverageEntry {
+ int offset;
+ int counter;
+ char *filename;
+ int line;
+ int column;
+};
+
+static void
+free_coverage_entry (gpointer data, gpointer userdata)
+{
+ CoverageEntry *entry = (CoverageEntry *)data;
+ g_free (entry->filename);
+ g_free (entry);
+}
+
+static void
+obtain_coverage_for_method (MonoProfiler *prof, const MonoProfileCoverageEntry *entry)
+{
+ int offset = entry->iloffset - previous_offset;
+ CoverageEntry *e = g_new (CoverageEntry, 1);
+
+ previous_offset = entry->iloffset;
+
+ e->offset = offset;
+ e->counter = entry->counter;
+ e->filename = g_strdup(entry->filename ? entry->filename : "");
+ e->line = entry->line;
+ e->column = entry->col;
+
+ g_ptr_array_add (coverage_data, e);
+}
+
+static char *
+parse_generic_type_names(char *name)
+{
+ char *new_name, *ret;
+ int within_generic_declaration = 0, generic_members = 1;
+
+ if (name == NULL || *name == '\0')
+ return g_strdup ("");
+
+ if (!(ret = new_name = calloc (strlen (name) * 4 + 1, sizeof (char))))
+ return NULL;
+
+ do {
+ switch (*name) {
+ case '<':
+ within_generic_declaration = 1;
+ break;
+
+ case '>':
+ within_generic_declaration = 0;
+
+ if (*(name - 1) != '<') {
+ *new_name++ = '`';
+ *new_name++ = '0' + generic_members;
+ } else {
+ memcpy (new_name, "<>", 8);
+ new_name += 8;
+ }
+
+ generic_members = 0;
+ break;
+
+ case ',':
+ generic_members++;
+ break;
+
+ default:
+ if (!within_generic_declaration)
+ *new_name++ = *name;
+
+ break;
+ }
+ } while (*name++);
+
+ return ret;
+}
+
+static int method_id;
+static void
+build_method_buffer (gpointer key, gpointer value, gpointer userdata)
+{
+ MonoMethod *method = (MonoMethod *)value;
+ MonoProfiler *prof = (MonoProfiler *)userdata;
+ MonoClass *klass;
+ MonoImage *image;
+ char *class_name;
+ const char *image_name, *method_name, *sig, *first_filename;
+ LogBuffer *logbuffer;
+ int size = 1;
+ guint i;
+
+ previous_offset = 0;
+ coverage_data = g_ptr_array_new ();
+
+ mono_profiler_coverage_get (prof, method, obtain_coverage_for_method);
+
+ klass = mono_method_get_class (method);
+ image = mono_class_get_image (klass);
+ image_name = mono_image_get_name (image);
+
+ sig = mono_signature_get_desc (mono_method_signature (method), TRUE);
+ class_name = parse_generic_type_names (mono_type_get_name (mono_class_get_type (klass)));
+ method_name = mono_method_get_name (method);
+
+ if (coverage_data->len != 0) {
+ CoverageEntry *entry = coverage_data->pdata[0];
+ first_filename = entry->filename ? entry->filename : "";
+ } else
+ first_filename = "";
+
+ image_name = image_name ? image_name : "";
+ sig = sig ? sig : "";
+ method_name = method_name ? method_name : "";
+
+ size += strlen (image_name) + 1;
+ size += strlen (class_name) + 1;
+ size += strlen (method_name) + 1;
+ size += strlen (first_filename) + 1;
+ size += strlen (sig) + 1;
+
+ size += 10 + 10 + 5; /* token + method_id + n_entries*/
+
+ logbuffer = ensure_logbuf (size);
+ ENTER_LOG (logbuffer, "coverage-methods");
+
+ emit_byte (logbuffer, TYPE_COVERAGE_METHOD | TYPE_COVERAGE);
+ emit_string (logbuffer, image_name, strlen (image_name) + 1);
+ emit_string (logbuffer, class_name, strlen (class_name) + 1);
+ emit_string (logbuffer, method_name, strlen (method_name) + 1);
+ emit_string (logbuffer, sig, strlen (sig) + 1);
+ emit_string (logbuffer, first_filename, strlen (first_filename) + 1);
+
+ emit_uvalue (logbuffer, mono_method_get_token (method));
+ emit_uvalue (logbuffer, method_id);
+ emit_value (logbuffer, coverage_data->len);
+
+ EXIT_LOG (logbuffer);
+ safe_send (prof, ensure_logbuf (0));
+
+ for (i = 0; i < coverage_data->len; i++) {
+ CoverageEntry *entry = coverage_data->pdata[i];
+
+ size = 1;
+ size += 10 * 5; /* method_id, offset, count, line, column */
+
+ logbuffer = ensure_logbuf (size);
+ ENTER_LOG (logbuffer, "coverage-statement");
+
+ emit_byte (logbuffer, TYPE_COVERAGE_STATEMENT | TYPE_COVERAGE);
+ emit_uvalue (logbuffer, method_id);
+ emit_uvalue (logbuffer, entry->offset);
+ emit_uvalue (logbuffer, entry->counter);
+ emit_uvalue (logbuffer, entry->line);
+ emit_uvalue (logbuffer, entry->column);
+
+ EXIT_LOG (logbuffer);
+ safe_send (prof, ensure_logbuf (0));
+ }
+
+ method_id++;
+
+ g_free (class_name);
+
+ g_ptr_array_foreach (coverage_data, free_coverage_entry, NULL);
+ g_ptr_array_free (coverage_data, TRUE);
+ coverage_data = NULL;
+}
+
+/* This empties the queue */
+static guint
+count_queue (MonoLockFreeQueue *queue)
+{
+ MonoLockFreeQueueNode *node;
+ guint count = 0;
+
+ while ((node = mono_lock_free_queue_dequeue (queue))) {
+ count++;
+ mono_lock_free_queue_node_free (node);
+ }
+
+ return count;
+}
+
+static void
+build_class_buffer (gpointer key, gpointer value, gpointer userdata)
+{
+ MonoClass *klass = (MonoClass *)key;
+ MonoLockFreeQueue *class_methods = (MonoLockFreeQueue *)value;
+ MonoProfiler *prof = (MonoProfiler *)userdata;
+ MonoImage *image;
+ char *class_name;
+ const char *assembly_name;
+ int number_of_methods, partially_covered;
+ guint fully_covered;
+ LogBuffer *logbuffer;
+ int size = 1;
+
+ image = mono_class_get_image (klass);
+ assembly_name = mono_image_get_name (image);
+ class_name = mono_type_get_name (mono_class_get_type (klass));
+
+ assembly_name = assembly_name ? assembly_name : "";
+ number_of_methods = mono_class_num_methods (klass);
+ fully_covered = count_queue (class_methods);
+ /* We don't handle partial covered yet */
+ partially_covered = 0;
+
+ size += strlen (assembly_name) + 1;
+ size += strlen (class_name) + 1;
+ size += 30; /* number_of_methods, fully_covered, partially_covered */
+
+ logbuffer = ensure_logbuf (size);
+
+ ENTER_LOG (logbuffer, "coverage-class");
+ emit_byte (logbuffer, TYPE_COVERAGE_CLASS | TYPE_COVERAGE);
+ emit_string (logbuffer, assembly_name, strlen (assembly_name) + 1);
+ emit_string (logbuffer, class_name, strlen (class_name) + 1);
+ emit_uvalue (logbuffer, number_of_methods);
+ emit_uvalue (logbuffer, fully_covered);
+ emit_uvalue (logbuffer, partially_covered);
+ EXIT_LOG (logbuffer);
+
+ safe_send (prof, ensure_logbuf (0));
+
+ g_free (class_name);
+}
+
+static void
+get_coverage_for_image (MonoImage *image, int *number_of_methods, guint *fully_covered, int *partially_covered)
+{
+ MonoLockFreeQueue *image_methods = mono_conc_hashtable_lookup (image_to_methods, image);
+
+ *number_of_methods = mono_image_get_table_rows (image, MONO_TABLE_METHOD);
+ if (image_methods)
+ *fully_covered = count_queue (image_methods);
+ else
+ *fully_covered = 0;
+
+ // FIXME: We don't handle partially covered yet.
+ *partially_covered = 0;
+}
+
+static void
+build_assembly_buffer (gpointer key, gpointer value, gpointer userdata)
+{
+ MonoAssembly *assembly = (MonoAssembly *)value;
+ MonoProfiler *prof = (MonoProfiler *)userdata;
+ MonoImage *image = mono_assembly_get_image (assembly);
+ LogBuffer *logbuffer;
+ const char *name, *guid, *filename;
+ int size = 1;
+ int number_of_methods = 0, partially_covered = 0;
+ guint fully_covered = 0;
+
+ name = mono_image_get_name (image);
+ guid = mono_image_get_guid (image);
+ filename = mono_image_get_filename (image);
+
+ name = name ? name : "";
+ guid = guid ? guid : "";
+ filename = filename ? filename : "";
+
+ get_coverage_for_image (image, &number_of_methods, &fully_covered, &partially_covered);
+
+ size += strlen (name) + 1;
+ size += strlen (guid) + 1;
+ size += strlen (filename) + 1;
+ size += 30; /* number_of_methods, fully_covered, partially_covered */
+ logbuffer = ensure_logbuf (size);
+
+ ENTER_LOG (logbuffer, "coverage-assemblies");
+ emit_byte (logbuffer, TYPE_COVERAGE_ASSEMBLY | TYPE_COVERAGE);
+ emit_string (logbuffer, name, strlen (name) + 1);
+ emit_string (logbuffer, guid, strlen (guid) + 1);
+ emit_string (logbuffer, filename, strlen (filename) + 1);
+ emit_uvalue (logbuffer, number_of_methods);
+ emit_uvalue (logbuffer, fully_covered);
+ emit_uvalue (logbuffer, partially_covered);
+ EXIT_LOG (logbuffer);
+
+ safe_send (prof, ensure_logbuf (0));
+}
+
+static void
+dump_coverage (MonoProfiler *prof)
+{
+ if (!coverage_initialized)
+ return;
+
+ COVERAGE_DEBUG(fprintf (stderr, "Coverage: Started dump\n");)
+ method_id = 0;
+
+ mono_conc_hashtable_foreach (coverage_assemblies, build_assembly_buffer, prof);
+ mono_conc_hashtable_foreach (coverage_classes, build_class_buffer, prof);
+ mono_conc_hashtable_foreach (coverage_methods, build_method_buffer, prof);
+
+ COVERAGE_DEBUG(fprintf (stderr, "Coverage: Finished dump\n");)
+}
+
+static void
+process_method_enter (MonoProfiler *prof, MonoMethod *method)
+{
+ MonoClass *klass;
+ MonoImage *image;
+
+ if (!coverage_initialized)
+ return;
+
+ klass = mono_method_get_class (method);
+ image = mono_class_get_image (klass);
+
+ if (mono_conc_hashtable_lookup (suppressed_assemblies, (gpointer) mono_image_get_name (image)))
+ return;
+
+ mono_conc_hashtable_insert (entered_methods, method, method);
+}
+
+static MonoLockFreeQueueNode *
+create_method_node (MonoMethod *method)
+{
+ MethodNode *node = g_malloc (sizeof (MethodNode));
+ mono_lock_free_queue_node_init ((MonoLockFreeQueueNode *) node, FALSE);
+ node->method = method;
+
+ return (MonoLockFreeQueueNode *) node;
+}
+
+static gboolean
+coverage_filter (MonoProfiler *prof, MonoMethod *method)
+{
+ MonoClass *klass;
+ MonoImage *image;
+ MonoAssembly *assembly;
+ MonoMethodHeader *header;
+ guint32 iflags, flags, code_size;
+ char *fqn, *classname;
+ gboolean has_positive, found;
+ MonoLockFreeQueue *image_methods, *class_methods;
+ MonoLockFreeQueueNode *node;
+
+ if (!coverage_initialized)
+ return FALSE;
+
+ COVERAGE_DEBUG(fprintf (stderr, "Coverage filter for %s\n", mono_method_get_name (method));)
+
+ flags = mono_method_get_flags (method, &iflags);
+ if ((iflags & 0x1000 /*METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL*/) ||
+ (flags & 0x2000 /*METHOD_ATTRIBUTE_PINVOKE_IMPL*/)) {
+ COVERAGE_DEBUG(fprintf (stderr, " Internal call or pinvoke - ignoring\n");)
+ return FALSE;
+ }
+
+ // Don't need to do anything else if we're already tracking this method
+ if (mono_conc_hashtable_lookup (coverage_methods, method)) {
+ COVERAGE_DEBUG(fprintf (stderr, " Already tracking\n");)
+ return TRUE;
+ }
+
+ klass = mono_method_get_class (method);
+ image = mono_class_get_image (klass);
+
+ // Don't handle coverage for the core assemblies
+ if (mono_conc_hashtable_lookup (suppressed_assemblies, (gpointer) mono_image_get_name (image)) != NULL)
+ return FALSE;
+
+ if (prof->coverage_filters) {
+ /* Check already filtered classes first */
+ if (mono_conc_hashtable_lookup (filtered_classes, klass)) {
+ COVERAGE_DEBUG(fprintf (stderr, " Already filtered\n");)
+ return FALSE;
+ }
+
+ classname = mono_type_get_name (mono_class_get_type (klass));
+
+ fqn = g_strdup_printf ("[%s]%s", mono_image_get_name (image), classname);
+
+ COVERAGE_DEBUG(fprintf (stderr, " Looking for %s in filter\n", fqn);)
+ // Check positive filters first
+ has_positive = FALSE;
+ found = FALSE;
+ for (guint i = 0; i < prof->coverage_filters->len; ++i) {
+ char *filter = g_ptr_array_index (prof->coverage_filters, i);
+
+ if (filter [0] == '+') {
+ filter = &filter [1];
+
+ COVERAGE_DEBUG(fprintf (stderr, " Checking against +%s ...", filter);)
+
+ if (strstr (fqn, filter) != NULL) {
+ COVERAGE_DEBUG(fprintf (stderr, "matched\n");)
+ found = TRUE;
+ } else
+ COVERAGE_DEBUG(fprintf (stderr, "no match\n");)
+
+ has_positive = TRUE;
+ }
+ }
+
+ if (has_positive && !found) {
+ COVERAGE_DEBUG(fprintf (stderr, " Positive match was not found\n");)
+
+ mono_conc_hashtable_insert (filtered_classes, klass, klass);
+ g_free (fqn);
+ g_free (classname);
+
+ return FALSE;
+ }
+
+ for (guint i = 0; i < prof->coverage_filters->len; ++i) {
+ // FIXME: Is substring search sufficient?
+ char *filter = g_ptr_array_index (prof->coverage_filters, i);
+ if (filter [0] == '+')
+ continue;
+
+ // Skip '-'
+ filter = &filter [1];
+ COVERAGE_DEBUG(fprintf (stderr, " Checking against -%s ...", filter);)
+
+ if (strstr (fqn, filter) != NULL) {
+ COVERAGE_DEBUG(fprintf (stderr, "matched\n");)
+
+ mono_conc_hashtable_insert (filtered_classes, klass, klass);
+ g_free (fqn);
+ g_free (classname);
+
+ return FALSE;
+ } else
+ COVERAGE_DEBUG(fprintf (stderr, "no match\n");)
+
+ }
+
+ g_free (fqn);
+ g_free (classname);
+ }
+
+ COVERAGE_DEBUG(fprintf (stderr, " Handling coverage for %s\n", mono_method_get_name (method));)
+ header = mono_method_get_header (method);
+
+ mono_method_header_get_code (header, &code_size, NULL);
+
+ assembly = mono_image_get_assembly (image);
+
+ mono_conc_hashtable_insert (coverage_methods, method, method);
+ mono_conc_hashtable_insert (coverage_assemblies, assembly, assembly);
+
+ image_methods = mono_conc_hashtable_lookup (image_to_methods, image);
+
+ if (image_methods == NULL) {
+ image_methods = g_malloc (sizeof (MonoLockFreeQueue));
+ mono_lock_free_queue_init (image_methods);
+ mono_conc_hashtable_insert (image_to_methods, image, image_methods);
+ }
+
+ node = create_method_node (method);
+ mono_lock_free_queue_enqueue (image_methods, node);
+
+ class_methods = mono_conc_hashtable_lookup (coverage_classes, klass);
+
+ if (class_methods == NULL) {
+ class_methods = g_malloc (sizeof (MonoLockFreeQueue));
+ mono_lock_free_queue_init (class_methods);
+ mono_conc_hashtable_insert (coverage_classes, klass, class_methods);
+ }
+
+ node = create_method_node (method);
+ mono_lock_free_queue_enqueue (class_methods, node);
+
+ return TRUE;
+}
+
+#define LINE_BUFFER_SIZE 4096
+/* Max file limit of 128KB */
+#define MAX_FILE_SIZE 128 * 1024
+static char *
+get_file_content (FILE *stream)
+{
+ char *buffer;
+ ssize_t bytes_read;
+ long filesize;
+ int res, offset = 0;
+
+ res = fseek (stream, 0, SEEK_END);
+ if (res < 0)
+ return NULL;
+
+ filesize = ftell (stream);
+ if (filesize < 0)
+ return NULL;
+
+ res = fseek (stream, 0, SEEK_SET);
+ if (res < 0)
+ return NULL;
+
+ if (filesize > MAX_FILE_SIZE)
+ return NULL;
+
+ buffer = g_malloc ((filesize + 1) * sizeof (char));
+ while ((bytes_read = fread (buffer + offset, 1, LINE_BUFFER_SIZE, stream)) > 0)
+ offset += bytes_read;
+
+ /* NULL terminate our buffer */
+ buffer[filesize] = '\0';
+ return buffer;
+}
+
+static char *
+get_next_line (char *contents, char **next_start)
+{
+ char *p = contents;
+
+ if (p == NULL || *p == '\0') {
+ *next_start = NULL;
+ return NULL;
+ }
+
+ while (*p != '\n' && *p != '\0')
+ p++;
+
+ if (*p == '\n') {
+ *p = '\0';
+ *next_start = p + 1;
+ } else
+ *next_start = NULL;
+
+ return contents;
+}
+
+static void
+init_suppressed_assemblies (void)
+{
+ char *content;
+ char *line;
+ FILE *sa_file;
+
+ mono_mutex_init (&suppressed_assemblies_mutex);
+ suppressed_assemblies = mono_conc_hashtable_new (&suppressed_assemblies_mutex, g_str_hash, g_str_equal);
+ sa_file = fopen (SUPPRESSION_DIR "/mono-profiler-log.suppression", "r");
+ if (sa_file == NULL)
+ return;
+
+ /* Don't need to free @content as it is referred to by the lines stored in @suppressed_assemblies */
+ content = get_file_content (sa_file);
+ if (content == NULL) {
+ g_error ("mono-profiler-log.suppression is greater than 128kb - aborting\n");
+ }
+
+ while ((line = get_next_line (content, &content))) {
+ line = g_strchomp (g_strchug (line));
+ mono_conc_hashtable_insert (suppressed_assemblies, line, line);
+ }
+
+ fclose (sa_file);
+}
+
+static MonoConcurrentHashTable *
+init_hashtable (mono_mutex_t *mutex)
+{
+ mono_mutex_init (mutex);
+ return mono_conc_hashtable_new (mutex, NULL, NULL);
+}
+
+static void
+destroy_hashtable (MonoConcurrentHashTable *hashtable, mono_mutex_t *mutex)
+{
+ mono_conc_hashtable_destroy (hashtable);
+ mono_mutex_destroy (mutex);
+}
+
+#endif /* DISABLE_HELPER_THREAD */
+
+static void
+coverage_init (MonoProfiler *prof)
+{
+#ifndef DISABLE_HELPER_THREAD
+ assert (!coverage_initialized);
+
+ COVERAGE_DEBUG(fprintf (stderr, "Coverage initialized\n");)
+
+ coverage_methods = init_hashtable (&coverage_methods_mutex);
+ coverage_assemblies = init_hashtable (&coverage_assemblies_mutex);
+ coverage_classes = init_hashtable (&coverage_classes_mutex);
+ filtered_classes = init_hashtable (&filtered_classes_mutex);
+ entered_methods = init_hashtable (&entered_methods_mutex);
+ image_to_methods = init_hashtable (&image_to_methods_mutex);
+ init_suppressed_assemblies ();
+
+ coverage_initialized = TRUE;
#endif /* DISABLE_HELPER_THREAD */
+}
static void
log_shutdown (MonoProfiler *prof)
#ifndef DISABLE_HELPER_THREAD
counters_and_perfcounters_sample (prof);
+ dump_coverage (prof);
+
if (prof->command_port) {
char c = 1;
ign_res (write (prof->pipes [1], &c, 1));
else
fclose (prof->file);
- mono_conc_hashtable_destroy (prof->method_table);
- mono_mutex_destroy (&prof->method_table_mutex);
+ destroy_hashtable (prof->method_table, &prof->method_table_mutex);
+
+ if (coverage_initialized) {
+ destroy_hashtable (coverage_methods, &coverage_methods_mutex);
+ destroy_hashtable (coverage_assemblies, &coverage_assemblies_mutex);
+ destroy_hashtable (coverage_classes, &coverage_classes_mutex);
+ destroy_hashtable (filtered_classes, &filtered_classes_mutex);
+ destroy_hashtable (entered_methods, &entered_methods_mutex);
+ destroy_hashtable (image_to_methods, &image_to_methods_mutex);
+ destroy_hashtable (suppressed_assemblies, &suppressed_assemblies_mutex);
+ }
free (prof);
}
if (len < 0) {
if (errno == EINTR)
continue;
-
+
g_warning ("Error in proflog server: %s", strerror (errno));
return NULL;
}
-
+
if (FD_ISSET (prof->pipes [0], &rfds)) {
char c;
int r = read (prof->pipes [0], &c, 1);
if (strcmp (buf, "heapshot\n") == 0) {
heapshot_requested = 1;
//fprintf (stderr, "perform heapshot\n");
- if (runtime_inited && !thread) {
+ if (InterlockedRead (&runtime_inited) && !thread) {
thread = mono_thread_attach (mono_get_root_domain ());
/*fprintf (stderr, "attached\n");*/
}
return !pthread_create (&prof->writer_thread, NULL, writer_thread, prof);
}
+static void
+runtime_initialized (MonoProfiler *profiler)
+{
+#ifndef DISABLE_HELPER_THREAD
+ if (hs_mode_ondemand || need_helper_thread) {
+ if (!start_helper_thread (profiler))
+ profiler->command_port = 0;
+ }
+#endif
+
+ start_writer_thread (profiler);
+
+ InterlockedWrite (&runtime_inited, 1);
+#ifndef DISABLE_HELPER_THREAD
+ counters_init (profiler);
+ counters_sample (profiler, 0);
+#endif
+ /* ensure the main thread data and startup are available soon */
+ safe_send (profiler, ensure_logbuf (0));
+}
+
static MonoProfiler*
-create_profiler (const char *filename)
+create_profiler (const char *filename, GPtrArray *filters)
{
MonoProfiler *prof;
char *nf;
int force_delete = 0;
- int need_helper_thread = 0;
prof = calloc (1, sizeof (MonoProfiler));
prof->command_port = command_port;
if (do_counters && !need_helper_thread) {
need_helper_thread = 1;
}
-#ifndef DISABLE_HELPER_THREAD
- if (hs_mode_ondemand || need_helper_thread) {
- if (!start_helper_thread (prof))
- prof->command_port = 0;
- }
-#else
+
+#ifdef DISABLE_HELPER_THREAD
if (hs_mode_ondemand)
fprintf (stderr, "Ondemand heapshot unavailable on this arch.\n");
+
+ if (do_coverage)
+ fprintf (stderr, "Coverage unavailable on this arch.\n");
+
#endif
mono_lock_free_queue_init (&prof->writer_queue);
mono_mutex_init (&prof->method_table_mutex);
prof->method_table = mono_conc_hashtable_new (&prof->method_table_mutex, NULL, NULL);
- start_writer_thread (prof);
+ if (do_coverage)
+ coverage_init (prof);
+ prof->coverage_filters = filters;
prof->startup_time = current_time ();
return prof;
printf ("Log profiler version %d.%d (format: %d)\n", LOG_VERSION_MAJOR, LOG_VERSION_MINOR, LOG_DATA_VERSION);
printf ("Usage: mono --profile=log[:OPTION1[,OPTION2...]] program.exe\n");
printf ("Options:\n");
- printf ("\thelp show this usage info\n");
- printf ("\t[no]alloc enable/disable recording allocation info\n");
- printf ("\t[no]calls enable/disable recording enter/leave method events\n");
- printf ("\theapshot[=MODE] record heap shot info (by default at each major collection)\n");
- printf ("\t MODE: every XXms milliseconds, every YYgc collections, ondemand\n");
- printf ("\tcounters sample counters every 1s\n");
- printf ("\tsample[=TYPE] use statistical sampling mode (by default cycles/1000)\n");
- printf ("\t TYPE: cycles,instr,cacherefs,cachemiss,branches,branchmiss\n");
- printf ("\t TYPE can be followed by /FREQUENCY\n");
- printf ("\ttime=fast use a faster (but more inaccurate) timer\n");
- printf ("\tmaxframes=NUM collect up to NUM stack frames\n");
- printf ("\tcalldepth=NUM ignore method events for call chain depth bigger than NUM\n");
- printf ("\toutput=FILENAME write the data to file FILENAME (-FILENAME to overwrite)\n");
- printf ("\toutput=|PROGRAM write the data to the stdin of PROGRAM\n");
- printf ("\t %%t is subtituted with date and time, %%p with the pid\n");
- printf ("\treport create a report instead of writing the raw data to a file\n");
- printf ("\tzip compress the output data\n");
- printf ("\tport=PORTNUM use PORTNUM for the listening command server\n");
+ printf ("\thelp show this usage info\n");
+ printf ("\t[no]alloc enable/disable recording allocation info\n");
+ printf ("\t[no]calls enable/disable recording enter/leave method events\n");
+ printf ("\theapshot[=MODE] record heap shot info (by default at each major collection)\n");
+ printf ("\t MODE: every XXms milliseconds, every YYgc collections, ondemand\n");
+ printf ("\tcounters sample counters every 1s\n");
+ printf ("\tsample[=TYPE] use statistical sampling mode (by default cycles/1000)\n");
+ printf ("\t TYPE: cycles,instr,cacherefs,cachemiss,branches,branchmiss\n");
+ printf ("\t TYPE can be followed by /FREQUENCY\n");
+ printf ("\ttime=fast use a faster (but more inaccurate) timer\n");
+ printf ("\tmaxframes=NUM collect up to NUM stack frames\n");
+ printf ("\tcalldepth=NUM ignore method events for call chain depth bigger than NUM\n");
+ printf ("\toutput=FILENAME write the data to file FILENAME (-FILENAME to overwrite)\n");
+ printf ("\toutput=|PROGRAM write the data to the stdin of PROGRAM\n");
+ printf ("\t %%t is subtituted with date and time, %%p with the pid\n");
+ printf ("\treport create a report instead of writing the raw data to a file\n");
+ printf ("\tzip compress the output data\n");
+ printf ("\tport=PORTNUM use PORTNUM for the listening command server\n");
+ printf ("\tcoverage enable collection of code coverage data\n");
+ printf ("\tcovfilter=ASSEMBLY add an assembly to the code coverage filters\n");
+ printf ("\t add a + to include the assembly or a - to exclude it\n");
+ printf ("\t filter=-mscorlib\n");
+ printf ("\tcovfilter-file=FILE use FILE to generate the list of assemblies to be filtered\n");
if (do_exit)
exit (1);
}
free (val);
}
-/*
+/*
* declaration to silence the compiler: this is the entry point that
* mono will load from the shared library and call.
*/
mono_profiler_startup (const char *desc)
{
MonoProfiler *prof;
+ GPtrArray *filters = NULL;
char *filename = NULL;
const char *p;
const char *opt;
int calls_enabled = 0;
int allocs_enabled = 0;
int only_counters = 0;
+ int only_coverage = 0;
int events = MONO_PROFILE_GC|MONO_PROFILE_ALLOCATIONS|
MONO_PROFILE_GC_MOVES|MONO_PROFILE_CLASS_EVENTS|MONO_PROFILE_THREADS|
MONO_PROFILE_ENTER_LEAVE|MONO_PROFILE_JIT_COMPILATION|MONO_PROFILE_EXCEPTIONS|
- MONO_PROFILE_MONITOR_EVENTS|MONO_PROFILE_MODULE_EVENTS|MONO_PROFILE_GC_ROOTS;
+ MONO_PROFILE_MONITOR_EVENTS|MONO_PROFILE_MODULE_EVENTS|MONO_PROFILE_GC_ROOTS|
+ MONO_PROFILE_INS_COVERAGE;
p = desc;
if (strncmp (p, "log", 3))
only_counters = 1;
continue;
}
+ if ((opt = match_option (p, "coverage", NULL)) != p) {
+ do_coverage = 1;
+ events |= MONO_PROFILE_ENTER_LEAVE;
+ debug_coverage = (g_getenv ("MONO_PROFILER_DEBUG_COVERAGE") != NULL);
+ continue;
+ }
+ if ((opt = match_option (p, "onlycoverage", NULL)) != p) {
+ only_coverage = 1;
+ continue;
+ }
+ if ((opt = match_option (p, "covfilter-file", &val)) != p) {
+ FILE *filter_file;
+ char *line, *content;
+
+ if (filters == NULL)
+ filters = g_ptr_array_new ();
+
+ filter_file = fopen (val, "r");
+ if (filter_file == NULL) {
+ fprintf (stderr, "Unable to open %s\n", val);
+ exit (0);
+ }
+
+ /* Don't need to free content as it is referred to by the lines stored in @filters */
+ content = get_file_content (filter_file);
+ if (content == NULL)
+ fprintf (stderr, "WARNING: %s is greater than 128kb - ignoring\n", val);
+
+ while ((line = get_next_line (content, &content)))
+ g_ptr_array_add (filters, g_strchug (g_strchomp (line)));
+
+ fclose (filter_file);
+ continue;
+ }
+ if ((opt = match_option (p, "covfilter", &val)) != p) {
+ if (filters == NULL)
+ filters = g_ptr_array_new ();
+
+ g_ptr_array_add (filters, val);
+ continue;
+ }
if (opt == p) {
usage (0);
exit (0);
events |= MONO_PROFILE_ALLOCATIONS;
if (only_counters)
events = 0;
+ if (only_coverage)
+ events = MONO_PROFILE_ENTER_LEAVE | MONO_PROFILE_INS_COVERAGE;
+
utils_init (fast_time);
- prof = create_profiler (filename);
+ prof = create_profiler (filename, filters);
if (!prof)
return;
init_thread ();
mono_profiler_install_exception (throw_exc, method_exc_leave, clause_exc);
mono_profiler_install_monitor (monitor_event);
mono_profiler_install_runtime_initialized (runtime_initialized);
+ if (do_coverage)
+ mono_profiler_install_coverage_filter (coverage_filter);
-
if (do_mono_sample && sample_type == SAMPLE_CYCLES && !only_counters) {
events |= MONO_PROFILE_STATISTICAL;
mono_profiler_set_statistical_mode (sampling_mode, 1000000 / sample_freq);
TLS_INIT (tlsbuffer);
TLS_INIT (tlsmethodlist);
}
-