X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mono%2Fprofiler%2Fdecode.c;h=0df2d3cd1af74d33f445fa11a8beaef0d820a95e;hb=6723d1e5cc970ec89c146ceb6f8895db96057919;hp=c06eb91a161c96d8f76582d2c37eedbb5bd02e08;hpb=83fac07316934d3901381c6f7f2a6b1ec25414f8;p=mono.git diff --git a/mono/profiler/decode.c b/mono/profiler/decode.c index c06eb91a161..0df2d3cd1af 100644 --- a/mono/profiler/decode.c +++ b/mono/profiler/decode.c @@ -1,11 +1,57 @@ /* * decode.c: mprof-report program source: decode and analyze the log profiler data * - * Author: + * Authors: * Paolo Molaro (lupus@ximian.com) + * Alex Rønne Petersen (alexrp@xamarin.com) * * Copyright 2010 Novell, Inc (http://www.novell.com) */ + +/* + * 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 "utils.c" #include "proflog.h" @@ -49,6 +95,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) @@ -525,6 +572,29 @@ add_image (intptr_t image, char *name) num_images++; } +static int num_assemblies; + +typedef struct _AssemblyDesc AssemblyDesc; +struct _AssemblyDesc { + AssemblyDesc *next; + intptr_t assembly; + char *asmname; +}; + +static AssemblyDesc* assembly_hash [SMALL_HASH_SIZE] = {0}; + +static void +add_assembly (intptr_t assembly, char *name) +{ + int slot = ((assembly >> 2) & 0xffff) % SMALL_HASH_SIZE; + AssemblyDesc *cd = malloc (sizeof (AssemblyDesc)); + cd->assembly = assembly; + cd->asmname = pstrdup (name); + cd->next = assembly_hash [slot]; + assembly_hash [slot] = cd; + num_assemblies++; +} + typedef struct _BackTrace BackTrace; typedef struct { uint64_t count; @@ -1204,7 +1274,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 @@ -1429,6 +1499,8 @@ add_backtrace (int count, MethodDesc **methods) typedef struct _MonitorDesc MonitorDesc; typedef struct _ThreadContext ThreadContext; +typedef struct _DomainContext DomainContext; +typedef struct _RemCtxContext RemCtxContext; typedef struct { FILE *file; @@ -1445,7 +1517,11 @@ typedef struct { int port; uint64_t startup_time; ThreadContext *threads; - ThreadContext *current; + ThreadContext *current_thread; + DomainContext *domains; + DomainContext *current_domain; + RemCtxContext *remctxs; + RemCtxContext *current_remctx; } ProfContext; struct _ThreadContext { @@ -1470,6 +1546,18 @@ struct _ThreadContext { uint64_t gc_start_times [3]; }; +struct _DomainContext { + DomainContext *next; + intptr_t domain_id; + const char *friendly_name; +}; + +struct _RemCtxContext { + RemCtxContext *next; + intptr_t remctx_id; + intptr_t domain_id; +}; + static void ensure_buffer (ProfContext *ctx, int size) { @@ -1489,7 +1577,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); @@ -1503,8 +1591,8 @@ static ThreadContext* get_thread (ProfContext *ctx, intptr_t thread_id) { ThreadContext *thread; - if (ctx->current && ctx->current->thread_id == thread_id) - return ctx->current; + if (ctx->current_thread && ctx->current_thread->thread_id == thread_id) + return ctx->current_thread; thread = ctx->threads; while (thread) { if (thread->thread_id == thread_id) { @@ -1525,11 +1613,57 @@ get_thread (ProfContext *ctx, intptr_t thread_id) return thread; } +static DomainContext * +get_domain (ProfContext *ctx, intptr_t domain_id) +{ + if (ctx->current_domain && ctx->current_domain->domain_id == domain_id) + return ctx->current_domain; + + DomainContext *domain = ctx->domains; + + while (domain) { + if (domain->domain_id == domain_id) + return domain; + + domain = domain->next; + } + + domain = calloc (sizeof (DomainContext), 1); + domain->next = ctx->domains; + ctx->domains = domain; + domain->domain_id = domain_id; + + return domain; +} + +static RemCtxContext * +get_remctx (ProfContext *ctx, intptr_t remctx_id) +{ + if (ctx->current_remctx && ctx->current_remctx->remctx_id == remctx_id) + return ctx->current_remctx; + + RemCtxContext *remctx = ctx->remctxs; + + while (remctx) { + if (remctx->remctx_id == remctx_id) + return remctx; + + remctx = remctx->next; + } + + remctx = calloc (sizeof (RemCtxContext), 1); + remctx->next = ctx->remctxs; + ctx->remctxs = remctx; + remctx->remctx_id = remctx_id; + + return remctx; +} + static ThreadContext* load_thread (ProfContext *ctx, intptr_t thread_id) { ThreadContext *thread = get_thread (ctx, thread_id); - ctx->current = thread; + ctx->current_thread = thread; return thread; } @@ -1711,6 +1845,7 @@ typedef struct { uint64_t live; uint64_t max_live; TraceDesc traces; + TraceDesc destroy_traces; } HandleInfo; static HandleInfo handle_info [4]; @@ -1857,12 +1992,18 @@ tracked_creation (uintptr_t obj, ClassDesc *cd, uint64_t size, BackTrace *bt, ui } static void -track_handle (uintptr_t obj, int htype, uint32_t handle) +track_handle (uintptr_t obj, int htype, uint32_t handle, BackTrace *bt, uint64_t timestamp) { int i; for (i = 0; i < num_tracked_objects; ++i) { - if (tracked_objects [i] == obj) - fprintf (outfile, "Object %p referenced from handle %u\n", (void*)obj, handle); + if (tracked_objects [i] != obj) + continue; + fprintf (outfile, "Object %p referenced from handle %u at %.3f secs.\n", (void*)obj, handle, (timestamp - startup_time) / 1000000000.0); + if (bt && bt->count) { + int k; + for (k = 0; k < bt->count; ++k) + fprintf (outfile, "\t%s\n", bt->methods [k]->name); + } } } @@ -1883,7 +2024,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); } } @@ -1896,6 +2037,149 @@ found_object (uintptr_t obj) tracked_objects [num_tracked_objects - 1] = obj; } +static int num_jit_helpers = 0; +static int jit_helpers_code_size = 0; + +static const char* +code_buffer_desc (int type) +{ + switch (type) { + case MONO_PROFILER_CODE_BUFFER_METHOD: + return "method"; + case MONO_PROFILER_CODE_BUFFER_METHOD_TRAMPOLINE: + return "method trampoline"; + case MONO_PROFILER_CODE_BUFFER_UNBOX_TRAMPOLINE: + return "unbox trampoline"; + case MONO_PROFILER_CODE_BUFFER_IMT_TRAMPOLINE: + return "imt trampoline"; + case MONO_PROFILER_CODE_BUFFER_GENERICS_TRAMPOLINE: + return "generics trampoline"; + case MONO_PROFILER_CODE_BUFFER_SPECIFIC_TRAMPOLINE: + return "specific trampoline"; + case MONO_PROFILER_CODE_BUFFER_HELPER: + return "misc helper"; + case MONO_PROFILER_CODE_BUFFER_MONITOR: + return "monitor/lock"; + case MONO_PROFILER_CODE_BUFFER_DELEGATE_INVOKE: + return "delegate invoke"; + case MONO_PROFILER_CODE_BUFFER_EXCEPTION_HANDLING: + return "exception handling"; + default: + return "unspecified"; + } +} + +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)*/ @@ -1997,36 +2281,78 @@ decode_buffer (ProfContext *ctx) fprintf (outfile, "moved obj %p to %p\n", (void*)OBJ_ADDR (obj1diff), (void*)OBJ_ADDR (obj2diff)); } } - } else if (subtype == TYPE_GC_HANDLE_CREATED) { + } else if (subtype == TYPE_GC_HANDLE_CREATED || subtype == TYPE_GC_HANDLE_CREATED_BT) { + int has_bt = subtype == TYPE_GC_HANDLE_CREATED_BT; + int num_bt = 0; + MethodDesc *sframes [8]; + MethodDesc **frames = sframes; int htype = decode_uleb128 (p, &p); uint32_t handle = decode_uleb128 (p, &p); intptr_t objdiff = decode_sleb128 (p, &p); + if (has_bt) { + num_bt = 8; + frames = decode_bt (sframes, &num_bt, p, &p, ptr_base); + if (!frames) { + fprintf (outfile, "Cannot load backtrace\n"); + return 0; + } + } if (htype > 3) return 0; - handle_info [htype].created++; - handle_info [htype].live++; - add_trace_thread (thread, &handle_info [htype].traces, 1); - /* FIXME: we don't take into account timing here */ - if (handle_info [htype].live > handle_info [htype].max_live) - handle_info [htype].max_live = handle_info [htype].live; - if (num_tracked_objects) - track_handle (OBJ_ADDR (objdiff), htype, handle); + if ((thread_filter && thread_filter == thread->thread_id) || (time_base >= time_from && time_base < time_to)) { + handle_info [htype].created++; + handle_info [htype].live++; + if (handle_info [htype].live > handle_info [htype].max_live) + handle_info [htype].max_live = handle_info [htype].live; + BackTrace *bt; + if (has_bt) + bt = add_trace_methods (frames, num_bt, &handle_info [htype].traces, 1); + else + bt = add_trace_thread (thread, &handle_info [htype].traces, 1); + if (num_tracked_objects) + track_handle (OBJ_ADDR (objdiff), htype, handle, bt, time_base); + } if (debug) fprintf (outfile, "handle (%s) %u created for object %p\n", get_handle_name (htype), handle, (void*)OBJ_ADDR (objdiff)); - } else if (subtype == TYPE_GC_HANDLE_DESTROYED) { + if (frames != sframes) + free (frames); + } else if (subtype == TYPE_GC_HANDLE_DESTROYED || subtype == TYPE_GC_HANDLE_DESTROYED_BT) { + int has_bt = subtype == TYPE_GC_HANDLE_DESTROYED_BT; + int num_bt = 0; + MethodDesc *sframes [8]; + MethodDesc **frames = sframes; int htype = decode_uleb128 (p, &p); uint32_t handle = decode_uleb128 (p, &p); + if (has_bt) { + num_bt = 8; + frames = decode_bt (sframes, &num_bt, p, &p, ptr_base); + if (!frames) { + fprintf (outfile, "Cannot load backtrace\n"); + return 0; + } + } if (htype > 3) return 0; - handle_info [htype].destroyed ++; - handle_info [htype].live--; + if ((thread_filter && thread_filter == thread->thread_id) || (time_base >= time_from && time_base < time_to)) { + handle_info [htype].destroyed ++; + handle_info [htype].live--; + BackTrace *bt; + if (has_bt) + bt = add_trace_methods (frames, num_bt, &handle_info [htype].destroy_traces, 1); + else + bt = add_trace_thread (thread, &handle_info [htype].destroy_traces, 1); + /* TODO: track_handle_free () - would need to record and keep track of the associated object address... */ + } if (debug) fprintf (outfile, "handle (%s) %u destroyed\n", get_handle_name (htype), handle); + if (frames != sframes) + free (frames); } break; } case TYPE_METADATA: { - int error = *p & TYPE_LOAD_ERR; + int subtype = *p & 0xf0; + const char *load_str = subtype == TYPE_END_LOAD ? "loaded" : "unloaded"; uint64_t tdiff = decode_uleb128 (p + 1, &p); int mtype = *p++; intptr_t ptrdiff = decode_sleb128 (p, &p); @@ -2040,8 +2366,8 @@ decode_buffer (ProfContext *ctx) return 0; } if (debug) - fprintf (outfile, "loaded class %p (%s in %p) at %llu\n", (void*)(ptr_base + ptrdiff), p, (void*)(ptr_base + imptrdiff), (unsigned long long) time_base); - if (!error) + fprintf (outfile, "%s class %p (%s in %p) at %llu\n", load_str, (void*)(ptr_base + ptrdiff), p, (void*)(ptr_base + imptrdiff), (unsigned long long) time_base); + if (subtype == TYPE_END_LOAD) add_class (ptr_base + ptrdiff, (char*)p); while (*p) p++; p++; @@ -2052,24 +2378,74 @@ decode_buffer (ProfContext *ctx) return 0; } if (debug) - fprintf (outfile, "loaded image %p (%s) at %llu\n", (void*)(ptr_base + ptrdiff), p, (unsigned long long) time_base); - if (!error) + fprintf (outfile, "%s image %p (%s) at %llu\n", load_str, (void*)(ptr_base + ptrdiff), p, (unsigned long long) time_base); + if (subtype == TYPE_END_LOAD) add_image (ptr_base + ptrdiff, (char*)p); while (*p) p++; p++; - } else if (mtype == TYPE_THREAD) { - ThreadContext *nt; + } else if (mtype == TYPE_ASSEMBLY) { uint64_t flags = decode_uleb128 (p, &p); if (flags) { - fprintf (outfile, "non-zero flags in thread\n"); + fprintf (outfile, "non-zero flags in assembly\n"); return 0; } - nt = get_thread (ctx, ptr_base + ptrdiff); - nt->name = pstrdup ((char*)p); if (debug) - fprintf (outfile, "thread %p named: %s\n", (void*)(ptr_base + ptrdiff), p); + fprintf (outfile, "%s assembly %p (%s) at %llu\n", load_str, (void*)(ptr_base + ptrdiff), p, (unsigned long long) time_base); + if (subtype == TYPE_END_LOAD) + add_assembly (ptr_base + ptrdiff, (char*)p); while (*p) p++; p++; + } else if (mtype == TYPE_DOMAIN) { + uint64_t flags = decode_uleb128 (p, &p); + if (flags) { + fprintf (outfile, "non-zero flags in domain\n"); + return 0; + } + DomainContext *nd = get_domain (ctx, ptr_base + ptrdiff); + /* no subtype means it's a name event, rather than start/stop */ + if (subtype == 0) + nd->friendly_name = pstrdup ((char *) p); + if (debug) { + if (subtype == 0) + fprintf (outfile, "domain %p named at %llu: %s\n", (void *) (ptr_base + ptrdiff), (unsigned long long) time_base, p); + else + fprintf (outfile, "%s thread %p at %llu\n", load_str, (void *) (ptr_base + ptrdiff), (unsigned long long) time_base); + } + if (subtype == 0) { + while (*p) p++; + p++; + } + } else if (mtype == TYPE_CONTEXT) { + uint64_t flags = decode_uleb128 (p, &p); + if (flags) { + fprintf (outfile, "non-zero flags in context\n"); + return 0; + } + intptr_t domaindiff = decode_sleb128 (p, &p); + if (debug) + fprintf (outfile, "%s context %p (%p) at %llu\n", load_str, (void*)(ptr_base + ptrdiff), (void *) (ptr_base + domaindiff), (unsigned long long) time_base); + if (subtype == TYPE_END_LOAD) + get_remctx (ctx, ptr_base + ptrdiff)->domain_id = ptr_base + domaindiff; + } else if (mtype == TYPE_THREAD) { + uint64_t flags = decode_uleb128 (p, &p); + if (flags) { + fprintf (outfile, "non-zero flags in thread\n"); + return 0; + } + ThreadContext *nt = get_thread (ctx, ptr_base + ptrdiff); + /* no subtype means it's a name event, rather than start/stop */ + if (subtype == 0) + nt->name = pstrdup ((char*)p); + if (debug) { + if (subtype == 0) + fprintf (outfile, "thread %p named at %llu: %s\n", (void*)(ptr_base + ptrdiff), (unsigned long long) time_base, p); + else + fprintf (outfile, "%s thread %p at %llu\n", load_str, (void *) (ptr_base + ptrdiff), (unsigned long long) time_base); + } + if (subtype == 0) { + while (*p) p++; + p++; + } } break; } @@ -2354,19 +2730,46 @@ decode_buffer (ProfContext *ctx) } break; } + case TYPE_RUNTIME: { + int subtype = *p & 0xf0; + uint64_t tdiff = decode_uleb128 (p + 1, &p); + LOG_TIME (time_base, tdiff); + time_base += tdiff; + if (subtype == TYPE_JITHELPER) { + int type = decode_uleb128 (p, &p); + intptr_t codediff = decode_sleb128 (p, &p); + int codelen = decode_uleb128 (p, &p); + const char *name; + if (type == MONO_PROFILER_CODE_BUFFER_SPECIFIC_TRAMPOLINE) { + name = (void*)p; + while (*p) p++; + p++; + } else { + name = code_buffer_desc (type); + } + num_jit_helpers++; + jit_helpers_code_size += codelen; + if (debug) + fprintf (outfile, "jit helper %s, size: %d, code: %p\n", name, codelen, (void*)(ptr_base + codediff)); + } + break; + } case TYPE_SAMPLE: { int subtype = *p & 0xf0; if (subtype == TYPE_SAMPLE_HIT) { int i; int sample_type = decode_uleb128 (p + 1, &p); uint64_t tstamp = decode_uleb128 (p, &p); + void *tid = (void *) thread_id; + if (ctx->data_version > 10) + tid = (void *) (ptr_base + decode_sleb128 (p, &p)); int count = decode_uleb128 (p, &p); for (i = 0; i < count; ++i) { uintptr_t ip = ptr_base + decode_sleb128 (p, &p); if ((tstamp >= time_from && tstamp < time_to)) add_stat_sample (sample_type, ip); if (debug) - fprintf (outfile, "sample hit, type: %d at %p\n", sample_type, (void*)ip); + fprintf (outfile, "sample hit, type: %d at %p for thread %p\n", sample_type, (void*)ip, tid); } if (ctx->data_version > 5) { count = decode_uleb128 (p, &p); @@ -2500,6 +2903,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); @@ -2621,6 +3127,24 @@ dump_threads (ProfContext *ctx) } } +static void +dump_domains (ProfContext *ctx) +{ + fprintf (outfile, "\nDomain summary\n"); + + for (DomainContext *domain = ctx->domains; domain; domain = domain->next) + fprintf (outfile, "\tDomain: %p, friendly name: \"%s\"\n", (void *) domain->domain_id, domain->friendly_name); +} + +static void +dump_remctxs (ProfContext *ctx) +{ + fprintf (outfile, "\nContext summary\n"); + + for (RemCtxContext *remctx = ctx->remctxs; remctx; remctx = remctx->next) + fprintf (outfile, "\tContext: %p, domain: %p\n", (void *) remctx->remctx_id, (void *) remctx->domain_id); +} + static void dump_exceptions (void) { @@ -2702,6 +3226,7 @@ dump_gcs (void) (unsigned long long) (handle_info [i].destroyed), (unsigned long long) (handle_info [i].max_live)); dump_traces (&handle_info [i].traces, "created"); + dump_traces (&handle_info [i].destroy_traces, "destroyed"); } } @@ -2724,6 +3249,8 @@ dump_jit (void) } fprintf (outfile, "\tCompiled methods: %d\n", compiled_methods); fprintf (outfile, "\tGenerated code size: %d\n", code_size); + fprintf (outfile, "\tJIT helpers: %d\n", num_jit_helpers); + fprintf (outfile, "\tJIT helpers code size: %d\n", jit_helpers_code_size); } static void @@ -2812,7 +3339,18 @@ dump_metadata (void) } } } - + fprintf (outfile, "\tLoaded assemblies: %d\n", num_assemblies); + if (verbose) { + AssemblyDesc *assembly; + int i; + for (i = 0; i < SMALL_HASH_SIZE; ++i) { + assembly = assembly_hash [i]; + while (assembly) { + fprintf (outfile, "\t\t%s\n", assembly->asmname); + assembly = assembly->next; + } + } + } } static void @@ -3007,6 +3545,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, "<"); + 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); +} + +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, "\n"); + fprintf (coverage_outfile, "\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\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\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\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\n", coverage->offset, coverage->count, coverage->line, coverage->column); + } + fprintf (coverage_outfile, "\t\n"); + } + } + + if (coverage_outfile) { + fprintf (coverage_outfile, "\n"); + fclose (coverage_outfile); + coverage_outfile = NULL; + } +} + static void flush_context (ProfContext *ctx) { @@ -3021,7 +3713,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) @@ -3051,6 +3743,16 @@ print_reports (ProfContext *ctx, const char *reps, int parse_only) dump_threads (ctx); continue; } + if ((opt = match_option (p, "domain")) != p) { + if (!parse_only) + dump_domains (ctx); + continue; + } + if ((opt = match_option (p, "context")) != p) { + if (!parse_only) + dump_remctxs (ctx); + continue; + } if ((opt = match_option (p, "gc")) != p) { if (!parse_only) dump_gcs (); @@ -3101,6 +3803,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; @@ -3129,7 +3836,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); @@ -3144,6 +3851,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 @@ -3255,6 +3963,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; } @@ -3275,4 +3990,3 @@ main (int argc, char *argv[]) print_reports (ctx, reports, 0); return 0; } -