2009-06-03 05:37:05 +08:00
|
|
|
/*
|
|
|
|
* builtin-report.c
|
|
|
|
*
|
|
|
|
* Builtin report command: Analyze the perf.data input file,
|
|
|
|
* look up and read DSOs and symbol information and display
|
|
|
|
* a histogram of results, along various sorting keys.
|
|
|
|
*/
|
2009-05-27 15:10:38 +08:00
|
|
|
#include "builtin.h"
|
2009-05-26 15:17:18 +08:00
|
|
|
|
2009-06-03 05:37:05 +08:00
|
|
|
#include "util/util.h"
|
|
|
|
|
2009-06-04 21:19:47 +08:00
|
|
|
#include "util/color.h"
|
2009-07-02 01:46:08 +08:00
|
|
|
#include <linux/list.h>
|
2009-05-27 15:50:13 +08:00
|
|
|
#include "util/cache.h"
|
2009-07-01 23:28:37 +08:00
|
|
|
#include <linux/rbtree.h>
|
2009-05-29 01:55:04 +08:00
|
|
|
#include "util/symbol.h"
|
2009-06-02 04:50:19 +08:00
|
|
|
#include "util/string.h"
|
2009-06-26 22:28:01 +08:00
|
|
|
#include "util/callchain.h"
|
2009-07-01 06:01:20 +08:00
|
|
|
#include "util/strlist.h"
|
2009-08-07 19:55:24 +08:00
|
|
|
#include "util/values.h"
|
2009-05-18 23:45:42 +08:00
|
|
|
|
2009-05-26 15:17:18 +08:00
|
|
|
#include "perf.h"
|
2009-08-17 04:05:48 +08:00
|
|
|
#include "util/debug.h"
|
2009-06-25 23:05:54 +08:00
|
|
|
#include "util/header.h"
|
2009-05-26 15:17:18 +08:00
|
|
|
|
|
|
|
#include "util/parse-options.h"
|
|
|
|
#include "util/parse-events.h"
|
|
|
|
|
2009-08-14 18:21:53 +08:00
|
|
|
#include "util/thread.h"
|
|
|
|
|
2009-05-27 15:33:18 +08:00
|
|
|
static char const *input_name = "perf.data";
|
2009-06-04 20:13:04 +08:00
|
|
|
|
2009-08-05 18:25:21 +08:00
|
|
|
static char default_sort_order[] = "comm,dso,symbol";
|
2009-06-04 20:13:04 +08:00
|
|
|
static char *sort_order = default_sort_order;
|
2009-07-11 09:47:28 +08:00
|
|
|
static char *dso_list_str, *comm_list_str, *sym_list_str,
|
|
|
|
*col_width_list_str;
|
2009-07-01 06:01:22 +08:00
|
|
|
static struct strlist *dso_list, *comm_list, *sym_list;
|
2009-07-11 09:47:28 +08:00
|
|
|
static char *field_sep;
|
2009-06-04 20:13:04 +08:00
|
|
|
|
2009-08-19 17:18:26 +08:00
|
|
|
static int force;
|
2009-05-18 23:45:42 +08:00
|
|
|
static int input;
|
|
|
|
static int show_mask = SHOW_KERNEL | SHOW_USER | SHOW_HV;
|
|
|
|
|
2009-05-30 00:48:59 +08:00
|
|
|
static int full_paths;
|
2009-07-11 23:18:37 +08:00
|
|
|
static int show_nr_samples;
|
2009-05-27 00:48:58 +08:00
|
|
|
|
2009-08-07 19:55:24 +08:00
|
|
|
static int show_threads;
|
|
|
|
static struct perf_read_values show_threads_values;
|
|
|
|
|
2009-08-10 21:26:32 +08:00
|
|
|
static char default_pretty_printing_style[] = "normal";
|
|
|
|
static char *pretty_printing_style = default_pretty_printing_style;
|
|
|
|
|
2009-05-18 23:45:42 +08:00
|
|
|
static unsigned long page_size;
|
|
|
|
static unsigned long mmap_window = 32;
|
|
|
|
|
2009-06-18 20:32:19 +08:00
|
|
|
static char default_parent_pattern[] = "^sys_|^do_page_fault";
|
|
|
|
static char *parent_pattern = default_parent_pattern;
|
2009-06-18 13:01:03 +08:00
|
|
|
static regex_t parent_regex;
|
2009-06-17 21:51:44 +08:00
|
|
|
|
2009-06-18 20:32:19 +08:00
|
|
|
static int exclude_other = 1;
|
2009-07-05 13:39:18 +08:00
|
|
|
|
2009-07-05 13:39:21 +08:00
|
|
|
static char callchain_default_opt[] = "fractal,0.5";
|
|
|
|
|
2009-06-26 22:28:01 +08:00
|
|
|
static int callchain;
|
2009-07-05 13:39:21 +08:00
|
|
|
|
2009-08-12 17:07:25 +08:00
|
|
|
static char __cwd[PATH_MAX];
|
|
|
|
static char *cwd = __cwd;
|
|
|
|
static int cwdlen;
|
|
|
|
|
2009-08-14 18:21:53 +08:00
|
|
|
static struct rb_root threads;
|
|
|
|
static struct thread *last_match;
|
|
|
|
|
2009-08-17 02:56:37 +08:00
|
|
|
static struct perf_header *header;
|
|
|
|
|
2009-07-05 13:39:21 +08:00
|
|
|
static
|
|
|
|
struct callchain_param callchain_param = {
|
2009-08-08 08:16:24 +08:00
|
|
|
.mode = CHAIN_GRAPH_REL,
|
2009-07-05 13:39:21 +08:00
|
|
|
.min_percent = 0.5
|
|
|
|
};
|
2009-06-18 20:32:19 +08:00
|
|
|
|
2009-06-25 17:27:12 +08:00
|
|
|
static u64 sample_type;
|
|
|
|
|
2009-07-11 09:47:28 +08:00
|
|
|
static int repsep_fprintf(FILE *fp, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int n;
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
if (!field_sep)
|
|
|
|
n = vfprintf(fp, fmt, ap);
|
|
|
|
else {
|
|
|
|
char *bf = NULL;
|
|
|
|
n = vasprintf(&bf, fmt, ap);
|
|
|
|
if (n > 0) {
|
|
|
|
char *sep = bf;
|
2009-08-15 18:26:57 +08:00
|
|
|
|
2009-07-11 09:47:28 +08:00
|
|
|
while (1) {
|
|
|
|
sep = strchr(sep, *field_sep);
|
|
|
|
if (sep == NULL)
|
|
|
|
break;
|
|
|
|
*sep = '.';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fputs(bf, fp);
|
|
|
|
free(bf);
|
|
|
|
}
|
|
|
|
va_end(ap);
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int dsos__col_width,
|
|
|
|
comms__col_width,
|
|
|
|
threads__col_width;
|
|
|
|
|
2009-05-28 02:20:24 +08:00
|
|
|
/*
|
|
|
|
* histogram, sorted on item, collects counts
|
|
|
|
*/
|
|
|
|
|
|
|
|
static struct rb_root hist;
|
|
|
|
|
|
|
|
struct hist_entry {
|
2009-06-26 22:28:01 +08:00
|
|
|
struct rb_node rb_node;
|
|
|
|
|
|
|
|
struct thread *thread;
|
|
|
|
struct map *map;
|
|
|
|
struct dso *dso;
|
|
|
|
struct symbol *sym;
|
|
|
|
struct symbol *parent;
|
|
|
|
u64 ip;
|
|
|
|
char level;
|
|
|
|
struct callchain_node callchain;
|
|
|
|
struct rb_root sorted_chain;
|
|
|
|
|
|
|
|
u64 count;
|
2009-05-28 02:20:24 +08:00
|
|
|
};
|
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
/*
|
|
|
|
* configurable sorting bits
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct sort_entry {
|
|
|
|
struct list_head list;
|
|
|
|
|
2009-08-15 18:26:57 +08:00
|
|
|
const char *header;
|
2009-05-28 17:08:33 +08:00
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
int64_t (*cmp)(struct hist_entry *, struct hist_entry *);
|
2009-06-03 18:37:36 +08:00
|
|
|
int64_t (*collapse)(struct hist_entry *, struct hist_entry *);
|
2009-07-11 09:47:28 +08:00
|
|
|
size_t (*print)(FILE *fp, struct hist_entry *, unsigned int width);
|
|
|
|
unsigned int *width;
|
2009-07-11 23:18:35 +08:00
|
|
|
bool elide;
|
2009-05-28 02:20:25 +08:00
|
|
|
};
|
|
|
|
|
2009-06-17 21:51:44 +08:00
|
|
|
static int64_t cmp_null(void *l, void *r)
|
|
|
|
{
|
|
|
|
if (!l && !r)
|
|
|
|
return 0;
|
|
|
|
else if (!l)
|
|
|
|
return -1;
|
|
|
|
else
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2009-06-03 18:37:36 +08:00
|
|
|
/* --sort pid */
|
|
|
|
|
2009-05-28 02:20:24 +08:00
|
|
|
static int64_t
|
2009-05-28 02:20:25 +08:00
|
|
|
sort__thread_cmp(struct hist_entry *left, struct hist_entry *right)
|
2009-05-28 02:20:24 +08:00
|
|
|
{
|
2009-05-28 02:20:25 +08:00
|
|
|
return right->thread->pid - left->thread->pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
2009-07-11 09:47:28 +08:00
|
|
|
sort__thread_print(FILE *fp, struct hist_entry *self, unsigned int width)
|
2009-05-28 02:20:25 +08:00
|
|
|
{
|
2009-07-11 09:47:28 +08:00
|
|
|
return repsep_fprintf(fp, "%*s:%5d", width - 6,
|
|
|
|
self->thread->comm ?: "", self->thread->pid);
|
2009-05-28 02:20:25 +08:00
|
|
|
}
|
2009-05-28 02:20:24 +08:00
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
static struct sort_entry sort_thread = {
|
2009-07-11 09:47:28 +08:00
|
|
|
.header = "Command: Pid",
|
2009-05-28 02:20:25 +08:00
|
|
|
.cmp = sort__thread_cmp,
|
|
|
|
.print = sort__thread_print,
|
2009-07-11 09:47:28 +08:00
|
|
|
.width = &threads__col_width,
|
2009-05-28 02:20:25 +08:00
|
|
|
};
|
|
|
|
|
2009-06-03 18:37:36 +08:00
|
|
|
/* --sort comm */
|
|
|
|
|
2009-05-28 02:20:27 +08:00
|
|
|
static int64_t
|
|
|
|
sort__comm_cmp(struct hist_entry *left, struct hist_entry *right)
|
2009-06-03 18:37:36 +08:00
|
|
|
{
|
|
|
|
return right->thread->pid - left->thread->pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int64_t
|
|
|
|
sort__comm_collapse(struct hist_entry *left, struct hist_entry *right)
|
2009-05-28 02:20:27 +08:00
|
|
|
{
|
|
|
|
char *comm_l = left->thread->comm;
|
|
|
|
char *comm_r = right->thread->comm;
|
|
|
|
|
2009-06-17 21:51:44 +08:00
|
|
|
if (!comm_l || !comm_r)
|
|
|
|
return cmp_null(comm_l, comm_r);
|
2009-05-28 02:20:27 +08:00
|
|
|
|
|
|
|
return strcmp(comm_l, comm_r);
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
2009-07-11 09:47:28 +08:00
|
|
|
sort__comm_print(FILE *fp, struct hist_entry *self, unsigned int width)
|
2009-05-28 02:20:27 +08:00
|
|
|
{
|
2009-07-11 09:47:28 +08:00
|
|
|
return repsep_fprintf(fp, "%*s", width, self->thread->comm);
|
2009-05-28 02:20:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct sort_entry sort_comm = {
|
2009-07-11 09:47:28 +08:00
|
|
|
.header = "Command",
|
2009-06-03 18:37:36 +08:00
|
|
|
.cmp = sort__comm_cmp,
|
|
|
|
.collapse = sort__comm_collapse,
|
|
|
|
.print = sort__comm_print,
|
2009-07-11 09:47:28 +08:00
|
|
|
.width = &comms__col_width,
|
2009-05-28 02:20:27 +08:00
|
|
|
};
|
|
|
|
|
2009-06-03 18:37:36 +08:00
|
|
|
/* --sort dso */
|
|
|
|
|
2009-05-28 02:20:28 +08:00
|
|
|
static int64_t
|
|
|
|
sort__dso_cmp(struct hist_entry *left, struct hist_entry *right)
|
|
|
|
{
|
|
|
|
struct dso *dso_l = left->dso;
|
|
|
|
struct dso *dso_r = right->dso;
|
|
|
|
|
2009-06-17 21:51:44 +08:00
|
|
|
if (!dso_l || !dso_r)
|
|
|
|
return cmp_null(dso_l, dso_r);
|
2009-05-28 02:20:28 +08:00
|
|
|
|
|
|
|
return strcmp(dso_l->name, dso_r->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
2009-07-11 09:47:28 +08:00
|
|
|
sort__dso_print(FILE *fp, struct hist_entry *self, unsigned int width)
|
2009-05-28 02:20:28 +08:00
|
|
|
{
|
2009-06-03 05:24:45 +08:00
|
|
|
if (self->dso)
|
2009-07-11 09:47:28 +08:00
|
|
|
return repsep_fprintf(fp, "%-*s", width, self->dso->name);
|
2009-06-03 05:24:45 +08:00
|
|
|
|
2009-07-11 09:47:28 +08:00
|
|
|
return repsep_fprintf(fp, "%*llx", width, (u64)self->ip);
|
2009-05-28 02:20:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct sort_entry sort_dso = {
|
2009-07-11 09:47:28 +08:00
|
|
|
.header = "Shared Object",
|
2009-05-28 02:20:28 +08:00
|
|
|
.cmp = sort__dso_cmp,
|
|
|
|
.print = sort__dso_print,
|
2009-07-11 09:47:28 +08:00
|
|
|
.width = &dsos__col_width,
|
2009-05-28 02:20:28 +08:00
|
|
|
};
|
|
|
|
|
2009-06-03 18:37:36 +08:00
|
|
|
/* --sort symbol */
|
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
static int64_t
|
|
|
|
sort__sym_cmp(struct hist_entry *left, struct hist_entry *right)
|
|
|
|
{
|
perf_counter tools: Define and use our own u64, s64 etc. definitions
On 64-bit powerpc, __u64 is defined to be unsigned long rather than
unsigned long long. This causes compiler warnings every time we
print a __u64 value with %Lx.
Rather than changing __u64, we define our own u64 to be unsigned long
long on all architectures, and similarly s64 as signed long long.
For consistency we also define u32, s32, u16, s16, u8 and s8. These
definitions are put in a new header, types.h, because these definitions
are needed in util/string.h and util/symbol.h.
The main change here is the mechanical change of __[us]{64,32,16,8}
to remove the "__". The other changes are:
* Create types.h
* Include types.h in perf.h, util/string.h and util/symbol.h
* Add types.h to the LIB_H definition in Makefile
* Added (u64) casts in process_overflow_event() and print_sym_table()
to kill two remaining warnings.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: benh@kernel.crashing.org
LKML-Reference: <19003.33494.495844.956580@cargo.ozlabs.ibm.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2009-06-19 20:21:42 +08:00
|
|
|
u64 ip_l, ip_r;
|
2009-05-28 02:20:24 +08:00
|
|
|
|
|
|
|
if (left->sym == right->sym)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ip_l = left->sym ? left->sym->start : left->ip;
|
|
|
|
ip_r = right->sym ? right->sym->start : right->ip;
|
|
|
|
|
|
|
|
return (int64_t)(ip_r - ip_l);
|
|
|
|
}
|
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
static size_t
|
2009-07-11 09:47:28 +08:00
|
|
|
sort__sym_print(FILE *fp, struct hist_entry *self, unsigned int width __used)
|
2009-05-28 02:20:25 +08:00
|
|
|
{
|
|
|
|
size_t ret = 0;
|
|
|
|
|
|
|
|
if (verbose)
|
2009-08-07 01:43:17 +08:00
|
|
|
ret += repsep_fprintf(fp, "%#018llx %c ", (u64)self->ip,
|
|
|
|
dso__symtab_origin(self->dso));
|
2009-06-03 05:24:45 +08:00
|
|
|
|
2009-07-11 23:18:33 +08:00
|
|
|
ret += repsep_fprintf(fp, "[%c] ", self->level);
|
2009-06-05 20:13:18 +08:00
|
|
|
if (self->sym) {
|
2009-07-11 23:18:33 +08:00
|
|
|
ret += repsep_fprintf(fp, "%s", self->sym->name);
|
2009-07-02 14:09:46 +08:00
|
|
|
|
|
|
|
if (self->sym->module)
|
2009-07-11 09:47:28 +08:00
|
|
|
ret += repsep_fprintf(fp, "\t[%s]",
|
|
|
|
self->sym->module->name);
|
2009-06-05 20:13:18 +08:00
|
|
|
} else {
|
2009-07-11 09:47:28 +08:00
|
|
|
ret += repsep_fprintf(fp, "%#016llx", (u64)self->ip);
|
2009-06-05 20:13:18 +08:00
|
|
|
}
|
2009-05-28 02:20:25 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct sort_entry sort_sym = {
|
2009-06-04 21:16:56 +08:00
|
|
|
.header = "Symbol",
|
2009-05-28 17:08:33 +08:00
|
|
|
.cmp = sort__sym_cmp,
|
|
|
|
.print = sort__sym_print,
|
2009-05-28 02:20:25 +08:00
|
|
|
};
|
|
|
|
|
2009-06-18 13:01:03 +08:00
|
|
|
/* --sort parent */
|
2009-06-17 21:51:44 +08:00
|
|
|
|
|
|
|
static int64_t
|
2009-06-18 13:01:03 +08:00
|
|
|
sort__parent_cmp(struct hist_entry *left, struct hist_entry *right)
|
2009-06-17 21:51:44 +08:00
|
|
|
{
|
2009-06-18 13:01:03 +08:00
|
|
|
struct symbol *sym_l = left->parent;
|
|
|
|
struct symbol *sym_r = right->parent;
|
2009-06-17 21:51:44 +08:00
|
|
|
|
|
|
|
if (!sym_l || !sym_r)
|
|
|
|
return cmp_null(sym_l, sym_r);
|
|
|
|
|
|
|
|
return strcmp(sym_l->name, sym_r->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
2009-07-11 09:47:28 +08:00
|
|
|
sort__parent_print(FILE *fp, struct hist_entry *self, unsigned int width)
|
2009-06-17 21:51:44 +08:00
|
|
|
{
|
2009-07-11 09:47:28 +08:00
|
|
|
return repsep_fprintf(fp, "%-*s", width,
|
|
|
|
self->parent ? self->parent->name : "[other]");
|
2009-06-17 21:51:44 +08:00
|
|
|
}
|
|
|
|
|
2009-07-11 09:47:28 +08:00
|
|
|
static unsigned int parent_symbol__col_width;
|
|
|
|
|
2009-06-18 13:01:03 +08:00
|
|
|
static struct sort_entry sort_parent = {
|
2009-07-11 09:47:28 +08:00
|
|
|
.header = "Parent symbol",
|
2009-06-18 13:01:03 +08:00
|
|
|
.cmp = sort__parent_cmp,
|
|
|
|
.print = sort__parent_print,
|
2009-07-11 09:47:28 +08:00
|
|
|
.width = &parent_symbol__col_width,
|
2009-06-17 21:51:44 +08:00
|
|
|
};
|
|
|
|
|
2009-06-03 18:37:36 +08:00
|
|
|
static int sort__need_collapse = 0;
|
2009-06-18 13:01:03 +08:00
|
|
|
static int sort__has_parent = 0;
|
2009-06-03 18:37:36 +08:00
|
|
|
|
2009-05-28 02:20:26 +08:00
|
|
|
struct sort_dimension {
|
2009-08-15 18:26:57 +08:00
|
|
|
const char *name;
|
2009-06-05 20:13:18 +08:00
|
|
|
struct sort_entry *entry;
|
|
|
|
int taken;
|
2009-05-28 02:20:26 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct sort_dimension sort_dimensions[] = {
|
|
|
|
{ .name = "pid", .entry = &sort_thread, },
|
2009-05-28 02:20:27 +08:00
|
|
|
{ .name = "comm", .entry = &sort_comm, },
|
2009-05-28 02:20:28 +08:00
|
|
|
{ .name = "dso", .entry = &sort_dso, },
|
2009-05-28 02:20:26 +08:00
|
|
|
{ .name = "symbol", .entry = &sort_sym, },
|
2009-06-18 13:01:03 +08:00
|
|
|
{ .name = "parent", .entry = &sort_parent, },
|
2009-05-28 02:20:26 +08:00
|
|
|
};
|
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
static LIST_HEAD(hist_entry__sort_list);
|
|
|
|
|
2009-08-15 18:26:57 +08:00
|
|
|
static int sort_dimension__add(const char *tok)
|
2009-05-28 02:20:26 +08:00
|
|
|
{
|
2009-07-01 18:37:06 +08:00
|
|
|
unsigned int i;
|
2009-05-28 02:20:26 +08:00
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sort_dimensions); i++) {
|
|
|
|
struct sort_dimension *sd = &sort_dimensions[i];
|
|
|
|
|
|
|
|
if (sd->taken)
|
|
|
|
continue;
|
|
|
|
|
2009-06-03 16:07:39 +08:00
|
|
|
if (strncasecmp(tok, sd->name, strlen(tok)))
|
2009-05-28 02:20:26 +08:00
|
|
|
continue;
|
|
|
|
|
2009-06-03 18:37:36 +08:00
|
|
|
if (sd->entry->collapse)
|
|
|
|
sort__need_collapse = 1;
|
|
|
|
|
2009-06-18 13:01:03 +08:00
|
|
|
if (sd->entry == &sort_parent) {
|
|
|
|
int ret = regcomp(&parent_regex, parent_pattern, REG_EXTENDED);
|
2009-06-17 21:51:44 +08:00
|
|
|
if (ret) {
|
|
|
|
char err[BUFSIZ];
|
|
|
|
|
2009-06-18 13:01:03 +08:00
|
|
|
regerror(ret, &parent_regex, err, sizeof(err));
|
|
|
|
fprintf(stderr, "Invalid regex: %s\n%s",
|
|
|
|
parent_pattern, err);
|
2009-06-17 21:51:44 +08:00
|
|
|
exit(-1);
|
|
|
|
}
|
2009-06-18 13:01:03 +08:00
|
|
|
sort__has_parent = 1;
|
2009-06-17 21:51:44 +08:00
|
|
|
}
|
|
|
|
|
2009-05-28 02:20:26 +08:00
|
|
|
list_add_tail(&sd->entry->list, &hist_entry__sort_list);
|
|
|
|
sd->taken = 1;
|
2009-06-03 16:07:39 +08:00
|
|
|
|
2009-05-28 02:20:26 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return -ESRCH;
|
|
|
|
}
|
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
static int64_t
|
|
|
|
hist_entry__cmp(struct hist_entry *left, struct hist_entry *right)
|
|
|
|
{
|
|
|
|
struct sort_entry *se;
|
|
|
|
int64_t cmp = 0;
|
|
|
|
|
|
|
|
list_for_each_entry(se, &hist_entry__sort_list, list) {
|
|
|
|
cmp = se->cmp(left, right);
|
|
|
|
if (cmp)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return cmp;
|
|
|
|
}
|
|
|
|
|
2009-06-03 18:37:36 +08:00
|
|
|
static int64_t
|
|
|
|
hist_entry__collapse(struct hist_entry *left, struct hist_entry *right)
|
|
|
|
{
|
|
|
|
struct sort_entry *se;
|
|
|
|
int64_t cmp = 0;
|
|
|
|
|
|
|
|
list_for_each_entry(se, &hist_entry__sort_list, list) {
|
|
|
|
int64_t (*f)(struct hist_entry *, struct hist_entry *);
|
|
|
|
|
|
|
|
f = se->collapse ?: se->cmp;
|
|
|
|
|
|
|
|
cmp = f(left, right);
|
|
|
|
if (cmp)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return cmp;
|
|
|
|
}
|
|
|
|
|
2009-07-02 23:58:21 +08:00
|
|
|
static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
size_t ret = 0;
|
|
|
|
|
|
|
|
ret += fprintf(fp, "%s", " ");
|
|
|
|
|
|
|
|
for (i = 0; i < depth; i++)
|
|
|
|
if (depth_mask & (1 << i))
|
|
|
|
ret += fprintf(fp, "| ");
|
|
|
|
else
|
|
|
|
ret += fprintf(fp, " ");
|
|
|
|
|
|
|
|
ret += fprintf(fp, "\n");
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2009-06-26 22:28:01 +08:00
|
|
|
static size_t
|
2009-07-02 23:58:21 +08:00
|
|
|
ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain, int depth,
|
|
|
|
int depth_mask, int count, u64 total_samples,
|
|
|
|
int hits)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
size_t ret = 0;
|
|
|
|
|
|
|
|
ret += fprintf(fp, "%s", " ");
|
|
|
|
for (i = 0; i < depth; i++) {
|
|
|
|
if (depth_mask & (1 << i))
|
|
|
|
ret += fprintf(fp, "|");
|
|
|
|
else
|
|
|
|
ret += fprintf(fp, " ");
|
|
|
|
if (!count && i == depth - 1) {
|
|
|
|
double percent;
|
|
|
|
|
|
|
|
percent = hits * 100.0 / total_samples;
|
2009-07-03 02:14:35 +08:00
|
|
|
ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent);
|
2009-07-02 23:58:21 +08:00
|
|
|
} else
|
|
|
|
ret += fprintf(fp, "%s", " ");
|
|
|
|
}
|
|
|
|
if (chain->sym)
|
|
|
|
ret += fprintf(fp, "%s\n", chain->sym->name);
|
|
|
|
else
|
|
|
|
ret += fprintf(fp, "%p\n", (void *)(long)chain->ip);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-08-08 08:16:25 +08:00
|
|
|
static struct symbol *rem_sq_bracket;
|
|
|
|
static struct callchain_list rem_hits;
|
|
|
|
|
|
|
|
static void init_rem_hits(void)
|
|
|
|
{
|
|
|
|
rem_sq_bracket = malloc(sizeof(*rem_sq_bracket) + 6);
|
|
|
|
if (!rem_sq_bracket) {
|
|
|
|
fprintf(stderr, "Not enough memory to display remaining hits\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
strcpy(rem_sq_bracket->name, "[...]");
|
|
|
|
rem_hits.sym = rem_sq_bracket;
|
|
|
|
}
|
|
|
|
|
2009-07-02 23:58:21 +08:00
|
|
|
static size_t
|
|
|
|
callchain__fprintf_graph(FILE *fp, struct callchain_node *self,
|
|
|
|
u64 total_samples, int depth, int depth_mask)
|
|
|
|
{
|
|
|
|
struct rb_node *node, *next;
|
|
|
|
struct callchain_node *child;
|
|
|
|
struct callchain_list *chain;
|
|
|
|
int new_depth_mask = depth_mask;
|
2009-07-05 13:39:21 +08:00
|
|
|
u64 new_total;
|
2009-08-08 08:16:25 +08:00
|
|
|
u64 remaining;
|
2009-07-02 23:58:21 +08:00
|
|
|
size_t ret = 0;
|
|
|
|
int i;
|
|
|
|
|
2009-07-05 13:39:21 +08:00
|
|
|
if (callchain_param.mode == CHAIN_GRAPH_REL)
|
2009-08-07 13:11:05 +08:00
|
|
|
new_total = self->children_hit;
|
2009-07-05 13:39:21 +08:00
|
|
|
else
|
|
|
|
new_total = total_samples;
|
|
|
|
|
2009-08-08 08:16:25 +08:00
|
|
|
remaining = new_total;
|
|
|
|
|
2009-07-02 23:58:21 +08:00
|
|
|
node = rb_first(&self->rb_root);
|
|
|
|
while (node) {
|
2009-08-08 08:16:25 +08:00
|
|
|
u64 cumul;
|
|
|
|
|
2009-07-02 23:58:21 +08:00
|
|
|
child = rb_entry(node, struct callchain_node, rb_node);
|
2009-08-08 08:16:25 +08:00
|
|
|
cumul = cumul_hits(child);
|
|
|
|
remaining -= cumul;
|
2009-07-02 23:58:21 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The depth mask manages the output of pipes that show
|
|
|
|
* the depth. We don't want to keep the pipes of the current
|
2009-08-08 08:16:25 +08:00
|
|
|
* level for the last child of this depth.
|
|
|
|
* Except if we have remaining filtered hits. They will
|
|
|
|
* supersede the last child
|
2009-07-02 23:58:21 +08:00
|
|
|
*/
|
|
|
|
next = rb_next(node);
|
2009-08-08 08:16:25 +08:00
|
|
|
if (!next && (callchain_param.mode != CHAIN_GRAPH_REL || !remaining))
|
2009-07-02 23:58:21 +08:00
|
|
|
new_depth_mask &= ~(1 << (depth - 1));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* But we keep the older depth mask for the line seperator
|
|
|
|
* to keep the level link until we reach the last child
|
|
|
|
*/
|
|
|
|
ret += ipchain__fprintf_graph_line(fp, depth, depth_mask);
|
|
|
|
i = 0;
|
|
|
|
list_for_each_entry(chain, &child->val, list) {
|
|
|
|
if (chain->ip >= PERF_CONTEXT_MAX)
|
|
|
|
continue;
|
|
|
|
ret += ipchain__fprintf_graph(fp, chain, depth,
|
|
|
|
new_depth_mask, i++,
|
2009-07-05 13:39:21 +08:00
|
|
|
new_total,
|
2009-08-08 08:16:25 +08:00
|
|
|
cumul);
|
2009-07-02 23:58:21 +08:00
|
|
|
}
|
2009-07-05 13:39:21 +08:00
|
|
|
ret += callchain__fprintf_graph(fp, child, new_total,
|
2009-07-02 23:58:21 +08:00
|
|
|
depth + 1,
|
|
|
|
new_depth_mask | (1 << depth));
|
|
|
|
node = next;
|
|
|
|
}
|
|
|
|
|
2009-08-08 08:16:25 +08:00
|
|
|
if (callchain_param.mode == CHAIN_GRAPH_REL &&
|
|
|
|
remaining && remaining != new_total) {
|
|
|
|
|
|
|
|
if (!rem_sq_bracket)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
new_depth_mask &= ~(1 << (depth - 1));
|
|
|
|
|
|
|
|
ret += ipchain__fprintf_graph(fp, &rem_hits, depth,
|
|
|
|
new_depth_mask, 0, new_total,
|
|
|
|
remaining);
|
|
|
|
}
|
|
|
|
|
2009-07-02 23:58:21 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
callchain__fprintf_flat(FILE *fp, struct callchain_node *self,
|
|
|
|
u64 total_samples)
|
2009-06-26 22:28:01 +08:00
|
|
|
{
|
|
|
|
struct callchain_list *chain;
|
|
|
|
size_t ret = 0;
|
|
|
|
|
|
|
|
if (!self)
|
|
|
|
return 0;
|
|
|
|
|
2009-07-02 23:58:21 +08:00
|
|
|
ret += callchain__fprintf_flat(fp, self->parent, total_samples);
|
2009-06-26 22:28:01 +08:00
|
|
|
|
|
|
|
|
2009-07-01 11:35:14 +08:00
|
|
|
list_for_each_entry(chain, &self->val, list) {
|
|
|
|
if (chain->ip >= PERF_CONTEXT_MAX)
|
|
|
|
continue;
|
|
|
|
if (chain->sym)
|
|
|
|
ret += fprintf(fp, " %s\n", chain->sym->name);
|
|
|
|
else
|
|
|
|
ret += fprintf(fp, " %p\n",
|
2009-07-01 18:37:06 +08:00
|
|
|
(void *)(long)chain->ip);
|
2009-07-01 11:35:14 +08:00
|
|
|
}
|
2009-06-26 22:28:01 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
|
|
|
hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self,
|
|
|
|
u64 total_samples)
|
|
|
|
{
|
|
|
|
struct rb_node *rb_node;
|
|
|
|
struct callchain_node *chain;
|
|
|
|
size_t ret = 0;
|
|
|
|
|
|
|
|
rb_node = rb_first(&self->sorted_chain);
|
|
|
|
while (rb_node) {
|
|
|
|
double percent;
|
|
|
|
|
|
|
|
chain = rb_entry(rb_node, struct callchain_node, rb_node);
|
|
|
|
percent = chain->hit * 100.0 / total_samples;
|
2009-07-05 13:39:21 +08:00
|
|
|
switch (callchain_param.mode) {
|
|
|
|
case CHAIN_FLAT:
|
2009-07-03 02:14:35 +08:00
|
|
|
ret += percent_color_fprintf(fp, " %6.2f%%\n",
|
|
|
|
percent);
|
2009-07-02 23:58:21 +08:00
|
|
|
ret += callchain__fprintf_flat(fp, chain, total_samples);
|
2009-07-05 13:39:21 +08:00
|
|
|
break;
|
|
|
|
case CHAIN_GRAPH_ABS: /* Falldown */
|
|
|
|
case CHAIN_GRAPH_REL:
|
2009-07-02 23:58:21 +08:00
|
|
|
ret += callchain__fprintf_graph(fp, chain,
|
|
|
|
total_samples, 1, 1);
|
2009-08-15 18:26:57 +08:00
|
|
|
case CHAIN_NONE:
|
2009-07-05 13:39:21 +08:00
|
|
|
default:
|
|
|
|
break;
|
2009-07-02 23:58:21 +08:00
|
|
|
}
|
2009-06-26 22:28:01 +08:00
|
|
|
ret += fprintf(fp, "\n");
|
|
|
|
rb_node = rb_next(rb_node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
static size_t
|
perf_counter tools: Define and use our own u64, s64 etc. definitions
On 64-bit powerpc, __u64 is defined to be unsigned long rather than
unsigned long long. This causes compiler warnings every time we
print a __u64 value with %Lx.
Rather than changing __u64, we define our own u64 to be unsigned long
long on all architectures, and similarly s64 as signed long long.
For consistency we also define u32, s32, u16, s16, u8 and s8. These
definitions are put in a new header, types.h, because these definitions
are needed in util/string.h and util/symbol.h.
The main change here is the mechanical change of __[us]{64,32,16,8}
to remove the "__". The other changes are:
* Create types.h
* Include types.h in perf.h, util/string.h and util/symbol.h
* Add types.h to the LIB_H definition in Makefile
* Added (u64) casts in process_overflow_event() and print_sym_table()
to kill two remaining warnings.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: benh@kernel.crashing.org
LKML-Reference: <19003.33494.495844.956580@cargo.ozlabs.ibm.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2009-06-19 20:21:42 +08:00
|
|
|
hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples)
|
2009-05-28 02:20:25 +08:00
|
|
|
{
|
|
|
|
struct sort_entry *se;
|
|
|
|
size_t ret;
|
|
|
|
|
2009-06-18 20:32:19 +08:00
|
|
|
if (exclude_other && !self->parent)
|
|
|
|
return 0;
|
|
|
|
|
2009-07-03 02:14:34 +08:00
|
|
|
if (total_samples)
|
2009-07-11 09:47:28 +08:00
|
|
|
ret = percent_color_fprintf(fp,
|
|
|
|
field_sep ? "%.2f" : " %6.2f%%",
|
|
|
|
(self->count * 100.0) / total_samples);
|
2009-07-03 02:14:34 +08:00
|
|
|
else
|
2009-07-11 09:47:28 +08:00
|
|
|
ret = fprintf(fp, field_sep ? "%lld" : "%12lld ", self->count);
|
2009-05-28 02:20:25 +08:00
|
|
|
|
2009-07-11 23:18:37 +08:00
|
|
|
if (show_nr_samples) {
|
|
|
|
if (field_sep)
|
|
|
|
fprintf(fp, "%c%lld", *field_sep, self->count);
|
|
|
|
else
|
|
|
|
fprintf(fp, "%11lld", self->count);
|
|
|
|
}
|
2009-05-28 02:20:25 +08:00
|
|
|
|
2009-06-04 21:16:56 +08:00
|
|
|
list_for_each_entry(se, &hist_entry__sort_list, list) {
|
2009-07-11 23:18:35 +08:00
|
|
|
if (se->elide)
|
2009-06-18 20:32:19 +08:00
|
|
|
continue;
|
|
|
|
|
2009-07-11 09:47:28 +08:00
|
|
|
fprintf(fp, "%s", field_sep ?: " ");
|
|
|
|
ret += se->print(fp, self, se->width ? *se->width : 0);
|
2009-06-04 21:16:56 +08:00
|
|
|
}
|
2009-05-28 02:20:25 +08:00
|
|
|
|
|
|
|
ret += fprintf(fp, "\n");
|
|
|
|
|
2009-06-26 22:28:01 +08:00
|
|
|
if (callchain)
|
|
|
|
hist_entry_callchain__fprintf(fp, self, total_samples);
|
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-06-17 21:51:44 +08:00
|
|
|
/*
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2009-07-11 09:47:28 +08:00
|
|
|
static void dso__calc_col_width(struct dso *self)
|
|
|
|
{
|
|
|
|
if (!col_width_list_str && !field_sep &&
|
|
|
|
(!dso_list || strlist__has_entry(dso_list, self->name))) {
|
|
|
|
unsigned int slen = strlen(self->name);
|
|
|
|
if (slen > dsos__col_width)
|
|
|
|
dsos__col_width = slen;
|
|
|
|
}
|
|
|
|
|
|
|
|
self->slen_calculated = 1;
|
|
|
|
}
|
|
|
|
|
2009-08-31 12:45:18 +08:00
|
|
|
static void thread__comm_adjust(struct thread *self)
|
2009-08-18 22:03:46 +08:00
|
|
|
{
|
2009-08-31 12:45:18 +08:00
|
|
|
char *comm = self->comm;
|
2009-08-18 22:03:46 +08:00
|
|
|
|
|
|
|
if (!col_width_list_str && !field_sep &&
|
|
|
|
(!comm_list || strlist__has_entry(comm_list, comm))) {
|
|
|
|
unsigned int slen = strlen(comm);
|
|
|
|
|
|
|
|
if (slen > comms__col_width) {
|
|
|
|
comms__col_width = slen;
|
|
|
|
threads__col_width = slen + 6;
|
|
|
|
}
|
|
|
|
}
|
2009-08-31 12:45:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int thread__set_comm_adjust(struct thread *self, const char *comm)
|
|
|
|
{
|
|
|
|
int ret = thread__set_comm(self, comm);
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
thread__comm_adjust(self);
|
2009-08-18 22:03:46 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-06-17 21:51:44 +08:00
|
|
|
static struct symbol *
|
|
|
|
resolve_symbol(struct thread *thread, struct map **mapp,
|
perf_counter tools: Define and use our own u64, s64 etc. definitions
On 64-bit powerpc, __u64 is defined to be unsigned long rather than
unsigned long long. This causes compiler warnings every time we
print a __u64 value with %Lx.
Rather than changing __u64, we define our own u64 to be unsigned long
long on all architectures, and similarly s64 as signed long long.
For consistency we also define u32, s32, u16, s16, u8 and s8. These
definitions are put in a new header, types.h, because these definitions
are needed in util/string.h and util/symbol.h.
The main change here is the mechanical change of __[us]{64,32,16,8}
to remove the "__". The other changes are:
* Create types.h
* Include types.h in perf.h, util/string.h and util/symbol.h
* Add types.h to the LIB_H definition in Makefile
* Added (u64) casts in process_overflow_event() and print_sym_table()
to kill two remaining warnings.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: benh@kernel.crashing.org
LKML-Reference: <19003.33494.495844.956580@cargo.ozlabs.ibm.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2009-06-19 20:21:42 +08:00
|
|
|
struct dso **dsop, u64 *ipp)
|
2009-06-17 21:51:44 +08:00
|
|
|
{
|
|
|
|
struct dso *dso = dsop ? *dsop : NULL;
|
|
|
|
struct map *map = mapp ? *mapp : NULL;
|
2009-06-22 22:52:51 +08:00
|
|
|
u64 ip = *ipp;
|
2009-06-17 21:51:44 +08:00
|
|
|
|
|
|
|
if (!thread)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (dso)
|
|
|
|
goto got_dso;
|
|
|
|
|
|
|
|
if (map)
|
|
|
|
goto got_map;
|
|
|
|
|
|
|
|
map = thread__find_map(thread, ip);
|
|
|
|
if (map != NULL) {
|
2009-07-11 09:47:28 +08:00
|
|
|
/*
|
|
|
|
* We have to do this here as we may have a dso
|
|
|
|
* with no symbol hit that has a name longer than
|
|
|
|
* the ones with symbols sampled.
|
|
|
|
*/
|
2009-07-11 23:18:35 +08:00
|
|
|
if (!sort_dso.elide && !map->dso->slen_calculated)
|
2009-07-11 09:47:28 +08:00
|
|
|
dso__calc_col_width(map->dso);
|
|
|
|
|
2009-06-17 21:51:44 +08:00
|
|
|
if (mapp)
|
|
|
|
*mapp = map;
|
|
|
|
got_map:
|
|
|
|
ip = map->map_ip(map, ip);
|
|
|
|
|
|
|
|
dso = map->dso;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* If this is outside of all known maps,
|
|
|
|
* and is a negative address, try to look it
|
|
|
|
* up in the kernel dso, as it might be a
|
|
|
|
* vsyscall (which executes in user-mode):
|
|
|
|
*/
|
|
|
|
if ((long long)ip < 0)
|
|
|
|
dso = kernel_dso;
|
|
|
|
}
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf(" ...... dso: %s\n", dso ? dso->name : "<not found>");
|
|
|
|
dump_printf(" ...... map: %Lx -> %Lx\n", *ipp, ip);
|
2009-06-22 22:52:51 +08:00
|
|
|
*ipp = ip;
|
2009-06-17 21:51:44 +08:00
|
|
|
|
|
|
|
if (dsop)
|
|
|
|
*dsop = dso;
|
|
|
|
|
|
|
|
if (!dso)
|
|
|
|
return NULL;
|
|
|
|
got_dso:
|
|
|
|
return dso->find_symbol(dso, ip);
|
|
|
|
}
|
|
|
|
|
2009-06-19 04:20:45 +08:00
|
|
|
static int call__match(struct symbol *sym)
|
2009-06-17 21:51:44 +08:00
|
|
|
{
|
2009-06-18 13:01:03 +08:00
|
|
|
if (sym->name && !regexec(&parent_regex, sym->name, 0, NULL, 0))
|
2009-06-19 04:20:45 +08:00
|
|
|
return 1;
|
2009-06-17 21:51:44 +08:00
|
|
|
|
2009-06-19 04:20:45 +08:00
|
|
|
return 0;
|
2009-06-17 21:51:44 +08:00
|
|
|
}
|
|
|
|
|
2009-07-01 11:35:14 +08:00
|
|
|
static struct symbol **
|
2009-07-01 18:37:06 +08:00
|
|
|
resolve_callchain(struct thread *thread, struct map *map __used,
|
2009-07-01 11:35:14 +08:00
|
|
|
struct ip_callchain *chain, struct hist_entry *entry)
|
|
|
|
{
|
|
|
|
u64 context = PERF_CONTEXT_MAX;
|
2009-07-03 19:17:28 +08:00
|
|
|
struct symbol **syms = NULL;
|
2009-07-01 18:37:06 +08:00
|
|
|
unsigned int i;
|
2009-07-01 11:35:14 +08:00
|
|
|
|
|
|
|
if (callchain) {
|
|
|
|
syms = calloc(chain->nr, sizeof(*syms));
|
|
|
|
if (!syms) {
|
|
|
|
fprintf(stderr, "Can't allocate memory for symbols\n");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < chain->nr; i++) {
|
|
|
|
u64 ip = chain->ips[i];
|
|
|
|
struct dso *dso = NULL;
|
|
|
|
struct symbol *sym;
|
|
|
|
|
|
|
|
if (ip >= PERF_CONTEXT_MAX) {
|
|
|
|
context = ip;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (context) {
|
2009-07-01 17:17:20 +08:00
|
|
|
case PERF_CONTEXT_HV:
|
|
|
|
dso = hypervisor_dso;
|
|
|
|
break;
|
2009-07-01 11:35:14 +08:00
|
|
|
case PERF_CONTEXT_KERNEL:
|
|
|
|
dso = kernel_dso;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
sym = resolve_symbol(thread, NULL, &dso, &ip);
|
|
|
|
|
|
|
|
if (sym) {
|
|
|
|
if (sort__has_parent && call__match(sym) &&
|
|
|
|
!entry->parent)
|
|
|
|
entry->parent = sym;
|
|
|
|
if (!callchain)
|
|
|
|
break;
|
|
|
|
syms[i] = sym;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return syms;
|
|
|
|
}
|
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
/*
|
|
|
|
* collect histogram counts
|
|
|
|
*/
|
|
|
|
|
2009-05-28 02:20:24 +08:00
|
|
|
static int
|
|
|
|
hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
|
perf_counter tools: Define and use our own u64, s64 etc. definitions
On 64-bit powerpc, __u64 is defined to be unsigned long rather than
unsigned long long. This causes compiler warnings every time we
print a __u64 value with %Lx.
Rather than changing __u64, we define our own u64 to be unsigned long
long on all architectures, and similarly s64 as signed long long.
For consistency we also define u32, s32, u16, s16, u8 and s8. These
definitions are put in a new header, types.h, because these definitions
are needed in util/string.h and util/symbol.h.
The main change here is the mechanical change of __[us]{64,32,16,8}
to remove the "__". The other changes are:
* Create types.h
* Include types.h in perf.h, util/string.h and util/symbol.h
* Add types.h to the LIB_H definition in Makefile
* Added (u64) casts in process_overflow_event() and print_sym_table()
to kill two remaining warnings.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: benh@kernel.crashing.org
LKML-Reference: <19003.33494.495844.956580@cargo.ozlabs.ibm.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2009-06-19 20:21:42 +08:00
|
|
|
struct symbol *sym, u64 ip, struct ip_callchain *chain,
|
|
|
|
char level, u64 count)
|
2009-05-18 23:45:42 +08:00
|
|
|
{
|
2009-05-28 02:20:24 +08:00
|
|
|
struct rb_node **p = &hist.rb_node;
|
|
|
|
struct rb_node *parent = NULL;
|
|
|
|
struct hist_entry *he;
|
2009-07-01 11:35:14 +08:00
|
|
|
struct symbol **syms = NULL;
|
2009-05-28 02:20:24 +08:00
|
|
|
struct hist_entry entry = {
|
|
|
|
.thread = thread,
|
|
|
|
.map = map,
|
|
|
|
.dso = dso,
|
|
|
|
.sym = sym,
|
|
|
|
.ip = ip,
|
|
|
|
.level = level,
|
2009-06-11 03:45:22 +08:00
|
|
|
.count = count,
|
2009-06-18 20:32:19 +08:00
|
|
|
.parent = NULL,
|
2009-06-26 22:28:01 +08:00
|
|
|
.sorted_chain = RB_ROOT
|
2009-05-28 02:20:24 +08:00
|
|
|
};
|
|
|
|
int cmp;
|
|
|
|
|
2009-07-01 11:35:14 +08:00
|
|
|
if ((sort__has_parent || callchain) && chain)
|
|
|
|
syms = resolve_callchain(thread, map, chain, &entry);
|
2009-06-17 21:51:44 +08:00
|
|
|
|
2009-05-28 02:20:24 +08:00
|
|
|
while (*p != NULL) {
|
|
|
|
parent = *p;
|
|
|
|
he = rb_entry(parent, struct hist_entry, rb_node);
|
|
|
|
|
|
|
|
cmp = hist_entry__cmp(&entry, he);
|
|
|
|
|
|
|
|
if (!cmp) {
|
2009-06-11 03:45:22 +08:00
|
|
|
he->count += count;
|
2009-07-01 11:35:14 +08:00
|
|
|
if (callchain) {
|
|
|
|
append_chain(&he->callchain, chain, syms);
|
|
|
|
free(syms);
|
|
|
|
}
|
2009-05-28 02:20:24 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmp < 0)
|
|
|
|
p = &(*p)->rb_left;
|
|
|
|
else
|
|
|
|
p = &(*p)->rb_right;
|
2009-05-19 20:30:23 +08:00
|
|
|
}
|
2009-05-28 02:20:24 +08:00
|
|
|
|
|
|
|
he = malloc(sizeof(*he));
|
|
|
|
if (!he)
|
|
|
|
return -ENOMEM;
|
|
|
|
*he = entry;
|
2009-06-26 22:28:01 +08:00
|
|
|
if (callchain) {
|
|
|
|
callchain_init(&he->callchain);
|
2009-07-01 11:35:14 +08:00
|
|
|
append_chain(&he->callchain, chain, syms);
|
|
|
|
free(syms);
|
2009-06-26 22:28:01 +08:00
|
|
|
}
|
2009-05-28 02:20:24 +08:00
|
|
|
rb_link_node(&he->rb_node, parent, p);
|
|
|
|
rb_insert_color(&he->rb_node, &hist);
|
|
|
|
|
|
|
|
return 0;
|
2009-05-18 23:45:42 +08:00
|
|
|
}
|
|
|
|
|
2009-06-03 18:37:36 +08:00
|
|
|
static void hist_entry__free(struct hist_entry *he)
|
|
|
|
{
|
|
|
|
free(he);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* collapse the histogram
|
|
|
|
*/
|
|
|
|
|
|
|
|
static struct rb_root collapse_hists;
|
|
|
|
|
|
|
|
static void collapse__insert_entry(struct hist_entry *he)
|
|
|
|
{
|
|
|
|
struct rb_node **p = &collapse_hists.rb_node;
|
|
|
|
struct rb_node *parent = NULL;
|
|
|
|
struct hist_entry *iter;
|
|
|
|
int64_t cmp;
|
|
|
|
|
|
|
|
while (*p != NULL) {
|
|
|
|
parent = *p;
|
|
|
|
iter = rb_entry(parent, struct hist_entry, rb_node);
|
|
|
|
|
|
|
|
cmp = hist_entry__collapse(iter, he);
|
|
|
|
|
|
|
|
if (!cmp) {
|
|
|
|
iter->count += he->count;
|
|
|
|
hist_entry__free(he);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cmp < 0)
|
|
|
|
p = &(*p)->rb_left;
|
|
|
|
else
|
|
|
|
p = &(*p)->rb_right;
|
|
|
|
}
|
|
|
|
|
|
|
|
rb_link_node(&he->rb_node, parent, p);
|
|
|
|
rb_insert_color(&he->rb_node, &collapse_hists);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void collapse__resort(void)
|
|
|
|
{
|
|
|
|
struct rb_node *next;
|
|
|
|
struct hist_entry *n;
|
|
|
|
|
|
|
|
if (!sort__need_collapse)
|
|
|
|
return;
|
|
|
|
|
|
|
|
next = rb_first(&hist);
|
|
|
|
while (next) {
|
|
|
|
n = rb_entry(next, struct hist_entry, rb_node);
|
|
|
|
next = rb_next(&n->rb_node);
|
|
|
|
|
|
|
|
rb_erase(&n->rb_node, &hist);
|
|
|
|
collapse__insert_entry(n);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-05-28 02:20:24 +08:00
|
|
|
/*
|
|
|
|
* reverse the map, sort on count.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static struct rb_root output_hists;
|
|
|
|
|
2009-07-03 02:14:33 +08:00
|
|
|
static void output__insert_entry(struct hist_entry *he, u64 min_callchain_hits)
|
2009-05-27 03:19:04 +08:00
|
|
|
{
|
2009-05-28 02:20:24 +08:00
|
|
|
struct rb_node **p = &output_hists.rb_node;
|
2009-05-27 03:19:04 +08:00
|
|
|
struct rb_node *parent = NULL;
|
2009-05-28 02:20:24 +08:00
|
|
|
struct hist_entry *iter;
|
2009-05-27 03:19:04 +08:00
|
|
|
|
2009-07-05 13:39:21 +08:00
|
|
|
if (callchain)
|
|
|
|
callchain_param.sort(&he->sorted_chain, &he->callchain,
|
|
|
|
min_callchain_hits, &callchain_param);
|
2009-06-26 22:28:01 +08:00
|
|
|
|
2009-05-27 03:19:04 +08:00
|
|
|
while (*p != NULL) {
|
|
|
|
parent = *p;
|
2009-05-28 02:20:24 +08:00
|
|
|
iter = rb_entry(parent, struct hist_entry, rb_node);
|
2009-05-27 03:19:04 +08:00
|
|
|
|
2009-05-28 02:20:24 +08:00
|
|
|
if (he->count > iter->count)
|
2009-05-27 03:19:04 +08:00
|
|
|
p = &(*p)->rb_left;
|
|
|
|
else
|
|
|
|
p = &(*p)->rb_right;
|
|
|
|
}
|
|
|
|
|
2009-05-28 02:20:24 +08:00
|
|
|
rb_link_node(&he->rb_node, parent, p);
|
|
|
|
rb_insert_color(&he->rb_node, &output_hists);
|
2009-05-27 03:19:04 +08:00
|
|
|
}
|
|
|
|
|
2009-07-03 02:14:33 +08:00
|
|
|
static void output__resort(u64 total_samples)
|
2009-05-27 03:19:04 +08:00
|
|
|
{
|
2009-06-03 18:37:36 +08:00
|
|
|
struct rb_node *next;
|
2009-05-28 02:20:24 +08:00
|
|
|
struct hist_entry *n;
|
2009-06-04 10:02:33 +08:00
|
|
|
struct rb_root *tree = &hist;
|
2009-07-03 02:14:33 +08:00
|
|
|
u64 min_callchain_hits;
|
|
|
|
|
2009-07-05 13:39:21 +08:00
|
|
|
min_callchain_hits = total_samples * (callchain_param.min_percent / 100);
|
2009-05-27 03:19:04 +08:00
|
|
|
|
2009-06-03 18:37:36 +08:00
|
|
|
if (sort__need_collapse)
|
2009-06-04 10:02:33 +08:00
|
|
|
tree = &collapse_hists;
|
|
|
|
|
|
|
|
next = rb_first(tree);
|
2009-06-03 18:37:36 +08:00
|
|
|
|
2009-05-28 02:20:24 +08:00
|
|
|
while (next) {
|
|
|
|
n = rb_entry(next, struct hist_entry, rb_node);
|
|
|
|
next = rb_next(&n->rb_node);
|
2009-05-27 03:19:04 +08:00
|
|
|
|
2009-06-04 10:02:33 +08:00
|
|
|
rb_erase(&n->rb_node, tree);
|
2009-07-03 02:14:33 +08:00
|
|
|
output__insert_entry(n, min_callchain_hits);
|
2009-05-27 03:19:04 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
perf_counter tools: Define and use our own u64, s64 etc. definitions
On 64-bit powerpc, __u64 is defined to be unsigned long rather than
unsigned long long. This causes compiler warnings every time we
print a __u64 value with %Lx.
Rather than changing __u64, we define our own u64 to be unsigned long
long on all architectures, and similarly s64 as signed long long.
For consistency we also define u32, s32, u16, s16, u8 and s8. These
definitions are put in a new header, types.h, because these definitions
are needed in util/string.h and util/symbol.h.
The main change here is the mechanical change of __[us]{64,32,16,8}
to remove the "__". The other changes are:
* Create types.h
* Include types.h in perf.h, util/string.h and util/symbol.h
* Add types.h to the LIB_H definition in Makefile
* Added (u64) casts in process_overflow_event() and print_sym_table()
to kill two remaining warnings.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: benh@kernel.crashing.org
LKML-Reference: <19003.33494.495844.956580@cargo.ozlabs.ibm.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2009-06-19 20:21:42 +08:00
|
|
|
static size_t output__fprintf(FILE *fp, u64 total_samples)
|
2009-05-27 03:19:04 +08:00
|
|
|
{
|
2009-05-28 02:20:24 +08:00
|
|
|
struct hist_entry *pos;
|
2009-05-28 03:36:22 +08:00
|
|
|
struct sort_entry *se;
|
2009-05-27 03:19:04 +08:00
|
|
|
struct rb_node *nd;
|
|
|
|
size_t ret = 0;
|
2009-07-11 09:47:28 +08:00
|
|
|
unsigned int width;
|
|
|
|
char *col_width = col_width_list_str;
|
2009-08-10 21:26:32 +08:00
|
|
|
int raw_printing_style;
|
|
|
|
|
|
|
|
raw_printing_style = !strcmp(pretty_printing_style, "raw");
|
2009-05-27 03:19:04 +08:00
|
|
|
|
2009-08-08 08:16:25 +08:00
|
|
|
init_rem_hits();
|
|
|
|
|
2009-07-11 23:18:35 +08:00
|
|
|
fprintf(fp, "# Samples: %Ld\n", (u64)total_samples);
|
2009-05-28 17:08:33 +08:00
|
|
|
fprintf(fp, "#\n");
|
|
|
|
|
|
|
|
fprintf(fp, "# Overhead");
|
2009-07-11 23:18:37 +08:00
|
|
|
if (show_nr_samples) {
|
|
|
|
if (field_sep)
|
|
|
|
fprintf(fp, "%cSamples", *field_sep);
|
|
|
|
else
|
|
|
|
fputs(" Samples ", fp);
|
|
|
|
}
|
2009-06-18 20:32:19 +08:00
|
|
|
list_for_each_entry(se, &hist_entry__sort_list, list) {
|
2009-07-11 23:18:35 +08:00
|
|
|
if (se->elide)
|
2009-06-18 20:32:19 +08:00
|
|
|
continue;
|
2009-07-11 09:47:28 +08:00
|
|
|
if (field_sep) {
|
|
|
|
fprintf(fp, "%c%s", *field_sep, se->header);
|
2009-06-18 20:32:19 +08:00
|
|
|
continue;
|
2009-07-11 09:47:28 +08:00
|
|
|
}
|
|
|
|
width = strlen(se->header);
|
|
|
|
if (se->width) {
|
|
|
|
if (col_width_list_str) {
|
|
|
|
if (col_width) {
|
|
|
|
*se->width = atoi(col_width);
|
|
|
|
col_width = strchr(col_width, ',');
|
|
|
|
if (col_width)
|
|
|
|
++col_width;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
width = *se->width = max(*se->width, width);
|
|
|
|
}
|
|
|
|
fprintf(fp, " %*s", width, se->header);
|
2009-06-18 20:32:19 +08:00
|
|
|
}
|
2009-05-28 17:08:33 +08:00
|
|
|
fprintf(fp, "\n");
|
|
|
|
|
2009-07-11 09:47:28 +08:00
|
|
|
if (field_sep)
|
|
|
|
goto print_entries;
|
|
|
|
|
2009-05-28 17:08:33 +08:00
|
|
|
fprintf(fp, "# ........");
|
2009-07-11 23:18:37 +08:00
|
|
|
if (show_nr_samples)
|
|
|
|
fprintf(fp, " ..........");
|
2009-05-28 03:36:22 +08:00
|
|
|
list_for_each_entry(se, &hist_entry__sort_list, list) {
|
2009-07-01 18:37:06 +08:00
|
|
|
unsigned int i;
|
2009-05-28 17:08:33 +08:00
|
|
|
|
2009-07-11 23:18:35 +08:00
|
|
|
if (se->elide)
|
2009-06-18 20:32:19 +08:00
|
|
|
continue;
|
|
|
|
|
2009-06-02 21:34:25 +08:00
|
|
|
fprintf(fp, " ");
|
2009-07-11 09:47:28 +08:00
|
|
|
if (se->width)
|
|
|
|
width = *se->width;
|
|
|
|
else
|
|
|
|
width = strlen(se->header);
|
|
|
|
for (i = 0; i < width; i++)
|
2009-05-28 17:08:33 +08:00
|
|
|
fprintf(fp, ".");
|
2009-05-28 03:36:22 +08:00
|
|
|
}
|
2009-05-28 17:08:33 +08:00
|
|
|
fprintf(fp, "\n");
|
|
|
|
|
|
|
|
fprintf(fp, "#\n");
|
2009-05-28 03:36:22 +08:00
|
|
|
|
2009-07-11 09:47:28 +08:00
|
|
|
print_entries:
|
2009-05-28 02:20:24 +08:00
|
|
|
for (nd = rb_first(&output_hists); nd; nd = rb_next(nd)) {
|
|
|
|
pos = rb_entry(nd, struct hist_entry, rb_node);
|
|
|
|
ret += hist_entry__fprintf(fp, pos, total_samples);
|
2009-05-27 03:19:04 +08:00
|
|
|
}
|
|
|
|
|
2009-06-18 20:32:19 +08:00
|
|
|
if (sort_order == default_sort_order &&
|
|
|
|
parent_pattern == default_parent_pattern) {
|
2009-06-04 20:13:04 +08:00
|
|
|
fprintf(fp, "#\n");
|
2009-08-05 18:25:21 +08:00
|
|
|
fprintf(fp, "# (For a higher level overview, try: perf report --sort comm,dso)\n");
|
2009-06-04 20:13:04 +08:00
|
|
|
fprintf(fp, "#\n");
|
|
|
|
}
|
2009-06-04 21:16:56 +08:00
|
|
|
fprintf(fp, "\n");
|
2009-06-04 20:13:04 +08:00
|
|
|
|
2009-08-08 08:16:25 +08:00
|
|
|
free(rem_sq_bracket);
|
|
|
|
|
2009-08-07 19:55:24 +08:00
|
|
|
if (show_threads)
|
2009-08-10 21:26:32 +08:00
|
|
|
perf_read_values_display(fp, &show_threads_values,
|
|
|
|
raw_printing_style);
|
2009-08-07 19:55:24 +08:00
|
|
|
|
2009-05-27 03:19:04 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-06-04 22:53:49 +08:00
|
|
|
static unsigned long total = 0,
|
|
|
|
total_mmap = 0,
|
|
|
|
total_comm = 0,
|
|
|
|
total_fork = 0,
|
2009-06-18 17:40:28 +08:00
|
|
|
total_unknown = 0,
|
|
|
|
total_lost = 0;
|
2009-05-28 02:20:24 +08:00
|
|
|
|
2009-06-19 04:20:45 +08:00
|
|
|
static int validate_chain(struct ip_callchain *chain, event_t *event)
|
2009-06-18 14:00:17 +08:00
|
|
|
{
|
|
|
|
unsigned int chain_size;
|
|
|
|
|
|
|
|
chain_size = event->header.size;
|
|
|
|
chain_size -= (unsigned long)&event->ip.__more_data - (unsigned long)event;
|
|
|
|
|
perf_counter tools: Define and use our own u64, s64 etc. definitions
On 64-bit powerpc, __u64 is defined to be unsigned long rather than
unsigned long long. This causes compiler warnings every time we
print a __u64 value with %Lx.
Rather than changing __u64, we define our own u64 to be unsigned long
long on all architectures, and similarly s64 as signed long long.
For consistency we also define u32, s32, u16, s16, u8 and s8. These
definitions are put in a new header, types.h, because these definitions
are needed in util/string.h and util/symbol.h.
The main change here is the mechanical change of __[us]{64,32,16,8}
to remove the "__". The other changes are:
* Create types.h
* Include types.h in perf.h, util/string.h and util/symbol.h
* Add types.h to the LIB_H definition in Makefile
* Added (u64) casts in process_overflow_event() and print_sym_table()
to kill two remaining warnings.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: benh@kernel.crashing.org
LKML-Reference: <19003.33494.495844.956580@cargo.ozlabs.ibm.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2009-06-19 20:21:42 +08:00
|
|
|
if (chain->nr*sizeof(u64) > chain_size)
|
2009-06-18 14:00:17 +08:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
static int
|
2009-06-25 17:27:12 +08:00
|
|
|
process_sample_event(event_t *event, unsigned long offset, unsigned long head)
|
2009-06-04 05:14:49 +08:00
|
|
|
{
|
|
|
|
char level;
|
|
|
|
int show = 0;
|
|
|
|
struct dso *dso = NULL;
|
2009-08-14 18:21:53 +08:00
|
|
|
struct thread *thread;
|
perf_counter tools: Define and use our own u64, s64 etc. definitions
On 64-bit powerpc, __u64 is defined to be unsigned long rather than
unsigned long long. This causes compiler warnings every time we
print a __u64 value with %Lx.
Rather than changing __u64, we define our own u64 to be unsigned long
long on all architectures, and similarly s64 as signed long long.
For consistency we also define u32, s32, u16, s16, u8 and s8. These
definitions are put in a new header, types.h, because these definitions
are needed in util/string.h and util/symbol.h.
The main change here is the mechanical change of __[us]{64,32,16,8}
to remove the "__". The other changes are:
* Create types.h
* Include types.h in perf.h, util/string.h and util/symbol.h
* Add types.h to the LIB_H definition in Makefile
* Added (u64) casts in process_overflow_event() and print_sym_table()
to kill two remaining warnings.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: benh@kernel.crashing.org
LKML-Reference: <19003.33494.495844.956580@cargo.ozlabs.ibm.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2009-06-19 20:21:42 +08:00
|
|
|
u64 ip = event->ip.ip;
|
|
|
|
u64 period = 1;
|
2009-06-04 05:14:49 +08:00
|
|
|
struct map *map = NULL;
|
2009-06-14 21:04:15 +08:00
|
|
|
void *more_data = event->ip.__more_data;
|
2009-06-19 04:20:45 +08:00
|
|
|
struct ip_callchain *chain = NULL;
|
2009-07-01 07:00:48 +08:00
|
|
|
int cpumode;
|
2009-06-04 05:14:49 +08:00
|
|
|
|
2009-08-14 18:21:53 +08:00
|
|
|
thread = threads__findnew(event->ip.pid, &threads, &last_match);
|
|
|
|
|
2009-06-25 17:27:12 +08:00
|
|
|
if (sample_type & PERF_SAMPLE_PERIOD) {
|
perf_counter tools: Define and use our own u64, s64 etc. definitions
On 64-bit powerpc, __u64 is defined to be unsigned long rather than
unsigned long long. This causes compiler warnings every time we
print a __u64 value with %Lx.
Rather than changing __u64, we define our own u64 to be unsigned long
long on all architectures, and similarly s64 as signed long long.
For consistency we also define u32, s32, u16, s16, u8 and s8. These
definitions are put in a new header, types.h, because these definitions
are needed in util/string.h and util/symbol.h.
The main change here is the mechanical change of __[us]{64,32,16,8}
to remove the "__". The other changes are:
* Create types.h
* Include types.h in perf.h, util/string.h and util/symbol.h
* Add types.h to the LIB_H definition in Makefile
* Added (u64) casts in process_overflow_event() and print_sym_table()
to kill two remaining warnings.
Signed-off-by: Paul Mackerras <paulus@samba.org>
Acked-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: benh@kernel.crashing.org
LKML-Reference: <19003.33494.495844.956580@cargo.ozlabs.ibm.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
2009-06-19 20:21:42 +08:00
|
|
|
period = *(u64 *)more_data;
|
|
|
|
more_data += sizeof(u64);
|
2009-06-14 21:04:15 +08:00
|
|
|
}
|
2009-06-11 03:45:22 +08:00
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("%p [%p]: PERF_EVENT_SAMPLE (IP, %d): %d/%d: %p period: %Ld\n",
|
2009-06-04 05:14:49 +08:00
|
|
|
(void *)(offset + head),
|
|
|
|
(void *)(long)(event->header.size),
|
|
|
|
event->header.misc,
|
2009-08-12 03:21:38 +08:00
|
|
|
event->ip.pid, event->ip.tid,
|
2009-06-10 21:03:06 +08:00
|
|
|
(void *)(long)ip,
|
2009-06-11 03:45:22 +08:00
|
|
|
(long long)period);
|
2009-06-04 05:14:49 +08:00
|
|
|
|
2009-06-25 17:27:12 +08:00
|
|
|
if (sample_type & PERF_SAMPLE_CALLCHAIN) {
|
2009-07-01 18:37:06 +08:00
|
|
|
unsigned int i;
|
2009-06-14 21:04:15 +08:00
|
|
|
|
|
|
|
chain = (void *)more_data;
|
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("... chain: nr:%Lu\n", chain->nr);
|
2009-06-14 21:04:15 +08:00
|
|
|
|
2009-06-18 14:00:17 +08:00
|
|
|
if (validate_chain(chain, event) < 0) {
|
|
|
|
eprintf("call-chain problem with event, skipping it.\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dump_trace) {
|
2009-06-14 21:04:15 +08:00
|
|
|
for (i = 0; i < chain->nr; i++)
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("..... %2d: %016Lx\n", i, chain->ips[i]);
|
2009-06-14 21:04:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf(" ... thread: %s:%d\n", thread->comm, thread->pid);
|
2009-06-04 05:14:49 +08:00
|
|
|
|
|
|
|
if (thread == NULL) {
|
2009-06-18 14:00:17 +08:00
|
|
|
eprintf("problem processing %d event, skipping it.\n",
|
2009-06-04 05:14:49 +08:00
|
|
|
event->header.type);
|
|
|
|
return -1;
|
|
|
|
}
|
2009-05-28 02:20:24 +08:00
|
|
|
|
2009-07-01 06:01:21 +08:00
|
|
|
if (comm_list && !strlist__has_entry(comm_list, thread->comm))
|
|
|
|
return 0;
|
|
|
|
|
2009-07-01 07:00:48 +08:00
|
|
|
cpumode = event->header.misc & PERF_EVENT_MISC_CPUMODE_MASK;
|
|
|
|
|
|
|
|
if (cpumode == PERF_EVENT_MISC_KERNEL) {
|
2009-06-04 05:14:49 +08:00
|
|
|
show = SHOW_KERNEL;
|
|
|
|
level = 'k';
|
2009-05-28 02:20:24 +08:00
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
dso = kernel_dso;
|
2009-06-03 16:39:26 +08:00
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf(" ...... dso: %s\n", dso->name);
|
2009-05-27 15:10:38 +08:00
|
|
|
|
2009-07-01 07:00:48 +08:00
|
|
|
} else if (cpumode == PERF_EVENT_MISC_USER) {
|
2009-05-27 15:10:38 +08:00
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
show = SHOW_USER;
|
|
|
|
level = '.';
|
2009-05-28 02:20:24 +08:00
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
} else {
|
|
|
|
show = SHOW_HV;
|
|
|
|
level = 'H';
|
2009-07-01 07:00:49 +08:00
|
|
|
|
|
|
|
dso = hypervisor_dso;
|
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf(" ...... dso: [hypervisor]\n");
|
2009-06-04 05:14:49 +08:00
|
|
|
}
|
2009-05-18 23:45:42 +08:00
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
if (show & show_mask) {
|
2009-06-17 21:51:44 +08:00
|
|
|
struct symbol *sym = resolve_symbol(thread, &map, &dso, &ip);
|
2009-05-18 23:45:42 +08:00
|
|
|
|
2009-08-13 05:19:57 +08:00
|
|
|
if (dso_list && (!dso || !dso->name ||
|
|
|
|
!strlist__has_entry(dso_list, dso->name)))
|
2009-07-01 06:01:20 +08:00
|
|
|
return 0;
|
|
|
|
|
2009-08-13 05:19:57 +08:00
|
|
|
if (sym_list && (!sym || !strlist__has_entry(sym_list, sym->name)))
|
2009-07-01 06:01:22 +08:00
|
|
|
return 0;
|
|
|
|
|
2009-06-17 21:51:44 +08:00
|
|
|
if (hist_entry__add(thread, map, dso, sym, ip, chain, level, period)) {
|
2009-06-18 14:00:17 +08:00
|
|
|
eprintf("problem incrementing symbol count, skipping event\n");
|
2009-06-04 05:14:49 +08:00
|
|
|
return -1;
|
2009-05-19 20:30:23 +08:00
|
|
|
}
|
2009-05-18 23:45:42 +08:00
|
|
|
}
|
2009-06-11 03:45:22 +08:00
|
|
|
total += period;
|
2009-05-18 23:45:42 +08:00
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2009-06-03 15:38:58 +08:00
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
static int
|
|
|
|
process_mmap_event(event_t *event, unsigned long offset, unsigned long head)
|
|
|
|
{
|
2009-08-14 18:21:53 +08:00
|
|
|
struct thread *thread;
|
2009-08-12 17:07:25 +08:00
|
|
|
struct map *map = map__new(&event->mmap, cwd, cwdlen);
|
2009-06-04 05:14:49 +08:00
|
|
|
|
2009-08-14 18:21:53 +08:00
|
|
|
thread = threads__findnew(event->mmap.pid, &threads, &last_match);
|
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("%p [%p]: PERF_EVENT_MMAP %d/%d: [%p(%p) @ %p]: %s\n",
|
2009-06-04 05:14:49 +08:00
|
|
|
(void *)(offset + head),
|
|
|
|
(void *)(long)(event->header.size),
|
2009-06-04 22:53:49 +08:00
|
|
|
event->mmap.pid,
|
2009-08-12 03:21:38 +08:00
|
|
|
event->mmap.tid,
|
2009-06-04 05:14:49 +08:00
|
|
|
(void *)(long)event->mmap.start,
|
|
|
|
(void *)(long)event->mmap.len,
|
|
|
|
(void *)(long)event->mmap.pgoff,
|
|
|
|
event->mmap.filename);
|
|
|
|
|
|
|
|
if (thread == NULL || map == NULL) {
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("problem processing PERF_EVENT_MMAP, skipping event.\n");
|
2009-06-04 19:41:22 +08:00
|
|
|
return 0;
|
2009-06-04 05:14:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
thread__insert_map(thread, map);
|
|
|
|
total_mmap++;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
process_comm_event(event_t *event, unsigned long offset, unsigned long head)
|
|
|
|
{
|
2009-08-14 18:21:53 +08:00
|
|
|
struct thread *thread;
|
|
|
|
|
|
|
|
thread = threads__findnew(event->comm.pid, &threads, &last_match);
|
2009-06-04 05:14:49 +08:00
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("%p [%p]: PERF_EVENT_COMM: %s:%d\n",
|
2009-06-04 05:14:49 +08:00
|
|
|
(void *)(offset + head),
|
|
|
|
(void *)(long)(event->header.size),
|
|
|
|
event->comm.comm, event->comm.pid);
|
|
|
|
|
|
|
|
if (thread == NULL ||
|
2009-08-18 22:03:46 +08:00
|
|
|
thread__set_comm_adjust(thread, event->comm.comm)) {
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("problem processing PERF_EVENT_COMM, skipping event.\n");
|
2009-06-04 05:14:49 +08:00
|
|
|
return -1;
|
2009-05-18 23:45:42 +08:00
|
|
|
}
|
2009-06-04 05:14:49 +08:00
|
|
|
total_comm++;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-04 22:53:49 +08:00
|
|
|
static int
|
2009-07-23 22:52:41 +08:00
|
|
|
process_task_event(event_t *event, unsigned long offset, unsigned long head)
|
2009-06-04 22:53:49 +08:00
|
|
|
{
|
2009-08-14 18:21:53 +08:00
|
|
|
struct thread *thread;
|
|
|
|
struct thread *parent;
|
|
|
|
|
|
|
|
thread = threads__findnew(event->fork.pid, &threads, &last_match);
|
|
|
|
parent = threads__findnew(event->fork.ppid, &threads, &last_match);
|
2009-06-04 22:53:49 +08:00
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("%p [%p]: PERF_EVENT_%s: (%d:%d):(%d:%d)\n",
|
2009-06-04 22:53:49 +08:00
|
|
|
(void *)(offset + head),
|
|
|
|
(void *)(long)(event->header.size),
|
2009-07-23 22:52:41 +08:00
|
|
|
event->header.type == PERF_EVENT_FORK ? "FORK" : "EXIT",
|
|
|
|
event->fork.pid, event->fork.tid,
|
|
|
|
event->fork.ppid, event->fork.ptid);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* A thread clone will have the same PID for both
|
|
|
|
* parent and child.
|
|
|
|
*/
|
|
|
|
if (thread == parent)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (event->header.type == PERF_EVENT_EXIT)
|
|
|
|
return 0;
|
2009-06-04 22:53:49 +08:00
|
|
|
|
|
|
|
if (!thread || !parent || thread__fork(thread, parent)) {
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("problem processing PERF_EVENT_FORK, skipping event.\n");
|
2009-06-04 22:53:49 +08:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
total_fork++;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-18 17:40:28 +08:00
|
|
|
static int
|
|
|
|
process_lost_event(event_t *event, unsigned long offset, unsigned long head)
|
|
|
|
{
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("%p [%p]: PERF_EVENT_LOST: id:%Ld: lost:%Ld\n",
|
2009-06-18 17:40:28 +08:00
|
|
|
(void *)(offset + head),
|
|
|
|
(void *)(long)(event->header.size),
|
|
|
|
event->lost.id,
|
|
|
|
event->lost.lost);
|
|
|
|
|
|
|
|
total_lost += event->lost.lost;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-25 04:46:04 +08:00
|
|
|
static int
|
|
|
|
process_read_event(event_t *event, unsigned long offset, unsigned long head)
|
|
|
|
{
|
2009-08-17 02:56:37 +08:00
|
|
|
struct perf_counter_attr *attr;
|
|
|
|
|
|
|
|
attr = perf_header__find_attr(event->read.id, header);
|
2009-08-07 01:40:28 +08:00
|
|
|
|
2009-08-07 19:55:24 +08:00
|
|
|
if (show_threads) {
|
2009-08-15 18:26:57 +08:00
|
|
|
const char *name = attr ? __event_name(attr->type, attr->config)
|
2009-08-07 19:55:24 +08:00
|
|
|
: "unknown";
|
|
|
|
perf_read_values_add_value(&show_threads_values,
|
|
|
|
event->read.pid, event->read.tid,
|
|
|
|
event->read.id,
|
|
|
|
name,
|
|
|
|
event->read.value);
|
|
|
|
}
|
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("%p [%p]: PERF_EVENT_READ: %d %d %s %Lu\n",
|
2009-06-25 04:46:04 +08:00
|
|
|
(void *)(offset + head),
|
|
|
|
(void *)(long)(event->header.size),
|
|
|
|
event->read.pid,
|
|
|
|
event->read.tid,
|
2009-08-07 01:40:28 +08:00
|
|
|
attr ? __event_name(attr->type, attr->config)
|
|
|
|
: "FAIL",
|
2009-06-25 04:46:04 +08:00
|
|
|
event->read.value);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
static int
|
|
|
|
process_event(event_t *event, unsigned long offset, unsigned long head)
|
|
|
|
{
|
2009-06-14 20:44:07 +08:00
|
|
|
trace_event(event);
|
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
switch (event->header.type) {
|
2009-06-25 17:27:12 +08:00
|
|
|
case PERF_EVENT_SAMPLE:
|
|
|
|
return process_sample_event(event, offset, head);
|
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
case PERF_EVENT_MMAP:
|
|
|
|
return process_mmap_event(event, offset, head);
|
|
|
|
|
|
|
|
case PERF_EVENT_COMM:
|
|
|
|
return process_comm_event(event, offset, head);
|
|
|
|
|
2009-06-04 22:53:49 +08:00
|
|
|
case PERF_EVENT_FORK:
|
2009-07-23 22:52:41 +08:00
|
|
|
case PERF_EVENT_EXIT:
|
|
|
|
return process_task_event(event, offset, head);
|
2009-06-04 22:53:49 +08:00
|
|
|
|
2009-06-18 17:40:28 +08:00
|
|
|
case PERF_EVENT_LOST:
|
|
|
|
return process_lost_event(event, offset, head);
|
|
|
|
|
2009-06-25 04:46:04 +08:00
|
|
|
case PERF_EVENT_READ:
|
|
|
|
return process_read_event(event, offset, head);
|
|
|
|
|
2009-06-04 05:29:14 +08:00
|
|
|
/*
|
|
|
|
* We dont process them right now but they are fine:
|
|
|
|
*/
|
2009-06-04 22:53:49 +08:00
|
|
|
|
2009-06-04 05:29:14 +08:00
|
|
|
case PERF_EVENT_THROTTLE:
|
|
|
|
case PERF_EVENT_UNTHROTTLE:
|
|
|
|
return 0;
|
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
default:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __cmd_report(void)
|
|
|
|
{
|
2009-06-04 05:14:49 +08:00
|
|
|
int ret, rc = EXIT_FAILURE;
|
2009-06-04 05:14:49 +08:00
|
|
|
unsigned long offset = 0;
|
2009-06-25 23:05:54 +08:00
|
|
|
unsigned long head, shift;
|
2009-08-15 18:26:57 +08:00
|
|
|
struct stat input_stat;
|
2009-08-31 12:45:18 +08:00
|
|
|
struct thread *idle;
|
2009-06-04 05:14:49 +08:00
|
|
|
event_t *event;
|
|
|
|
uint32_t size;
|
2009-06-04 05:14:49 +08:00
|
|
|
char *buf;
|
2009-06-04 05:14:49 +08:00
|
|
|
|
2009-08-31 12:45:18 +08:00
|
|
|
idle = register_idle_thread(&threads, &last_match);
|
|
|
|
thread__comm_adjust(idle);
|
2009-06-04 05:14:49 +08:00
|
|
|
|
2009-08-07 19:55:24 +08:00
|
|
|
if (show_threads)
|
|
|
|
perf_read_values_init(&show_threads_values);
|
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
input = open(input_name, O_RDONLY);
|
|
|
|
if (input < 0) {
|
2009-06-07 23:58:23 +08:00
|
|
|
fprintf(stderr, " failed to open file: %s", input_name);
|
|
|
|
if (!strcmp(input_name, "perf.data"))
|
|
|
|
fprintf(stderr, " (try 'perf record' first)");
|
|
|
|
fprintf(stderr, "\n");
|
2009-06-04 05:14:49 +08:00
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
2009-08-15 18:26:57 +08:00
|
|
|
ret = fstat(input, &input_stat);
|
2009-06-04 05:14:49 +08:00
|
|
|
if (ret < 0) {
|
|
|
|
perror("failed to stat file");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
2009-08-27 15:59:02 +08:00
|
|
|
if (!force && input_stat.st_uid && (input_stat.st_uid != geteuid())) {
|
|
|
|
fprintf(stderr, "file: %s not owned by current user or root\n", input_name);
|
2009-08-19 17:18:26 +08:00
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
2009-08-15 18:26:57 +08:00
|
|
|
if (!input_stat.st_size) {
|
2009-06-04 05:14:49 +08:00
|
|
|
fprintf(stderr, "zero-sized file, nothing to do!\n");
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
2009-06-25 23:05:54 +08:00
|
|
|
header = perf_header__read(input);
|
|
|
|
head = header->data_offset;
|
2009-06-19 05:22:55 +08:00
|
|
|
|
2009-08-17 02:56:37 +08:00
|
|
|
sample_type = perf_header__sample_type(header);
|
2009-06-25 17:27:12 +08:00
|
|
|
|
2009-07-05 13:39:17 +08:00
|
|
|
if (!(sample_type & PERF_SAMPLE_CALLCHAIN)) {
|
|
|
|
if (sort__has_parent) {
|
|
|
|
fprintf(stderr, "selected --sort parent, but no"
|
|
|
|
" callchain data. Did you call"
|
|
|
|
" perf record without -g?\n");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
if (callchain) {
|
2009-08-18 05:07:48 +08:00
|
|
|
fprintf(stderr, "selected -g but no callchain data."
|
2009-07-05 13:39:17 +08:00
|
|
|
" Did you call perf record without"
|
|
|
|
" -g?\n");
|
|
|
|
exit(-1);
|
|
|
|
}
|
2009-08-08 08:16:24 +08:00
|
|
|
} else if (callchain_param.mode != CHAIN_NONE && !callchain) {
|
|
|
|
callchain = 1;
|
|
|
|
if (register_callchain_param(&callchain_param) < 0) {
|
|
|
|
fprintf(stderr, "Can't register callchain"
|
|
|
|
" params\n");
|
|
|
|
exit(-1);
|
|
|
|
}
|
2009-06-19 05:22:55 +08:00
|
|
|
}
|
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
if (load_kernel() < 0) {
|
|
|
|
perror("failed to load kernel symbols");
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!full_paths) {
|
|
|
|
if (getcwd(__cwd, sizeof(__cwd)) == NULL) {
|
|
|
|
perror("failed to get the current directory");
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
cwdlen = strlen(cwd);
|
|
|
|
} else {
|
|
|
|
cwd = NULL;
|
|
|
|
cwdlen = 0;
|
|
|
|
}
|
2009-06-25 23:05:54 +08:00
|
|
|
|
|
|
|
shift = page_size * (head / page_size);
|
|
|
|
offset += shift;
|
|
|
|
head -= shift;
|
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
remap:
|
|
|
|
buf = (char *)mmap(NULL, page_size * mmap_window, PROT_READ,
|
|
|
|
MAP_SHARED, input, offset);
|
|
|
|
if (buf == MAP_FAILED) {
|
|
|
|
perror("failed to mmap file");
|
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
more:
|
|
|
|
event = (event_t *)(buf + head);
|
|
|
|
|
|
|
|
size = event->header.size;
|
|
|
|
if (!size)
|
|
|
|
size = 8;
|
|
|
|
|
|
|
|
if (head + event->header.size >= page_size * mmap_window) {
|
2009-08-15 18:26:57 +08:00
|
|
|
int munmap_ret;
|
2009-06-04 05:14:49 +08:00
|
|
|
|
2009-06-25 23:05:54 +08:00
|
|
|
shift = page_size * (head / page_size);
|
|
|
|
|
2009-08-15 18:26:57 +08:00
|
|
|
munmap_ret = munmap(buf, page_size * mmap_window);
|
|
|
|
assert(munmap_ret == 0);
|
2009-06-04 05:14:49 +08:00
|
|
|
|
|
|
|
offset += shift;
|
|
|
|
head -= shift;
|
|
|
|
goto remap;
|
|
|
|
}
|
|
|
|
|
|
|
|
size = event->header.size;
|
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("\n%p [%p]: event: %d\n",
|
2009-06-06 00:07:51 +08:00
|
|
|
(void *)(offset + head),
|
|
|
|
(void *)(long)event->header.size,
|
|
|
|
event->header.type);
|
|
|
|
|
2009-06-04 05:14:49 +08:00
|
|
|
if (!size || process_event(event, offset, head) < 0) {
|
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf("%p [%p]: skipping unknown header type: %d\n",
|
2009-06-03 15:38:58 +08:00
|
|
|
(void *)(offset + head),
|
|
|
|
(void *)(long)(event->header.size),
|
|
|
|
event->header.type);
|
2009-05-27 19:35:35 +08:00
|
|
|
|
2009-05-27 00:53:17 +08:00
|
|
|
total_unknown++;
|
2009-05-27 02:51:47 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* assume we lost track of the stream, check alignment, and
|
|
|
|
* increment a single u64 in the hope to catch on again 'soon'.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (unlikely(head & 7))
|
|
|
|
head &= ~7ULL;
|
|
|
|
|
|
|
|
size = 8;
|
2009-05-27 00:48:58 +08:00
|
|
|
}
|
2009-05-18 23:45:42 +08:00
|
|
|
|
2009-05-27 02:51:47 +08:00
|
|
|
head += size;
|
2009-05-27 01:03:36 +08:00
|
|
|
|
2009-06-25 23:05:54 +08:00
|
|
|
if (offset + head >= header->data_offset + header->data_size)
|
2009-06-19 05:22:55 +08:00
|
|
|
goto done;
|
|
|
|
|
2009-08-15 18:26:57 +08:00
|
|
|
if (offset + head < (unsigned long)input_stat.st_size)
|
2009-05-18 23:45:42 +08:00
|
|
|
goto more;
|
|
|
|
|
2009-06-19 05:22:55 +08:00
|
|
|
done:
|
2009-05-18 23:45:42 +08:00
|
|
|
rc = EXIT_SUCCESS;
|
|
|
|
close(input);
|
2009-05-27 00:48:58 +08:00
|
|
|
|
2009-08-17 01:24:21 +08:00
|
|
|
dump_printf(" IP events: %10ld\n", total);
|
|
|
|
dump_printf(" mmap events: %10ld\n", total_mmap);
|
|
|
|
dump_printf(" comm events: %10ld\n", total_comm);
|
|
|
|
dump_printf(" fork events: %10ld\n", total_fork);
|
|
|
|
dump_printf(" lost events: %10ld\n", total_lost);
|
|
|
|
dump_printf(" unknown events: %10ld\n", total_unknown);
|
2009-05-27 00:48:58 +08:00
|
|
|
|
2009-06-03 15:38:58 +08:00
|
|
|
if (dump_trace)
|
2009-05-27 00:48:58 +08:00
|
|
|
return 0;
|
|
|
|
|
2009-06-05 00:54:00 +08:00
|
|
|
if (verbose >= 3)
|
2009-08-14 18:21:53 +08:00
|
|
|
threads__fprintf(stdout, &threads);
|
2009-06-05 00:54:00 +08:00
|
|
|
|
2009-05-28 02:20:24 +08:00
|
|
|
if (verbose >= 2)
|
2009-05-27 15:10:38 +08:00
|
|
|
dsos__fprintf(stdout);
|
|
|
|
|
2009-06-03 18:37:36 +08:00
|
|
|
collapse__resort();
|
2009-07-03 02:14:33 +08:00
|
|
|
output__resort(total);
|
2009-05-28 02:20:24 +08:00
|
|
|
output__fprintf(stdout, total);
|
2009-05-18 23:45:42 +08:00
|
|
|
|
2009-08-07 19:55:24 +08:00
|
|
|
if (show_threads)
|
|
|
|
perf_read_values_destroy(&show_threads_values);
|
|
|
|
|
2009-05-18 23:45:42 +08:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2009-07-02 23:58:21 +08:00
|
|
|
static int
|
|
|
|
parse_callchain_opt(const struct option *opt __used, const char *arg,
|
|
|
|
int unset __used)
|
|
|
|
{
|
2009-07-03 02:14:33 +08:00
|
|
|
char *tok;
|
|
|
|
char *endptr;
|
|
|
|
|
2009-07-02 23:58:21 +08:00
|
|
|
callchain = 1;
|
|
|
|
|
|
|
|
if (!arg)
|
|
|
|
return 0;
|
|
|
|
|
2009-07-03 02:14:33 +08:00
|
|
|
tok = strtok((char *)arg, ",");
|
|
|
|
if (!tok)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* get the output mode */
|
|
|
|
if (!strncmp(tok, "graph", strlen(arg)))
|
2009-07-05 13:39:21 +08:00
|
|
|
callchain_param.mode = CHAIN_GRAPH_ABS;
|
2009-07-02 23:58:21 +08:00
|
|
|
|
2009-07-03 02:14:33 +08:00
|
|
|
else if (!strncmp(tok, "flat", strlen(arg)))
|
2009-07-05 13:39:21 +08:00
|
|
|
callchain_param.mode = CHAIN_FLAT;
|
|
|
|
|
|
|
|
else if (!strncmp(tok, "fractal", strlen(arg)))
|
|
|
|
callchain_param.mode = CHAIN_GRAPH_REL;
|
|
|
|
|
2009-08-08 08:16:24 +08:00
|
|
|
else if (!strncmp(tok, "none", strlen(arg))) {
|
|
|
|
callchain_param.mode = CHAIN_NONE;
|
|
|
|
callchain = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-07-02 23:58:21 +08:00
|
|
|
else
|
|
|
|
return -1;
|
|
|
|
|
2009-07-03 02:14:33 +08:00
|
|
|
/* get the min percentage */
|
|
|
|
tok = strtok(NULL, ",");
|
|
|
|
if (!tok)
|
2009-07-05 13:39:21 +08:00
|
|
|
goto setup;
|
2009-07-03 02:14:33 +08:00
|
|
|
|
2009-07-05 13:39:21 +08:00
|
|
|
callchain_param.min_percent = strtod(tok, &endptr);
|
2009-07-03 02:14:33 +08:00
|
|
|
if (tok == endptr)
|
|
|
|
return -1;
|
|
|
|
|
2009-07-05 13:39:21 +08:00
|
|
|
setup:
|
|
|
|
if (register_callchain_param(&callchain_param) < 0) {
|
|
|
|
fprintf(stderr, "Can't register callchain params\n");
|
|
|
|
return -1;
|
|
|
|
}
|
2009-07-02 23:58:21 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-05-26 15:17:18 +08:00
|
|
|
static const char * const report_usage[] = {
|
|
|
|
"perf report [<options>] <command>",
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct option options[] = {
|
|
|
|
OPT_STRING('i', "input", &input_name, "file",
|
|
|
|
"input file name"),
|
2009-05-27 06:46:14 +08:00
|
|
|
OPT_BOOLEAN('v', "verbose", &verbose,
|
|
|
|
"be more verbose (show symbol address, etc)"),
|
2009-05-27 00:48:58 +08:00
|
|
|
OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
|
|
|
|
"dump raw trace in ASCII"),
|
2009-08-15 18:26:57 +08:00
|
|
|
OPT_STRING('k', "vmlinux", &vmlinux_name, "file", "vmlinux pathname"),
|
2009-08-19 17:18:26 +08:00
|
|
|
OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
|
2009-07-02 14:09:46 +08:00
|
|
|
OPT_BOOLEAN('m', "modules", &modules,
|
|
|
|
"load module symbols - WARNING: use only with -k and LIVE kernel"),
|
2009-07-11 23:18:37 +08:00
|
|
|
OPT_BOOLEAN('n', "show-nr-samples", &show_nr_samples,
|
|
|
|
"Show a column with the number of samples"),
|
2009-08-07 19:55:24 +08:00
|
|
|
OPT_BOOLEAN('T', "threads", &show_threads,
|
|
|
|
"Show per-thread event counters"),
|
2009-08-10 21:26:32 +08:00
|
|
|
OPT_STRING(0, "pretty", &pretty_printing_style, "key",
|
|
|
|
"pretty printing style key: normal raw"),
|
2009-05-28 16:52:00 +08:00
|
|
|
OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
|
2009-06-18 13:01:03 +08:00
|
|
|
"sort by key(s): pid, comm, dso, symbol, parent"),
|
2009-05-30 00:48:59 +08:00
|
|
|
OPT_BOOLEAN('P', "full-paths", &full_paths,
|
|
|
|
"Don't shorten the pathnames taking into account the cwd"),
|
2009-06-18 13:01:03 +08:00
|
|
|
OPT_STRING('p', "parent", &parent_pattern, "regex",
|
|
|
|
"regex filter to identify parent, see: '--sort parent'"),
|
2009-06-18 20:32:19 +08:00
|
|
|
OPT_BOOLEAN('x', "exclude-other", &exclude_other,
|
|
|
|
"Only display entries with parent-match"),
|
2009-07-16 21:44:29 +08:00
|
|
|
OPT_CALLBACK_DEFAULT('g', "call-graph", NULL, "output_type,min_percent",
|
2009-07-03 02:14:33 +08:00
|
|
|
"Display callchains using output_type and min percent threshold. "
|
2009-07-16 21:44:29 +08:00
|
|
|
"Default: fractal,0.5", &parse_callchain_opt, callchain_default_opt),
|
2009-07-01 06:01:20 +08:00
|
|
|
OPT_STRING('d', "dsos", &dso_list_str, "dso[,dso...]",
|
|
|
|
"only consider symbols in these dsos"),
|
2009-07-01 06:01:21 +08:00
|
|
|
OPT_STRING('C', "comms", &comm_list_str, "comm[,comm...]",
|
|
|
|
"only consider symbols in these comms"),
|
2009-07-01 06:01:22 +08:00
|
|
|
OPT_STRING('S', "symbols", &sym_list_str, "symbol[,symbol...]",
|
|
|
|
"only consider these symbols"),
|
2009-07-11 09:47:28 +08:00
|
|
|
OPT_STRING('w', "column-widths", &col_width_list_str,
|
|
|
|
"width[,width...]",
|
|
|
|
"don't try to adjust column width, use these fixed values"),
|
|
|
|
OPT_STRING('t', "field-separator", &field_sep, "separator",
|
|
|
|
"separator for columns, no spaces will be added between "
|
|
|
|
"columns '.' is reserved."),
|
2009-05-26 15:17:18 +08:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
2009-06-03 16:07:39 +08:00
|
|
|
static void setup_sorting(void)
|
|
|
|
{
|
|
|
|
char *tmp, *tok, *str = strdup(sort_order);
|
|
|
|
|
|
|
|
for (tok = strtok_r(str, ", ", &tmp);
|
|
|
|
tok; tok = strtok_r(NULL, ", ", &tmp)) {
|
|
|
|
if (sort_dimension__add(tok) < 0) {
|
|
|
|
error("Unknown --sort key: `%s'", tok);
|
|
|
|
usage_with_options(report_usage, options);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
free(str);
|
|
|
|
}
|
|
|
|
|
2009-07-01 06:01:21 +08:00
|
|
|
static void setup_list(struct strlist **list, const char *list_str,
|
2009-07-11 23:18:35 +08:00
|
|
|
struct sort_entry *se, const char *list_name,
|
|
|
|
FILE *fp)
|
2009-07-01 06:01:21 +08:00
|
|
|
{
|
|
|
|
if (list_str) {
|
|
|
|
*list = strlist__new(true, list_str);
|
|
|
|
if (!*list) {
|
|
|
|
fprintf(stderr, "problems parsing %s list\n",
|
|
|
|
list_name);
|
|
|
|
exit(129);
|
|
|
|
}
|
2009-07-11 23:18:35 +08:00
|
|
|
if (strlist__nr_entries(*list) == 1) {
|
|
|
|
fprintf(fp, "# %s: %s\n", list_name,
|
|
|
|
strlist__entry(*list, 0)->s);
|
|
|
|
se->elide = true;
|
|
|
|
}
|
2009-07-01 06:01:21 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-01 18:37:06 +08:00
|
|
|
int cmd_report(int argc, const char **argv, const char *prefix __used)
|
2009-05-26 15:17:18 +08:00
|
|
|
{
|
2009-05-29 01:55:04 +08:00
|
|
|
symbol__init();
|
2009-05-26 15:17:18 +08:00
|
|
|
|
|
|
|
page_size = getpagesize();
|
|
|
|
|
2009-06-04 22:24:37 +08:00
|
|
|
argc = parse_options(argc, argv, options, report_usage, 0);
|
2009-05-26 15:17:18 +08:00
|
|
|
|
2009-05-28 02:20:25 +08:00
|
|
|
setup_sorting();
|
|
|
|
|
2009-07-11 23:18:35 +08:00
|
|
|
if (parent_pattern != default_parent_pattern) {
|
2009-06-18 20:32:19 +08:00
|
|
|
sort_dimension__add("parent");
|
2009-07-11 23:18:35 +08:00
|
|
|
sort_parent.elide = 1;
|
|
|
|
} else
|
2009-06-18 20:32:19 +08:00
|
|
|
exclude_other = 0;
|
|
|
|
|
2009-06-04 22:24:37 +08:00
|
|
|
/*
|
|
|
|
* Any (unrecognized) arguments left?
|
|
|
|
*/
|
|
|
|
if (argc)
|
|
|
|
usage_with_options(report_usage, options);
|
|
|
|
|
2009-05-27 15:50:13 +08:00
|
|
|
setup_pager();
|
|
|
|
|
2009-07-11 23:18:35 +08:00
|
|
|
setup_list(&dso_list, dso_list_str, &sort_dso, "dso", stdout);
|
|
|
|
setup_list(&comm_list, comm_list_str, &sort_comm, "comm", stdout);
|
|
|
|
setup_list(&sym_list, sym_list_str, &sort_sym, "symbol", stdout);
|
2009-07-01 06:01:20 +08:00
|
|
|
|
2009-07-11 09:47:28 +08:00
|
|
|
if (field_sep && *field_sep == '.') {
|
|
|
|
fputs("'.' is the only non valid --field-separator argument\n",
|
|
|
|
stderr);
|
|
|
|
exit(129);
|
|
|
|
}
|
|
|
|
|
2009-05-26 15:17:18 +08:00
|
|
|
return __cmd_report();
|
|
|
|
}
|