[Profiler] Add coverage information to the log profiler
authoriain holmes <iain@xamarin.com>
Tue, 12 May 2015 14:43:31 +0000 (15:43 +0100)
committeriain holmes <iain@xamarin.com>
Fri, 15 May 2015 15:05:21 +0000 (16:05 +0100)
Store coverage information in the mlpd file. Extend the mprof-report tool to
give a basic coverage summary.

mono/profiler/Makefile.am
mono/profiler/decode.c
mono/profiler/mono-profiler-log.suppression [new file with mode: 0644]
mono/profiler/proflog.c
mono/profiler/proflog.h

index 115232490c3930eef2241e06b2840690ca67e189..df6ab41f35f2d990c50297c5c768a7e9214bff39 100644 (file)
@@ -6,6 +6,7 @@ endif
 
 AM_CPPFLAGS = \
        -fexceptions -DMONO_USE_EXC_TABLES      \
+       -DSUPPRESSION_DIR=\""$(datadir)/mono-$(API_VER)/mono/profiler"\"        \
        -I$(top_srcdir)         \
        $(GLIB_CFLAGS)
 
@@ -68,7 +69,7 @@ libmono_profiler_vtune_la_LIBADD = $(VTUNE_LIBS) $(LIBMONO) $(GLIB_LIBS) $(LIBIC
 endif
 
 mprof_report_SOURCES = decode.c
-mprof_report_LDADD = $(Z_LIBS)
+mprof_report_LDADD = $(Z_LIBS) $(GLIB_LIBS) $(LIBICONV)
 
 PLOG_TESTS_SRC=test-alloc.cs test-busy.cs test-monitor.cs test-excleave.cs \
        test-heapshot.cs test-traces.cs
@@ -91,5 +92,8 @@ else
 check-local: testlog
 endif
 
+suppressiondir = $(datadir)/mono-$(API_VER)/mono/profiler
+suppression_DATA = mono-profiler-log.suppression
+
 EXTRA_DIST=utils.c utils.h proflog.h perf_event.h \
        $(PLOG_TESTS_SRC) ptestrunner.pl
index 76ff0f25e34b5e2c290f50db22d307172014d957..c7f0539b7e3273f44f2694730b557999925d8665 100644 (file)
@@ -6,6 +6,51 @@
  *
  * Copyright 2010 Novell, Inc (http://www.novell.com)
  */
+
+/*
+ * The Coverage XML output schema
+ * <coverage>
+ *   <assembly/>
+ *   <class/>
+ *   <method>
+ *     <statement/>
+ *   </method>
+ * </coverage>
+ *
+ * Elements:
+ *   <coverage> - The root element of the documentation. It can contain any number of
+ *                <assembly>, <class> or <method> elements.
+ *                Attributes:
+ *                   - version: The version number for the file format - (eg: "0.3")
+ *   <assembly> - 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
+ *   <class> - 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
+ *   <method> - Contains data about methods. Can contain any number of <statement> 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
+ *   <statement> - 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 <config.h>
 #include "utils.c"
 #include "proflog.h"
@@ -49,6 +94,7 @@ static uint64_t time_to = 0xffffffffffffffffULL;
 static int use_time_filter = 0;
 static uint64_t startup_time = 0;
 static FILE* outfile = NULL;
+static FILE* coverage_outfile = NULL;
 
 static int32_t
 read_int16 (unsigned char *p)
@@ -1204,7 +1250,7 @@ heap_shot_obj_add_refs (HeapShot *hs, uintptr_t objaddr, uintptr_t num, uintptr_
        /* should not happen */
        printf ("failed heap obj update\n");
        return NULL;
-       
+
 }
 
 static uintptr_t
@@ -1489,7 +1535,7 @@ load_data (ProfContext *ctx, int size)
                if (r == 0)
                        return size == 0? 1: 0;
                return r == size;
-       } else 
+       } else
 #endif
        {
                int r = fread (ctx->buf, size, 1, ctx->file);
@@ -1883,7 +1929,7 @@ track_obj_reference (uintptr_t obj, uintptr_t parent, ClassDesc *cd)
 {
        int i;
        for (i = 0; i < num_tracked_objects; ++i) {
-               if (tracked_objects [i] == obj) 
+               if (tracked_objects [i] == obj)
                        fprintf (outfile, "Object %p referenced from %p (%s).\n", (void*)obj, (void*)parent, cd->name);
        }
 }
@@ -1928,6 +1974,117 @@ code_buffer_desc (int type)
        }
 }
 
