From 5b681a16bd7aaf579f6b4a32eed36ee05bd4f562 Mon Sep 17 00:00:00 2001 From: Ludovic Henry Date: Mon, 25 Sep 2017 12:27:47 -0400 Subject: [PATCH] [profiler] Add dedicated coverage profiler (#5622) This is to remove the need to depend on the log profiler and mprof-report to generate code coverage reports --- man/mono.1 | 7 +- man/mprof-report.1 | 12 - mono/profiler/Makefile.am | 8 + mono/profiler/coverage.c | 923 ++++++++++++++++++++++++++++++++++++++ mono/profiler/log-args.c | 1 + 5 files changed, 937 insertions(+), 14 deletions(-) create mode 100644 mono/profiler/coverage.c diff --git a/man/mono.1 b/man/mono.1 index 7d2f14c151e..5ac6ea81002 100644 --- a/man/mono.1 +++ b/man/mono.1 @@ -908,8 +908,11 @@ your profiler. For a sample of how to write your own custom profiler look in the Mono source tree for in the samples/profiler.c. .SH CODE COVERAGE -Mono ships with a code coverage module in the \f[I]log\f[] profiler. -Check the `coverage' option on the mprof-report(1) page for more details. +Mono ships with a code coverage module in the \f[I]coverage\f[] profiler. +To enable it, pass \fB--profile=coverage\fR to your mono invocation. It +will by default output a coverage.xml in the current directory. Use +\fBmono --profile=coverage:help sample.exe\fR for more information on the +different options. .SH AOT PROFILING You can improve startup performance by using the AOT profiler. .PP diff --git a/man/mprof-report.1 b/man/mprof-report.1 index 56663251bf8..45ea4f77b88 100644 --- a/man/mprof-report.1 +++ b/man/mprof-report.1 @@ -213,9 +213,6 @@ The following commands are available: .IP \[bu] 2 \f[I]nocounters\f[]: disables sampling of runtime and performance counters, which is normally done every 1 second. -.IP \[bu] 2 -\f[I]coverage\f[]: collect code coverage data. This implies enabling -the \f[I]calls\f[] option. .RE .SS Analyzing the profile data .PP @@ -338,8 +335,6 @@ version .IP \[bu] 2 \f[I]counters\f[]: counters samples .IP \[bu] 2 -\f[I]coverage\f[]: code coverage data -.IP \[bu] 2 \f[I]stats\f[]: event statistics .PP It is possible to limit some of the data displayed to a timeframe @@ -411,13 +406,6 @@ By default mprof-report will print the summary data to the console. To print it to a file, instead, use the option: .PP \f[B]--out=FILENAME\f[] -.SS Processing code coverage data -.PP -If you ran the profiler with the \f[I]coverage\f[] option, you can -process the collected coverage data into an XML file by running -mprof-report like this: -.PP -\f[B]mprof-report --coverage-out=coverage.xml output.mlpd\f[] .SS Dealing with profiler slowness .PP If the profiler needs to collect lots of data, the execution of the diff --git a/mono/profiler/Makefile.am b/mono/profiler/Makefile.am index 88cd9d37a1a..212278b57e4 100644 --- a/mono/profiler/Makefile.am +++ b/mono/profiler/Makefile.am @@ -25,6 +25,8 @@ lib_LTLIBRARIES = \ libmono-profiler-iomap-static.la \ libmono-profiler-log.la \ libmono-profiler-log-static.la \ + libmono-profiler-coverage.la \ + libmono-profiler-coverage-static.la \ $(vtune_libs) suppressiondir = $(datadir)/mono-$(API_VER)/mono/profiler @@ -83,6 +85,12 @@ libmono_profiler_log_la_LDFLAGS = $(prof_ldflags) libmono_profiler_log_static_la_SOURCES = log.c log-args.c libmono_profiler_log_static_la_LDFLAGS = -static +libmono_profiler_coverage_la_SOURCES = coverage.c +libmono_profiler_coverage_la_LIBADD = $(libmono_dep) $(GLIB_LIBS) $(Z_LIBS) +libmono_profiler_coverage_la_LDFLAGS = $(prof_ldflags) +libmono_profiler_coverage_static_la_SOURCES = coverage.c +libmono_profiler_coverage_static_la_LDFLAGS = -static + if HAVE_VTUNE libmono_profiler_vtune_la_SOURCES = vtune.c libmono_profiler_vtune_la_CFLAGS = $(VTUNE_CFLAGS) diff --git a/mono/profiler/coverage.c b/mono/profiler/coverage.c new file mode 100644 index 00000000000..5a9d5b69c4f --- /dev/null +++ b/mono/profiler/coverage.c @@ -0,0 +1,923 @@ +/* + * coverage.c: mono coverage profiler + * + * Authors: + * Paolo Molaro (lupus@ximian.com) + * Alex Rønne Petersen (alexrp@xamarin.com) + * Ludovic Henry (ludovic@xamarin.com) + * + * Licensed under the MIT license. See LICENSE file in the project root for full license information. + */ + +/* + * The Coverage XML output schema + * + * + * + * + * + * + * + * + * Elements: + * - The root element of the documentation. It can contain any number of + * , or elements. + * Attributes: + * - version: The version number for the file format - (eg: "0.3") + * - Contains data about assemblies. Has no child elements + * Attributes: + * - name: The name of the assembly - (eg: "System.Xml") + * - guid: The GUID of the assembly + * - filename: The filename of the assembly + * - method-count: The number of methods in the assembly + * - full: The number of fully covered methods + * - partial: The number of partially covered methods + * - Contains data about classes. Has no child elements + * Attributes: + * - name: The name of the class + * - method-count: The number of methods in the class + * - full: The number of fully covered methods + * - partial: The number of partially covered methods + * - Contains data about methods. Can contain any number of elements + * Attributes: + * - assembly: The name of the parent assembly + * - class: The name of the parent class + * - name: The name of the method, with all it's parameters + * - filename: The name of the source file containing this method + * - token + * - Contains data about IL statements. Has no child elements + * Attributes: + * - offset: The offset of the statement in the IL code after the previous + * statement's offset + * - counter: 1 if the line was covered, 0 if it was not + * - line: The line number in the parent method's file + * - column: The column on the line + */ + +#include +#include + +#include + +#ifdef HAVE_DLFCN_H +#include +#endif +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_SYS_MMAN_H +#include +#endif +#if defined (HAVE_SYS_ZLIB) +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +// Statistics for profiler events. +static gint32 coverage_methods_ctr, + coverage_statements_ctr, + coverage_classes_ctr, + coverage_assemblies_ctr; + +struct _MonoProfiler { + MonoProfilerHandle handle; + + FILE* file; + + char *args; + + mono_mutex_t mutex; + GPtrArray *data; + + GPtrArray *filters; + MonoConcurrentHashTable *filtered_classes; + MonoConcurrentHashTable *suppressed_assemblies; + + MonoConcurrentHashTable *methods; + MonoConcurrentHashTable *assemblies; + MonoConcurrentHashTable *classes; + + MonoConcurrentHashTable *image_to_methods; + + guint32 previous_offset; +}; + +typedef struct { + //Where to compress the output file + gboolean use_zip; + + //Name of the generated xml file + const char *output_filename; + + //Filter files used by the code coverage mode + GPtrArray *cov_filter_files; +} ProfilerConfig; + +static ProfilerConfig coverage_config; +static struct _MonoProfiler coverage_profiler; + +/* This is a very basic escape function that escapes < > and & + Ideally we'd use g_markup_escape_string but that function isn't + available in Mono's eglib. This was written without looking at the + source of that function in glib. */ +static char * +escape_string_for_xml (const char *string) +{ + GString *string_builder = g_string_new (NULL); + const char *start, *p; + + start = p = string; + while (*p) { + while (*p && *p != '&' && *p != '<' && *p != '>') + p++; + + g_string_append_len (string_builder, start, p - start); + + if (*p == '\0') + break; + + switch (*p) { + case '<': + g_string_append (string_builder, "<"); + break; + + case '>': + g_string_append (string_builder, ">"); + break; + + case '&': + g_string_append (string_builder, "&"); + break; + + default: + break; + } + + p++; + start = p; + } + + return g_string_free (string_builder, FALSE); +} + +typedef struct { + MonoLockFreeQueueNode node; + MonoMethod *method; +} MethodNode; + +typedef struct { + int offset; + int counter; + char *filename; + int line; + int column; +} CoverageEntry; + +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 MonoProfilerCoverageData *entry) +{ + g_assert (prof == &coverage_profiler); + + int offset = entry->il_offset - coverage_profiler.previous_offset; + CoverageEntry *e = g_new (CoverageEntry, 1); + + coverage_profiler.previous_offset = entry->il_offset; + + e->offset = offset; + e->counter = entry->counter; + e->filename = g_strdup(entry->file_name ? entry->file_name : ""); + e->line = entry->line; + e->column = entry->column; + + g_ptr_array_add (coverage_profiler.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 = (char *) g_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 void +dump_method (gpointer key, gpointer value, gpointer userdata) +{ + MonoMethod *method = (MonoMethod *)value; + MonoClass *klass; + MonoImage *image; + char *class_name, *escaped_image_name, *escaped_class_name, *escaped_method_name, *escaped_method_signature, *escaped_method_filename; + const char *image_name, *method_name, *method_signature, *method_filename; + guint i; + + coverage_profiler.previous_offset = 0; + coverage_profiler.data = g_ptr_array_new (); + + mono_profiler_get_coverage_data (coverage_profiler.handle, method, obtain_coverage_for_method); + + klass = mono_method_get_class (method); + image = mono_class_get_image (klass); + image_name = mono_image_get_name (image); + + method_signature = 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_profiler.data->len != 0) { + CoverageEntry *entry = (CoverageEntry *)coverage_profiler.data->pdata[0]; + method_filename = entry->filename ? entry->filename : ""; + } else + method_filename = ""; + + image_name = image_name ? image_name : ""; + method_signature = method_signature ? method_signature : ""; + method_name = method_name ? method_name : ""; + + escaped_image_name = escape_string_for_xml (image_name); + escaped_class_name = escape_string_for_xml (class_name); + escaped_method_name = escape_string_for_xml (method_name); + escaped_method_signature = escape_string_for_xml (method_signature); + escaped_method_filename = escape_string_for_xml (method_filename); + + fprintf (coverage_profiler.file, "\t\n", + escaped_image_name, escaped_class_name, escaped_method_name, escaped_method_signature, escaped_method_filename, mono_method_get_token (method)); + + g_free (escaped_image_name); + g_free (escaped_class_name); + g_free (escaped_method_name); + g_free (escaped_method_signature); + g_free (escaped_method_filename); + + for (i = 0; i < coverage_profiler.data->len; i++) { + CoverageEntry *entry = (CoverageEntry *)coverage_profiler.data->pdata[i]; + + fprintf (coverage_profiler.file, "\t\t\n", + entry->offset, entry->counter, entry->line, entry->column); + } + + fprintf (coverage_profiler.file, "\t\n"); + + g_free (class_name); + + g_ptr_array_foreach (coverage_profiler.data, free_coverage_entry, NULL); + g_ptr_array_free (coverage_profiler.data, TRUE); +} + +/* 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_thread_hazardous_try_free (node, g_free); + } + + return count; +} + +static void +dump_classes_for_image (gpointer key, gpointer value, gpointer userdata) +{ + MonoClass *klass = (MonoClass *)key; + MonoLockFreeQueue *class_methods = (MonoLockFreeQueue *)value; + MonoImage *image; + char *class_name, *escaped_class_name; + const char *image_name; + int number_of_methods, partially_covered; + guint fully_covered; + + image = mono_class_get_image (klass); + image_name = mono_image_get_name (image); + + if (!image_name || strcmp (image_name, mono_image_get_name (((MonoImage*) userdata))) != 0) + return; + + class_name = mono_type_get_name (mono_class_get_type (klass)); + + number_of_methods = mono_class_num_methods (klass); + fully_covered = count_queue (class_methods); + /* We don't handle partial covered yet */ + partially_covered = 0; + + escaped_class_name = escape_string_for_xml (class_name); + + fprintf (coverage_profiler.file, "\t\n", + escaped_class_name, number_of_methods, fully_covered, partially_covered); + + g_free (escaped_class_name); + + 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 = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.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 +dump_assembly (gpointer key, gpointer value, gpointer userdata) +{ + MonoAssembly *assembly = (MonoAssembly *)value; + MonoImage *image = mono_assembly_get_image (assembly); + const char *image_name, *image_guid, *image_filename; + char *escaped_image_name, *escaped_image_filename; + int number_of_methods = 0, partially_covered = 0; + guint fully_covered = 0; + + image_name = mono_image_get_name (image); + image_guid = mono_image_get_guid (image); + image_filename = mono_image_get_filename (image); + + image_name = image_name ? image_name : ""; + image_guid = image_guid ? image_guid : ""; + image_filename = image_filename ? image_filename : ""; + + get_coverage_for_image (image, &number_of_methods, &fully_covered, &partially_covered); + + escaped_image_name = escape_string_for_xml (image_name); + escaped_image_filename = escape_string_for_xml (image_filename); + + fprintf (coverage_profiler.file, "\t\n", + escaped_image_name, image_guid, escaped_image_filename, number_of_methods, fully_covered, partially_covered); + + g_free (escaped_image_name); + g_free (escaped_image_filename); + + mono_conc_hashtable_foreach (coverage_profiler.classes, dump_classes_for_image, image); +} + +static void +dump_coverage (void) +{ + fprintf (coverage_profiler.file, "\n"); + fprintf (coverage_profiler.file, "\n"); + + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_foreach (coverage_profiler.assemblies, dump_assembly, NULL); + mono_conc_hashtable_foreach (coverage_profiler.methods, dump_method, NULL); + mono_os_mutex_unlock (&coverage_profiler.mutex); + + fprintf (coverage_profiler.file, "\n"); +} + +static MonoLockFreeQueueNode * +create_method_node (MonoMethod *method) +{ + MethodNode *node = (MethodNode *) 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) +{ + MonoError error; + 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; + + g_assert (prof == &coverage_profiler); + + flags = mono_method_get_flags (method, &iflags); + if ((iflags & METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL) || + (flags & METHOD_ATTRIBUTE_PINVOKE_IMPL)) + return FALSE; + + // Don't need to do anything else if we're already tracking this method + if (mono_conc_hashtable_lookup (coverage_profiler.methods, method)) + 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 (coverage_profiler.suppressed_assemblies, (gpointer) mono_image_get_name (image)) != NULL) + return FALSE; + + if (coverage_profiler.filters) { + /* Check already filtered classes first */ + if (mono_conc_hashtable_lookup (coverage_profiler.filtered_classes, klass)) + return FALSE; + + classname = mono_type_get_name (mono_class_get_type (klass)); + + fqn = g_strdup_printf ("[%s]%s", mono_image_get_name (image), classname); + + // Check positive filters first + has_positive = FALSE; + found = FALSE; + for (guint i = 0; i < coverage_profiler.filters->len; ++i) { + char *filter = (char *)g_ptr_array_index (coverage_profiler.filters, i); + + if (filter [0] == '+') { + filter = &filter [1]; + + if (strstr (fqn, filter) != NULL) + found = TRUE; + + has_positive = TRUE; + } + } + + if (has_positive && !found) { + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_insert (coverage_profiler.filtered_classes, klass, klass); + mono_os_mutex_unlock (&coverage_profiler.mutex); + g_free (fqn); + g_free (classname); + + return FALSE; + } + + for (guint i = 0; i < coverage_profiler.filters->len; ++i) { + // FIXME: Is substring search sufficient? + char *filter = (char *)g_ptr_array_index (coverage_profiler.filters, i); + if (filter [0] == '+') + continue; + + // Skip '-' + filter = &filter [1]; + + if (strstr (fqn, filter) != NULL) { + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_insert (coverage_profiler.filtered_classes, klass, klass); + mono_os_mutex_unlock (&coverage_profiler.mutex); + g_free (fqn); + g_free (classname); + + return FALSE; + } + } + + g_free (fqn); + g_free (classname); + } + + header = mono_method_get_header_checked (method, &error); + mono_error_cleanup (&error); + + mono_method_header_get_code (header, &code_size, NULL); + + assembly = mono_image_get_assembly (image); + + // Need to keep the assemblies around for as long as they are kept in the hashtable + // Nunit, for example, has a habit of unloading them before the coverage statistics are + // generated causing a crash. See https://bugzilla.xamarin.com/show_bug.cgi?id=39325 + mono_assembly_addref (assembly); + + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_insert (coverage_profiler.methods, method, method); + mono_conc_hashtable_insert (coverage_profiler.assemblies, assembly, assembly); + mono_os_mutex_unlock (&coverage_profiler.mutex); + + image_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.image_to_methods, image); + + if (image_methods == NULL) { + image_methods = (MonoLockFreeQueue *) g_malloc (sizeof (MonoLockFreeQueue)); + mono_lock_free_queue_init (image_methods); + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_insert (coverage_profiler.image_to_methods, image, image_methods); + mono_os_mutex_unlock (&coverage_profiler.mutex); + } + + node = create_method_node (method); + mono_lock_free_queue_enqueue (image_methods, node); + + class_methods = (MonoLockFreeQueue *)mono_conc_hashtable_lookup (coverage_profiler.classes, klass); + + if (class_methods == NULL) { + class_methods = (MonoLockFreeQueue *) g_malloc (sizeof (MonoLockFreeQueue)); + mono_lock_free_queue_init (class_methods); + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_insert (coverage_profiler.classes, klass, class_methods); + mono_os_mutex_unlock (&coverage_profiler.mutex); + } + + 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 (const gchar *filename) +{ + char *buffer; + ssize_t bytes_read; + long filesize; + int res, offset = 0; + FILE *stream; + + stream = fopen (filename, "r"); + if (stream == NULL) + return NULL; + + res = fseek (stream, 0, SEEK_END); + if (res < 0) { + fclose (stream); + return NULL; + } + + filesize = ftell (stream); + if (filesize < 0) { + fclose (stream); + return NULL; + } + + res = fseek (stream, 0, SEEK_SET); + if (res < 0) { + fclose (stream); + return NULL; + } + + if (filesize > MAX_FILE_SIZE) { + fclose (stream); + return NULL; + } + + buffer = (char *) 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'; + + fclose (stream); + 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; + + coverage_profiler.suppressed_assemblies = mono_conc_hashtable_new (g_str_hash, g_str_equal); + + /* Don't need to free content as it is referred to by the lines stored in @filters */ + content = get_file_content (SUPPRESSION_DIR "/mono-profiler-coverage.suppression"); + if (content == NULL) + content = get_file_content (SUPPRESSION_DIR "/mono-profiler-log.suppression"); + if (content == NULL) + return; + + while ((line = get_next_line (content, &content))) { + line = g_strchomp (g_strchug (line)); + /* No locking needed as we're doing initialization */ + mono_conc_hashtable_insert (coverage_profiler.suppressed_assemblies, line, line); + } +} + +static void +parse_cov_filter_file (GPtrArray *filters, const char *file) +{ + char *content; + char *line; + + /* Don't need to free content as it is referred to by the lines stored in @filters */ + content = get_file_content (file); + if (content == NULL) { + mono_profiler_printf_err ("Could not open coverage filter file '%s'.", file); + return; + } + + while ((line = get_next_line (content, &content))) + g_ptr_array_add (filters, g_strchug (g_strchomp (line))); +} + +static void +unref_coverage_assemblies (gpointer key, gpointer value, gpointer userdata) +{ + MonoAssembly *assembly = (MonoAssembly *)value; + mono_assembly_close (assembly); +} + +static void +log_shutdown (MonoProfiler *prof) +{ + g_assert (prof == &coverage_profiler); + + dump_coverage (); + + mono_os_mutex_lock (&coverage_profiler.mutex); + mono_conc_hashtable_foreach (coverage_profiler.assemblies, unref_coverage_assemblies, NULL); + mono_os_mutex_unlock (&coverage_profiler.mutex); + + mono_conc_hashtable_destroy (coverage_profiler.methods); + mono_conc_hashtable_destroy (coverage_profiler.assemblies); + mono_conc_hashtable_destroy (coverage_profiler.classes); + mono_conc_hashtable_destroy (coverage_profiler.filtered_classes); + + mono_conc_hashtable_destroy (coverage_profiler.image_to_methods); + mono_conc_hashtable_destroy (coverage_profiler.suppressed_assemblies); + mono_os_mutex_destroy (&coverage_profiler.mutex); + + if (*coverage_config.output_filename == '|') { + pclose (coverage_profiler.file); + } else if (*coverage_config.output_filename == '#') { + // do nothing + } else { + fclose (coverage_profiler.file); + } + + g_free (coverage_profiler.args); +} + +static void +runtime_initialized (MonoProfiler *profiler) +{ + mono_counters_register ("Event: Coverage methods", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_methods_ctr); + mono_counters_register ("Event: Coverage statements", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_statements_ctr); + mono_counters_register ("Event: Coverage classes", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_classes_ctr); + mono_counters_register ("Event: Coverage assemblies", MONO_COUNTER_UINT | MONO_COUNTER_PROFILER | MONO_COUNTER_MONOTONIC, &coverage_assemblies_ctr); +} + +static void usage (void); + +static gboolean +match_option (const char *arg, const char *opt_name, const char **rval) +{ + if (rval) { + const char *end = strchr (arg, '='); + + *rval = NULL; + if (!end) + return !strcmp (arg, opt_name); + + if (strncmp (arg, opt_name, strlen (opt_name)) || (end - arg) > strlen (opt_name) + 1) + return FALSE; + *rval = end + 1; + return TRUE; + } else { + //FIXME how should we handle passing a value to an arg that doesn't expect it? + return !strcmp (arg, opt_name); + } +} + +static void +parse_arg (const char *arg) +{ + const char *val; + + if (match_option (arg, "help", NULL)) { + usage (); + // } else if (match_option (arg, "zip", NULL)) { + // coverage_config.use_zip = TRUE; + } else if (match_option (arg, "output", &val)) { + coverage_config.output_filename = g_strdup (val); + // } else if (match_option (arg, "covfilter", &val)) { + // g_error ("not supported"); + } else if (match_option (arg, "covfilter-file", &val)) { + if (coverage_config.cov_filter_files == NULL) + coverage_config.cov_filter_files = g_ptr_array_new (); + g_ptr_array_add (coverage_config.cov_filter_files, g_strdup (val)); + } else { + mono_profiler_printf_err ("Could not parse argument: %s", arg); + } +} + +static void +parse_args (const char *desc) +{ + const char *p; + gboolean in_quotes = FALSE; + char quote_char = '\0'; + char *buffer = malloc (strlen (desc)); + int buffer_pos = 0; + + for (p = desc; *p; p++){ + switch (*p){ + case ',': + if (!in_quotes) { + if (buffer_pos != 0){ + buffer [buffer_pos] = 0; + parse_arg (buffer); + buffer_pos = 0; + } + } else { + buffer [buffer_pos++] = *p; + } + break; + + case '\\': + if (p [1]) { + buffer [buffer_pos++] = p[1]; + p++; + } + break; + case '\'': + case '"': + if (in_quotes) { + if (quote_char == *p) + in_quotes = FALSE; + else + buffer [buffer_pos++] = *p; + } else { + in_quotes = TRUE; + quote_char = *p; + } + break; + default: + buffer [buffer_pos++] = *p; + break; + } + } + + if (buffer_pos != 0) { + buffer [buffer_pos] = 0; + parse_arg (buffer); + } + + g_free (buffer); +} + +static void +usage (void) +{ + mono_profiler_printf ("Mono coverage profiler"); + mono_profiler_printf ("Usage: mono --profile=coverage[:OPTION1[,OPTION2...]] program.exe\n"); + mono_profiler_printf ("Options:"); + mono_profiler_printf ("\thelp show this usage info"); + + // mono_profiler_printf ("\tcovfilter=ASSEMBLY add ASSEMBLY to the code coverage filters"); + // mono_profiler_printf ("\t prefix a + to include the assembly or a - to exclude it"); + // mono_profiler_printf ("\t e.g. covfilter=-mscorlib"); + mono_profiler_printf ("\tcovfilter-file=FILE use FILE to generate the list of assemblies to be filtered"); + mono_profiler_printf ("\toutput=FILENAME write the data to file FILENAME (the file is always overwritten)"); + mono_profiler_printf ("\toutput=+FILENAME write the data to file FILENAME.pid (the file is always overwritten)"); + mono_profiler_printf ("\toutput=|PROGRAM write the data to the stdin of PROGRAM"); + mono_profiler_printf ("\toutput=|PROGRAM write the data to the stdin of PROGRAM"); + // mono_profiler_printf ("\tzip compress the output data"); +} + +MONO_API void +mono_profiler_init_coverage (const char *desc); + +void +mono_profiler_init_coverage (const char *desc) +{ + GPtrArray *filters = NULL; + + parse_args (desc [strlen("coverage")] == ':' ? desc + strlen ("coverage") + 1 : ""); + + if (coverage_config.cov_filter_files) { + filters = g_ptr_array_new (); + int i; + for (i = 0; i < coverage_config.cov_filter_files->len; ++i) { + const char *name = coverage_config.cov_filter_files->pdata [i]; + parse_cov_filter_file (filters, name); + } + } + + coverage_profiler.args = g_strdup (desc); + + //If coverage_config.output_filename begin with +, append the pid at the end + if (!coverage_config.output_filename) + coverage_config.output_filename = "coverage.xml"; + else if (*coverage_config.output_filename == '+') + coverage_config.output_filename = g_strdup_printf ("%s.%d", coverage_config.output_filename + 1, getpid ()); + + if (*coverage_config.output_filename == '|') + coverage_profiler.file = popen (coverage_config.output_filename + 1, "w"); + else if (*coverage_config.output_filename == '#') + coverage_profiler.file = fdopen (strtol (coverage_config.output_filename + 1, NULL, 10), "a"); + else + coverage_profiler.file = fopen (coverage_config.output_filename, "w"); + + if (!coverage_profiler.file) { + mono_profiler_printf_err ("Could not create coverage profiler output file '%s'.", coverage_config.output_filename); + exit (1); + } + + mono_os_mutex_init (&coverage_profiler.mutex); + coverage_profiler.methods = mono_conc_hashtable_new (NULL, NULL); + coverage_profiler.assemblies = mono_conc_hashtable_new (NULL, NULL); + coverage_profiler.classes = mono_conc_hashtable_new (NULL, NULL); + coverage_profiler.filtered_classes = mono_conc_hashtable_new (NULL, NULL); + coverage_profiler.image_to_methods = mono_conc_hashtable_new (NULL, NULL); + init_suppressed_assemblies (); + + coverage_profiler.filters = filters; + + MonoProfilerHandle handle = coverage_profiler.handle = mono_profiler_create (&coverage_profiler); + + /* + * Required callbacks. These are either necessary for the profiler itself + * to function, or provide metadata that's needed if other events (e.g. + * allocations, exceptions) are dynamically enabled/disabled. + */ + + mono_profiler_set_runtime_shutdown_end_callback (handle, log_shutdown); + mono_profiler_set_runtime_initialized_callback (handle, runtime_initialized); + + mono_profiler_enable_coverage (); + mono_profiler_set_coverage_filter_callback (handle, coverage_filter); +} diff --git a/mono/profiler/log-args.c b/mono/profiler/log-args.c index 1b9c8358694..3cfadd1e513 100644 --- a/mono/profiler/log-args.c +++ b/mono/profiler/log-args.c @@ -81,6 +81,7 @@ parse_arg (const char *arg, ProfilerConfig *config) } else if (match_option (arg, "calls", NULL)) { config->enter_leave = TRUE; } else if (match_option (arg, "coverage", NULL)) { + g_warning ("the log profiler support for code coverage is obsolete, use the \"coverage\" profiler"); config->collect_coverage = TRUE; } else if (match_option (arg, "zip", NULL)) { config->use_zip = TRUE; -- 2.25.1