eac23d1c38
This patch changes perf report to ask for the ID info on all events be default if recording from multiple CPUs. Perf report, annotate and diff will now process the events in order if the kernel is able to provide timestamps on all events. This ensures that events such as COMM and MMAP which are necessary to correctly interpret samples are processed prior to those samples so that they are attributed correctly. Before: # perf record ./cachetest # perf report # Events: 6K cycles # # Overhead Command Shared Object Symbol # ........ ....... ................. ............................... # 74.11% :3259 [unknown] [k] 0x4a6c 1.50% cachetest ld-2.11.2.so [.] 0x1777c 1.46% :3259 [kernel.kallsyms] [k] .perf_event_mmap_ctx 1.25% :3259 [kernel.kallsyms] [k] restore 0.74% :3259 [kernel.kallsyms] [k] ._raw_spin_lock 0.71% :3259 [kernel.kallsyms] [k] .filemap_fault 0.66% :3259 [kernel.kallsyms] [k] .memset 0.54% cachetest [kernel.kallsyms] [k] .sha_transform 0.54% :3259 [kernel.kallsyms] [k] .copy_4K_page 0.54% :3259 [kernel.kallsyms] [k] .find_get_page 0.52% :3259 [kernel.kallsyms] [k] .trace_hardirqs_off 0.50% :3259 [kernel.kallsyms] [k] .__do_fault <SNIP> After: # perf report # Events: 6K cycles # # Overhead Command Shared Object Symbol # ........ ....... ................. ............................... # 44.28% cachetest cachetest [.] sumArrayNaive 22.53% cachetest cachetest [.] sumArrayOptimal 6.59% cachetest ld-2.11.2.so [.] 0x1777c 2.13% cachetest [unknown] [k] 0x340 1.46% cachetest [kernel.kallsyms] [k] .perf_event_mmap_ctx 1.25% cachetest [kernel.kallsyms] [k] restore 0.74% cachetest [kernel.kallsyms] [k] ._raw_spin_lock 0.71% cachetest [kernel.kallsyms] [k] .filemap_fault 0.66% cachetest [kernel.kallsyms] [k] .memset 0.54% cachetest [kernel.kallsyms] [k] .copy_4K_page 0.54% cachetest [kernel.kallsyms] [k] .find_get_page 0.54% cachetest [kernel.kallsyms] [k] .sha_transform 0.52% cachetest [kernel.kallsyms] [k] .trace_hardirqs_off 0.50% cachetest [kernel.kallsyms] [k] .__do_fault <SNIP> Cc: Frederic Weisbecker <fweisbec@gmail.com> Cc: Ingo Molnar <mingo@elte.hu> Cc: Paul Mackerras <paulus@samba.org> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> LKML-Reference: <1291872833-839-1-git-send-email-imunsie@au1.ibm.com> Signed-off-by: Ian Munsie <imunsie@au1.ibm.com> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
230 lines
6.1 KiB
C
230 lines
6.1 KiB
C
/*
|
|
* builtin-diff.c
|
|
*
|
|
* Builtin diff command: Analyze two perf.data input files, look up and read
|
|
* DSOs and symbol information, sort them and produce a diff.
|
|
*/
|
|
#include "builtin.h"
|
|
|
|
#include "util/debug.h"
|
|
#include "util/event.h"
|
|
#include "util/hist.h"
|
|
#include "util/session.h"
|
|
#include "util/sort.h"
|
|
#include "util/symbol.h"
|
|
#include "util/util.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
static char const *input_old = "perf.data.old",
|
|
*input_new = "perf.data";
|
|
static char diff__default_sort_order[] = "dso,symbol";
|
|
static bool force;
|
|
static bool show_displacement;
|
|
|
|
static int hists__add_entry(struct hists *self,
|
|
struct addr_location *al, u64 period)
|
|
{
|
|
if (__hists__add_entry(self, al, NULL, period) != NULL)
|
|
return 0;
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static int diff__process_sample_event(event_t *event,
|
|
struct sample_data *sample,
|
|
struct perf_session *session)
|
|
{
|
|
struct addr_location al;
|
|
|
|
if (event__preprocess_sample(event, session, &al, sample, NULL) < 0) {
|
|
pr_warning("problem processing %d event, skipping it.\n",
|
|
event->header.type);
|
|
return -1;
|
|
}
|
|
|
|
if (al.filtered || al.sym == NULL)
|
|
return 0;
|
|
|
|
if (hists__add_entry(&session->hists, &al, sample->period)) {
|
|
pr_warning("problem incrementing symbol period, skipping event\n");
|
|
return -1;
|
|
}
|
|
|
|
session->hists.stats.total_period += sample->period;
|
|
return 0;
|
|
}
|
|
|
|
static struct perf_event_ops event_ops = {
|
|
.sample = diff__process_sample_event,
|
|
.mmap = event__process_mmap,
|
|
.comm = event__process_comm,
|
|
.exit = event__process_task,
|
|
.fork = event__process_task,
|
|
.lost = event__process_lost,
|
|
.ordered_samples = true,
|
|
.ordering_requires_timestamps = true,
|
|
};
|
|
|
|
static void perf_session__insert_hist_entry_by_name(struct rb_root *root,
|
|
struct hist_entry *he)
|
|
{
|
|
struct rb_node **p = &root->rb_node;
|
|
struct rb_node *parent = NULL;
|
|
struct hist_entry *iter;
|
|
|
|
while (*p != NULL) {
|
|
parent = *p;
|
|
iter = rb_entry(parent, struct hist_entry, rb_node);
|
|
if (hist_entry__cmp(he, iter) < 0)
|
|
p = &(*p)->rb_left;
|
|
else
|
|
p = &(*p)->rb_right;
|
|
}
|
|
|
|
rb_link_node(&he->rb_node, parent, p);
|
|
rb_insert_color(&he->rb_node, root);
|
|
}
|
|
|
|
static void hists__resort_entries(struct hists *self)
|
|
{
|
|
unsigned long position = 1;
|
|
struct rb_root tmp = RB_ROOT;
|
|
struct rb_node *next = rb_first(&self->entries);
|
|
|
|
while (next != NULL) {
|
|
struct hist_entry *n = rb_entry(next, struct hist_entry, rb_node);
|
|
|
|
next = rb_next(&n->rb_node);
|
|
rb_erase(&n->rb_node, &self->entries);
|
|
n->position = position++;
|
|
perf_session__insert_hist_entry_by_name(&tmp, n);
|
|
}
|
|
|
|
self->entries = tmp;
|
|
}
|
|
|
|
static void hists__set_positions(struct hists *self)
|
|
{
|
|
hists__output_resort(self);
|
|
hists__resort_entries(self);
|
|
}
|
|
|
|
static struct hist_entry *hists__find_entry(struct hists *self,
|
|
struct hist_entry *he)
|
|
{
|
|
struct rb_node *n = self->entries.rb_node;
|
|
|
|
while (n) {
|
|
struct hist_entry *iter = rb_entry(n, struct hist_entry, rb_node);
|
|
int64_t cmp = hist_entry__cmp(he, iter);
|
|
|
|
if (cmp < 0)
|
|
n = n->rb_left;
|
|
else if (cmp > 0)
|
|
n = n->rb_right;
|
|
else
|
|
return iter;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void hists__match(struct hists *older, struct hists *newer)
|
|
{
|
|
struct rb_node *nd;
|
|
|
|
for (nd = rb_first(&newer->entries); nd; nd = rb_next(nd)) {
|
|
struct hist_entry *pos = rb_entry(nd, struct hist_entry, rb_node);
|
|
pos->pair = hists__find_entry(older, pos);
|
|
}
|
|
}
|
|
|
|
static int __cmd_diff(void)
|
|
{
|
|
int ret, i;
|
|
struct perf_session *session[2];
|
|
|
|
session[0] = perf_session__new(input_old, O_RDONLY, force, false, &event_ops);
|
|
session[1] = perf_session__new(input_new, O_RDONLY, force, false, &event_ops);
|
|
if (session[0] == NULL || session[1] == NULL)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < 2; ++i) {
|
|
ret = perf_session__process_events(session[i], &event_ops);
|
|
if (ret)
|
|
goto out_delete;
|
|
}
|
|
|
|
hists__output_resort(&session[1]->hists);
|
|
if (show_displacement)
|
|
hists__set_positions(&session[0]->hists);
|
|
|
|
hists__match(&session[0]->hists, &session[1]->hists);
|
|
hists__fprintf(&session[1]->hists, &session[0]->hists,
|
|
show_displacement, stdout);
|
|
out_delete:
|
|
for (i = 0; i < 2; ++i)
|
|
perf_session__delete(session[i]);
|
|
return ret;
|
|
}
|
|
|
|
static const char * const diff_usage[] = {
|
|
"perf diff [<options>] [old_file] [new_file]",
|
|
NULL,
|
|
};
|
|
|
|
static const struct option options[] = {
|
|
OPT_INCR('v', "verbose", &verbose,
|
|
"be more verbose (show symbol address, etc)"),
|
|
OPT_BOOLEAN('M', "displacement", &show_displacement,
|
|
"Show position displacement relative to baseline"),
|
|
OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
|
|
"dump raw trace in ASCII"),
|
|
OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
|
|
OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules,
|
|
"load module symbols - WARNING: use only with -k and LIVE kernel"),
|
|
OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
|
|
"only consider symbols in these dsos"),
|
|
OPT_STRING('C', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
|
|
"only consider symbols in these comms"),
|
|
OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
|
|
"only consider these symbols"),
|
|
OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
|
|
"sort by key(s): pid, comm, dso, symbol, parent"),
|
|
OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator",
|
|
"separator for columns, no spaces will be added between "
|
|
"columns '.' is reserved."),
|
|
OPT_END()
|
|
};
|
|
|
|
int cmd_diff(int argc, const char **argv, const char *prefix __used)
|
|
{
|
|
sort_order = diff__default_sort_order;
|
|
argc = parse_options(argc, argv, options, diff_usage, 0);
|
|
if (argc) {
|
|
if (argc > 2)
|
|
usage_with_options(diff_usage, options);
|
|
if (argc == 2) {
|
|
input_old = argv[0];
|
|
input_new = argv[1];
|
|
} else
|
|
input_new = argv[0];
|
|
} else if (symbol_conf.default_guest_vmlinux_name ||
|
|
symbol_conf.default_guest_kallsyms) {
|
|
input_old = "perf.data.host";
|
|
input_new = "perf.data.guest";
|
|
}
|
|
|
|
symbol_conf.exclude_other = false;
|
|
if (symbol__init() < 0)
|
|
return -1;
|
|
|
|
setup_sorting(diff_usage, options);
|
|
setup_pager();
|
|
|
|
sort_entry__setup_elide(&sort_dso, symbol_conf.dso_list, "dso", NULL);
|
|
sort_entry__setup_elide(&sort_comm, symbol_conf.comm_list, "comm", NULL);
|
|
sort_entry__setup_elide(&sort_sym, symbol_conf.sym_list, "symbol", NULL);
|
|
|
|
return __cmd_diff();
|
|
}
|