+typedef struct _CoverageAssembly CoverageAssembly;
+struct _CoverageAssembly {
+       char *name;
+       char *guid;
+       char *filename;
+       int number_of_methods;
+       int fully_covered;
+       int partially_covered;
+};
+
+typedef struct _CoverageClass CoverageClass;
+struct _CoverageClass {
+       char *assembly_name;
+       char *class_name;
+       int number_of_methods;
+       int fully_covered;
+       int partially_covered;
+};
+
+typedef struct _CoverageCoverage CoverageCoverage;
+struct _CoverageCoverage {
+       int method_id;
+       int offset;
+       int count;
+       int line;
+       int column;
+};
+
+typedef struct _CoverageMethod CoverageMethod;
+struct _CoverageMethod {
+       char *assembly_name;
+       char *class_name;
+       char *method_name;
+       char *method_signature;
+       char *filename;
+       int token;
+       int n_statements;
+       int method_id;
+       GPtrArray *coverage;
+};
+static GPtrArray *coverage_assemblies = NULL;
+static GPtrArray *coverage_methods = NULL;
+static GPtrArray *coverage_statements = NULL;
+static GHashTable *coverage_methods_hash = NULL;
+static GPtrArray *coverage_classes = NULL;
+static GHashTable *coverage_assembly_classes = NULL;
+
+static void
+gather_coverage_statements (void)
+{
+       for (guint i = 0; i < coverage_statements->len; i++) {
+               CoverageCoverage *coverage = coverage_statements->pdata[i];
+               CoverageMethod *method = g_hash_table_lookup (coverage_methods_hash, GINT_TO_POINTER (coverage->method_id));
+               if (method == NULL) {
+                       fprintf (outfile, "Cannot find method with ID: %d\n", coverage->method_id);
+                       continue;
+               }
+
+               g_ptr_array_add (method->coverage, coverage);
+       }
+}
+
+static void
+coverage_add_assembly (CoverageAssembly *assembly)
+{
+       if (coverage_assemblies == NULL)
+               coverage_assemblies = g_ptr_array_new ();
+
+       g_ptr_array_add (coverage_assemblies, assembly);
+}
+
+static void
+coverage_add_method (CoverageMethod *method)
+{
+       if (coverage_methods == NULL) {
+               coverage_methods = g_ptr_array_new ();
+               coverage_methods_hash = g_hash_table_new (NULL, NULL);
+       }
+
+       g_ptr_array_add (coverage_methods, method);
+       g_hash_table_insert (coverage_methods_hash, GINT_TO_POINTER (method->method_id), method);
+}
+
+static void
+coverage_add_class (CoverageClass *klass)
+{
+       GPtrArray *classes = NULL;
+
+       if (coverage_classes == NULL) {
+               coverage_classes = g_ptr_array_new ();
+               coverage_assembly_classes = g_hash_table_new (g_str_hash, g_str_equal);
+       }
+
+       g_ptr_array_add (coverage_classes, klass);
+       classes = g_hash_table_lookup (coverage_assembly_classes, klass->assembly_name);
+       if (classes == NULL) {
+               classes = g_ptr_array_new ();
+               g_hash_table_insert (coverage_assembly_classes, klass->assembly_name, classes);
+       }
+       g_ptr_array_add (classes, klass);
+}
+
+static void
+coverage_add_coverage (CoverageCoverage *coverage)
+{
+       if (coverage_statements == NULL)
+               coverage_statements = g_ptr_array_new ();
+
+       g_ptr_array_add (coverage_statements, coverage);
+}
+
 #define OBJ_ADDR(diff) ((obj_base + diff) << 3)
 #define LOG_TIME(base,diff) /*fprintf("outfile, time %llu + %llu near offset %d\n", base, diff, p - ctx->buf)*/
 
