forked from Minki/linux
21ef97f05a
If we are running the new perf on an old kernel without support for sample_id_all, we should fall back to the old unordered processing of events. If we didn't than we would *always* process events without timestamps out of order, whether or not we hit a reordering race. In other words, instead of there being a chance of not attributing samples correctly, we would guarantee that samples would not be attributed. While processing all events without timestamps before events with timestamps may seem like an intuitive solution, it falls down as PERF_RECORD_EXIT events would also be processed before any samples. Even with a workaround for that case, samples before/after an exec would not be attributed correctly. This patch allows commands to indicate whether they need to fall back to unordered processing, so that commands that do not care about timestamps on every event will not be affected. If we do fallback, this will print out a warning if report -D was invoked. This patch adds the test in perf_session__new so that we only need to test once per session. Commands that do not use an event_ops (such as record and top) can simply pass NULL in it's place. Acked-by: Thomas Gleixner <tglx@linutronix.de> Cc: Frederic Weisbecker <fweisbec@gmail.com> Cc: Ingo Molnar <mingo@elte.hu> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> LKML-Reference: <1291951882-sup-6069@au1.ibm.com> Signed-off-by: Ian Munsie <imunsie@au1.ibm.com> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
229 lines
6.1 KiB
C
229 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,
|
|
};
|
|
|
|
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();
|
|
}
|