644 lines
13 KiB
C
644 lines
13 KiB
C
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <sys/time.h>
|
|
#include <sys/par.h>
|
|
#include <sys/param.h>
|
|
#include <sys/prf.h>
|
|
#include <assert.h>
|
|
|
|
#include "data.h"
|
|
#include "term.h"
|
|
#include "freelist.h"
|
|
#include "top.h"
|
|
#include "prf.h"
|
|
|
|
typedef struct stacktrace_s {
|
|
int st_nsamples;
|
|
int st_state;
|
|
uint64_t st_stack[PRF_MAXSTACK];
|
|
} stacktrace;
|
|
|
|
typedef struct func_sym_s {
|
|
const char *fs_name;
|
|
uint64_t fs_lowpc;
|
|
uint64_t fs_child_hits;
|
|
uint64_t fs_self_hits;
|
|
uint64_t fs_sum_depth;
|
|
} func_sym_t;
|
|
|
|
#define sym_avg_depth(symp) ((symp)->fs_sum_depth / \
|
|
((symp)->fs_child_hits + (symp)->fs_self_hits))
|
|
|
|
typedef struct addr_range_s {
|
|
uint64_t ar_begin;
|
|
uint64_t ar_end;
|
|
} addr_range_t;
|
|
|
|
typedef struct stack_stats_s {
|
|
uint64_t ss_nstacks;
|
|
uint64_t ss_sum_depth;
|
|
uint64_t ss_max_depth;
|
|
} stack_stats_t;
|
|
|
|
static uint64_t total_hits = 0;
|
|
static stacktrace traces[MAXCPU];
|
|
static stack_stats_t stack_stats;
|
|
|
|
func_sym_t *syms = NULL;
|
|
uint64_t nsyms = 0;
|
|
uint64_t nsyms_max = 0;
|
|
#define NSYMS_BLOCK 1024
|
|
|
|
#define ST_KEEP 0x01
|
|
#define ST_DISCARD 0x02
|
|
|
|
#define MAX_FILTER_ADDRS 25
|
|
addr_range_t filter_out_addrs[MAX_FILTER_ADDRS];
|
|
uint64_t nfilter_out_addrs = 0;
|
|
addr_range_t filter_in_addrs[MAX_FILTER_ADDRS];
|
|
uint64_t nfilter_in_addrs = 0;
|
|
|
|
static sort_order_proto(order_self_hits);
|
|
static sort_order_proto(order_incl_hits);
|
|
static sort_order_proto(order_child_hits);
|
|
static sort_order_proto(order_depth);
|
|
static filter_proto(filter_func);
|
|
|
|
static sort_order_t topfunc_sort_orders[] = {
|
|
"self", order_self_hits,
|
|
"child", order_child_hits,
|
|
"incl", order_incl_hits,
|
|
"depth", order_depth,
|
|
|
|
NULL, NULL
|
|
};
|
|
|
|
static void topfunc_usage(void);
|
|
static void *topfunc_get_first(top_state *ts);
|
|
static void *topfunc_get_next(top_state *ts, void*);
|
|
static void topfunc_zero_interval_counters(top_state *ts);
|
|
static int topfunc_display_objs(top_state *ts, int lineno);
|
|
static int topfunc_display_stats(top_state *ts, int lineno);
|
|
static int topfunc_display_help_title(top_state *ts, int lineno);
|
|
static int topfunc_display_help_text(top_state *ts, int lineno);
|
|
static int topfunc_handle_event(top_state *ts, tstamp_event_entry_t*);
|
|
static int topfunc_handle_keypress(top_state *ts, int ch);
|
|
static int topfunc_handle_option(top_state *ts, int opt, const char *optarg);
|
|
static void topfunc_init(top_state *ts);
|
|
|
|
static top_impl_t topfunc_impl = {
|
|
NULL, /* topfunc_destroy */
|
|
topfunc_usage,
|
|
topfunc_sort_orders,
|
|
"profile",
|
|
"u:",
|
|
topfunc_get_first,
|
|
topfunc_get_next,
|
|
topfunc_zero_interval_counters,
|
|
topfunc_display_objs,
|
|
topfunc_display_stats,
|
|
topfunc_display_help_title,
|
|
topfunc_display_help_text,
|
|
topfunc_handle_event,
|
|
topfunc_handle_keypress,
|
|
topfunc_handle_option,
|
|
topfunc_init
|
|
};
|
|
|
|
static const char *topfunc_help_title[] = {
|
|
"Topfunc version 1.0",
|
|
"",
|
|
"A top function usage display for Unix",
|
|
NULL
|
|
};
|
|
|
|
static const char *topfunc_help_text[] = {
|
|
"f\t- display only call-stacks containing function (\"none\" disables)",
|
|
"F\t- filter out call-stacks containing function (\"none\" disables)",
|
|
NULL
|
|
};
|
|
|
|
#define SIZEOF_QUAL (sizeof(((tstamp_event_entry_t*)NULL)->qual[0]))
|
|
#define NUM_STACK32_PER_QUAL (SIZEOF_QUAL / sizeof(uint32_t))
|
|
#define NUM_STACK32_PER_EVENT (NUM_STACK32_PER_QUAL * TSTAMP_NUM_QUALS)
|
|
#define NUM_STACK64_PER_QUAL (SIZEOF_QUAL / sizeof(uint64_t))
|
|
#define NUM_STACK64_PER_EVENT (NUM_STACK64_PER_QUAL * TSTAMP_NUM_QUALS)
|
|
|
|
static const char *namelist = "/unix";
|
|
|
|
static int
|
|
sym_lookup(uint64_t pc)
|
|
{
|
|
int low, high, current;
|
|
|
|
low = 0;
|
|
high = nsyms;
|
|
|
|
while (low < high) {
|
|
current = ((high - low) / 2) + low;
|
|
|
|
if (syms[current].fs_lowpc <= pc) {
|
|
if (low == current)
|
|
break;
|
|
low = current;
|
|
}
|
|
else {
|
|
high = current;
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
{
|
|
if (syms[current].fs_lowpc > pc)
|
|
abort();
|
|
|
|
if ((current + 1) >= nsyms)
|
|
abort();
|
|
|
|
if (syms[current+1].fs_lowpc < pc)
|
|
abort();
|
|
}
|
|
#endif
|
|
|
|
return current;
|
|
}
|
|
|
|
static void
|
|
stack_commit(stacktrace *trace)
|
|
{
|
|
int ii;
|
|
uint64_t *sp;
|
|
int bucket;
|
|
|
|
stack_stats.ss_nstacks++;
|
|
|
|
if (trace->st_nsamples > stack_stats.ss_max_depth)
|
|
stack_stats.ss_max_depth = trace->st_nsamples;
|
|
|
|
stack_stats.ss_sum_depth += trace->st_nsamples;
|
|
|
|
sp = trace->st_stack;
|
|
|
|
bucket = sym_lookup(*sp);
|
|
syms[bucket].fs_self_hits++;
|
|
syms[bucket].fs_sum_depth += (trace->st_nsamples - 1);
|
|
sp++;
|
|
|
|
for (ii = 1; ii < trace->st_nsamples; ii++, sp++)
|
|
{
|
|
bucket = sym_lookup(*sp);
|
|
syms[bucket].fs_child_hits++;
|
|
syms[bucket].fs_sum_depth += (trace->st_nsamples - ii - 1);
|
|
}
|
|
|
|
total_hits++;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
func_hit(tstamp_event_entry_t *ev, uint64_t addr_raw)
|
|
{
|
|
addr_range_t *ar;
|
|
stacktrace *trace = &traces[ev->cpu];
|
|
int ii;
|
|
uint64_t addr = (addr_raw & ~0x3ULL);
|
|
|
|
if (addr_raw & PRF_STACKSTART) {
|
|
trace->st_nsamples = 0;
|
|
trace->st_state = 0;
|
|
}
|
|
|
|
/* Note that syms[nsyms-1] should be _etext! */
|
|
assert(addr >= syms[0].fs_lowpc && addr < syms[nsyms-1].fs_lowpc);
|
|
|
|
if (trace->st_state & ST_DISCARD)
|
|
return 0;
|
|
|
|
/* Only allow call-stacks containing functions in
|
|
filter_in_addrs.
|
|
*/
|
|
for (ii = 0, ar = filter_in_addrs; ii < nfilter_in_addrs; ii++, ar++)
|
|
if (addr >= ar->ar_begin && addr <= ar->ar_end)
|
|
trace->st_state |= ST_KEEP;
|
|
|
|
/* Only allow call-stacks not containing functions in
|
|
filter_out_addrs.
|
|
*/
|
|
for (ii = 0, ar = filter_out_addrs; ii < nfilter_out_addrs; ii++, ar++)
|
|
{
|
|
if (addr >= ar->ar_begin && addr <= ar->ar_end)
|
|
{
|
|
trace->st_state |= ST_DISCARD;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
trace->st_stack[trace->st_nsamples] = addr;
|
|
trace->st_nsamples++;
|
|
|
|
if (addr_raw & PRF_STACKEND) {
|
|
if (nfilter_in_addrs && !(trace->st_state & ST_KEEP))
|
|
return 0;
|
|
|
|
stack_commit(trace);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
handle_stack32_event(tstamp_event_entry_t *ev)
|
|
{
|
|
int npc;
|
|
uint32_t *sptr;
|
|
|
|
for (sptr = (uint32_t*) ev->qual, npc = 0;
|
|
npc < NUM_STACK32_PER_EVENT; npc++, sptr++)
|
|
{
|
|
if (!func_hit(ev, (uint64_t) *sptr))
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void
|
|
handle_stack64_event(tstamp_event_entry_t *ev)
|
|
{
|
|
int npc;
|
|
uint64_t *sptr;
|
|
|
|
for (sptr = (uint64_t*) ev->qual, npc = 0;
|
|
npc < NUM_STACK64_PER_EVENT; npc++, sptr++)
|
|
{
|
|
if (!func_hit(ev, (uint64_t) *sptr))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
int
|
|
topfunc_handle_event(top_state *ts, tstamp_event_entry_t *ev)
|
|
{
|
|
switch (ev->evt) {
|
|
case TSTAMP_EV_PROF_STACK64:
|
|
handle_stack64_event(ev);
|
|
break;
|
|
case TSTAMP_EV_PROF_STACK32:
|
|
handle_stack32_event(ev);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static sort_order_proto(order_incl_hits)
|
|
{
|
|
func_sym_t *fsa = *(func_sym_t**)a;
|
|
func_sym_t *fsb = *(func_sym_t**)b;
|
|
return ((int64_t) (fsb->fs_self_hits + fsb->fs_child_hits)
|
|
- (fsa->fs_self_hits + fsa->fs_child_hits));
|
|
}
|
|
|
|
static sort_order_proto(order_self_hits)
|
|
{
|
|
func_sym_t *fsa = *(func_sym_t**)a;
|
|
func_sym_t *fsb = *(func_sym_t**)b;
|
|
return ((int64_t) fsb->fs_self_hits - fsa->fs_self_hits);
|
|
}
|
|
|
|
static sort_order_proto(order_child_hits)
|
|
{
|
|
func_sym_t *fsa = *(func_sym_t**)a;
|
|
func_sym_t *fsb = *(func_sym_t**)b;
|
|
return ((int64_t) fsb->fs_child_hits - fsa->fs_child_hits);
|
|
}
|
|
|
|
static sort_order_proto(order_depth)
|
|
{
|
|
func_sym_t *fsa = *(func_sym_t**)a;
|
|
func_sym_t *fsb = *(func_sym_t**)b;
|
|
return ((int64_t) sym_avg_depth(fsb) - sym_avg_depth(fsa));
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static filter_proto(filter_func)
|
|
{
|
|
func_sym_t *fs = *(func_sym_t**)obj;
|
|
return !(fs->fs_child_hits == 0 && fs->fs_self_hits == 0);
|
|
}
|
|
|
|
static char title[128];
|
|
static const char *title_fmt = "%40s %6s %6s %6s";
|
|
static const char *line_fmt = "%40s %5.1f%% %5.1f%% %6llu";
|
|
|
|
static void
|
|
display_init(void)
|
|
{
|
|
sprintf(title, title_fmt,
|
|
"FUNC",
|
|
"SELF",
|
|
"CHILD",
|
|
"DEPTH");
|
|
}
|
|
|
|
static int top_iter_index = 0;
|
|
|
|
/*ARGSUSED*/
|
|
static void*
|
|
topfunc_get_first(top_state *ts)
|
|
{
|
|
top_iter_index = 0;
|
|
return &syms[top_iter_index];
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void*
|
|
topfunc_get_next(top_state *ts, void *last)
|
|
{
|
|
if (++top_iter_index == nsyms)
|
|
return NULL;
|
|
|
|
return &syms[top_iter_index];
|
|
}
|
|
|
|
int
|
|
topfunc_display_help_title(top_state *ts, int line_start)
|
|
{
|
|
return term_draw_lines(top_term(ts), line_start, topfunc_help_title);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
int
|
|
topfunc_display_help_text(top_state *ts, int line_start)
|
|
{
|
|
return term_draw_lines(top_term(ts), line_start, topfunc_help_text);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
int
|
|
topfunc_display_stats(top_state *ts, int line_start)
|
|
{
|
|
int line = line_start;
|
|
char linebuf[128];
|
|
double avg_depth;
|
|
|
|
if (stack_stats.ss_nstacks == 0)
|
|
avg_depth = 0;
|
|
else
|
|
avg_depth = ((double) stack_stats.ss_sum_depth)
|
|
/ stack_stats.ss_nstacks;
|
|
|
|
sprintf(linebuf, "%llu stacks: %.1f avg depth, %llu max depth",
|
|
stack_stats.ss_nstacks,
|
|
avg_depth,
|
|
stack_stats.ss_max_depth);
|
|
term_draw_line(top_term(ts), line++, linebuf);
|
|
|
|
return line - line_start;
|
|
}
|
|
|
|
int
|
|
topfunc_display_objs(top_state *ts, int line_start)
|
|
{
|
|
int line;
|
|
char linebuf[160];
|
|
int ii;
|
|
func_sym_t **fs;
|
|
|
|
line = line_start;
|
|
|
|
term_draw_bold_line(top_term(ts), line++, title);
|
|
|
|
fs = (func_sym_t**) ts->ts_top_objs;
|
|
|
|
for (ii = 0; ii < ts->ts_ntop_objs; ii++, fs++) {
|
|
assert((*fs)->fs_name != NULL);
|
|
|
|
sprintf(linebuf, line_fmt,
|
|
(*fs)->fs_name,
|
|
float_percent((*fs)->fs_self_hits, total_hits),
|
|
float_percent((*fs)->fs_child_hits, total_hits),
|
|
sym_avg_depth(*fs));
|
|
|
|
term_draw_line(top_term(ts), line++, linebuf);
|
|
}
|
|
|
|
return line - line_start;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
void
|
|
topfunc_zero_interval_counters(top_state *ts)
|
|
{
|
|
int ii;
|
|
func_sym_t *fs;
|
|
|
|
for (fs = syms, ii = 0; ii < nsyms; ii++, fs++) {
|
|
fs->fs_child_hits = 0;
|
|
fs->fs_self_hits = 0;
|
|
fs->fs_sum_depth = 0;
|
|
}
|
|
|
|
stack_stats.ss_max_depth = 0;
|
|
stack_stats.ss_nstacks = 0;
|
|
stack_stats.ss_sum_depth = 0;
|
|
|
|
total_hits = 0;
|
|
}
|
|
|
|
static int
|
|
add_function_filter(const char *fn, addr_range_t *filter, uint64_t *nfilter)
|
|
{
|
|
func_sym_t *fs;
|
|
int ii;
|
|
uint64_t end;
|
|
|
|
if (!strcmp("none", fn)) {
|
|
*nfilter = 0;
|
|
return 1;
|
|
}
|
|
|
|
for (fs = syms, ii = 0; ii < nsyms; ii++, fs++) {
|
|
if (!strcmp(fs->fs_name, fn))
|
|
break;
|
|
}
|
|
if (ii == nsyms) {
|
|
top_error("Function not found: %s", fn);
|
|
return 0;
|
|
}
|
|
|
|
if ((ii + 1) == nsyms)
|
|
end = (uint64_t) -1;
|
|
else
|
|
end = (fs+1)->fs_lowpc - 1;
|
|
|
|
if (((*nfilter) + 1) == MAX_FILTER_ADDRS) {
|
|
top_error("Maximum number of functions reached.");
|
|
return 0;
|
|
}
|
|
|
|
filter[*nfilter].ar_begin = fs->fs_lowpc;
|
|
filter[*nfilter].ar_end = end;
|
|
(*nfilter)++;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
int
|
|
topfunc_handle_keypress(top_state *ts, int ch)
|
|
{
|
|
switch (ch) {
|
|
case 'F':
|
|
case 'f':
|
|
{
|
|
char line[80];
|
|
const char *prompt;
|
|
uint64_t *nfilter;
|
|
addr_range_t *filter;
|
|
|
|
if (ch == 'F') {
|
|
prompt = "Function to filter out";
|
|
filter = filter_out_addrs;
|
|
nfilter = &nfilter_out_addrs;
|
|
}
|
|
else {
|
|
prompt = "Function to display";
|
|
filter = filter_in_addrs;
|
|
nfilter = &nfilter_in_addrs;
|
|
}
|
|
|
|
term_getstr(top_term(ts), 0, top_prompt_line(ts),
|
|
prompt,
|
|
line, sizeof(line));
|
|
if (!*line)
|
|
break;
|
|
|
|
if (add_function_filter(line, filter, nfilter)) {
|
|
top_update(ts);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
topfunc_usage(void)
|
|
{
|
|
fprintf(stderr,
|
|
"\t-u <unix> use <unix> as kernel binary for symbols\n"
|
|
);
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static int
|
|
topfunc_handle_option(top_state *ts, int opt, const char *optarg)
|
|
{
|
|
switch (opt) {
|
|
case 'u':
|
|
namelist = optarg;
|
|
break;
|
|
|
|
case '?':
|
|
default:
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
sym_add(char *name, symaddr_t lowpc)
|
|
{
|
|
assert(name != NULL);
|
|
|
|
if (nsyms == nsyms_max) {
|
|
nsyms_max += NSYMS_BLOCK;
|
|
syms = (func_sym_t*) realloc(syms,
|
|
sizeof(func_sym_t) * nsyms_max);
|
|
if (syms == NULL) {
|
|
perror("realloc");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
syms[nsyms].fs_name = name;
|
|
syms[nsyms].fs_lowpc = lowpc;
|
|
nsyms++;
|
|
}
|
|
|
|
static int
|
|
sym_cmp(const void *a, const void *b)
|
|
{
|
|
const func_sym_t *fsa = (func_sym_t*)a;
|
|
const func_sym_t *fsb = (func_sym_t*)b;
|
|
return (fsa->fs_lowpc - fsb->fs_lowpc);
|
|
}
|
|
|
|
static void
|
|
sym_init(const char *objfile)
|
|
{
|
|
int fd;
|
|
int dwarf_failed;
|
|
|
|
fd = open(objfile, O_RDONLY);
|
|
if (fd < 0) {
|
|
perror("unable to open namelist file");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
dwarf_failed = rdsymtab(fd, sym_add);
|
|
|
|
if (rdelfsymtab(fd, sym_add) && dwarf_failed) {
|
|
fprintf(stderr, "unable to read elf or dwarf symtab of "
|
|
"%s\n", objfile);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (nsyms != 0)
|
|
qsort(syms, nsyms, sizeof(func_sym_t), sym_cmp);
|
|
}
|
|
|
|
int
|
|
list_repeated(symaddr_t addr)
|
|
{
|
|
func_sym_t *fs;
|
|
int ii;
|
|
|
|
for (fs = syms, ii = 0; ii < nsyms; ii++, fs++)
|
|
if (fs->fs_lowpc == addr)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
top_impl_init(top_state *ts)
|
|
{
|
|
display_init();
|
|
|
|
top_set_impl(ts, &topfunc_impl);
|
|
top_set_sort_order(ts, order_incl_hits);
|
|
top_set_filter_out(ts, filter_func);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*ARGSUSED*/
|
|
static void
|
|
topfunc_init(top_state *ts)
|
|
{
|
|
sym_init(namelist);
|
|
}
|