@@ -2556,6 +2713,109 @@ decode_buffer (ProfContext *ctx)
                        }
                        break;
                }
+               case TYPE_COVERAGE:{
+                       int subtype = *p & 0xf0;
+                       switch (subtype) {
+                       case TYPE_COVERAGE_METHOD: {
+                               CoverageMethod *method = g_new0 (CoverageMethod, 1);
+                               const char *assembly, *klass, *name, *sig, *filename;
+                               int token, n_offsets, method_id;
+
+                               p++;
+                               assembly = (void *)p; while (*p) p++; p++;
+                               klass = (void *)p; while (*p) p++; p++;
+                               name = (void *)p; while (*p) p++; p++;
+                               sig = (void *)p; while (*p) p++; p++;
+                               filename = (void *)p; while (*p) p++; p++;
+
+                               token = decode_uleb128 (p, &p);
+                               method_id = decode_uleb128 (p, &p);
+                               n_offsets = decode_uleb128 (p, &p);
+
+                               method->assembly_name = g_strdup (assembly);
+                               method->class_name = g_strdup (klass);
+                               method->method_name = g_strdup (name);
+                               method->method_signature = g_strdup (sig);
+                               method->filename = g_strdup (filename);
+                               method->token = token;
+                               method->n_statements = n_offsets;
+                               method->coverage = g_ptr_array_new ();
+                               method->method_id = method_id;
+
+                               coverage_add_method (method);
+
+                               break;
+                       }
+                       case TYPE_COVERAGE_STATEMENT: {
+                               CoverageCoverage *coverage = g_new0 (CoverageCoverage, 1);
+                               int offset, count, line, column, method_id;
+
+                               p++;
+                               method_id = decode_uleb128 (p, &p);
+                               offset = decode_uleb128 (p, &p);
+                               count = decode_uleb128 (p, &p);
+                               line = decode_uleb128 (p, &p);
+                               column = decode_uleb128 (p, &p);
+
+                               coverage->method_id = method_id;
+                               coverage->offset = offset;
+                               coverage->count = count;
+                               coverage->line = line;
+                               coverage->column = column;
+
+                               coverage_add_coverage (coverage);
+                               break;
+                       }
+                       case TYPE_COVERAGE_ASSEMBLY: {
+                               CoverageAssembly *assembly = g_new0 (CoverageAssembly, 1);
+                               char *name, *guid, *filename;
+                               int number_of_methods, fully_covered, partially_covered;
+                               p++;
+
+                               name = (void *)p; while (*p) p++; p++;
+                               guid = (void *)p; while (*p) p++; p++;
+                               filename = (void *)p; while (*p) p++; p++;
+                               number_of_methods = decode_uleb128 (p, &p);
+                               fully_covered = decode_uleb128 (p, &p);
+                               partially_covered = decode_uleb128 (p, &p);
+
+                               assembly->name = g_strdup (name);
+                               assembly->guid = g_strdup (guid);
+                               assembly->filename = g_strdup (filename);
+                               assembly->number_of_methods = number_of_methods;
+                               assembly->fully_covered = fully_covered;
+                               assembly->partially_covered = partially_covered;
+
+                               coverage_add_assembly (assembly);
+                               break;
+                       }
+                       case TYPE_COVERAGE_CLASS: {
+                               CoverageClass *klass = g_new0 (CoverageClass, 1);
+                               char *assembly_name, *class_name;
+                               int number_of_methods, fully_covered, partially_covered;
+                               p++;
+
+                               assembly_name = (void *)p; while (*p) p++; p++;
+                               class_name = (void *)p; while (*p) p++; p++;
+                               number_of_methods = decode_uleb128 (p, &p);
+                               fully_covered = decode_uleb128 (p, &p);
+                               partially_covered = decode_uleb128 (p, &p);
+
+                               klass->assembly_name = g_strdup (assembly_name);
+                               klass->class_name = g_strdup (class_name);
+                               klass->number_of_methods = number_of_methods;
+                               klass->fully_covered = fully_covered;
+                               klass->partially_covered = partially_covered;
+
+                               coverage_add_class (klass);
+                               break;
+                       }
+
+                       default:
+                               break;
+                       }
+                       break;
+               }
                default:
                        fprintf (outfile, "unhandled profiler event: 0x%x at file offset: %llu + %lld (len: %d\n)\n", *p, (unsigned long long) file_offset, (long long) (p - ctx->buf), len);
                        exit (1);
