- Remove <unistd.h> include from bloatblame, it's no longer needed and the link(2) prototype it drags in was causing warnings whenever I used a local variable named 'link'.
- Use uint32 for all quantities, and double for all differences, so that we can overflow without breaking deltas such as (busy == allocs - frees). This doesn't help sorting, however: if one of the sort keys has overflowed past 0 just a bit, while the other is a very large unsigned number, beware.
- Separate graph link (half an edge, structurally speaking -- no per-edge stats) from graph edge, so that an edge is two links and some stats. This avoids bloat and copying in connect_nodes (which is soon to become generic and move to tmreader.[ch]).
- Factor data structures better: we now have {allocs,frees} x {bytes,calls} x {direct, total}, declaring the second set as struct tmallcounts and the third as tmcounts. So, for example, total number of calls to allocators would be allocs.calls.total; bytes freed directly by a graphnode (library, component, or method) would be frees.bytes.direct.
- Teach tmreader_eventloop about 'F' (TM_EVENT_FREE) events: it now updates the direct free byte and call counts for a method, its component, and its library when it reads the event. Of course, bloatblame ignores this info, because it is concerned only with bloat (total memory allocated).
- Right-align numbers in the first (trace-malloc stats) table.
git-svn-id: svn://10.0.0.236/trunk@74785 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
parent
30ea99bee8
commit
2d47b3e145
@ -46,7 +46,6 @@ extern int optind;
|
||||
#endif
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "prtypes.h"
|
||||
#include "prlog.h"
|
||||
@ -61,54 +60,57 @@ static int js_mode = 0;
|
||||
static int do_tree_dump = 0;
|
||||
static int unified_output = 0;
|
||||
static char *function_dump = NULL;
|
||||
static int32 min_subtotal = 0;
|
||||
static uint32 min_subtotal = 0;
|
||||
|
||||
static void connect_nodes(tmgraphnode *from, tmgraphnode *to, tmcallsite *site)
|
||||
{
|
||||
tmgraphlink *link;
|
||||
tmgraphedge *edge;
|
||||
|
||||
for (edge = from->out; edge; edge = edge->next) {
|
||||
if (edge->node == to) {
|
||||
for (link = from->out; link; link = link->next) {
|
||||
if (link->node == to) {
|
||||
/*
|
||||
* Say the stack looks like this: ... => JS => js => JS => js.
|
||||
* We must avoid overcounting JS=>js because the first edge total
|
||||
* includes the second JS=>js edge's total (which is because the
|
||||
* lower site's total includes all its kids' totals).
|
||||
*/
|
||||
edge = TM_LINK_TO_EDGE(link, TM_EDGE_OUT_LINK);
|
||||
if (!to->low || to->low < from->low) {
|
||||
edge[0].bytes.direct += site->bytes.direct;
|
||||
edge[1].bytes.direct += site->bytes.direct;
|
||||
edge[0].bytes.total += site->bytes.total;
|
||||
edge[1].bytes.total += site->bytes.total;
|
||||
/* Add the direct and total counts to edge->allocs. */
|
||||
edge->allocs.bytes.direct += site->allocs.bytes.direct;
|
||||
edge->allocs.bytes.total += site->allocs.bytes.total;
|
||||
edge->allocs.calls.direct += site->allocs.calls.direct;
|
||||
edge->allocs.calls.total += site->allocs.calls.total;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
edge = (tmgraphedge*) malloc(2 * sizeof(tmgraphedge));
|
||||
|
||||
edge = (tmgraphedge*) malloc(sizeof(tmgraphedge));
|
||||
if (!edge) {
|
||||
perror(program);
|
||||
exit(1);
|
||||
}
|
||||
edge[0].node = to;
|
||||
edge[0].next = from->out;
|
||||
from->out = &edge[0];
|
||||
edge[1].node = from;
|
||||
edge[1].next = to->in;
|
||||
to->in = &edge[1];
|
||||
edge[0].bytes.direct = edge[1].bytes.direct = site->bytes.direct;
|
||||
edge[0].bytes.total = edge[1].bytes.total = site->bytes.total;
|
||||
edge->links[TM_EDGE_OUT_LINK].node = to;
|
||||
edge->links[TM_EDGE_OUT_LINK].next = from->out;
|
||||
from->out = &edge->links[TM_EDGE_OUT_LINK];
|
||||
edge->links[TM_EDGE_IN_LINK].node = from;
|
||||
edge->links[TM_EDGE_IN_LINK].next = to->in;
|
||||
to->in = &edge->links[TM_EDGE_IN_LINK];
|
||||
edge->allocs = site->allocs;
|
||||
}
|
||||
|
||||
static void compute_callsite_totals(tmcallsite *site)
|
||||
{
|
||||
tmcallsite *kid;
|
||||
|
||||
site->bytes.total += site->bytes.direct;
|
||||
site->allocs.total += site->allocs.direct;
|
||||
site->allocs.bytes.total += site->allocs.bytes.direct;
|
||||
site->allocs.calls.total += site->allocs.calls.direct;
|
||||
for (kid = site->kids; kid; kid = kid->siblings) {
|
||||
compute_callsite_totals(kid);
|
||||
site->bytes.total += kid->bytes.total;
|
||||
site->allocs.total += kid->allocs.total;
|
||||
site->allocs.bytes.total += kid->allocs.bytes.total;
|
||||
site->allocs.calls.total += kid->allocs.calls.total;
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,8 +129,8 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
pmeth = parent->method;
|
||||
if (pmeth && pmeth != meth) {
|
||||
if (!meth->low) {
|
||||
meth->bytes.total += site->bytes.total;
|
||||
meth->allocs.total += site->allocs.total;
|
||||
meth->allocs.bytes.total += site->allocs.bytes.total;
|
||||
meth->allocs.calls.total += site->allocs.calls.total;
|
||||
}
|
||||
connect_nodes(pmeth, meth, site);
|
||||
|
||||
@ -137,8 +139,10 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
pcomp = pmeth->up;
|
||||
if (pcomp && pcomp != comp) {
|
||||
if (!comp->low) {
|
||||
comp->bytes.total += site->bytes.total;
|
||||
comp->allocs.total += site->allocs.total;
|
||||
comp->allocs.bytes.total
|
||||
+= site->allocs.bytes.total;
|
||||
comp->allocs.calls.total
|
||||
+= site->allocs.calls.total;
|
||||
}
|
||||
connect_nodes(pcomp, comp, site);
|
||||
|
||||
@ -147,8 +151,10 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
plib = pcomp->up;
|
||||
if (plib && plib != lib) {
|
||||
if (!lib->low) {
|
||||
lib->bytes.total += site->bytes.total;
|
||||
lib->allocs.total += site->allocs.total;
|
||||
lib->allocs.bytes.total
|
||||
+= site->allocs.bytes.total;
|
||||
lib->allocs.calls.total
|
||||
+= site->allocs.calls.total;
|
||||
}
|
||||
connect_nodes(plib, lib, site);
|
||||
}
|
||||
@ -172,7 +178,8 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
fprintf(fp, "%c%*s%3d %3d %s %lu %ld\n",
|
||||
site->kids ? '+' : '-', level, "", level, kidnum,
|
||||
meth ? tmgraphnode_name(meth) : "???",
|
||||
(unsigned long)site->bytes.direct, (long)site->bytes.total);
|
||||
(unsigned long)site->allocs.bytes.direct,
|
||||
(long)site->allocs.bytes.total);
|
||||
}
|
||||
nkids = 0;
|
||||
level++;
|
||||
@ -195,8 +202,16 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
}
|
||||
}
|
||||
|
||||
/* Linked list bubble-sort (waterson and brendan went bald hacking this). */
|
||||
#define BUBBLE_SORT_LINKED_LIST(listp, nodetype) \
|
||||
/*
|
||||
* Linked list bubble-sort (waterson and brendan went bald hacking this).
|
||||
*
|
||||
* Sort the list in non-increasing order, using the expression passed as the
|
||||
* 'lessthan' formal macro parameter. This expression should use 'curr' as
|
||||
* the pointer to the current node (of type nodetype) and 'next' as the next
|
||||
* node pointer. It should return true if curr is less than next, and false
|
||||
* otherwise.
|
||||
*/
|
||||
#define BUBBLE_SORT_LINKED_LIST(listp, nodetype, lessthan) \
|
||||
PR_BEGIN_MACRO \
|
||||
nodetype *curr, **currp, *next, **nextp, *tmp; \
|
||||
\
|
||||
@ -204,7 +219,7 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
while ((curr = *currp) != NULL && curr->next) { \
|
||||
nextp = &curr->next; \
|
||||
while ((next = *nextp) != NULL) { \
|
||||
if (curr->bytes.total < next->bytes.total) { \
|
||||
if (lessthan) { \
|
||||
tmp = curr->next; \
|
||||
*currp = tmp; \
|
||||
if (tmp == next) { \
|
||||
@ -234,7 +249,8 @@ static PRIntn tabulate_node(PLHashEntry *he, PRIntn i, void *arg)
|
||||
tmgraphnode **table = (tmgraphnode**) arg;
|
||||
|
||||
table[i] = node;
|
||||
BUBBLE_SORT_LINKED_LIST(&node->down, tmgraphnode);
|
||||
BUBBLE_SORT_LINKED_LIST(&node->down, tmgraphnode,
|
||||
(curr->allocs.bytes.total < next->allocs.bytes.total));
|
||||
return HT_ENUMERATE_NEXT;
|
||||
}
|
||||
|
||||
@ -242,18 +258,18 @@ static PRIntn tabulate_node(PLHashEntry *he, PRIntn i, void *arg)
|
||||
static int node_table_compare(const void *p1, const void *p2)
|
||||
{
|
||||
const tmgraphnode *node1, *node2;
|
||||
int32 key1, key2;
|
||||
uint32 key1, key2;
|
||||
|
||||
node1 = *(const tmgraphnode**) p1;
|
||||
node2 = *(const tmgraphnode**) p2;
|
||||
if (sort_by_direct) {
|
||||
key1 = node1->bytes.direct;
|
||||
key2 = node2->bytes.direct;
|
||||
key1 = node1->allocs.bytes.direct;
|
||||
key2 = node2->allocs.bytes.direct;
|
||||
} else {
|
||||
key1 = node1->bytes.total;
|
||||
key2 = node2->bytes.total;
|
||||
key1 = node1->allocs.bytes.total;
|
||||
key2 = node2->allocs.bytes.total;
|
||||
}
|
||||
return key2 - key1;
|
||||
return (key2 < key1) ? -1 : (key2 > key1) ? 1 : 0;
|
||||
}
|
||||
|
||||
static int mean_size_compare(const void *p1, const void *p2)
|
||||
@ -263,12 +279,12 @@ static int mean_size_compare(const void *p1, const void *p2)
|
||||
|
||||
node1 = *(const tmgraphnode**) p1;
|
||||
node2 = *(const tmgraphnode**) p2;
|
||||
div1 = (double)node1->allocs.direct;
|
||||
div2 = (double)node2->allocs.direct;
|
||||
div1 = (double)node1->allocs.calls.direct;
|
||||
div2 = (double)node2->allocs.calls.direct;
|
||||
if (div1 == 0 || div2 == 0)
|
||||
return div2 - div1;
|
||||
key1 = (double)node1->bytes.direct / div1;
|
||||
key2 = (double)node2->bytes.direct / div2;
|
||||
key1 = (double)node1->allocs.bytes.direct / div1;
|
||||
key2 = (double)node2->allocs.bytes.direct / div2;
|
||||
if (key1 < key2)
|
||||
return 1;
|
||||
if (key1 > key2)
|
||||
@ -289,50 +305,57 @@ static const char *prettybig(uint32 num, char *buf, size_t limit)
|
||||
return buf;
|
||||
}
|
||||
|
||||
static double percent(int32 num, int32 total)
|
||||
static double percent(uint32 num, uint32 total)
|
||||
{
|
||||
if (num == 0)
|
||||
return 0.0;
|
||||
return ((double) num * 100) / (double) total;
|
||||
}
|
||||
|
||||
static void sort_graphedge_list(tmgraphedge **listp)
|
||||
static void sort_graphlink_list(tmgraphlink **listp, int which)
|
||||
{
|
||||
BUBBLE_SORT_LINKED_LIST(listp, tmgraphedge);
|
||||
BUBBLE_SORT_LINKED_LIST(listp, tmgraphlink,
|
||||
(TM_LINK_TO_EDGE(curr, which)->allocs.bytes.total
|
||||
< TM_LINK_TO_EDGE(next, which)->allocs.bytes.total));
|
||||
}
|
||||
|
||||
static void dump_graphedge_list(tmgraphedge *list, const char *name, FILE *fp)
|
||||
static void dump_graphlink_list(tmgraphlink *list, int which, const char *name,
|
||||
FILE *fp)
|
||||
{
|
||||
tmcounts bytes;
|
||||
tmgraphlink *link;
|
||||
tmgraphedge *edge;
|
||||
char buf[16];
|
||||
|
||||
bytes.direct = bytes.total = 0;
|
||||
for (edge = list; edge; edge = edge->next) {
|
||||
bytes.direct += edge->bytes.direct;
|
||||
bytes.total += edge->bytes.total;
|
||||
for (link = list; link; link = link->next) {
|
||||
edge = TM_LINK_TO_EDGE(link, which);
|
||||
bytes.direct += edge->allocs.bytes.direct;
|
||||
bytes.total += edge->allocs.bytes.total;
|
||||
}
|
||||
|
||||
if (js_mode) {
|
||||
fprintf(fp,
|
||||
" %s:{dbytes:%ld, tbytes:%ld, edges:[\n",
|
||||
name, (long) bytes.direct, (long) bytes.total);
|
||||
for (edge = list; edge; edge = edge->next) {
|
||||
for (link = list; link; link = link->next) {
|
||||
edge = TM_LINK_TO_EDGE(link, which);
|
||||
fprintf(fp,
|
||||
" {node:%d, dbytes:%ld, tbytes:%ld},\n",
|
||||
edge->node->sort,
|
||||
(long) edge->bytes.direct,
|
||||
(long) edge->bytes.total);
|
||||
link->node->sort,
|
||||
(long) edge->allocs.bytes.direct,
|
||||
(long) edge->allocs.bytes.total);
|
||||
}
|
||||
fputs(" ]},\n", fp);
|
||||
} else {
|
||||
fputs("<td valign=top>", fp);
|
||||
for (edge = list; edge; edge = edge->next) {
|
||||
for (link = list; link; link = link->next) {
|
||||
edge = TM_LINK_TO_EDGE(link, which);
|
||||
fprintf(fp,
|
||||
"<a href='#%s'>%s (%1.2f%%)</a>\n",
|
||||
tmgraphnode_name(edge->node),
|
||||
prettybig(edge->bytes.total, buf, sizeof buf),
|
||||
percent(edge->bytes.total, bytes.total));
|
||||
tmgraphnode_name(link->node),
|
||||
prettybig(edge->allocs.bytes.total, buf, sizeof buf),
|
||||
percent(edge->allocs.bytes.total, bytes.total));
|
||||
}
|
||||
fputs("</td>", fp);
|
||||
}
|
||||
@ -381,7 +404,7 @@ static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
|
||||
for (i = 0; i < count; i++) {
|
||||
/* Don't bother with truly puny nodes. */
|
||||
node = table[i];
|
||||
if (node->bytes.total < min_subtotal)
|
||||
if (node->allocs.bytes.total < min_subtotal)
|
||||
break;
|
||||
|
||||
name = tmgraphnode_name(node);
|
||||
@ -390,16 +413,18 @@ static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
|
||||
" {name:'%s', dbytes:%ld, tbytes:%ld,"
|
||||
" dallocs:%ld, tallocs:%ld,\n",
|
||||
name,
|
||||
(long) node->bytes.direct, (long) node->bytes.total,
|
||||
(long) node->allocs.direct, (long) node->allocs.total);
|
||||
(long) node->allocs.bytes.direct,
|
||||
(long) node->allocs.bytes.total,
|
||||
(long) node->allocs.calls.direct,
|
||||
(long) node->allocs.calls.total);
|
||||
} else {
|
||||
namelen = strlen(name);
|
||||
fprintf(fp,
|
||||
"<tr>"
|
||||
"<td valign=top><a name='%s'>%.*s%s</a></td>",
|
||||
name,
|
||||
(namelen > 45) ? 45 : (int)namelen, name,
|
||||
(namelen > 45) ? "<i>...</i>" : "");
|
||||
(namelen > 40) ? 40 : (int)namelen, name,
|
||||
(namelen > 40) ? "<i>...</i>" : "");
|
||||
if (node->down) {
|
||||
fprintf(fp,
|
||||
"<td valign=top><a href='#%s'><i>down</i></a></td>",
|
||||
@ -417,20 +442,25 @@ static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
|
||||
fprintf(fp,
|
||||
"<td valign=top>%s/%s (%1.2f%%/%1.2f%%)</td>"
|
||||
"<td valign=top>%s/%s (%1.2f%%/%1.2f%%)</td>",
|
||||
prettybig(node->bytes.total, buf1, sizeof buf1),
|
||||
prettybig(node->bytes.direct, buf2, sizeof buf2),
|
||||
percent(node->bytes.total, tmr->calltree_root.bytes.total),
|
||||
percent(node->bytes.direct, tmr->calltree_root.bytes.total),
|
||||
prettybig(node->allocs.total, buf3, sizeof buf3),
|
||||
prettybig(node->allocs.direct, buf4, sizeof buf4),
|
||||
percent(node->allocs.total, tmr->calltree_root.allocs.total),
|
||||
percent(node->allocs.direct, tmr->calltree_root.allocs.total));
|
||||
prettybig(node->allocs.bytes.total, buf1, sizeof buf1),
|
||||
prettybig(node->allocs.bytes.direct, buf2, sizeof buf2),
|
||||
percent(node->allocs.bytes.total,
|
||||
tmr->calltree_root.allocs.bytes.total),
|
||||
percent(node->allocs.bytes.direct,
|
||||
tmr->calltree_root.allocs.bytes.total),
|
||||
prettybig(node->allocs.calls.total, buf3, sizeof buf3),
|
||||
prettybig(node->allocs.calls.direct, buf4, sizeof buf4),
|
||||
percent(node->allocs.calls.total,
|
||||
tmr->calltree_root.allocs.calls.total),
|
||||
percent(node->allocs.calls.direct,
|
||||
tmr->calltree_root.allocs.calls.total));
|
||||
}
|
||||
|
||||
sort_graphedge_list(&node->in);
|
||||
dump_graphedge_list(node->in, "fin", fp); /* 'in' is a JS keyword! */
|
||||
sort_graphedge_list(&node->out);
|
||||
dump_graphedge_list(node->out, "out", fp);
|
||||
/* NB: we must use 'fin' because 'in' is a JS keyword! */
|
||||
sort_graphlink_list(&node->in, TM_EDGE_IN_LINK);
|
||||
dump_graphlink_list(node->in, TM_EDGE_IN_LINK, "fin", fp);
|
||||
sort_graphlink_list(&node->out, TM_EDGE_OUT_LINK);
|
||||
dump_graphlink_list(node->out, TM_EDGE_OUT_LINK, "out", fp);
|
||||
|
||||
if (js_mode)
|
||||
fputs(" },\n", fp);
|
||||
@ -460,12 +490,12 @@ static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
|
||||
double allocs, bytes, mean, variance, sigma;
|
||||
|
||||
node = table[i];
|
||||
allocs = (double)node->allocs.direct;
|
||||
allocs = (double)node->allocs.calls.direct;
|
||||
if (!allocs)
|
||||
continue;
|
||||
|
||||
/* Compute direct-size mean and standard deviation. */
|
||||
bytes = (double)node->bytes.direct;
|
||||
bytes = (double)node->allocs.bytes.direct;
|
||||
mean = bytes / allocs;
|
||||
variance = allocs * node->sqsum - bytes * bytes;
|
||||
if (variance < 0 || allocs == 1)
|
||||
@ -487,7 +517,7 @@ static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
|
||||
(namelen > 65) ? "<i>...</i>" : "",
|
||||
prettybig((uint32)mean, buf1, sizeof buf1),
|
||||
prettybig((uint32)sigma, buf2, sizeof buf2),
|
||||
prettybig(node->allocs.direct, buf3, sizeof buf3));
|
||||
prettybig(node->allocs.calls.direct, buf3, sizeof buf3));
|
||||
}
|
||||
fputs("</table>\n", fp);
|
||||
}
|
||||
@ -504,26 +534,26 @@ static void my_tmevent_handler(tmreader *tmr, tmevent *event)
|
||||
fprintf(stdout,
|
||||
"<p><table border=1>"
|
||||
"<tr><th>Counter</th><th>Value</th></tr>\n"
|
||||
"<tr><td>maximum actual stack depth</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>maximum callsite tree depth</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>number of parent callsites</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>maximum kids per parent</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>hits looking for a kid</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>misses looking for a kid</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>steps over other kids</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>callsite recurrences</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>number of stack backtraces</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace malloc failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace dladdr failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>malloc calls</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>malloc failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>calloc calls</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>calloc failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>realloc calls</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>realloc failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>free calls</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>free(null) calls</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>maximum actual stack depth</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>maximum callsite tree depth</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>number of parent callsites</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>maximum kids per parent</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>hits looking for a kid</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>misses looking for a kid</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>steps over other kids</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>callsite recurrences</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>number of stack backtraces</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace malloc failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace dladdr failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>malloc calls</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>malloc failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>calloc calls</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>calloc failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>realloc calls</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>realloc failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>free calls</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>free(null) calls</td><td align=right>%lu</td></tr>\n"
|
||||
"</table>",
|
||||
(unsigned long) event->u.stats.tmstats.calltree_maxstack,
|
||||
(unsigned long) event->u.stats.tmstats.calltree_maxdepth,
|
||||
@ -664,10 +694,10 @@ int main(int argc, char **argv)
|
||||
"// direct and total byte and allocator-call counts\n"
|
||||
"var dbytes = %ld, tbytes = %ld,"
|
||||
" dallocs = %ld, tallocs = %ld;\n",
|
||||
(long) tmr->calltree_root.bytes.direct,
|
||||
(long) tmr->calltree_root.bytes.total,
|
||||
(long) tmr->calltree_root.allocs.direct,
|
||||
(long) tmr->calltree_root.allocs.total);
|
||||
(long) tmr->calltree_root.allocs.bytes.direct,
|
||||
(long) tmr->calltree_root.allocs.bytes.total,
|
||||
(long) tmr->calltree_root.allocs.calls.direct,
|
||||
(long) tmr->calltree_root.allocs.calls.total);
|
||||
}
|
||||
|
||||
dump_graph(tmr, tmr->libraries, "libraries", "Library", stdout);
|
||||
|
||||
@ -248,8 +248,10 @@ static PLHashEntry *graphnode_allocentry(void *pool, const void *key)
|
||||
node->in = node->out = NULL;
|
||||
node->up = node->down = node->next = NULL;
|
||||
node->low = 0;
|
||||
node->bytes.direct = node->bytes.total = 0;
|
||||
node->allocs.direct = node->allocs.total = 0;
|
||||
node->allocs.bytes.direct = node->allocs.bytes.total = 0;
|
||||
node->allocs.calls.direct = node->allocs.calls.total = 0;
|
||||
node->frees.bytes.direct = node->frees.bytes.total = 0;
|
||||
node->frees.calls.direct = node->frees.calls.total = 0;
|
||||
node->sqsum = 0;
|
||||
node->sort = -1;
|
||||
return &node->entry;
|
||||
@ -508,8 +510,10 @@ int tmreader_eventloop(tmreader *tmr, const char *filename,
|
||||
*PL_HashTableRawLookup(tmr->methods, mhash, mkey);
|
||||
site->method = meth;
|
||||
site->offset = event.u.site.offset;
|
||||
site->bytes.direct = site->bytes.total = 0;
|
||||
site->allocs.direct = site->allocs.total = 0;
|
||||
site->allocs.bytes.direct = site->allocs.bytes.total = 0;
|
||||
site->allocs.calls.direct = site->allocs.calls.total = 0;
|
||||
site->frees.bytes.direct = site->frees.bytes.total = 0;
|
||||
site->frees.calls.direct = site->frees.calls.total = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -517,9 +521,9 @@ int tmreader_eventloop(tmreader *tmr, const char *filename,
|
||||
case TM_EVENT_CALLOC:
|
||||
case TM_EVENT_REALLOC: {
|
||||
tmcallsite *site;
|
||||
int32 size, oldsize, delta;
|
||||
uint32 size, oldsize;
|
||||
double delta, sqdelta, sqszdelta;
|
||||
tmgraphnode *meth, *comp, *lib;
|
||||
double sqdelta, sqszdelta;
|
||||
|
||||
site = tmreader_callsite(tmr, event.serial);
|
||||
if (!site) {
|
||||
@ -528,15 +532,15 @@ int tmreader_eventloop(tmreader *tmr, const char *filename,
|
||||
continue;
|
||||
}
|
||||
|
||||
size = (int32)event.u.alloc.size;
|
||||
oldsize = (int32)event.u.alloc.oldsize;
|
||||
delta = size - oldsize;
|
||||
site->bytes.direct += delta;
|
||||
size = event.u.alloc.size;
|
||||
oldsize = event.u.alloc.oldsize;
|
||||
delta = (double)size - (double)oldsize;
|
||||
site->allocs.bytes.direct += delta;
|
||||
if (event.type != TM_EVENT_REALLOC)
|
||||
site->allocs.direct++;
|
||||
site->allocs.calls.direct++;
|
||||
meth = site->method;
|
||||
if (meth) {
|
||||
meth->bytes.direct += delta;
|
||||
meth->allocs.bytes.direct += delta;
|
||||
sqdelta = delta * delta;
|
||||
if (event.type == TM_EVENT_REALLOC) {
|
||||
sqszdelta = ((double)size * size)
|
||||
@ -544,25 +548,25 @@ int tmreader_eventloop(tmreader *tmr, const char *filename,
|
||||
meth->sqsum += sqszdelta;
|
||||
} else {
|
||||
meth->sqsum += sqdelta;
|
||||
meth->allocs.direct++;
|
||||
meth->allocs.calls.direct++;
|
||||
}
|
||||
comp = meth->up;
|
||||
if (comp) {
|
||||
comp->bytes.direct += delta;
|
||||
comp->allocs.bytes.direct += delta;
|
||||
if (event.type == TM_EVENT_REALLOC) {
|
||||
comp->sqsum += sqszdelta;
|
||||
} else {
|
||||
comp->sqsum += sqdelta;
|
||||
comp->allocs.direct++;
|
||||
comp->allocs.calls.direct++;
|
||||
}
|
||||
lib = comp->up;
|
||||
if (lib) {
|
||||
lib->bytes.direct += delta;
|
||||
lib->allocs.bytes.direct += delta;
|
||||
if (event.type == TM_EVENT_REALLOC) {
|
||||
lib->sqsum += sqszdelta;
|
||||
} else {
|
||||
lib->sqsum += sqdelta;
|
||||
lib->allocs.direct++;
|
||||
lib->allocs.calls.direct++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -570,8 +574,37 @@ int tmreader_eventloop(tmreader *tmr, const char *filename,
|
||||
break;
|
||||
}
|
||||
|
||||
case TM_EVENT_FREE:
|
||||
case TM_EVENT_FREE: {
|
||||
tmcallsite *site;
|
||||
uint32 size;
|
||||
tmgraphnode *meth, *comp, *lib;
|
||||
|
||||
site = tmreader_callsite(tmr, event.serial);
|
||||
if (!site) {
|
||||
fprintf(stderr, "%s: no callsite for '%c' (%lu)!\n",
|
||||
tmr->program, event.type, (unsigned long) event.serial);
|
||||
continue;
|
||||
}
|
||||
size = event.u.alloc.size;
|
||||
site->frees.bytes.direct += size;
|
||||
site->frees.calls.direct++;
|
||||
meth = site->method;
|
||||
if (meth) {
|
||||
meth->frees.bytes.direct += size;
|
||||
meth->frees.calls.direct++;
|
||||
comp = meth->up;
|
||||
if (comp) {
|
||||
comp->frees.bytes.direct += size;
|
||||
comp->frees.calls.direct++;
|
||||
lib = comp->up;
|
||||
if (lib) {
|
||||
lib->frees.bytes.direct += size;
|
||||
lib->frees.calls.direct++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TM_EVENT_STATS:
|
||||
break;
|
||||
|
||||
@ -44,6 +44,8 @@ PR_BEGIN_EXTERN_C
|
||||
typedef struct tmreader tmreader;
|
||||
typedef struct tmevent tmevent;
|
||||
typedef struct tmcounts tmcounts;
|
||||
typedef struct tmallcounts tmallcounts;
|
||||
typedef struct tmgraphlink tmgraphlink;
|
||||
typedef struct tmgraphedge tmgraphedge;
|
||||
typedef struct tmgraphnode tmgraphnode;
|
||||
typedef struct tmcallsite tmcallsite;
|
||||
@ -75,20 +77,25 @@ struct tmevent {
|
||||
};
|
||||
|
||||
struct tmcounts {
|
||||
int32 direct; /* things allocated by this node's code */
|
||||
int32 total; /* direct + things from all descendents */
|
||||
uint32 direct; /* things allocated by this node's code */
|
||||
uint32 total; /* direct + things from all descendents */
|
||||
};
|
||||
|
||||
struct tmallcounts {
|
||||
tmcounts bytes;
|
||||
tmcounts calls;
|
||||
};
|
||||
|
||||
struct tmgraphnode {
|
||||
PLHashEntry entry; /* key is serial or name, value must be name */
|
||||
tmgraphedge *in;
|
||||
tmgraphedge *out;
|
||||
tmgraphlink *in;
|
||||
tmgraphlink *out;
|
||||
tmgraphnode *up; /* parent in supergraph, e.g., JS for JS_*() */
|
||||
tmgraphnode *down; /* subgraph kids, declining bytes.total order */
|
||||
tmgraphnode *next; /* next kid in supergraph node's down list */
|
||||
int low; /* 0 or lowest current tree walk level */
|
||||
tmcounts bytes; /* bytes (direct and total) allocated */
|
||||
tmcounts allocs; /* number of allocations */
|
||||
tmallcounts allocs;
|
||||
tmallcounts frees;
|
||||
double sqsum; /* sum of squared bytes.direct */
|
||||
int sort; /* sorted index in node table, -1 if no table */
|
||||
};
|
||||
@ -98,21 +105,39 @@ struct tmgraphnode {
|
||||
#define tmlibrary_serial(lib) ((uint32) (lib)->entry.key)
|
||||
#define tmcomponent_name(comp) ((const char*) (comp)->entry.key)
|
||||
|
||||
struct tmgraphedge {
|
||||
tmgraphedge *next;
|
||||
tmgraphnode *node;
|
||||
tmcounts bytes;
|
||||
/* Half a graphedge, not including per-edge allocation stats. */
|
||||
struct tmgraphlink {
|
||||
tmgraphlink *next; /* next fanning out from or into a node */
|
||||
tmgraphnode *node; /* the other node (to if OUT, from if IN) */
|
||||
};
|
||||
|
||||
/*
|
||||
* It's safe to downcast a "from" tmgraphlink (one linked from a node's out
|
||||
* pointer) to tmgraphedge. To go from an "out" (linked via tmgraphedge.from)
|
||||
* or "in" (linked via tmgraphedge.to) list link to its containing edge, use
|
||||
* TM_LINK_TO_EDGE(link, which).
|
||||
*/
|
||||
struct tmgraphedge {
|
||||
tmgraphlink links[2];
|
||||
tmallcounts allocs;
|
||||
tmallcounts frees;
|
||||
};
|
||||
|
||||
/* Indices into tmgraphedge.links -- out must come first. */
|
||||
#define TM_EDGE_OUT_LINK 0
|
||||
#define TM_EDGE_IN_LINK 1
|
||||
|
||||
#define TM_LINK_TO_EDGE(link,which) ((tmgraphedge*) &(link)[-(which)])
|
||||
|
||||
struct tmcallsite {
|
||||
PLHashEntry entry;
|
||||
tmcallsite *parent;
|
||||
tmcallsite *siblings;
|
||||
tmcallsite *kids;
|
||||
tmgraphnode *method;
|
||||
uint32 offset;
|
||||
tmcounts bytes;
|
||||
tmcounts allocs;
|
||||
PLHashEntry entry; /* key is site serial number */
|
||||
tmcallsite *parent; /* calling site */
|
||||
tmcallsite *siblings; /* other sites reached from parent */
|
||||
tmcallsite *kids; /* sites reached from here */
|
||||
tmgraphnode *method; /* method node in tmr->methods graph */
|
||||
uint32 offset; /* pc offset from start of method */
|
||||
tmallcounts allocs;
|
||||
tmallcounts frees;
|
||||
};
|
||||
|
||||
struct tmreader {
|
||||
|
||||
@ -46,7 +46,6 @@ extern int optind;
|
||||
#endif
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include "prtypes.h"
|
||||
#include "prlog.h"
|
||||
@ -61,54 +60,57 @@ static int js_mode = 0;
|
||||
static int do_tree_dump = 0;
|
||||
static int unified_output = 0;
|
||||
static char *function_dump = NULL;
|
||||
static int32 min_subtotal = 0;
|
||||
static uint32 min_subtotal = 0;
|
||||
|
||||
static void connect_nodes(tmgraphnode *from, tmgraphnode *to, tmcallsite *site)
|
||||
{
|
||||
tmgraphlink *link;
|
||||
tmgraphedge *edge;
|
||||
|
||||
for (edge = from->out; edge; edge = edge->next) {
|
||||
if (edge->node == to) {
|
||||
for (link = from->out; link; link = link->next) {
|
||||
if (link->node == to) {
|
||||
/*
|
||||
* Say the stack looks like this: ... => JS => js => JS => js.
|
||||
* We must avoid overcounting JS=>js because the first edge total
|
||||
* includes the second JS=>js edge's total (which is because the
|
||||
* lower site's total includes all its kids' totals).
|
||||
*/
|
||||
edge = TM_LINK_TO_EDGE(link, TM_EDGE_OUT_LINK);
|
||||
if (!to->low || to->low < from->low) {
|
||||
edge[0].bytes.direct += site->bytes.direct;
|
||||
edge[1].bytes.direct += site->bytes.direct;
|
||||
edge[0].bytes.total += site->bytes.total;
|
||||
edge[1].bytes.total += site->bytes.total;
|
||||
/* Add the direct and total counts to edge->allocs. */
|
||||
edge->allocs.bytes.direct += site->allocs.bytes.direct;
|
||||
edge->allocs.bytes.total += site->allocs.bytes.total;
|
||||
edge->allocs.calls.direct += site->allocs.calls.direct;
|
||||
edge->allocs.calls.total += site->allocs.calls.total;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
edge = (tmgraphedge*) malloc(2 * sizeof(tmgraphedge));
|
||||
|
||||
edge = (tmgraphedge*) malloc(sizeof(tmgraphedge));
|
||||
if (!edge) {
|
||||
perror(program);
|
||||
exit(1);
|
||||
}
|
||||
edge[0].node = to;
|
||||
edge[0].next = from->out;
|
||||
from->out = &edge[0];
|
||||
edge[1].node = from;
|
||||
edge[1].next = to->in;
|
||||
to->in = &edge[1];
|
||||
edge[0].bytes.direct = edge[1].bytes.direct = site->bytes.direct;
|
||||
edge[0].bytes.total = edge[1].bytes.total = site->bytes.total;
|
||||
edge->links[TM_EDGE_OUT_LINK].node = to;
|
||||
edge->links[TM_EDGE_OUT_LINK].next = from->out;
|
||||
from->out = &edge->links[TM_EDGE_OUT_LINK];
|
||||
edge->links[TM_EDGE_IN_LINK].node = from;
|
||||
edge->links[TM_EDGE_IN_LINK].next = to->in;
|
||||
to->in = &edge->links[TM_EDGE_IN_LINK];
|
||||
edge->allocs = site->allocs;
|
||||
}
|
||||
|
||||
static void compute_callsite_totals(tmcallsite *site)
|
||||
{
|
||||
tmcallsite *kid;
|
||||
|
||||
site->bytes.total += site->bytes.direct;
|
||||
site->allocs.total += site->allocs.direct;
|
||||
site->allocs.bytes.total += site->allocs.bytes.direct;
|
||||
site->allocs.calls.total += site->allocs.calls.direct;
|
||||
for (kid = site->kids; kid; kid = kid->siblings) {
|
||||
compute_callsite_totals(kid);
|
||||
site->bytes.total += kid->bytes.total;
|
||||
site->allocs.total += kid->allocs.total;
|
||||
site->allocs.bytes.total += kid->allocs.bytes.total;
|
||||
site->allocs.calls.total += kid->allocs.calls.total;
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,8 +129,8 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
pmeth = parent->method;
|
||||
if (pmeth && pmeth != meth) {
|
||||
if (!meth->low) {
|
||||
meth->bytes.total += site->bytes.total;
|
||||
meth->allocs.total += site->allocs.total;
|
||||
meth->allocs.bytes.total += site->allocs.bytes.total;
|
||||
meth->allocs.calls.total += site->allocs.calls.total;
|
||||
}
|
||||
connect_nodes(pmeth, meth, site);
|
||||
|
||||
@ -137,8 +139,10 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
pcomp = pmeth->up;
|
||||
if (pcomp && pcomp != comp) {
|
||||
if (!comp->low) {
|
||||
comp->bytes.total += site->bytes.total;
|
||||
comp->allocs.total += site->allocs.total;
|
||||
comp->allocs.bytes.total
|
||||
+= site->allocs.bytes.total;
|
||||
comp->allocs.calls.total
|
||||
+= site->allocs.calls.total;
|
||||
}
|
||||
connect_nodes(pcomp, comp, site);
|
||||
|
||||
@ -147,8 +151,10 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
plib = pcomp->up;
|
||||
if (plib && plib != lib) {
|
||||
if (!lib->low) {
|
||||
lib->bytes.total += site->bytes.total;
|
||||
lib->allocs.total += site->allocs.total;
|
||||
lib->allocs.bytes.total
|
||||
+= site->allocs.bytes.total;
|
||||
lib->allocs.calls.total
|
||||
+= site->allocs.calls.total;
|
||||
}
|
||||
connect_nodes(plib, lib, site);
|
||||
}
|
||||
@ -172,7 +178,8 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
fprintf(fp, "%c%*s%3d %3d %s %lu %ld\n",
|
||||
site->kids ? '+' : '-', level, "", level, kidnum,
|
||||
meth ? tmgraphnode_name(meth) : "???",
|
||||
(unsigned long)site->bytes.direct, (long)site->bytes.total);
|
||||
(unsigned long)site->allocs.bytes.direct,
|
||||
(long)site->allocs.bytes.total);
|
||||
}
|
||||
nkids = 0;
|
||||
level++;
|
||||
@ -195,8 +202,16 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
}
|
||||
}
|
||||
|
||||
/* Linked list bubble-sort (waterson and brendan went bald hacking this). */
|
||||
#define BUBBLE_SORT_LINKED_LIST(listp, nodetype) \
|
||||
/*
|
||||
* Linked list bubble-sort (waterson and brendan went bald hacking this).
|
||||
*
|
||||
* Sort the list in non-increasing order, using the expression passed as the
|
||||
* 'lessthan' formal macro parameter. This expression should use 'curr' as
|
||||
* the pointer to the current node (of type nodetype) and 'next' as the next
|
||||
* node pointer. It should return true if curr is less than next, and false
|
||||
* otherwise.
|
||||
*/
|
||||
#define BUBBLE_SORT_LINKED_LIST(listp, nodetype, lessthan) \
|
||||
PR_BEGIN_MACRO \
|
||||
nodetype *curr, **currp, *next, **nextp, *tmp; \
|
||||
\
|
||||
@ -204,7 +219,7 @@ static void walk_callsite_tree(tmcallsite *site, int level, int kidnum, FILE *fp
|
||||
while ((curr = *currp) != NULL && curr->next) { \
|
||||
nextp = &curr->next; \
|
||||
while ((next = *nextp) != NULL) { \
|
||||
if (curr->bytes.total < next->bytes.total) { \
|
||||
if (lessthan) { \
|
||||
tmp = curr->next; \
|
||||
*currp = tmp; \
|
||||
if (tmp == next) { \
|
||||
@ -234,7 +249,8 @@ static PRIntn tabulate_node(PLHashEntry *he, PRIntn i, void *arg)
|
||||
tmgraphnode **table = (tmgraphnode**) arg;
|
||||
|
||||
table[i] = node;
|
||||
BUBBLE_SORT_LINKED_LIST(&node->down, tmgraphnode);
|
||||
BUBBLE_SORT_LINKED_LIST(&node->down, tmgraphnode,
|
||||
(curr->allocs.bytes.total < next->allocs.bytes.total));
|
||||
return HT_ENUMERATE_NEXT;
|
||||
}
|
||||
|
||||
@ -242,18 +258,18 @@ static PRIntn tabulate_node(PLHashEntry *he, PRIntn i, void *arg)
|
||||
static int node_table_compare(const void *p1, const void *p2)
|
||||
{
|
||||
const tmgraphnode *node1, *node2;
|
||||
int32 key1, key2;
|
||||
uint32 key1, key2;
|
||||
|
||||
node1 = *(const tmgraphnode**) p1;
|
||||
node2 = *(const tmgraphnode**) p2;
|
||||
if (sort_by_direct) {
|
||||
key1 = node1->bytes.direct;
|
||||
key2 = node2->bytes.direct;
|
||||
key1 = node1->allocs.bytes.direct;
|
||||
key2 = node2->allocs.bytes.direct;
|
||||
} else {
|
||||
key1 = node1->bytes.total;
|
||||
key2 = node2->bytes.total;
|
||||
key1 = node1->allocs.bytes.total;
|
||||
key2 = node2->allocs.bytes.total;
|
||||
}
|
||||
return key2 - key1;
|
||||
return (key2 < key1) ? -1 : (key2 > key1) ? 1 : 0;
|
||||
}
|
||||
|
||||
static int mean_size_compare(const void *p1, const void *p2)
|
||||
@ -263,12 +279,12 @@ static int mean_size_compare(const void *p1, const void *p2)
|
||||
|
||||
node1 = *(const tmgraphnode**) p1;
|
||||
node2 = *(const tmgraphnode**) p2;
|
||||
div1 = (double)node1->allocs.direct;
|
||||
div2 = (double)node2->allocs.direct;
|
||||
div1 = (double)node1->allocs.calls.direct;
|
||||
div2 = (double)node2->allocs.calls.direct;
|
||||
if (div1 == 0 || div2 == 0)
|
||||
return div2 - div1;
|
||||
key1 = (double)node1->bytes.direct / div1;
|
||||
key2 = (double)node2->bytes.direct / div2;
|
||||
key1 = (double)node1->allocs.bytes.direct / div1;
|
||||
key2 = (double)node2->allocs.bytes.direct / div2;
|
||||
if (key1 < key2)
|
||||
return 1;
|
||||
if (key1 > key2)
|
||||
@ -289,50 +305,57 @@ static const char *prettybig(uint32 num, char *buf, size_t limit)
|
||||
return buf;
|
||||
}
|
||||
|
||||
static double percent(int32 num, int32 total)
|
||||
static double percent(uint32 num, uint32 total)
|
||||
{
|
||||
if (num == 0)
|
||||
return 0.0;
|
||||
return ((double) num * 100) / (double) total;
|
||||
}
|
||||
|
||||
static void sort_graphedge_list(tmgraphedge **listp)
|
||||
static void sort_graphlink_list(tmgraphlink **listp, int which)
|
||||
{
|
||||
BUBBLE_SORT_LINKED_LIST(listp, tmgraphedge);
|
||||
BUBBLE_SORT_LINKED_LIST(listp, tmgraphlink,
|
||||
(TM_LINK_TO_EDGE(curr, which)->allocs.bytes.total
|
||||
< TM_LINK_TO_EDGE(next, which)->allocs.bytes.total));
|
||||
}
|
||||
|
||||
static void dump_graphedge_list(tmgraphedge *list, const char *name, FILE *fp)
|
||||
static void dump_graphlink_list(tmgraphlink *list, int which, const char *name,
|
||||
FILE *fp)
|
||||
{
|
||||
tmcounts bytes;
|
||||
tmgraphlink *link;
|
||||
tmgraphedge *edge;
|
||||
char buf[16];
|
||||
|
||||
bytes.direct = bytes.total = 0;
|
||||
for (edge = list; edge; edge = edge->next) {
|
||||
bytes.direct += edge->bytes.direct;
|
||||
bytes.total += edge->bytes.total;
|
||||
for (link = list; link; link = link->next) {
|
||||
edge = TM_LINK_TO_EDGE(link, which);
|
||||
bytes.direct += edge->allocs.bytes.direct;
|
||||
bytes.total += edge->allocs.bytes.total;
|
||||
}
|
||||
|
||||
if (js_mode) {
|
||||
fprintf(fp,
|
||||
" %s:{dbytes:%ld, tbytes:%ld, edges:[\n",
|
||||
name, (long) bytes.direct, (long) bytes.total);
|
||||
for (edge = list; edge; edge = edge->next) {
|
||||
for (link = list; link; link = link->next) {
|
||||
edge = TM_LINK_TO_EDGE(link, which);
|
||||
fprintf(fp,
|
||||
" {node:%d, dbytes:%ld, tbytes:%ld},\n",
|
||||
edge->node->sort,
|
||||
(long) edge->bytes.direct,
|
||||
(long) edge->bytes.total);
|
||||
link->node->sort,
|
||||
(long) edge->allocs.bytes.direct,
|
||||
(long) edge->allocs.bytes.total);
|
||||
}
|
||||
fputs(" ]},\n", fp);
|
||||
} else {
|
||||
fputs("<td valign=top>", fp);
|
||||
for (edge = list; edge; edge = edge->next) {
|
||||
for (link = list; link; link = link->next) {
|
||||
edge = TM_LINK_TO_EDGE(link, which);
|
||||
fprintf(fp,
|
||||
"<a href='#%s'>%s (%1.2f%%)</a>\n",
|
||||
tmgraphnode_name(edge->node),
|
||||
prettybig(edge->bytes.total, buf, sizeof buf),
|
||||
percent(edge->bytes.total, bytes.total));
|
||||
tmgraphnode_name(link->node),
|
||||
prettybig(edge->allocs.bytes.total, buf, sizeof buf),
|
||||
percent(edge->allocs.bytes.total, bytes.total));
|
||||
}
|
||||
fputs("</td>", fp);
|
||||
}
|
||||
@ -381,7 +404,7 @@ static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
|
||||
for (i = 0; i < count; i++) {
|
||||
/* Don't bother with truly puny nodes. */
|
||||
node = table[i];
|
||||
if (node->bytes.total < min_subtotal)
|
||||
if (node->allocs.bytes.total < min_subtotal)
|
||||
break;
|
||||
|
||||
name = tmgraphnode_name(node);
|
||||
@ -390,16 +413,18 @@ static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
|
||||
" {name:'%s', dbytes:%ld, tbytes:%ld,"
|
||||
" dallocs:%ld, tallocs:%ld,\n",
|
||||
name,
|
||||
(long) node->bytes.direct, (long) node->bytes.total,
|
||||
(long) node->allocs.direct, (long) node->allocs.total);
|
||||
(long) node->allocs.bytes.direct,
|
||||
(long) node->allocs.bytes.total,
|
||||
(long) node->allocs.calls.direct,
|
||||
(long) node->allocs.calls.total);
|
||||
} else {
|
||||
namelen = strlen(name);
|
||||
fprintf(fp,
|
||||
"<tr>"
|
||||
"<td valign=top><a name='%s'>%.*s%s</a></td>",
|
||||
name,
|
||||
(namelen > 45) ? 45 : (int)namelen, name,
|
||||
(namelen > 45) ? "<i>...</i>" : "");
|
||||
(namelen > 40) ? 40 : (int)namelen, name,
|
||||
(namelen > 40) ? "<i>...</i>" : "");
|
||||
if (node->down) {
|
||||
fprintf(fp,
|
||||
"<td valign=top><a href='#%s'><i>down</i></a></td>",
|
||||
@ -417,20 +442,25 @@ static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
|
||||
fprintf(fp,
|
||||
"<td valign=top>%s/%s (%1.2f%%/%1.2f%%)</td>"
|
||||
"<td valign=top>%s/%s (%1.2f%%/%1.2f%%)</td>",
|
||||
prettybig(node->bytes.total, buf1, sizeof buf1),
|
||||
prettybig(node->bytes.direct, buf2, sizeof buf2),
|
||||
percent(node->bytes.total, tmr->calltree_root.bytes.total),
|
||||
percent(node->bytes.direct, tmr->calltree_root.bytes.total),
|
||||
prettybig(node->allocs.total, buf3, sizeof buf3),
|
||||
prettybig(node->allocs.direct, buf4, sizeof buf4),
|
||||
percent(node->allocs.total, tmr->calltree_root.allocs.total),
|
||||
percent(node->allocs.direct, tmr->calltree_root.allocs.total));
|
||||
prettybig(node->allocs.bytes.total, buf1, sizeof buf1),
|
||||
prettybig(node->allocs.bytes.direct, buf2, sizeof buf2),
|
||||
percent(node->allocs.bytes.total,
|
||||
tmr->calltree_root.allocs.bytes.total),
|
||||
percent(node->allocs.bytes.direct,
|
||||
tmr->calltree_root.allocs.bytes.total),
|
||||
prettybig(node->allocs.calls.total, buf3, sizeof buf3),
|
||||
prettybig(node->allocs.calls.direct, buf4, sizeof buf4),
|
||||
percent(node->allocs.calls.total,
|
||||
tmr->calltree_root.allocs.calls.total),
|
||||
percent(node->allocs.calls.direct,
|
||||
tmr->calltree_root.allocs.calls.total));
|
||||
}
|
||||
|
||||
sort_graphedge_list(&node->in);
|
||||
dump_graphedge_list(node->in, "fin", fp); /* 'in' is a JS keyword! */
|
||||
sort_graphedge_list(&node->out);
|
||||
dump_graphedge_list(node->out, "out", fp);
|
||||
/* NB: we must use 'fin' because 'in' is a JS keyword! */
|
||||
sort_graphlink_list(&node->in, TM_EDGE_IN_LINK);
|
||||
dump_graphlink_list(node->in, TM_EDGE_IN_LINK, "fin", fp);
|
||||
sort_graphlink_list(&node->out, TM_EDGE_OUT_LINK);
|
||||
dump_graphlink_list(node->out, TM_EDGE_OUT_LINK, "out", fp);
|
||||
|
||||
if (js_mode)
|
||||
fputs(" },\n", fp);
|
||||
@ -460,12 +490,12 @@ static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
|
||||
double allocs, bytes, mean, variance, sigma;
|
||||
|
||||
node = table[i];
|
||||
allocs = (double)node->allocs.direct;
|
||||
allocs = (double)node->allocs.calls.direct;
|
||||
if (!allocs)
|
||||
continue;
|
||||
|
||||
/* Compute direct-size mean and standard deviation. */
|
||||
bytes = (double)node->bytes.direct;
|
||||
bytes = (double)node->allocs.bytes.direct;
|
||||
mean = bytes / allocs;
|
||||
variance = allocs * node->sqsum - bytes * bytes;
|
||||
if (variance < 0 || allocs == 1)
|
||||
@ -487,7 +517,7 @@ static void dump_graph(tmreader *tmr, PLHashTable *hashtbl, const char *varname,
|
||||
(namelen > 65) ? "<i>...</i>" : "",
|
||||
prettybig((uint32)mean, buf1, sizeof buf1),
|
||||
prettybig((uint32)sigma, buf2, sizeof buf2),
|
||||
prettybig(node->allocs.direct, buf3, sizeof buf3));
|
||||
prettybig(node->allocs.calls.direct, buf3, sizeof buf3));
|
||||
}
|
||||
fputs("</table>\n", fp);
|
||||
}
|
||||
@ -504,26 +534,26 @@ static void my_tmevent_handler(tmreader *tmr, tmevent *event)
|
||||
fprintf(stdout,
|
||||
"<p><table border=1>"
|
||||
"<tr><th>Counter</th><th>Value</th></tr>\n"
|
||||
"<tr><td>maximum actual stack depth</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>maximum callsite tree depth</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>number of parent callsites</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>maximum kids per parent</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>hits looking for a kid</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>misses looking for a kid</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>steps over other kids</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>callsite recurrences</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>number of stack backtraces</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace malloc failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace dladdr failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>malloc calls</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>malloc failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>calloc calls</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>calloc failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>realloc calls</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>realloc failures</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>free calls</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>free(null) calls</td><td>%lu</td></tr>\n"
|
||||
"<tr><td>maximum actual stack depth</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>maximum callsite tree depth</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>number of parent callsites</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>maximum kids per parent</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>hits looking for a kid</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>misses looking for a kid</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>steps over other kids</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>callsite recurrences</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>number of stack backtraces</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace malloc failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>backtrace dladdr failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>malloc calls</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>malloc failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>calloc calls</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>calloc failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>realloc calls</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>realloc failures</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>free calls</td><td align=right>%lu</td></tr>\n"
|
||||
"<tr><td>free(null) calls</td><td align=right>%lu</td></tr>\n"
|
||||
"</table>",
|
||||
(unsigned long) event->u.stats.tmstats.calltree_maxstack,
|
||||
(unsigned long) event->u.stats.tmstats.calltree_maxdepth,
|
||||
@ -664,10 +694,10 @@ int main(int argc, char **argv)
|
||||
"// direct and total byte and allocator-call counts\n"
|
||||
"var dbytes = %ld, tbytes = %ld,"
|
||||
" dallocs = %ld, tallocs = %ld;\n",
|
||||
(long) tmr->calltree_root.bytes.direct,
|
||||
(long) tmr->calltree_root.bytes.total,
|
||||
(long) tmr->calltree_root.allocs.direct,
|
||||
(long) tmr->calltree_root.allocs.total);
|
||||
(long) tmr->calltree_root.allocs.bytes.direct,
|
||||
(long) tmr->calltree_root.allocs.bytes.total,
|
||||
(long) tmr->calltree_root.allocs.calls.direct,
|
||||
(long) tmr->calltree_root.allocs.calls.total);
|
||||
}
|
||||
|
||||
dump_graph(tmr, tmr->libraries, "libraries", "Library", stdout);
|
||||
|
||||
@ -248,8 +248,10 @@ static PLHashEntry *graphnode_allocentry(void *pool, const void *key)
|
||||
node->in = node->out = NULL;
|
||||
node->up = node->down = node->next = NULL;
|
||||
node->low = 0;
|
||||
node->bytes.direct = node->bytes.total = 0;
|
||||
node->allocs.direct = node->allocs.total = 0;
|
||||
node->allocs.bytes.direct = node->allocs.bytes.total = 0;
|
||||
node->allocs.calls.direct = node->allocs.calls.total = 0;
|
||||
node->frees.bytes.direct = node->frees.bytes.total = 0;
|
||||
node->frees.calls.direct = node->frees.calls.total = 0;
|
||||
node->sqsum = 0;
|
||||
node->sort = -1;
|
||||
return &node->entry;
|
||||
@ -508,8 +510,10 @@ int tmreader_eventloop(tmreader *tmr, const char *filename,
|
||||
*PL_HashTableRawLookup(tmr->methods, mhash, mkey);
|
||||
site->method = meth;
|
||||
site->offset = event.u.site.offset;
|
||||
site->bytes.direct = site->bytes.total = 0;
|
||||
site->allocs.direct = site->allocs.total = 0;
|
||||
site->allocs.bytes.direct = site->allocs.bytes.total = 0;
|
||||
site->allocs.calls.direct = site->allocs.calls.total = 0;
|
||||
site->frees.bytes.direct = site->frees.bytes.total = 0;
|
||||
site->frees.calls.direct = site->frees.calls.total = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -517,9 +521,9 @@ int tmreader_eventloop(tmreader *tmr, const char *filename,
|
||||
case TM_EVENT_CALLOC:
|
||||
case TM_EVENT_REALLOC: {
|
||||
tmcallsite *site;
|
||||
int32 size, oldsize, delta;
|
||||
uint32 size, oldsize;
|
||||
double delta, sqdelta, sqszdelta;
|
||||
tmgraphnode *meth, *comp, *lib;
|
||||
double sqdelta, sqszdelta;
|
||||
|
||||
site = tmreader_callsite(tmr, event.serial);
|
||||
if (!site) {
|
||||
@ -528,15 +532,15 @@ int tmreader_eventloop(tmreader *tmr, const char *filename,
|
||||
continue;
|
||||
}
|
||||
|
||||
size = (int32)event.u.alloc.size;
|
||||
oldsize = (int32)event.u.alloc.oldsize;
|
||||
delta = size - oldsize;
|
||||
site->bytes.direct += delta;
|
||||
size = event.u.alloc.size;
|
||||
oldsize = event.u.alloc.oldsize;
|
||||
delta = (double)size - (double)oldsize;
|
||||
site->allocs.bytes.direct += delta;
|
||||
if (event.type != TM_EVENT_REALLOC)
|
||||
site->allocs.direct++;
|
||||
site->allocs.calls.direct++;
|
||||
meth = site->method;
|
||||
if (meth) {
|
||||
meth->bytes.direct += delta;
|
||||
meth->allocs.bytes.direct += delta;
|
||||
sqdelta = delta * delta;
|
||||
if (event.type == TM_EVENT_REALLOC) {
|
||||
sqszdelta = ((double)size * size)
|
||||
@ -544,25 +548,25 @@ int tmreader_eventloop(tmreader *tmr, const char *filename,
|
||||
meth->sqsum += sqszdelta;
|
||||
} else {
|
||||
meth->sqsum += sqdelta;
|
||||
meth->allocs.direct++;
|
||||
meth->allocs.calls.direct++;
|
||||
}
|
||||
comp = meth->up;
|
||||
if (comp) {
|
||||
comp->bytes.direct += delta;
|
||||
comp->allocs.bytes.direct += delta;
|
||||
if (event.type == TM_EVENT_REALLOC) {
|
||||
comp->sqsum += sqszdelta;
|
||||
} else {
|
||||
comp->sqsum += sqdelta;
|
||||
comp->allocs.direct++;
|
||||
comp->allocs.calls.direct++;
|
||||
}
|
||||
lib = comp->up;
|
||||
if (lib) {
|
||||
lib->bytes.direct += delta;
|
||||
lib->allocs.bytes.direct += delta;
|
||||
if (event.type == TM_EVENT_REALLOC) {
|
||||
lib->sqsum += sqszdelta;
|
||||
} else {
|
||||
lib->sqsum += sqdelta;
|
||||
lib->allocs.direct++;
|
||||
lib->allocs.calls.direct++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -570,8 +574,37 @@ int tmreader_eventloop(tmreader *tmr, const char *filename,
|
||||
break;
|
||||
}
|
||||
|
||||
case TM_EVENT_FREE:
|
||||
case TM_EVENT_FREE: {
|
||||
tmcallsite *site;
|
||||
uint32 size;
|
||||
tmgraphnode *meth, *comp, *lib;
|
||||
|
||||
site = tmreader_callsite(tmr, event.serial);
|
||||
if (!site) {
|
||||
fprintf(stderr, "%s: no callsite for '%c' (%lu)!\n",
|
||||
tmr->program, event.type, (unsigned long) event.serial);
|
||||
continue;
|
||||
}
|
||||
size = event.u.alloc.size;
|
||||
site->frees.bytes.direct += size;
|
||||
site->frees.calls.direct++;
|
||||
meth = site->method;
|
||||
if (meth) {
|
||||
meth->frees.bytes.direct += size;
|
||||
meth->frees.calls.direct++;
|
||||
comp = meth->up;
|
||||
if (comp) {
|
||||
comp->frees.bytes.direct += size;
|
||||
comp->frees.calls.direct++;
|
||||
lib = comp->up;
|
||||
if (lib) {
|
||||
lib->frees.bytes.direct += size;
|
||||
lib->frees.calls.direct++;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TM_EVENT_STATS:
|
||||
break;
|
||||
|
||||
@ -44,6 +44,8 @@ PR_BEGIN_EXTERN_C
|
||||
typedef struct tmreader tmreader;
|
||||
typedef struct tmevent tmevent;
|
||||
typedef struct tmcounts tmcounts;
|
||||
typedef struct tmallcounts tmallcounts;
|
||||
typedef struct tmgraphlink tmgraphlink;
|
||||
typedef struct tmgraphedge tmgraphedge;
|
||||
typedef struct tmgraphnode tmgraphnode;
|
||||
typedef struct tmcallsite tmcallsite;
|
||||
@ -75,20 +77,25 @@ struct tmevent {
|
||||
};
|
||||
|
||||
struct tmcounts {
|
||||
int32 direct; /* things allocated by this node's code */
|
||||
int32 total; /* direct + things from all descendents */
|
||||
uint32 direct; /* things allocated by this node's code */
|
||||
uint32 total; /* direct + things from all descendents */
|
||||
};
|
||||
|
||||
struct tmallcounts {
|
||||
tmcounts bytes;
|
||||
tmcounts calls;
|
||||
};
|
||||
|
||||
struct tmgraphnode {
|
||||
PLHashEntry entry; /* key is serial or name, value must be name */
|
||||
tmgraphedge *in;
|
||||
tmgraphedge *out;
|
||||
tmgraphlink *in;
|
||||
tmgraphlink *out;
|
||||
tmgraphnode *up; /* parent in supergraph, e.g., JS for JS_*() */
|
||||
tmgraphnode *down; /* subgraph kids, declining bytes.total order */
|
||||
tmgraphnode *next; /* next kid in supergraph node's down list */
|
||||
int low; /* 0 or lowest current tree walk level */
|
||||
tmcounts bytes; /* bytes (direct and total) allocated */
|
||||
tmcounts allocs; /* number of allocations */
|
||||
tmallcounts allocs;
|
||||
tmallcounts frees;
|
||||
double sqsum; /* sum of squared bytes.direct */
|
||||
int sort; /* sorted index in node table, -1 if no table */
|
||||
};
|
||||
@ -98,21 +105,39 @@ struct tmgraphnode {
|
||||
#define tmlibrary_serial(lib) ((uint32) (lib)->entry.key)
|
||||
#define tmcomponent_name(comp) ((const char*) (comp)->entry.key)
|
||||
|
||||
struct tmgraphedge {
|
||||
tmgraphedge *next;
|
||||
tmgraphnode *node;
|
||||
tmcounts bytes;
|
||||
/* Half a graphedge, not including per-edge allocation stats. */
|
||||
struct tmgraphlink {
|
||||
tmgraphlink *next; /* next fanning out from or into a node */
|
||||
tmgraphnode *node; /* the other node (to if OUT, from if IN) */
|
||||
};
|
||||
|
||||
/*
|
||||
* It's safe to downcast a "from" tmgraphlink (one linked from a node's out
|
||||
* pointer) to tmgraphedge. To go from an "out" (linked via tmgraphedge.from)
|
||||
* or "in" (linked via tmgraphedge.to) list link to its containing edge, use
|
||||
* TM_LINK_TO_EDGE(link, which).
|
||||
*/
|
||||
struct tmgraphedge {
|
||||
tmgraphlink links[2];
|
||||
tmallcounts allocs;
|
||||
tmallcounts frees;
|
||||
};
|
||||
|
||||
/* Indices into tmgraphedge.links -- out must come first. */
|
||||
#define TM_EDGE_OUT_LINK 0
|
||||
#define TM_EDGE_IN_LINK 1
|
||||
|
||||
#define TM_LINK_TO_EDGE(link,which) ((tmgraphedge*) &(link)[-(which)])
|
||||
|
||||
struct tmcallsite {
|
||||
PLHashEntry entry;
|
||||
tmcallsite *parent;
|
||||
tmcallsite *siblings;
|
||||
tmcallsite *kids;
|
||||
tmgraphnode *method;
|
||||
uint32 offset;
|
||||
tmcounts bytes;
|
||||
tmcounts allocs;
|
||||
PLHashEntry entry; /* key is site serial number */
|
||||
tmcallsite *parent; /* calling site */
|
||||
tmcallsite *siblings; /* other sites reached from parent */
|
||||
tmcallsite *kids; /* sites reached from here */
|
||||
tmgraphnode *method; /* method node in tmr->methods graph */
|
||||
uint32 offset; /* pc offset from start of method */
|
||||
tmallcounts allocs;
|
||||
tmallcounts frees;
|
||||
};
|
||||
|
||||
struct tmreader {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user