|
@@ -10,9 +10,9 @@
|
|
#include "util/util.h"
|
|
#include "util/util.h"
|
|
|
|
|
|
#include "util/color.h"
|
|
#include "util/color.h"
|
|
-#include "util/list.h"
|
|
|
|
|
|
+#include <linux/list.h>
|
|
#include "util/cache.h"
|
|
#include "util/cache.h"
|
|
-#include "util/rbtree.h"
|
|
|
|
|
|
+#include <linux/rbtree.h>
|
|
#include "util/symbol.h"
|
|
#include "util/symbol.h"
|
|
#include "util/string.h"
|
|
#include "util/string.h"
|
|
#include "util/callchain.h"
|
|
#include "util/callchain.h"
|
|
@@ -46,6 +46,8 @@ static int dump_trace = 0;
|
|
static int verbose;
|
|
static int verbose;
|
|
#define eprintf(x...) do { if (verbose) fprintf(stderr, x); } while (0)
|
|
#define eprintf(x...) do { if (verbose) fprintf(stderr, x); } while (0)
|
|
|
|
|
|
|
|
+static int modules;
|
|
|
|
+
|
|
static int full_paths;
|
|
static int full_paths;
|
|
|
|
|
|
static unsigned long page_size;
|
|
static unsigned long page_size;
|
|
@@ -56,8 +58,17 @@ static char *parent_pattern = default_parent_pattern;
|
|
static regex_t parent_regex;
|
|
static regex_t parent_regex;
|
|
|
|
|
|
static int exclude_other = 1;
|
|
static int exclude_other = 1;
|
|
|
|
+
|
|
|
|
+static char callchain_default_opt[] = "fractal,0.5";
|
|
|
|
+
|
|
static int callchain;
|
|
static int callchain;
|
|
|
|
|
|
|
|
+static
|
|
|
|
+struct callchain_param callchain_param = {
|
|
|
|
+ .mode = CHAIN_GRAPH_ABS,
|
|
|
|
+ .min_percent = 0.5
|
|
|
|
+};
|
|
|
|
+
|
|
static u64 sample_type;
|
|
static u64 sample_type;
|
|
|
|
|
|
struct ip_event {
|
|
struct ip_event {
|
|
@@ -121,6 +132,7 @@ typedef union event_union {
|
|
static LIST_HEAD(dsos);
|
|
static LIST_HEAD(dsos);
|
|
static struct dso *kernel_dso;
|
|
static struct dso *kernel_dso;
|
|
static struct dso *vdso;
|
|
static struct dso *vdso;
|
|
|
|
+static struct dso *hypervisor_dso;
|
|
|
|
|
|
static void dsos__add(struct dso *dso)
|
|
static void dsos__add(struct dso *dso)
|
|
{
|
|
{
|
|
@@ -176,7 +188,7 @@ static void dsos__fprintf(FILE *fp)
|
|
|
|
|
|
static struct symbol *vdso__find_symbol(struct dso *dso, u64 ip)
|
|
static struct symbol *vdso__find_symbol(struct dso *dso, u64 ip)
|
|
{
|
|
{
|
|
- return dso__find_symbol(kernel_dso, ip);
|
|
|
|
|
|
+ return dso__find_symbol(dso, ip);
|
|
}
|
|
}
|
|
|
|
|
|
static int load_kernel(void)
|
|
static int load_kernel(void)
|
|
@@ -187,8 +199,8 @@ static int load_kernel(void)
|
|
if (!kernel_dso)
|
|
if (!kernel_dso)
|
|
return -1;
|
|
return -1;
|
|
|
|
|
|
- err = dso__load_kernel(kernel_dso, vmlinux, NULL, verbose);
|
|
|
|
- if (err) {
|
|
|
|
|
|
+ err = dso__load_kernel(kernel_dso, vmlinux, NULL, verbose, modules);
|
|
|
|
+ if (err <= 0) {
|
|
dso__delete(kernel_dso);
|
|
dso__delete(kernel_dso);
|
|
kernel_dso = NULL;
|
|
kernel_dso = NULL;
|
|
} else
|
|
} else
|
|
@@ -202,6 +214,11 @@ static int load_kernel(void)
|
|
|
|
|
|
dsos__add(vdso);
|
|
dsos__add(vdso);
|
|
|
|
|
|
|
|
+ hypervisor_dso = dso__new("[hypervisor]", 0);
|
|
|
|
+ if (!hypervisor_dso)
|
|
|
|
+ return -1;
|
|
|
|
+ dsos__add(hypervisor_dso);
|
|
|
|
+
|
|
return err;
|
|
return err;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -233,7 +250,7 @@ static u64 map__map_ip(struct map *map, u64 ip)
|
|
return ip - map->start + map->pgoff;
|
|
return ip - map->start + map->pgoff;
|
|
}
|
|
}
|
|
|
|
|
|
-static u64 vdso__map_ip(struct map *map, u64 ip)
|
|
|
|
|
|
+static u64 vdso__map_ip(struct map *map __used, u64 ip)
|
|
{
|
|
{
|
|
return ip;
|
|
return ip;
|
|
}
|
|
}
|
|
@@ -640,7 +657,11 @@ sort__sym_print(FILE *fp, struct hist_entry *self)
|
|
|
|
|
|
if (self->sym) {
|
|
if (self->sym) {
|
|
ret += fprintf(fp, "[%c] %s",
|
|
ret += fprintf(fp, "[%c] %s",
|
|
- self->dso == kernel_dso ? 'k' : '.', self->sym->name);
|
|
|
|
|
|
+ self->dso == kernel_dso ? 'k' :
|
|
|
|
+ self->dso == hypervisor_dso ? 'h' : '.', self->sym->name);
|
|
|
|
+
|
|
|
|
+ if (self->sym->module)
|
|
|
|
+ ret += fprintf(fp, "\t[%s]", self->sym->module->name);
|
|
} else {
|
|
} else {
|
|
ret += fprintf(fp, "%#016llx", (u64)self->ip);
|
|
ret += fprintf(fp, "%#016llx", (u64)self->ip);
|
|
}
|
|
}
|
|
@@ -705,7 +726,7 @@ static LIST_HEAD(hist_entry__sort_list);
|
|
|
|
|
|
static int sort_dimension__add(char *tok)
|
|
static int sort_dimension__add(char *tok)
|
|
{
|
|
{
|
|
- int i;
|
|
|
|
|
|
+ unsigned int i;
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sort_dimensions); i++) {
|
|
for (i = 0; i < ARRAY_SIZE(sort_dimensions); i++) {
|
|
struct sort_dimension *sd = &sort_dimensions[i];
|
|
struct sort_dimension *sd = &sort_dimensions[i];
|
|
@@ -775,8 +796,109 @@ hist_entry__collapse(struct hist_entry *left, struct hist_entry *right)
|
|
return cmp;
|
|
return cmp;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+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;
|
|
|
|
+}
|
|
|
|
+static size_t
|
|
|
|
+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;
|
|
|
|
+ ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent);
|
|
|
|
+ } 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;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+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;
|
|
|
|
+ u64 new_total;
|
|
|
|
+ size_t ret = 0;
|
|
|
|
+ int i;
|
|
|
|
+
|
|
|
|
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
|
|
|
|
+ new_total = self->cumul_hit;
|
|
|
|
+ else
|
|
|
|
+ new_total = total_samples;
|
|
|
|
+
|
|
|
|
+ node = rb_first(&self->rb_root);
|
|
|
|
+ while (node) {
|
|
|
|
+ child = rb_entry(node, struct callchain_node, rb_node);
|
|
|
|
+
|
|
|
|
+ /*
|
|
|
|
+ * The depth mask manages the output of pipes that show
|
|
|
|
+ * the depth. We don't want to keep the pipes of the current
|
|
|
|
+ * level for the last child of this depth
|
|
|
|
+ */
|
|
|
|
+ next = rb_next(node);
|
|
|
|
+ if (!next)
|
|
|
|
+ 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++,
|
|
|
|
+ new_total,
|
|
|
|
+ child->cumul_hit);
|
|
|
|
+ }
|
|
|
|
+ ret += callchain__fprintf_graph(fp, child, new_total,
|
|
|
|
+ depth + 1,
|
|
|
|
+ new_depth_mask | (1 << depth));
|
|
|
|
+ node = next;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
static size_t
|
|
static size_t
|
|
-callchain__fprintf(FILE *fp, struct callchain_node *self, u64 total_samples)
|
|
|
|
|
|
+callchain__fprintf_flat(FILE *fp, struct callchain_node *self,
|
|
|
|
+ u64 total_samples)
|
|
{
|
|
{
|
|
struct callchain_list *chain;
|
|
struct callchain_list *chain;
|
|
size_t ret = 0;
|
|
size_t ret = 0;
|
|
@@ -784,11 +906,18 @@ callchain__fprintf(FILE *fp, struct callchain_node *self, u64 total_samples)
|
|
if (!self)
|
|
if (!self)
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
- ret += callchain__fprintf(fp, self->parent, total_samples);
|
|
|
|
|
|
+ ret += callchain__fprintf_flat(fp, self->parent, total_samples);
|
|
|
|
|
|
|
|
|
|
- list_for_each_entry(chain, &self->val, list)
|
|
|
|
- ret += fprintf(fp, " %p\n", (void *)chain->ip);
|
|
|
|
|
|
+ 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",
|
|
|
|
+ (void *)(long)chain->ip);
|
|
|
|
+ }
|
|
|
|
|
|
return ret;
|
|
return ret;
|
|
}
|
|
}
|
|
@@ -807,8 +936,19 @@ hist_entry_callchain__fprintf(FILE *fp, struct hist_entry *self,
|
|
|
|
|
|
chain = rb_entry(rb_node, struct callchain_node, rb_node);
|
|
chain = rb_entry(rb_node, struct callchain_node, rb_node);
|
|
percent = chain->hit * 100.0 / total_samples;
|
|
percent = chain->hit * 100.0 / total_samples;
|
|
- ret += fprintf(fp, " %6.2f%%\n", percent);
|
|
|
|
- ret += callchain__fprintf(fp, chain, total_samples);
|
|
|
|
|
|
+ switch (callchain_param.mode) {
|
|
|
|
+ case CHAIN_FLAT:
|
|
|
|
+ ret += percent_color_fprintf(fp, " %6.2f%%\n",
|
|
|
|
+ percent);
|
|
|
|
+ ret += callchain__fprintf_flat(fp, chain, total_samples);
|
|
|
|
+ break;
|
|
|
|
+ case CHAIN_GRAPH_ABS: /* Falldown */
|
|
|
|
+ case CHAIN_GRAPH_REL:
|
|
|
|
+ ret += callchain__fprintf_graph(fp, chain,
|
|
|
|
+ total_samples, 1, 1);
|
|
|
|
+ default:
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
ret += fprintf(fp, "\n");
|
|
ret += fprintf(fp, "\n");
|
|
rb_node = rb_next(rb_node);
|
|
rb_node = rb_next(rb_node);
|
|
}
|
|
}
|
|
@@ -826,25 +966,10 @@ hist_entry__fprintf(FILE *fp, struct hist_entry *self, u64 total_samples)
|
|
if (exclude_other && !self->parent)
|
|
if (exclude_other && !self->parent)
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
- if (total_samples) {
|
|
|
|
- double percent = self->count * 100.0 / total_samples;
|
|
|
|
- char *color = PERF_COLOR_NORMAL;
|
|
|
|
-
|
|
|
|
- /*
|
|
|
|
- * We color high-overhead entries in red, mid-overhead
|
|
|
|
- * entries in green - and keep the low overhead places
|
|
|
|
- * normal:
|
|
|
|
- */
|
|
|
|
- if (percent >= 5.0) {
|
|
|
|
- color = PERF_COLOR_RED;
|
|
|
|
- } else {
|
|
|
|
- if (percent >= 0.5)
|
|
|
|
- color = PERF_COLOR_GREEN;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- ret = color_fprintf(fp, color, " %6.2f%%",
|
|
|
|
|
|
+ if (total_samples)
|
|
|
|
+ ret = percent_color_fprintf(fp, " %6.2f%%",
|
|
(self->count * 100.0) / total_samples);
|
|
(self->count * 100.0) / total_samples);
|
|
- } else
|
|
|
|
|
|
+ else
|
|
ret = fprintf(fp, "%12Ld ", self->count);
|
|
ret = fprintf(fp, "%12Ld ", self->count);
|
|
|
|
|
|
list_for_each_entry(se, &hist_entry__sort_list, list) {
|
|
list_for_each_entry(se, &hist_entry__sort_list, list) {
|
|
@@ -923,6 +1048,58 @@ static int call__match(struct symbol *sym)
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static struct symbol **
|
|
|
|
+resolve_callchain(struct thread *thread, struct map *map __used,
|
|
|
|
+ struct ip_callchain *chain, struct hist_entry *entry)
|
|
|
|
+{
|
|
|
|
+ u64 context = PERF_CONTEXT_MAX;
|
|
|
|
+ struct symbol **syms = NULL;
|
|
|
|
+ unsigned int i;
|
|
|
|
+
|
|
|
|
+ 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) {
|
|
|
|
+ case PERF_CONTEXT_HV:
|
|
|
|
+ dso = hypervisor_dso;
|
|
|
|
+ break;
|
|
|
|
+ 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;
|
|
|
|
+}
|
|
|
|
+
|
|
/*
|
|
/*
|
|
* collect histogram counts
|
|
* collect histogram counts
|
|
*/
|
|
*/
|
|
@@ -935,6 +1112,7 @@ hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
|
|
struct rb_node **p = &hist.rb_node;
|
|
struct rb_node **p = &hist.rb_node;
|
|
struct rb_node *parent = NULL;
|
|
struct rb_node *parent = NULL;
|
|
struct hist_entry *he;
|
|
struct hist_entry *he;
|
|
|
|
+ struct symbol **syms = NULL;
|
|
struct hist_entry entry = {
|
|
struct hist_entry entry = {
|
|
.thread = thread,
|
|
.thread = thread,
|
|
.map = map,
|
|
.map = map,
|
|
@@ -948,36 +1126,8 @@ hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
|
|
};
|
|
};
|
|
int cmp;
|
|
int cmp;
|
|
|
|
|
|
- if (sort__has_parent && chain) {
|
|
|
|
- u64 context = PERF_CONTEXT_MAX;
|
|
|
|
- int i;
|
|
|
|
-
|
|
|
|
- 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) {
|
|
|
|
- case PERF_CONTEXT_KERNEL:
|
|
|
|
- dso = kernel_dso;
|
|
|
|
- break;
|
|
|
|
- default:
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- sym = resolve_symbol(thread, NULL, &dso, &ip);
|
|
|
|
-
|
|
|
|
- if (sym && call__match(sym)) {
|
|
|
|
- entry.parent = sym;
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ if ((sort__has_parent || callchain) && chain)
|
|
|
|
+ syms = resolve_callchain(thread, map, chain, &entry);
|
|
|
|
|
|
while (*p != NULL) {
|
|
while (*p != NULL) {
|
|
parent = *p;
|
|
parent = *p;
|
|
@@ -987,8 +1137,10 @@ hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
|
|
|
|
|
|
if (!cmp) {
|
|
if (!cmp) {
|
|
he->count += count;
|
|
he->count += count;
|
|
- if (callchain)
|
|
|
|
- append_chain(&he->callchain, chain);
|
|
|
|
|
|
+ if (callchain) {
|
|
|
|
+ append_chain(&he->callchain, chain, syms);
|
|
|
|
+ free(syms);
|
|
|
|
+ }
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1004,7 +1156,8 @@ hist_entry__add(struct thread *thread, struct map *map, struct dso *dso,
|
|
*he = entry;
|
|
*he = entry;
|
|
if (callchain) {
|
|
if (callchain) {
|
|
callchain_init(&he->callchain);
|
|
callchain_init(&he->callchain);
|
|
- append_chain(&he->callchain, chain);
|
|
|
|
|
|
+ append_chain(&he->callchain, chain, syms);
|
|
|
|
+ free(syms);
|
|
}
|
|
}
|
|
rb_link_node(&he->rb_node, parent, p);
|
|
rb_link_node(&he->rb_node, parent, p);
|
|
rb_insert_color(&he->rb_node, &hist);
|
|
rb_insert_color(&he->rb_node, &hist);
|
|
@@ -1076,14 +1229,15 @@ static void collapse__resort(void)
|
|
|
|
|
|
static struct rb_root output_hists;
|
|
static struct rb_root output_hists;
|
|
|
|
|
|
-static void output__insert_entry(struct hist_entry *he)
|
|
|
|
|
|
+static void output__insert_entry(struct hist_entry *he, u64 min_callchain_hits)
|
|
{
|
|
{
|
|
struct rb_node **p = &output_hists.rb_node;
|
|
struct rb_node **p = &output_hists.rb_node;
|
|
struct rb_node *parent = NULL;
|
|
struct rb_node *parent = NULL;
|
|
struct hist_entry *iter;
|
|
struct hist_entry *iter;
|
|
|
|
|
|
if (callchain)
|
|
if (callchain)
|
|
- sort_chain_to_rbtree(&he->sorted_chain, &he->callchain);
|
|
|
|
|
|
+ callchain_param.sort(&he->sorted_chain, &he->callchain,
|
|
|
|
+ min_callchain_hits, &callchain_param);
|
|
|
|
|
|
while (*p != NULL) {
|
|
while (*p != NULL) {
|
|
parent = *p;
|
|
parent = *p;
|
|
@@ -1099,11 +1253,14 @@ static void output__insert_entry(struct hist_entry *he)
|
|
rb_insert_color(&he->rb_node, &output_hists);
|
|
rb_insert_color(&he->rb_node, &output_hists);
|
|
}
|
|
}
|
|
|
|
|
|
-static void output__resort(void)
|
|
|
|
|
|
+static void output__resort(u64 total_samples)
|
|
{
|
|
{
|
|
struct rb_node *next;
|
|
struct rb_node *next;
|
|
struct hist_entry *n;
|
|
struct hist_entry *n;
|
|
struct rb_root *tree = &hist;
|
|
struct rb_root *tree = &hist;
|
|
|
|
+ u64 min_callchain_hits;
|
|
|
|
+
|
|
|
|
+ min_callchain_hits = total_samples * (callchain_param.min_percent / 100);
|
|
|
|
|
|
if (sort__need_collapse)
|
|
if (sort__need_collapse)
|
|
tree = &collapse_hists;
|
|
tree = &collapse_hists;
|
|
@@ -1115,7 +1272,7 @@ static void output__resort(void)
|
|
next = rb_next(&n->rb_node);
|
|
next = rb_next(&n->rb_node);
|
|
|
|
|
|
rb_erase(&n->rb_node, tree);
|
|
rb_erase(&n->rb_node, tree);
|
|
- output__insert_entry(n);
|
|
|
|
|
|
+ output__insert_entry(n, min_callchain_hits);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1141,7 +1298,7 @@ static size_t output__fprintf(FILE *fp, u64 total_samples)
|
|
|
|
|
|
fprintf(fp, "# ........");
|
|
fprintf(fp, "# ........");
|
|
list_for_each_entry(se, &hist_entry__sort_list, list) {
|
|
list_for_each_entry(se, &hist_entry__sort_list, list) {
|
|
- int i;
|
|
|
|
|
|
+ unsigned int i;
|
|
|
|
|
|
if (exclude_other && (se == &sort_parent))
|
|
if (exclude_other && (se == &sort_parent))
|
|
continue;
|
|
continue;
|
|
@@ -1213,6 +1370,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
|
|
struct map *map = NULL;
|
|
struct map *map = NULL;
|
|
void *more_data = event->ip.__more_data;
|
|
void *more_data = event->ip.__more_data;
|
|
struct ip_callchain *chain = NULL;
|
|
struct ip_callchain *chain = NULL;
|
|
|
|
+ int cpumode;
|
|
|
|
|
|
if (sample_type & PERF_SAMPLE_PERIOD) {
|
|
if (sample_type & PERF_SAMPLE_PERIOD) {
|
|
period = *(u64 *)more_data;
|
|
period = *(u64 *)more_data;
|
|
@@ -1228,7 +1386,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
|
|
(long long)period);
|
|
(long long)period);
|
|
|
|
|
|
if (sample_type & PERF_SAMPLE_CALLCHAIN) {
|
|
if (sample_type & PERF_SAMPLE_CALLCHAIN) {
|
|
- int i;
|
|
|
|
|
|
+ unsigned int i;
|
|
|
|
|
|
chain = (void *)more_data;
|
|
chain = (void *)more_data;
|
|
|
|
|
|
@@ -1256,7 +1414,9 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
|
|
if (comm_list && !strlist__has_entry(comm_list, thread->comm))
|
|
if (comm_list && !strlist__has_entry(comm_list, thread->comm))
|
|
return 0;
|
|
return 0;
|
|
|
|
|
|
- if (event->header.misc & PERF_EVENT_MISC_KERNEL) {
|
|
|
|
|
|
+ cpumode = event->header.misc & PERF_EVENT_MISC_CPUMODE_MASK;
|
|
|
|
+
|
|
|
|
+ if (cpumode == PERF_EVENT_MISC_KERNEL) {
|
|
show = SHOW_KERNEL;
|
|
show = SHOW_KERNEL;
|
|
level = 'k';
|
|
level = 'k';
|
|
|
|
|
|
@@ -1264,7 +1424,7 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
|
|
|
|
|
|
dprintf(" ...... dso: %s\n", dso->name);
|
|
dprintf(" ...... dso: %s\n", dso->name);
|
|
|
|
|
|
- } else if (event->header.misc & PERF_EVENT_MISC_USER) {
|
|
|
|
|
|
+ } else if (cpumode == PERF_EVENT_MISC_USER) {
|
|
|
|
|
|
show = SHOW_USER;
|
|
show = SHOW_USER;
|
|
level = '.';
|
|
level = '.';
|
|
@@ -1272,6 +1432,9 @@ process_sample_event(event_t *event, unsigned long offset, unsigned long head)
|
|
} else {
|
|
} else {
|
|
show = SHOW_HV;
|
|
show = SHOW_HV;
|
|
level = 'H';
|
|
level = 'H';
|
|
|
|
+
|
|
|
|
+ dso = hypervisor_dso;
|
|
|
|
+
|
|
dprintf(" ...... dso: [hypervisor]\n");
|
|
dprintf(" ...... dso: [hypervisor]\n");
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1534,9 +1697,19 @@ static int __cmd_report(void)
|
|
|
|
|
|
sample_type = perf_header__sample_type();
|
|
sample_type = perf_header__sample_type();
|
|
|
|
|
|
- if (sort__has_parent && !(sample_type & PERF_SAMPLE_CALLCHAIN)) {
|
|
|
|
- fprintf(stderr, "selected --sort parent, but no callchain data\n");
|
|
|
|
- exit(-1);
|
|
|
|
|
|
+ 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) {
|
|
|
|
+ fprintf(stderr, "selected -c but no callchain data."
|
|
|
|
+ " Did you call perf record without"
|
|
|
|
+ " -g?\n");
|
|
|
|
+ exit(-1);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
if (load_kernel() < 0) {
|
|
if (load_kernel() < 0) {
|
|
@@ -1619,7 +1792,7 @@ more:
|
|
if (offset + head >= header->data_offset + header->data_size)
|
|
if (offset + head >= header->data_offset + header->data_size)
|
|
goto done;
|
|
goto done;
|
|
|
|
|
|
- if (offset + head < stat.st_size)
|
|
|
|
|
|
+ if (offset + head < (unsigned long)stat.st_size)
|
|
goto more;
|
|
goto more;
|
|
|
|
|
|
done:
|
|
done:
|
|
@@ -1643,12 +1816,58 @@ done:
|
|
dsos__fprintf(stdout);
|
|
dsos__fprintf(stdout);
|
|
|
|
|
|
collapse__resort();
|
|
collapse__resort();
|
|
- output__resort();
|
|
|
|
|
|
+ output__resort(total);
|
|
output__fprintf(stdout, total);
|
|
output__fprintf(stdout, total);
|
|
|
|
|
|
return rc;
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static int
|
|
|
|
+parse_callchain_opt(const struct option *opt __used, const char *arg,
|
|
|
|
+ int unset __used)
|
|
|
|
+{
|
|
|
|
+ char *tok;
|
|
|
|
+ char *endptr;
|
|
|
|
+
|
|
|
|
+ callchain = 1;
|
|
|
|
+
|
|
|
|
+ if (!arg)
|
|
|
|
+ return 0;
|
|
|
|
+
|
|
|
|
+ tok = strtok((char *)arg, ",");
|
|
|
|
+ if (!tok)
|
|
|
|
+ return -1;
|
|
|
|
+
|
|
|
|
+ /* get the output mode */
|
|
|
|
+ if (!strncmp(tok, "graph", strlen(arg)))
|
|
|
|
+ callchain_param.mode = CHAIN_GRAPH_ABS;
|
|
|
|
+
|
|
|
|
+ else if (!strncmp(tok, "flat", strlen(arg)))
|
|
|
|
+ callchain_param.mode = CHAIN_FLAT;
|
|
|
|
+
|
|
|
|
+ else if (!strncmp(tok, "fractal", strlen(arg)))
|
|
|
|
+ callchain_param.mode = CHAIN_GRAPH_REL;
|
|
|
|
+
|
|
|
|
+ else
|
|
|
|
+ return -1;
|
|
|
|
+
|
|
|
|
+ /* get the min percentage */
|
|
|
|
+ tok = strtok(NULL, ",");
|
|
|
|
+ if (!tok)
|
|
|
|
+ goto setup;
|
|
|
|
+
|
|
|
|
+ callchain_param.min_percent = strtod(tok, &endptr);
|
|
|
|
+ if (tok == endptr)
|
|
|
|
+ return -1;
|
|
|
|
+
|
|
|
|
+setup:
|
|
|
|
+ if (register_callchain_param(&callchain_param) < 0) {
|
|
|
|
+ fprintf(stderr, "Can't register callchain params\n");
|
|
|
|
+ return -1;
|
|
|
|
+ }
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
static const char * const report_usage[] = {
|
|
static const char * const report_usage[] = {
|
|
"perf report [<options>] <command>",
|
|
"perf report [<options>] <command>",
|
|
NULL
|
|
NULL
|
|
@@ -1662,6 +1881,8 @@ static const struct option options[] = {
|
|
OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
|
|
OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
|
|
"dump raw trace in ASCII"),
|
|
"dump raw trace in ASCII"),
|
|
OPT_STRING('k', "vmlinux", &vmlinux, "file", "vmlinux pathname"),
|
|
OPT_STRING('k', "vmlinux", &vmlinux, "file", "vmlinux pathname"),
|
|
|
|
+ OPT_BOOLEAN('m', "modules", &modules,
|
|
|
|
+ "load module symbols - WARNING: use only with -k and LIVE kernel"),
|
|
OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
|
|
OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
|
|
"sort by key(s): pid, comm, dso, symbol, parent"),
|
|
"sort by key(s): pid, comm, dso, symbol, parent"),
|
|
OPT_BOOLEAN('P', "full-paths", &full_paths,
|
|
OPT_BOOLEAN('P', "full-paths", &full_paths,
|
|
@@ -1670,7 +1891,9 @@ static const struct option options[] = {
|
|
"regex filter to identify parent, see: '--sort parent'"),
|
|
"regex filter to identify parent, see: '--sort parent'"),
|
|
OPT_BOOLEAN('x', "exclude-other", &exclude_other,
|
|
OPT_BOOLEAN('x', "exclude-other", &exclude_other,
|
|
"Only display entries with parent-match"),
|
|
"Only display entries with parent-match"),
|
|
- OPT_BOOLEAN('c', "callchain", &callchain, "Display callchains"),
|
|
|
|
|
|
+ OPT_CALLBACK_DEFAULT('c', "callchain", NULL, "output_type,min_percent",
|
|
|
|
+ "Display callchains using output_type and min percent threshold. "
|
|
|
|
+ "Default: flat,0", &parse_callchain_opt, callchain_default_opt),
|
|
OPT_STRING('d', "dsos", &dso_list_str, "dso[,dso...]",
|
|
OPT_STRING('d', "dsos", &dso_list_str, "dso[,dso...]",
|
|
"only consider symbols in these dsos"),
|
|
"only consider symbols in these dsos"),
|
|
OPT_STRING('C', "comms", &comm_list_str, "comm[,comm...]",
|
|
OPT_STRING('C', "comms", &comm_list_str, "comm[,comm...]",
|
|
@@ -1708,7 +1931,7 @@ static void setup_list(struct strlist **list, const char *list_str,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
-int cmd_report(int argc, const char **argv, const char *prefix)
|
|
|
|
|
|
+int cmd_report(int argc, const char **argv, const char *prefix __used)
|
|
{
|
|
{
|
|
symbol__init();
|
|
symbol__init();
|
|
|
|
|