@@ -3065,6 +3325,160 @@ dump_heap_shots (void)
        }
 }
 
+/* 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, "&lt;");
+                       break;
+
+               case '>':
+                       g_string_append (string_builder, "&gt;");
+                       break;
+
+               case '&':
+                       g_string_append (string_builder, "&amp;");
+                       break;
+
+               default:
+                       break;
+               }
+
+               p++;
+               start = p;
+       }
+
+       return g_string_free (string_builder, FALSE);
+}
+
+static int
+sort_assemblies (gconstpointer a, gconstpointer b)
+{
+       CoverageAssembly *assembly_a = *(CoverageAssembly **)a;
+       CoverageAssembly *assembly_b = *(CoverageAssembly **)b;
+
+       if (assembly_a->name == NULL && assembly_b->name == NULL)
+               return 0;
+       else if (assembly_a->name == NULL)
+               return -1;
+       else if (assembly_b->name == NULL)
+               return 1;
+
+       return strcmp (assembly_a->name, assembly_b->name);
+}
+
+static void
+dump_coverage (void)
+{
+       if (!coverage_methods && !coverage_assemblies)
+               return;
+
+       gather_coverage_statements ();
+       fprintf (outfile, "\nCoverage Summary:\n");
+
+       if (coverage_outfile) {
+               fprintf (coverage_outfile, "<?xml version=\"1.0\"?>\n");
+               fprintf (coverage_outfile, "<coverage version=\"0.3\">\n");
+       }
+
+       g_ptr_array_sort (coverage_assemblies, sort_assemblies);
+
+       for (guint i = 0; i < coverage_assemblies->len; i++) {
+               CoverageAssembly *assembly = coverage_assemblies->pdata[i];
+               GPtrArray *classes;
+
+               if (assembly->number_of_methods != 0) {
+                       int percentage = ((assembly->fully_covered + assembly->partially_covered) * 100) / assembly->number_of_methods;
+                       fprintf (outfile, "\t%s (%s) %d%% covered (%d methods - %d covered)\n", assembly->name, assembly->filename, percentage, assembly->number_of_methods, assembly->fully_covered);
+               } else
+                       fprintf (outfile, "\t%s (%s) ?%% covered (%d methods - %d covered)\n", assembly->name, assembly->filename, assembly->number_of_methods, assembly->fully_covered);
+
+               if (coverage_outfile) {
+                       char *escaped_name, *escaped_filename;
+                       escaped_name = escape_string_for_xml (assembly->name);
+                       escaped_filename = escape_string_for_xml (assembly->filename);
+
+                       fprintf (coverage_outfile, "\t<assembly name=\"%s\" guid=\"%s\" filename=\"%s\" method-count=\"%d\" full=\"%d\" partial=\"%d\"/>\n", escaped_name, assembly->guid, escaped_filename, assembly->number_of_methods, assembly->fully_covered, assembly->partially_covered);
+
+                       g_free (escaped_name);
+                       g_free (escaped_filename);
+               }
+
+               classes = g_hash_table_lookup (coverage_assembly_classes, assembly->name);
+               if (classes) {
+                       for (guint j = 0; j < classes->len; j++) {
+                               CoverageClass *klass = classes->pdata[j];
+
+                               if (klass->number_of_methods > 0) {
+                                       int percentage = ((klass->fully_covered + klass->partially_covered) * 100) / klass->number_of_methods;
+                                       fprintf (outfile, "\t\t%s %d%% covered (%d methods - %d covered)\n", klass->class_name, percentage, klass->number_of_methods, klass->fully_covered);
+                               } else
+                                       fprintf (outfile, "\t\t%s ?%% covered (%d methods - %d covered)\n", klass->class_name, klass->number_of_methods, klass->fully_covered);
+
+                               if (coverage_outfile) {
+                                       char *escaped_name;
+                                       escaped_name = escape_string_for_xml (klass->class_name);
+
+                                       fprintf (coverage_outfile, "\t\t<class name=\"%s\" method-count=\"%d\" full=\"%d\" partial=\"%d\"/>\n", escaped_name, klass->number_of_methods, klass->fully_covered, klass->partially_covered);
+                                       g_free (escaped_name);
+                               }
+                       }
+               }
+       }
+
+       for (guint i = 0; i < coverage_methods->len; i++) {
+               CoverageMethod *method = coverage_methods->pdata[i];
+
+               if (coverage_outfile) {
+                       char *escaped_assembly, *escaped_class, *escaped_method, *escaped_sig, *escaped_filename;
+
+                       escaped_assembly = escape_string_for_xml (method->assembly_name);
+                       escaped_class = escape_string_for_xml (method->class_name);
+                       escaped_method = escape_string_for_xml (method->method_name);
+                       escaped_sig = escape_string_for_xml (method->method_signature);
+                       escaped_filename = escape_string_for_xml (method->filename);
+
+                       fprintf (coverage_outfile, "\t<method assembly=\"%s\" class=\"%s\" name=\"%s (%s)\" filename=\"%s\" token=\"%d\">\n", escaped_assembly, escaped_class, escaped_method, escaped_sig, escaped_filename, method->token);
+
+                       g_free (escaped_assembly);
+                       g_free (escaped_class);
+                       g_free (escaped_method);
+                       g_free (escaped_sig);
+                       g_free (escaped_filename);
+
+                       for (guint j = 0; j < method->coverage->len; j++) {
+                               CoverageCoverage *coverage = method->coverage->pdata[j];
+                               fprintf (coverage_outfile, "\t\t<statement offset=\"%d\" counter=\"%d\" line=\"%d\" column=\"%d\"/>\n", coverage->offset, coverage->count, coverage->line, coverage->column);
+                       }
+                       fprintf (coverage_outfile, "\t</method>\n");
+               }
+       }
+
+       if (coverage_outfile) {
+               fprintf (coverage_outfile, "</coverage>\n");
+               fclose (coverage_outfile);
+               coverage_outfile = NULL;
+       }
+}
+
 static void
 flush_context (ProfContext *ctx)
 {
@@ -3079,7 +3493,7 @@ flush_context (ProfContext *ctx)
        }
 }
 
-static const char *reports = "header,jit,gc,sample,alloc,call,metadata,exception,monitor,thread,heapshot,counters";
+static const char *reports = "header,jit,gc,sample,alloc,call,metadata,exception,monitor,thread,heapshot,counters,coverage";
 
 static const char*
 match_option (const char *p, const char *opt)
@@ -3159,6 +3573,11 @@ print_reports (ProfContext *ctx, const char *reps, int parse_only)
                                dump_counters ();
                        continue;
                }
+               if ((opt = match_option (p, "coverage")) != p) {
+                       if (!parse_only)
+                               dump_coverage ();
+                       continue;
+               }
                return 0;
        }
        return 1;
@@ -3187,7 +3606,7 @@ usage (void)
        printf ("Options:\n");
        printf ("\t--help               display this help\n");
        printf ("\t--out=FILE           write to FILE instead of stdout\n");
-       printf ("\t--traces             collect and show backtraces\n"); 
+       printf ("\t--traces             collect and show backtraces\n");
        printf ("\t--maxframes=NUM      limit backtraces to NUM entries\n");
        printf ("\t--reports=R1[,R2...] print the specified reports. Defaults are:\n");
        printf ("\t                     %s\n", reports);
@@ -3202,6 +3621,7 @@ usage (void)
        printf ("\t--time=FROM-TO       consider data FROM seconds from startup up to TO seconds\n");
        printf ("\t--verbose            increase verbosity level\n");
        printf ("\t--debug              display decoding debug info for mprof-report devs\n");
+       printf ("\t--coverage-out=FILE  write the coverage info to FILE as XML\n");
 }
 
 int
@@ -3313,6 +3733,13 @@ main (int argc, char *argv[])
                } else if (strcmp ("--traces", argv [i]) == 0) {
                        show_traces = 1;
                        collect_traces = 1;
+               } else if (strncmp ("--coverage-out=", argv [i], 15) == 0) {
+                       const char *val = argv [i] + 15;
+                       coverage_outfile = fopen (val, "w");
+                       if (!coverage_outfile) {
+                               printf ("Cannot open output file: %s\n", val);
+                               return 1;
+                       }
                } else {
                        break;
                }
@@ -3333,4 +3760,3 @@ main (int argc, char *argv[])
        print_reports (ctx, reports, 0);
        return 0;
 }
-
diff --git a/mono/profiler/mono-profiler-log.suppression b/mono/profiler/mono-profiler-log.suppression
new file mode 100644 (file)
index 0000000..ed78111
--- /dev/null
@@ -0,0 +1,149 @@
+Commons.Xml.Relaxng
+Compat.ICSharpCode.SharpZipLib
+I18N
+I18N.CJK
+I18N.MidEast
+I18N.Other
+I18N.Rare
+I18N.West
+IBM.Data.DB2
+ICSharpCode.SharpZipLib
+gconf-sharp
+gdk-sharp
+glib-sharp
+gnome-sharp
+gnome-vfs-sharp
+gtk-sharp
+Mainsoft.Configuration
+Mainsoft.Web
+Managed.Windows.Forms
+MicrosoftAjaxLibrary
+Microsoft.Build
+Microsoft.Build.Engine
+Microsoft.Build.Framework
+Microsoft.Build.Tasks
+Microsoft.Build.Utilities
+Microsoft.CSharp
+Microsoft.VisualC
+Microsoft.Web.Infrastructure
+Mono.Addins
+Mono.Addins.Setup
+Mono.C5
+Mono.Cairo
+Mono.Cecil
+Mono.Cecil.Mdb
+Mono.CodeContracts
+Mono.CompilerServices.SymbolWriter
+Mono.CSharp
+Mono.Data.Sqlite
+Mono.Data.Tds
+Mono.Debugger.Soft
+Mono.Directory.LDAP
+Mono.Dynamic.Interpreter
+Mono.Http
+Mono.Management
+Mono.Messaging
+Mono.Messaging.RabbitMQ
+Mono.Options
+Mono.Parallel
+Mono.Posix
+Mono.Reactive.Testing
+Mono.Security
+Mono.Security.Win32
+Mono.ServiceModel.IdentitySelectors
+Mono.Simd
+Mono.Tasklets
+Mono.WebBrowser
+Mono.XBuild.Tasks
+Mono.Xml.Ext
+mscorlib
+Novell.Directory.Ldap
+pango-sharp
+System
+System.ComponentModel.Composition.4.5
+System.ComponentModel.DataAnnotations
+System.Configuration
+System.Configuration.Install
+System.Core
+System.Data
+System.Data.DataSetExtensions
+System.Data.Entity
+System.Data.Linq
+System.Data.OracleClient
+System.Data.Services
+System.Data.Services.Client
+System.Design
+System.DirectoryServices
+System.DirectoryServices.Protocols
+System.Drawing
+System.Drawing.Design
+System.Dynamic
+System.EnterpriseServices
+System.IdentityModel
+System.IdentityModel.Selectors
+System.Interactive
+System.Interactive.Async
+System.Interactive.Providers
+System.IO.Compression
+System.IO.Compression.FileSystem
+System.Json
+System.Json.Microsoft
+System.Management
+System.Messaging
+System.Net
+System.Net.Http
+System.Net.Http.Formatting
+System.Net.Http.WebRequest
+System.Numerics
+System.Reactive.Core
+System.Reactive.Debugger
+System.Reactive.Experimental
+System.Reactive.Interfaces
+System.Reactive.Linq
+System.Reactive.Observable.Aliases
+System.Reactive.PlatformServices
+System.Reactive.Providers
+System.Reactive.Runtime.Remoting
+System.Reactive.Windows.Forms
+System.Reactive.Windows.Threading
+System.Runtime.Caching
+System.Runtime.DurableInstancing
+System.Runtime.Remoting
+System.Runtime.Serialization
+System.Runtime.Serialization.Formatters.Soap
+System.Runtime.Serialization.Json
+System.Security
+System.ServiceModel
+System.ServiceModel.Activation
+System.ServiceModel.Discovery
+System.ServiceModel.Routing
+System.ServiceModel.Syndication
+System.ServiceModel.Web
+System.ServiceModel.Web.Extensions
+System.ServiceProcess
+System.Threading.Tasks.Dataflow
+System.Transactions
+System.Web
+System.Web.Abstractions
+System.Web.ApplicationServices
+System.Web.DynamicData
+System.Web.Extensions
+System.Web.Extensions.Design
+System.Web.Http
+System.Web.Http.SelfHost
+System.Web.Http.WebHost
+System.Web.Mvc3
+System.Web.Razor
+System.Web.Routing
+System.Web.Services
+SystemWebTestShim
+System.Web.WebPages
+System.Web.WebPages.Deployment
+System.Web.WebPages.Razor
+System.Windows
+System.Windows.Forms.DataVisualization
+System.Xaml
+System.Xml
+System.Xml.Linq
+System.Xml.Serialization
+WebMatrix.Data
index 60a38f2860cf69f3f0814650ed17a7c20b923f32..76cbc677f33b52d0aafc5cf9446f1e0146268820 100644 (file)
@@ -16,6 +16,9 @@
 #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>
@@ -103,6 +106,8 @@ static int do_mono_sample = 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:
@@ -349,7 +354,43 @@ typedef struct _LogBuffer LogBuffer;
  *             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;
@@ -413,6 +454,7 @@ struct _MonoProfiler {
        MonoConcurrentHashTable *method_table;
        mono_mutex_t method_table_mutex;
        BinaryObject *binary_objects;
+       GPtrArray *coverage_filters;
 };
 
 typedef struct _WriterQueueEntry WriterQueueEntry;
@@ -1173,6 +1215,10 @@ class_loaded (MonoProfiler *prof, MonoClass *klass, int result)
        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)
 {
@@ -1186,6 +1232,11 @@ 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);
 }
 
@@ -2493,8 +2544,566 @@ counters_and_perfcounters_sample (MonoProfiler *prof)
        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, "&lt;&gt;", 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;
+}
+
+static void
+init_suppressed_assemblies (void)
+{
+       size_t n;
+       char *line;
+
+       mono_mutex_init (&suppressed_assemblies_mutex);
+       suppressed_assemblies = mono_conc_hashtable_new (&suppressed_assemblies_mutex, g_str_hash, g_str_equal);
+       FILE *sa_file = fopen (SUPPRESSION_DIR "/mono-profiler-log.suppression", "r");
+       if (sa_file == NULL)
+               return;
+
+       n = 0;
+       line = NULL;
+       while (getline (&line, &n, sa_file) > -1) {
+               line = g_strchomp (g_strchug (line));
+               mono_conc_hashtable_insert (suppressed_assemblies, line, line);
+               n = 0;
+               line = NULL;
+       }
+
+       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)
 {
@@ -2504,6 +3113,8 @@ 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));
@@ -2537,8 +3148,14 @@ log_shutdown (MonoProfiler *prof)
        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);
+       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);
 }
@@ -2656,11 +3273,11 @@ helper_thread (void* arg)
                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);
@@ -2901,7 +3518,7 @@ runtime_initialized (MonoProfiler *profiler)
 }
 
 static MonoProfiler*
-create_profiler (const char *filename)
+create_profiler (const char *filename, GPtrArray *filters)
 {
        MonoProfiler *prof;
        char *nf;
@@ -2963,15 +3580,24 @@ create_profiler (const char *filename)
        if (do_counters && !need_helper_thread) {
                need_helper_thread = 1;
        }
+
 #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);
 
+       if (do_coverage)
+               coverage_init (prof);
+       prof->coverage_filters = filters;
+
        prof->startup_time = current_time ();
        return prof;
 }
@@ -2982,24 +3608,29 @@ usage (int do_exit)
        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);
 }
@@ -3124,7 +3755,7 @@ set_hsmode (char* val, int allow_empty)
        free (val);
 }
 
-/* 
+/*
  * declaration to silence the compiler: this is the entry point that
  * mono will load from the shared library and call.
  */
