minix/commands/sprofalyze/sprofalyze.c
Erik van der Kouwe ad898517ac sprofdiff for comparing sprofile results
This patch adds the sprofdiff tool, which compares two sets of profiling
output files. It sorts processes and symbols by difference in average
number of samples, placing those that took more time on the left first
and those that took more time on the right last. If multiple runs are
combined, a standard deviation is computed and this is used to compute
the significance level, which gives an indication of which differences
are likely to be due to chance.

This tool is run not on the raw profiling files, but on the output of
sprofalyze -d (a new option). Though having to use two tools and an
intermediate file seems a bit awkward, the advantage is that the
original source tree is not needed to resolve the symbols. For
comparisons, this is very useful. Also, the intermediate file is in a
text format that can easily be processed by scripts, which may be useful
for other purposes as well.
2012-08-11 22:09:42 +00:00

1009 lines
28 KiB
C
Executable file

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <minix/profile.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
/* user-configurable settings */
#define BINARY_HASHTAB_SIZE 1024
#define ENDPOINT_HASHTAB_SIZE 1024
#define DEBUG 0
#define NM "/usr/pkg/bin/nm"
static const char *default_binaries[] = {
"kernel/kernel",
"servers/",
"drivers/",
};
static const char *src_path = "/usr/src";
/* types */
#define LINE_WIDTH 80
#define SYMBOL_NAME_SIZE 52
#define SYMBOL_NAME_WIDTH 22
#define SYMBOL_SIZE_MAX 0x100000
#define PC_MAP_L1_SIZE 0x10000
#define PC_MAP_L2_SIZE 0x10000
struct binary_info;
struct symbol_count {
struct symbol_count *next;
struct binary_info *binary;
uint32_t addr;
int samples;
char name[SYMBOL_NAME_SIZE];
};
struct pc_map_l2 {
struct symbol_count *l2[PC_MAP_L2_SIZE];
};
struct pc_map_l1 {
struct pc_map_l2 *l1[PC_MAP_L1_SIZE];
};
struct binary_info {
char name[PROC_NAME_LEN];
const char *path;
int samples;
struct symbol_count *symbols;
struct pc_map_l1 *pc_map;
struct binary_info *next;
struct binary_info *hashtab_next;
char no_more_warnings;
};
struct endpoint_info {
endpoint_t endpoint;
struct binary_info *binary;
struct endpoint_info *hashtab_next;
};
union sprof_record {
struct sprof_sample sample;
struct sprof_proc proc;
};
/* global variables */
static struct binary_info *binaries;
static struct binary_info *binary_hashtab[BINARY_HASHTAB_SIZE];
static struct endpoint_info *endpoint_hashtab[ENDPOINT_HASHTAB_SIZE];
static double minimum_perc = 1.0;
static struct sprof_info_s sprof_info;
/* prototypes */
static struct binary_info *binary_add(const char *path);
static struct binary_info *binary_find(const char *name);
static struct binary_info *binary_hashtab_get(const char *name);
static struct binary_info **binary_hashtab_get_ptr(const char *name);
static void binary_load_pc_map(struct binary_info *binary_info);
static const char *binary_name(const char *path);
static int compare_binaries(const void *p1, const void *p2);
static int compare_symbols(const void *p1, const void *p2);
static int count_symbols(const struct binary_info *binary, int threshold);
static void dprint_symbols(const struct binary_info *binary);
static struct endpoint_info **endpoint_hashtab_get_ptr(endpoint_t endpoint);
static void load_trace(const char *path);
static void *malloc_checked(size_t size);
static unsigned name_hash(const char *name);
static float percent(int value, int percent_of);
static void print_diff(void);
static void print_report(void);
static void print_report_overall(void);
static void print_report_per_binary(const struct binary_info *binary);
static void print_reports_per_binary(void);
static void print_report_symbols(struct symbol_count **symbols,
unsigned symbol_count, int total, int show_binary);
static void print_separator(void);
static int read_hex(FILE *file, unsigned long *value);
static int read_newline(FILE *file);
static void read_nm_line(FILE *file, int line, char *name, char *type,
unsigned long *addr, unsigned long *size);
static void read_to_whitespace(FILE *file, char *buffer, size_t size);
static size_t sample_process(const union sprof_record *data, size_t size,
int *samples_read);
static struct binary_info *sample_load_binary(const struct sprof_proc *sample);
static void sample_store(struct binary_info *binary,
const struct sprof_sample *sample);
static char *strdup_checked(const char *s);
static void usage(const char *argv0);
#define MALLOC_CHECKED(type, count) \
((type *) malloc_checked(sizeof(type) * (count)))
#define LENGTHOF(array) (sizeof((array)) / sizeof((array)[0]))
#if DEBUG
#define dprintf(...) do { \
fprintf(stderr, "debug(%s:%d): ", __FUNCTION__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
} while(0)
#else
#define dprintf(...)
#endif
int main(int argc, char **argv) {
int opt, sprofdiff;
#ifdef DEBUG
/* disable buffering so the output mixes correctly */
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
#endif
/* parse arguments */
while ((opt = getopt(argc, argv, "b:dp:s:")) != -1) {
switch (opt) {
case 'b':
/* additional binary specified */
binary_add(optarg);
break;
case 'd':
/* generate output for sprofdiff */
sprofdiff = 1;
break;
case 'p':
/* minimum percentage specified */
minimum_perc = atof(optarg);
if (minimum_perc < 0 || minimum_perc > 100) {
fprintf(stderr, "error: cut-off percentage "
"makes no sense: %g\n", minimum_perc);
exit(1);
}
break;
case 's':
/* source tree directory specified */
src_path = optarg;
break;
default: usage(argv[0]);
}
}
/* load samples */
if (optind >= argc) usage(argv[0]);
for (; optind < argc; optind++) {
load_trace(argv[optind]);
}
/* print report */
if (sprofdiff) {
print_diff();
} else {
print_report();
}
return 0;
}
static struct binary_info *binary_add(const char *path) {
struct binary_info *binary, **ptr;
const char *name;
/* assumption: path won't be overwritten or deallocated in the future */
/* not too much effort escaping for popen, prevent problems here */
assert(path);
if (strchr(path, '"')) {
fprintf(stderr, "error: path \"%s\" contains a quote\n", path);
exit(1);
}
/* get filename */
name = binary_name(path);
dprintf("adding binary \"%s\" with name \"%.*s\"\n",
path, PROC_NAME_LEN, name);
if (strlen(name) == 0) {
fprintf(stderr, "error: path \"%s\" does not "
"contain a filename\n", path);
exit(1);
}
/* check in hashtable whether this entry is indeed new */
ptr = binary_hashtab_get_ptr(name);
if (*ptr) {
fprintf(stderr, "warning: ignoring \"%s\" because \"%s\" was "
"previously specified\n", path, (*ptr)->path);
return *ptr;
}
dprintf("using %.*s from \"%s\"\n", PROC_NAME_LEN, name, path);
/* allocate new binary_info */
binary = MALLOC_CHECKED(struct binary_info, 1);
memset(binary, 0, sizeof(struct binary_info));
binary->path = path;
strncpy(binary->name, name, sizeof(binary->name));
/* insert into linked list */
binary->next = binaries;
binaries = binary;
/* insert into hashtable */
*ptr = binary;
return binary;
}
static struct binary_info *binary_find(const char *name) {
struct binary_info *binary;
const char *current_name;
unsigned i;
char path[PATH_MAX + 1], *path_end;
assert(name);
/* name is required */
if (!*name) {
fprintf(stderr, "warning: binary unspecified in sample\n");
return NULL;
}
/* do we already know this binary? */
binary = binary_hashtab_get(name);
if (binary) return binary;
/* search for it */
dprintf("searching for binary \"%.*s\" in \"%s\"\n",
PROC_NAME_LEN, name, src_path);
for (i = 0; i < LENGTHOF(default_binaries); i++) {
snprintf(path, sizeof(path), "%s/%s", src_path,
default_binaries[i]);
current_name = binary_name(path);
assert(current_name);
if (*current_name) {
/* paths not ending in slash: use if name matches */
if (strncmp(name, current_name,
PROC_NAME_LEN) != 0) {
continue;
}
} else {
/* paths ending in slash: look in subdir named after
* binary
*/
path_end = path + strlen(path);
snprintf(path_end, sizeof(path) - (path_end - path),
"%.*s/%.*s", PROC_NAME_LEN, name,
PROC_NAME_LEN, name);
}
/* use access to find out whether the binary exists and is
* readable
*/
dprintf("checking whether \"%s\" exists\n", path);
if (access(path, R_OK) < 0) continue;
/* ok, this seems to be the one */
return binary_add(strdup_checked(path));
}
/* not found */
return NULL;
}
static struct binary_info *binary_hashtab_get(const char *name) {
return *binary_hashtab_get_ptr(name);
}
static struct binary_info **binary_hashtab_get_ptr(const char *name) {
struct binary_info *binary, **ptr;
/* get pointer to location of the binary in hash table */
ptr = &binary_hashtab[name_hash(name) % BINARY_HASHTAB_SIZE];
while ((binary = *ptr) && strncmp(binary->name, name,
PROC_NAME_LEN) != 0) {
ptr = &binary->hashtab_next;
}
dprintf("looked up binary \"%.*s\" in hash table, %sfound\n",
PROC_NAME_LEN, name, *ptr ? "" : "not ");
return ptr;
}
static void binary_load_pc_map(struct binary_info *binary_info) {
unsigned long addr, size;
char *command;
size_t command_len;
#if DEBUG
unsigned count = 0;
#endif
FILE *file;
int index_l1, index_l2, line;
char name[SYMBOL_NAME_SIZE];
struct pc_map_l2 *pc_map_l2, **pc_map_l2_ptr;
struct symbol_count *symbol, **symbol_ptr;
char type;
assert(binary_info);
assert(!strchr(NM, '"'));
assert(!strchr(binary_info->path, '"'));
/* does the file exist? */
if (access(binary_info->path, R_OK) < 0) {
fprintf(stderr, "warning: \"%s\" does not exist or "
"not readable.\n", binary_info->path);
fprintf(stderr, " Did you do a make?\n");
return;
}
/* execute nm to get symbols */
command_len = strlen(NM) + strlen(binary_info->path) + 32;
command = MALLOC_CHECKED(char, command_len);
snprintf(command, command_len, "\"%s\" -nP \"%s\"",
NM, binary_info->path);
dprintf("running command for extracting addresses: %s\n", command);
file = popen(command, "r");
if (!file) {
perror("failed to start " NM);
exit(-1);
}
/* read symbols from nm output */
assert(!binary_info->symbols);
symbol_ptr = &binary_info->symbols;
line = 1;
while (!feof(file)) {
/* read nm output line; can't use fscanf as it doesn't know
* where to stop
*/
read_nm_line(file, line++, name, &type, &addr, &size);
/* store only text symbols */
if (type != 't' && type != 'T') continue;
*symbol_ptr = symbol = MALLOC_CHECKED(struct symbol_count, 1);
memset(symbol, 0, sizeof(*symbol));
symbol->binary = binary_info;
symbol->addr = addr;
strncpy(symbol->name, name, SYMBOL_NAME_SIZE);
symbol_ptr = &symbol->next;
#if DEBUG
count++;
#endif
}
fclose(file);
dprintf("extracted %u symbols\n", count);
/* create program counter map from symbols */
assert(!binary_info->pc_map);
binary_info->pc_map = MALLOC_CHECKED(struct pc_map_l1, 1);
memset(binary_info->pc_map, 0, sizeof(struct pc_map_l1));
for (symbol = binary_info->symbols; symbol; symbol = symbol->next) {
/* compute size if not specified */
size = symbol->next ? (symbol->next->addr - symbol->addr) : 1;
if (size > SYMBOL_SIZE_MAX) size = SYMBOL_SIZE_MAX;
/* mark each address */
for (addr = symbol->addr; addr - symbol->addr < size; addr++) {
index_l1 = addr / PC_MAP_L2_SIZE;
assert(index_l1 < PC_MAP_L1_SIZE);
pc_map_l2_ptr = &binary_info->pc_map->l1[index_l1];
if (!(pc_map_l2 = *pc_map_l2_ptr)) {
*pc_map_l2_ptr = pc_map_l2 =
MALLOC_CHECKED(struct pc_map_l2, 1);
memset(pc_map_l2, 0, sizeof(struct pc_map_l2));
}
index_l2 = addr % PC_MAP_L2_SIZE;
pc_map_l2->l2[index_l2] = symbol;
}
}
}
static const char *binary_name(const char *path) {
const char *name, *p;
/* much like basename, but guarantees to not modify the path */
name = path;
for (p = path; *p; p++) {
if (*p == '/') name = p + 1;
}
return name;
}
static int compare_binaries(const void *p1, const void *p2) {
const struct binary_info *const *b1 = p1, *const *b2 = p2;
/* binaries with more samples come first */
assert(b1);
assert(b2);
assert(*b1);
assert(*b2);
if ((*b1)->samples > (*b2)->samples) return -1;
if ((*b1)->samples < (*b2)->samples) return 1;
return 0;
}
static int compare_symbols(const void *p1, const void *p2) {
const struct symbol_count *const *s1 = p1, *const *s2 = p2;
/* symbols with more samples come first */
assert(s1);
assert(s2);
assert(*s1);
assert(*s2);
if ((*s1)->samples > (*s2)->samples) return -1;
if ((*s1)->samples < (*s2)->samples) return 1;
return 0;
}
static int count_symbols(const struct binary_info *binary, int threshold) {
struct symbol_count *symbol;
int result = 0;
for (symbol = binary->symbols; symbol; symbol = symbol->next) {
if (symbol->samples >= threshold) result++;
}
return result;
}
static void dprint_symbols(const struct binary_info *binary) {
#if DEBUG
const struct symbol_count *symbol;
for (symbol = binary->symbols; symbol; symbol = symbol->next) {
dprintf("addr=0x%.8lx samples=%8d name=\"%.*s\"\n",
(unsigned long) symbol->addr, symbol->samples,
SYMBOL_NAME_SIZE, symbol->name);
}
#endif
}
static struct endpoint_info **endpoint_hashtab_get_ptr(endpoint_t endpoint) {
struct endpoint_info *epinfo, **ptr;
/* get pointer to location of the binary in hash table */
ptr = &endpoint_hashtab[(unsigned) endpoint % ENDPOINT_HASHTAB_SIZE];
while ((epinfo = *ptr) && epinfo->endpoint != endpoint) {
ptr = &epinfo->hashtab_next;
}
dprintf("looked up endpoint %ld in hash table, %sfound\n",
(long) endpoint, *ptr ? "" : "not ");
return ptr;
}
static void load_trace(const char *path) {
char buffer[1024];
size_t bufindex, bufsize;
FILE *file;
unsigned size_info, size_sample, size_proc;
int samples_read;
/* open trace file */
file = fopen(path, "rb");
if (!file) {
fprintf(stderr, "error: cannot open trace file \"%s\": %s\n",
path, strerror(errno));
exit(1);
}
/* check file format and update totals */
if (fscanf(file, "stat\n%u %u %u\n",
&size_info, &size_sample, &size_proc) != 3) {
fprintf(stderr, "error: file \"%s\" does not contain an "
"sprofile trace\n", path);
exit(1);
}
if ((size_info != sizeof(struct sprof_info_s)) ||
(size_sample != sizeof(struct sprof_sample)) ||
(size_proc != sizeof(struct sprof_proc))) {
fprintf(stderr, "error: file \"%s\" is incompatible with this "
"version of sprofalyze; recompile sprofalyze with the "
"MINIX version that created this file\n", path);
exit(1);
}
if (fread(&sprof_info, sizeof(sprof_info), 1, file) != 1) {
fprintf(stderr, "error: totals missing in file \"%s\"\n", path);
exit(1);
}
/* read and store samples */
samples_read = 0;
bufindex = 0;
bufsize = 0;
for (;;) {
/* enough left in the buffer? */
if (bufsize - bufindex < sizeof(union sprof_record)) {
/* not enough, read some more */
memmove(buffer, buffer + bufindex, bufsize - bufindex);
bufsize -= bufindex;
bufindex = 0;
bufsize += fread(buffer + bufsize, 1,
sizeof(buffer) - bufsize, file);
/* are we done? */
if (bufsize == 0) break;
}
/* process sample record (either struct sprof_sample or
* struct sprof_proc)
*/
bufindex += sample_process(
(const union sprof_record *) (buffer + bufindex),
bufsize - bufindex, &samples_read);
}
if (samples_read != sprof_info.system_samples) {
fprintf(stderr, "warning: number of system samples (%d) in "
"\"%s\" does not match number of records (%d)\n",
sprof_info.system_samples, path, samples_read);
}
fclose(file);
}
static void *malloc_checked(size_t size) {
void *p;
if (!size) return NULL;
p = malloc(size);
if (!p) {
fprintf(stderr, "error: malloc cannot allocate %lu bytes: %s\n",
(unsigned long) size, strerror(errno));
exit(-1);
}
return p;
}
static unsigned name_hash(const char *name) {
int i;
unsigned r = 0;
/* remember: strncpy initializes all bytes */
for (i = 0; i < PROC_NAME_LEN && name[i]; i++) {
r = r * 31 + name[i];
}
dprintf("name_hash(\"%.*s\") = 0x%.8x\n", PROC_NAME_LEN, name, r);
return r;
}
static float percent(int value, int percent_of) {
assert(value >= 0);
assert(value <= percent_of);
return (percent_of > 0) ? (value * 100.0 / percent_of) : 0;
}
static void print_diff(void) {
const struct binary_info *binary;
int binary_samples;
const struct symbol_count *symbol;
/* print out aggregates in a machine-readable format for sprofdiff */
printf("(total)\t\t%d\n", sprof_info.total_samples);
printf("(system)\t\t%d\n", sprof_info.system_samples);
printf("(idle)\t\t%d\n", sprof_info.idle_samples);
printf("(user)\t\t%d\n", sprof_info.user_samples);
for (binary = binaries; binary; binary = binary->next) {
binary_samples = 0;
for (symbol = binary->symbols; symbol; symbol = symbol->next) {
if (symbol->samples) {
printf("%.*s\t%.*s\t%d\n",
PROC_NAME_LEN, binary->name,
SYMBOL_NAME_SIZE, symbol->name,
symbol->samples);
}
binary_samples += symbol->samples;
}
printf("%.*s\t(total)\t%d\n",
PROC_NAME_LEN, binary->name,
binary_samples);
}
}
static void print_report(void) {
/* print out human-readable analysis */
printf("Showing processes and functions using at least %3.0f%% "
"time.\n\n", minimum_perc);
printf(" System process ticks: %10d (%3.0f%%)\n",
sprof_info.system_samples, percent(sprof_info.system_samples, sprof_info.total_samples));
printf(" User process ticks: %10d (%3.0f%%) "
"Details of system process\n",
sprof_info.user_samples, percent(sprof_info.user_samples, sprof_info.total_samples));
printf(" Idle time ticks: %10d (%3.0f%%) "
"samples, aggregated and\n",
sprof_info.idle_samples, percent(sprof_info.idle_samples, sprof_info.total_samples));
printf(" ---------- ---- "
"per process, are below.\n");
printf(" Total ticks: %10d (100%%)\n\n", sprof_info.total_samples);
print_report_overall();
print_reports_per_binary();
}
static void print_report_overall(void) {
struct binary_info *binary;
struct symbol_count *symbol, **symbols_sorted;
unsigned index, symbol_count;
int sample_threshold;
/* count number of symbols to display */
sample_threshold = sprof_info.system_samples * minimum_perc / 100;
symbol_count = 0;
for (binary = binaries; binary; binary = binary->next) {
symbol_count += count_symbols(binary, sample_threshold);
}
/* sort symbols by decreasing number of samples */
symbols_sorted = MALLOC_CHECKED(struct symbol_count *, symbol_count);
index = 0;
for (binary = binaries; binary; binary = binary->next) {
for (symbol = binary->symbols; symbol; symbol = symbol->next) {
if (symbol->samples >= sample_threshold) {
symbols_sorted[index++] = symbol;
}
}
}
assert(index == symbol_count);
qsort(symbols_sorted, symbol_count, sizeof(symbols_sorted[0]),
compare_symbols);
/* report most common symbols overall */
print_separator();
printf("Total system process time %*d samples\n",
LINE_WIDTH - 34, sprof_info.system_samples);
print_separator();
print_report_symbols(symbols_sorted, symbol_count, sprof_info.system_samples, 1);
free(symbols_sorted);
}
static void print_report_per_binary(const struct binary_info *binary) {
struct symbol_count *symbol, **symbols_sorted;
unsigned index, symbol_count;
int sample_threshold;
/* count number of symbols to display */
sample_threshold = binary->samples * minimum_perc / 100;
symbol_count = count_symbols(binary, sample_threshold);
/* sort symbols by decreasing number of samples */
symbols_sorted = MALLOC_CHECKED(struct symbol_count *, symbol_count);
index = 0;
for (symbol = binary->symbols; symbol; symbol = symbol->next) {
if (symbol->samples >= sample_threshold) {
symbols_sorted[index++] = symbol;
}
}
assert(index == symbol_count);
qsort(symbols_sorted, symbol_count, sizeof(symbols_sorted[0]),
compare_symbols);
/* report most common symbols for this binary */
print_separator();
printf("%-*.*s %4.1f%% of system process samples\n",
LINE_WIDTH - 32, PROC_NAME_LEN, binary->name,
percent(binary->samples, sprof_info.system_samples));
print_separator();
print_report_symbols(symbols_sorted, symbol_count, binary->samples, 0);
free(symbols_sorted);
}
static void print_reports_per_binary(void) {
struct binary_info *binary, **binaries_sorted;
unsigned binary_count, i, index;
int sample_threshold, samples_shown;
struct symbol_count *symbol;
/* count total per-binary samples */
binary_count = 0;
for (binary = binaries; binary; binary = binary->next) {
assert(!binary->samples);
for (symbol = binary->symbols; symbol; symbol = symbol->next) {
binary->samples += symbol->samples;
}
binary_count++;
}
/* sort binaries by decreasing number of samples */
binaries_sorted = MALLOC_CHECKED(struct binary_info *, binary_count);
index = 0;
for (binary = binaries; binary; binary = binary->next) {
binaries_sorted[index++] = binary;
}
assert(index == binary_count);
qsort(binaries_sorted, binary_count, sizeof(binaries_sorted[0]),
compare_binaries);
/* display reports for binaries with enough samples */
sample_threshold = sprof_info.system_samples * minimum_perc / 100;
samples_shown = 0;
for (i = 0; i < binary_count; i++) {
if (binaries_sorted[i]->samples < sample_threshold) break;
print_report_per_binary(binaries_sorted[i]);
samples_shown += binaries_sorted[i]->samples;
}
print_separator();
printf("processes <%3.0f%% (not showing functions) %*.1f%% of system "
"process samples\n", minimum_perc, LINE_WIDTH - 67,
percent(sprof_info.system_samples - samples_shown, sprof_info.system_samples));
print_separator();
free(binaries_sorted);
}
static void print_report_symbols(struct symbol_count **symbols,
unsigned symbol_count, int total, int show_process) {
unsigned bar_dots, bar_width, i, j, process_width;
int samples, samples_shown;
struct symbol_count *symbol;
/* find out how much space we have available */
process_width = show_process ? (PROC_NAME_LEN + 1) : 0;
bar_width = LINE_WIDTH - process_width - SYMBOL_NAME_WIDTH - 17;
/* print the symbol lines */
samples_shown = 0;
for (i = 0; i <= symbol_count; i++) {
if (i < symbol_count) {
/* first list the symbols themselves */
symbol = symbols[i];
printf("%*.*s %*.*s ",
process_width,
show_process ? PROC_NAME_LEN : 0,
symbol->binary->name,
SYMBOL_NAME_WIDTH,
SYMBOL_NAME_WIDTH,
symbol->name);
samples = symbol->samples;
} else {
/* at the end, list the remainder */
printf("%*s<%3.0f%% ",
process_width + SYMBOL_NAME_WIDTH - 4,
"",
minimum_perc);
samples = total - samples_shown;
}
assert(samples >= 0);
assert(samples <= total);
bar_dots = (total > 0) ? (samples * bar_width / total) : 0;
for (j = 0; j < bar_dots; j++) printf("*");
for (; j < bar_width; j++) printf(" ");
printf("%8d %5.1f%%\n", samples, percent(samples, total));
samples_shown += samples;
}
/* print remainder and summary */
print_separator();
printf("%-*.*s%*d 100.0%%\n\n", PROC_NAME_LEN, PROC_NAME_LEN,
(show_process || symbol_count == 0) ?
"total" : symbols[0]->binary->name,
LINE_WIDTH - PROC_NAME_LEN - 7, total);
}
static void print_separator(void) {
int i;
for (i = 0; i < LINE_WIDTH; i++) printf("-");
printf("\n");
}
static int read_hex(FILE *file, unsigned long *value) {
int c, cvalue;
unsigned index;
assert(file);
assert(value);
index = 0;
c = fgetc(file);
*value = 0;
while (index < 8) {
if (c >= '0' && c <= '9') {
cvalue = c - '0';
} else if (c >= 'A' && c <= 'F') {
cvalue = c - 'A' + 10;
} else if (c >= 'a' && c <= 'f') {
cvalue = c - 'a' + 10;
} else {
break;
}
*value = *value * 16 + cvalue;
index++;
c = fgetc(file);
}
if (c != EOF) ungetc(c, file);
return index;
}
static int read_newline(FILE *file) {
int c;
do {
c = fgetc(file);
} while (c != EOF && c != '\n' && isspace(c));
if (c == EOF || c == '\n') return 1;
ungetc(c, file);
return 0;
}
static void read_nm_line(FILE *file, int line, char *name, char *type,
unsigned long *addr, unsigned long *size) {
assert(file);
assert(name);
assert(type);
assert(addr);
assert(size);
*type = 0;
*addr = 0;
*size = 0;
if (read_newline(file)) {
memset(name, 0, SYMBOL_NAME_SIZE);
return;
}
/* name and type are compulsory */
read_to_whitespace(file, name, SYMBOL_NAME_SIZE);
if (read_newline(file)) {
fprintf(stderr, "error: bad output format from nm: "
"symbol type missing on line %d\n", line);
exit(-1);
}
*type = fgetc(file);
/* address is optional */
if (read_newline(file)) return;
if (!read_hex(file, addr)) {
fprintf(stderr, "error: bad output format from nm: junk where "
"address should be on line %d\n", line);
exit(-1);
}
/* size is optional */
if (read_newline(file)) return;
if (!read_hex(file, size)) {
fprintf(stderr, "error: bad output format from nm: junk where "
"size should be on line %d\n", line);
exit(-1);
}
/* nothing else expected */
if (read_newline(file)) return;
fprintf(stderr, "error: bad output format from nm: junk after size "
"on line %d\n", line);
exit(-1);
}
static void read_to_whitespace(FILE *file, char *buffer, size_t size) {
int c;
/* read up to and incl first whitespace, store at most size chars */
while ((c = fgetc(file)) != EOF && !isspace(c)) {
if (size > 0) {
*(buffer++) = c;
size--;
}
}
if (size > 0) *buffer = 0;
}
static size_t sample_process(const union sprof_record *data, size_t size,
int *samples_read) {
struct endpoint_info *epinfo, **ptr;
assert(data);
assert(samples_read);
/* do we have a proper sample? */
if (size < sizeof(data->proc) && size < sizeof(data->sample)) {
goto error;
}
/* do we know this endpoint? */
ptr = endpoint_hashtab_get_ptr(data->proc.proc);
if ((epinfo = *ptr)) {
/* endpoint known, store sample */
if (size < sizeof(data->sample)) goto error;
sample_store(epinfo->binary, &data->sample);
(*samples_read)++;
return sizeof(data->sample);
}
/* endpoint not known, add it */
*ptr = epinfo = MALLOC_CHECKED(struct endpoint_info, 1);
memset(epinfo, 0, sizeof(struct endpoint_info));
epinfo->endpoint = data->proc.proc;
/* fetch binary based on process name in sample */
if (size < sizeof(data->proc)) goto error;
epinfo->binary = sample_load_binary(&data->proc);
return sizeof(data->proc);
error:
fprintf(stderr, "warning: partial sample at end of trace, "
"was the trace file truncated?\n");
return size;
}
static struct binary_info *sample_load_binary(
const struct sprof_proc *sample) {
struct binary_info *binary;
/* locate binary */
binary = binary_find(sample->name);
if (!binary) {
fprintf(stderr, "warning: ignoring unknown binary \"%.*s\"\n",
PROC_NAME_LEN, sample->name);
fprintf(stderr, " did you include this executable in "
"the configuration?\n");
fprintf(stderr, " (use -b to add additional "
"binaries)\n");
return NULL;
}
/* load symbols if this hasn't been done yet */
if (!binary->pc_map) binary_load_pc_map(binary);
return binary;
}
static void sample_store(struct binary_info *binary,
const struct sprof_sample *sample) {
unsigned long index_l1;
struct pc_map_l2 *pc_map_l2;
struct symbol_count *symbol;
if (!binary || !binary->pc_map) return;
/* find the applicable symbol (two-level lookup) */
index_l1 = (unsigned long) sample->pc / PC_MAP_L2_SIZE;
assert(index_l1 < PC_MAP_L1_SIZE);
pc_map_l2 = binary->pc_map->l1[index_l1];
if (pc_map_l2) {
symbol = pc_map_l2->l2[(unsigned long) sample->pc % PC_MAP_L2_SIZE];
} else {
symbol = NULL;
}
if (symbol) {
symbol->samples++;
} else if (!binary->no_more_warnings) {
fprintf(stderr, "warning: address 0x%lx not associated with a "
"symbol\n", (unsigned long) sample->pc);
fprintf(stderr, " binary may not match the profiled "
"version\n");
fprintf(stderr, " path: \"%s\"\n", binary->path);
binary->no_more_warnings = 1;
dprint_symbols(binary);
}
}
static char *strdup_checked(const char *s) {
char *p;
if (!s) return NULL;
p = strdup(s);
if (!p) {
fprintf(stderr, "error: strdup failed: %s\n",
strerror(errno));
exit(-1);
}
return p;
}
static void usage(const char *argv0) {
printf("usage:\n");
printf(" %s [-d] [-p percentage] [-s src-tree-path] "
"[-b binary]... file...\n", argv0);
printf("\n");
printf("sprofalyze aggregates one or more sprofile traces and");
printf("reports where time was spent.\n");
printf("\n");
printf("arguments:\n");
printf("-d generates output that can be compared using sprofdiff\n");
printf("-p specifies the cut-off percentage below which binaries\n");
printf(" and functions will not be displayed\n");
printf("-s specifies the root of the source tree where sprofalyze\n");
printf(" should search for unstripped binaries to extract symbols\n");
printf(" from\n");
printf("-b specifies an additional system binary in the trace that\n");
printf(" is not in the source tree; may be specified multiple\n");
printf(" times\n");
exit(1);
}