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;
typedef struct _MonitorDesc MonitorDesc;
typedef struct _ThreadContext ThreadContext;
+typedef struct _DomainContext DomainContext;
+typedef struct _RemCtxContext RemCtxContext;
typedef struct {
FILE *file;
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 {
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)
{
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) {
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;
}
uint64_t live;
uint64_t max_live;
TraceDesc traces;
+ TraceDesc destroy_traces;
} HandleInfo;
static HandleInfo handle_info [4];
}
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);
+ }
}
}
#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)*/
+
+/* Stats */
+#define BUFFER_HEADER_SIZE 48
+
+typedef struct {
+ int count, min_size, max_size, bytes;
+} EventStat;
+
+static int buffer_count;
+static EventStat stats [256];
+
+static void
+record_event_stats (int type, int size)
+{
+ ++stats [type].count;
+ if (!stats [type].min_size)
+ stats [type].min_size = size;
+ stats [type].min_size = MIN (stats [type].min_size, size);
+ stats [type].max_size = MAX (stats [type].max_size, size);
+ stats [type].bytes += size;
+}
+
static int
decode_buffer (ProfContext *ctx)
{
thread = load_thread (ctx, thread_id);
if (!load_data (ctx, len))
return 0;
+
+ ++buffer_count;
+
if (!startup_time) {
startup_time = time_base;
if (use_time_filter) {
p = ctx->buf;
end = p + len;
while (p < end) {
+ unsigned char *start = p;
+ unsigned char event = *p;
switch (*p & 0xf) {
case TYPE_GC: {
int subtype = *p & 0xf0;
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);
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++;
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;
}
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);
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);
}
+ record_event_stats (event, p - start);
}
thread->last_time = time_base;
for (i = 0; i < thread->stack_id; ++i)
}
}
+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)
{
(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");
}
}
}
}
}
-
+ 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
}
}
+#define DUMP_EVENT_STAT(EVENT,SUBTYPE) dump_event (#EVENT, #SUBTYPE, EVENT, SUBTYPE);
+
+static void
+dump_event (const char *event_name, const char *subtype_name, int event, int subtype)
+{
+ int idx = event | subtype;
+ EventStat evt = stats [idx];
+ if (!evt.count)
+ return;
+
+ fprintf (outfile, "\t%16s\t%26s\tcount %6d\tmin %3d\tmax %6d\tbytes %d\n", event_name, subtype_name, evt.count, evt.min_size, evt.max_size, evt.bytes);
+}
+
+static void
+dump_stats (void)
+{
+ fprintf (outfile, "\nMlpd statistics\n");
+ fprintf (outfile, "\tBuffer count %d\toverhead %d (%d bytes per header)\n", buffer_count, buffer_count * BUFFER_HEADER_SIZE, BUFFER_HEADER_SIZE);
+ fprintf (outfile, "\nEvent details:\n");
+
+ DUMP_EVENT_STAT (TYPE_ALLOC, TYPE_ALLOC_NO_BT);
+ DUMP_EVENT_STAT (TYPE_ALLOC, TYPE_ALLOC_BT);
+
+ DUMP_EVENT_STAT (TYPE_GC, TYPE_GC_EVENT);
+ DUMP_EVENT_STAT (TYPE_GC, TYPE_GC_RESIZE);
+ DUMP_EVENT_STAT (TYPE_GC, TYPE_GC_MOVE);
+ DUMP_EVENT_STAT (TYPE_GC, TYPE_GC_HANDLE_CREATED);
+ DUMP_EVENT_STAT (TYPE_GC, TYPE_GC_HANDLE_DESTROYED);
+ DUMP_EVENT_STAT (TYPE_GC, TYPE_GC_HANDLE_CREATED_BT);
+ DUMP_EVENT_STAT (TYPE_GC, TYPE_GC_HANDLE_DESTROYED_BT);
+
+ DUMP_EVENT_STAT (TYPE_METADATA, TYPE_END_LOAD);
+ DUMP_EVENT_STAT (TYPE_METADATA, TYPE_END_UNLOAD);
+
+ DUMP_EVENT_STAT (TYPE_METHOD, TYPE_LEAVE);
+ DUMP_EVENT_STAT (TYPE_METHOD, TYPE_ENTER);
+ DUMP_EVENT_STAT (TYPE_METHOD, TYPE_EXC_LEAVE);
+ DUMP_EVENT_STAT (TYPE_METHOD, TYPE_JIT);
+
+ DUMP_EVENT_STAT (TYPE_EXCEPTION, TYPE_THROW);
+ DUMP_EVENT_STAT (TYPE_EXCEPTION, TYPE_CLAUSE);
+ DUMP_EVENT_STAT (TYPE_EXCEPTION, TYPE_EXCEPTION_BT);
+
+ DUMP_EVENT_STAT (TYPE_MONITOR, TYPE_MONITOR_NO_BT);
+ DUMP_EVENT_STAT (TYPE_MONITOR, TYPE_MONITOR_BT);
+
+ DUMP_EVENT_STAT (TYPE_HEAP, TYPE_HEAP_START);
+ DUMP_EVENT_STAT (TYPE_HEAP, TYPE_HEAP_END);
+ DUMP_EVENT_STAT (TYPE_HEAP, TYPE_HEAP_OBJECT);
+ DUMP_EVENT_STAT (TYPE_HEAP, TYPE_HEAP_ROOT);
+
+ DUMP_EVENT_STAT (TYPE_SAMPLE, TYPE_SAMPLE_HIT);
+ DUMP_EVENT_STAT (TYPE_SAMPLE, TYPE_SAMPLE_USYM);
+ DUMP_EVENT_STAT (TYPE_SAMPLE, TYPE_SAMPLE_UBIN);
+ DUMP_EVENT_STAT (TYPE_SAMPLE, TYPE_SAMPLE_COUNTERS_DESC);
+ DUMP_EVENT_STAT (TYPE_SAMPLE, TYPE_SAMPLE_COUNTERS);
+
+ DUMP_EVENT_STAT (TYPE_RUNTIME, TYPE_JITHELPER);
+
+ DUMP_EVENT_STAT (TYPE_COVERAGE, TYPE_COVERAGE_ASSEMBLY);
+ DUMP_EVENT_STAT (TYPE_COVERAGE, TYPE_COVERAGE_METHOD);
+ DUMP_EVENT_STAT (TYPE_COVERAGE, TYPE_COVERAGE_STATEMENT);
+ DUMP_EVENT_STAT (TYPE_COVERAGE, TYPE_COVERAGE_CLASS);
+}
+
+
+
static void
flush_context (ProfContext *ctx)
{
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 ();
dump_coverage ();
continue;
}
+ if ((opt = match_option (p, "stats")) != p) {
+ if (!parse_only)
+ dump_stats ();
+ continue;
+ }
return 0;
}
return 1;