@@ -3148,6 +3779,7 @@ void
 mono_profiler_startup (const char *desc)
 {
        MonoProfiler *prof;
+       GPtrArray *filters = NULL;
        char *filename = NULL;
        const char *p;
        const char *opt;
@@ -3155,10 +3787,12 @@ mono_profiler_startup (const char *desc)
        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))
@@ -3276,6 +3910,48 @@ mono_profiler_startup (const char *desc)
                        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;
+                       size_t n;
+
+                       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);
+                       }
+
+                       n = 0;
+                       line = NULL;
+                       while (getline (&line, &n, filter_file) > -1) {
+                               g_ptr_array_add (filters, g_strchug (g_strchomp (line)));
+                               n = 0;
+                               line = NULL;
+                       }
+
+                       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);
@@ -3289,9 +3965,12 @@ mono_profiler_startup (const char *desc)
                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 ();
@@ -3311,8 +3990,9 @@ mono_profiler_startup (const char *desc)
        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);
@@ -3324,4 +4004,3 @@ mono_profiler_startup (const char *desc)
        TLS_INIT (tlsbuffer);
        TLS_INIT (tlsmethodlist);
 }
-
index ea3b267dab327282843b4c1432e3b789737742b1..8b7260bb1eb134047029febaf63e6141ec54a79b 100644 (file)
@@ -5,7 +5,7 @@
 #define LOG_HEADER_ID 0x4D505A01
 #define LOG_VERSION_MAJOR 0
 #define LOG_VERSION_MINOR 4
-#define LOG_DATA_VERSION 9
+#define LOG_DATA_VERSION 10
 /*
  * Changes in data versions:
  * version 2: added offsets in heap walk
@@ -15,6 +15,7 @@
  * version 6: added optional backtrace in sampling info
  * version 8: added TYPE_RUNTIME and JIT helpers/trampolines
  * version 9: added MONO_PROFILER_CODE_BUFFER_EXCEPTION_HANDLING
+ * version 10: added TYPE_COVERAGE
  */
 
 enum {
@@ -27,6 +28,7 @@ enum {
        TYPE_HEAP,
        TYPE_SAMPLE,
        TYPE_RUNTIME,
+       TYPE_COVERAGE,
        /* extended type for TYPE_HEAP */
        TYPE_HEAP_START  = 0 << 4,
        TYPE_HEAP_END    = 1 << 4,
@@ -70,6 +72,11 @@ enum {
        TYPE_SAMPLE_COUNTERS      = 4 << 4,
        /* extended type for TYPE_RUNTIME */
        TYPE_JITHELPER = 1 << 4,
+       /* extended type for TYPE_COVERAGE */
+       TYPE_COVERAGE_ASSEMBLY = 0 << 4,
+       TYPE_COVERAGE_METHOD   = 1 << 4,
+       TYPE_COVERAGE_STATEMENT = 2 << 4,
+       TYPE_COVERAGE_CLASS = 3 << 4,
        TYPE_END
 };
 
@@ -84,4 +91,3 @@ enum {
 };
 
 #endif /* __MONO_PROFLOG_H__ */
-