#include #include #include "util.h" #include "debug.h" #include "builtin.h" #include #include "mem-events.h" #include "session.h" #include "hist.h" #include "tool.h" #include "data.h" #include "sort.h" struct c2c_hists { struct hists hists; struct perf_hpp_list list; }; struct c2c_hist_entry { struct c2c_hists *hists; /* * must be at the end, * because of its callchain dynamic entry */ struct hist_entry he; }; struct perf_c2c { struct perf_tool tool; struct c2c_hists hists; }; static struct perf_c2c c2c; static void *c2c_he_zalloc(size_t size) { struct c2c_hist_entry *c2c_he; c2c_he = zalloc(size + sizeof(*c2c_he)); if (!c2c_he) return NULL; return &c2c_he->he; } static void c2c_he_free(void *he) { struct c2c_hist_entry *c2c_he; c2c_he = container_of(he, struct c2c_hist_entry, he); if (c2c_he->hists) { hists__delete_entries(&c2c_he->hists->hists); free(c2c_he->hists); } free(c2c_he); } static struct hist_entry_ops c2c_entry_ops = { .new = c2c_he_zalloc, .free = c2c_he_free, }; static int c2c_hists__init(struct c2c_hists *hists, const char *sort); static struct hists* he__get_hists(struct hist_entry *he, const char *sort) { struct c2c_hist_entry *c2c_he; struct c2c_hists *hists; int ret; c2c_he = container_of(he, struct c2c_hist_entry, he); if (c2c_he->hists) return &c2c_he->hists->hists; hists = c2c_he->hists = zalloc(sizeof(*hists)); if (!hists) return NULL; ret = c2c_hists__init(hists, sort); if (ret) { free(hists); return NULL; } return &hists->hists; } static int process_sample_event(struct perf_tool *tool __maybe_unused, union perf_event *event, struct perf_sample *sample, struct perf_evsel *evsel __maybe_unused, struct machine *machine) { struct hists *hists = &c2c.hists.hists; struct hist_entry *he; struct addr_location al; struct mem_info *mi, *mi_dup; int ret; if (machine__resolve(machine, &al, sample) < 0) { pr_debug("problem processing %d event, skipping it.\n", event->header.type); return -1; } mi = sample__resolve_mem(sample, &al); if (mi == NULL) return -ENOMEM; mi_dup = memdup(mi, sizeof(*mi)); if (!mi_dup) goto free_mi; he = hists__add_entry_ops(hists, &c2c_entry_ops, &al, NULL, NULL, mi, sample, true); if (he == NULL) goto free_mi_dup; hists__inc_nr_samples(hists, he->filtered); ret = hist_entry__append_callchain(he, sample); if (!ret) { mi = mi_dup; mi_dup = memdup(mi, sizeof(*mi)); if (!mi_dup) goto free_mi; hists = he__get_hists(he, "offset"); if (!hists) goto free_mi_dup; he = hists__add_entry_ops(hists, &c2c_entry_ops, &al, NULL, NULL, mi, sample, true); if (he == NULL) goto free_mi_dup; hists__inc_nr_samples(hists, he->filtered); ret = hist_entry__append_callchain(he, sample); } out: addr_location__put(&al); return ret; free_mi_dup: free(mi_dup); free_mi: free(mi); ret = -ENOMEM; goto out; } static struct perf_c2c c2c = { .tool = { .sample = process_sample_event, .mmap = perf_event__process_mmap, .mmap2 = perf_event__process_mmap2, .comm = perf_event__process_comm, .exit = perf_event__process_exit, .fork = perf_event__process_fork, .lost = perf_event__process_lost, .ordered_events = true, .ordering_requires_timestamps = true, }, }; static const char * const c2c_usage[] = { "perf c2c {record|report}", NULL }; static const char * const __usage_report[] = { "perf c2c report", NULL }; static const char * const *report_c2c_usage = __usage_report; #define C2C_HEADER_MAX 2 struct c2c_header { struct { const char *text; int span; } line[C2C_HEADER_MAX]; }; struct c2c_dimension { struct c2c_header header; const char *name; int width; struct sort_entry *se; int64_t (*cmp)(struct perf_hpp_fmt *fmt, struct hist_entry *, struct hist_entry *); int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hist_entry *he); int (*color)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hist_entry *he); }; struct c2c_fmt { struct perf_hpp_fmt fmt; struct c2c_dimension *dim; }; static int c2c_width(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp __maybe_unused, struct hists *hists __maybe_unused) { struct c2c_fmt *c2c_fmt; struct c2c_dimension *dim; c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); dim = c2c_fmt->dim; return dim->se ? hists__col_len(hists, dim->se->se_width_idx) : c2c_fmt->dim->width; } static int c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hists *hists, int line, int *span) { struct perf_hpp_list *hpp_list = hists->hpp_list; struct c2c_fmt *c2c_fmt; struct c2c_dimension *dim; const char *text = NULL; int width = c2c_width(fmt, hpp, hists); c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); dim = c2c_fmt->dim; if (dim->se) { text = dim->header.line[line].text; /* Use the last line from sort_entry if not defined. */ if (!text && (line == hpp_list->nr_header_lines - 1)) text = dim->se->se_header; } else { text = dim->header.line[line].text; if (*span) { (*span)--; return 0; } else { *span = dim->header.line[line].span; } } if (text == NULL) text = ""; return scnprintf(hpp->buf, hpp->size, "%*s", width, text); } static struct c2c_dimension *dimensions[] = { NULL, }; static void fmt_free(struct perf_hpp_fmt *fmt) { struct c2c_fmt *c2c_fmt; c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); free(c2c_fmt); } static bool fmt_equal(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b) { struct c2c_fmt *c2c_a = container_of(a, struct c2c_fmt, fmt); struct c2c_fmt *c2c_b = container_of(b, struct c2c_fmt, fmt); return c2c_a->dim == c2c_b->dim; } static struct c2c_dimension *get_dimension(const char *name) { unsigned int i; for (i = 0; dimensions[i]; i++) { struct c2c_dimension *dim = dimensions[i]; if (!strcmp(dim->name, name)) return dim; }; return NULL; } static int c2c_se_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, struct hist_entry *he) { struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); struct c2c_dimension *dim = c2c_fmt->dim; size_t len = fmt->user_len; if (!len) len = hists__col_len(he->hists, dim->se->se_width_idx); return dim->se->se_snprintf(he, hpp->buf, hpp->size, len); } static int64_t c2c_se_cmp(struct perf_hpp_fmt *fmt, struct hist_entry *a, struct hist_entry *b) { struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); struct c2c_dimension *dim = c2c_fmt->dim; return dim->se->se_cmp(a, b); } static int64_t c2c_se_collapse(struct perf_hpp_fmt *fmt, struct hist_entry *a, struct hist_entry *b) { struct c2c_fmt *c2c_fmt = container_of(fmt, struct c2c_fmt, fmt); struct c2c_dimension *dim = c2c_fmt->dim; int64_t (*collapse_fn)(struct hist_entry *, struct hist_entry *); collapse_fn = dim->se->se_collapse ?: dim->se->se_cmp; return collapse_fn(a, b); } static struct c2c_fmt *get_format(const char *name) { struct c2c_dimension *dim = get_dimension(name); struct c2c_fmt *c2c_fmt; struct perf_hpp_fmt *fmt; if (!dim) return NULL; c2c_fmt = zalloc(sizeof(*c2c_fmt)); if (!c2c_fmt) return NULL; c2c_fmt->dim = dim; fmt = &c2c_fmt->fmt; INIT_LIST_HEAD(&fmt->list); INIT_LIST_HEAD(&fmt->sort_list); fmt->cmp = dim->se ? c2c_se_cmp : dim->cmp; fmt->sort = dim->se ? c2c_se_cmp : dim->cmp; fmt->entry = dim->se ? c2c_se_entry : dim->entry; fmt->header = c2c_header; fmt->width = c2c_width; fmt->collapse = dim->se ? c2c_se_collapse : dim->cmp; fmt->equal = fmt_equal; fmt->free = fmt_free; return c2c_fmt; } static int c2c_hists__init_output(struct perf_hpp_list *hpp_list, char *name) { struct c2c_fmt *c2c_fmt = get_format(name); if (!c2c_fmt) { reset_dimensions(); return output_field_add(hpp_list, name); } perf_hpp_list__column_register(hpp_list, &c2c_fmt->fmt); return 0; } static int c2c_hists__init_sort(struct perf_hpp_list *hpp_list, char *name) { struct c2c_fmt *c2c_fmt = get_format(name); if (!c2c_fmt) { reset_dimensions(); return sort_dimension__add(hpp_list, name, NULL, 0); } perf_hpp_list__register_sort_field(hpp_list, &c2c_fmt->fmt); return 0; } #define PARSE_LIST(_list, _fn) \ do { \ char *tmp, *tok; \ ret = 0; \ \ if (!_list) \ break; \ \ for (tok = strtok_r((char *)_list, ", ", &tmp); \ tok; tok = strtok_r(NULL, ", ", &tmp)) { \ ret = _fn(hpp_list, tok); \ if (ret == -EINVAL) { \ error("Invalid --fields key: `%s'", tok); \ break; \ } else if (ret == -ESRCH) { \ error("Unknown --fields key: `%s'", tok); \ break; \ } \ } \ } while (0) static int hpp_list__parse(struct perf_hpp_list *hpp_list, const char *output_, const char *sort_) { char *output = output_ ? strdup(output_) : NULL; char *sort = sort_ ? strdup(sort_) : NULL; int ret; PARSE_LIST(output, c2c_hists__init_output); PARSE_LIST(sort, c2c_hists__init_sort); /* copy sort keys to output fields */ perf_hpp__setup_output_field(hpp_list); /* * We dont need other sorting keys other than those * we already specified. It also really slows down * the processing a lot with big number of output * fields, so switching this off for c2c. */ #if 0 /* and then copy output fields to sort keys */ perf_hpp__append_sort_keys(&hists->list); #endif free(output); free(sort); return ret; } static int c2c_hists__init(struct c2c_hists *hists, const char *sort) { __hists__init(&hists->hists, &hists->list); /* * Initialize only with sort fields, we need to resort * later anyway, and that's where we add output fields * as well. */ perf_hpp_list__init(&hists->list); return hpp_list__parse(&hists->list, NULL, sort); } __maybe_unused static int c2c_hists__reinit(struct c2c_hists *c2c_hists, const char *output, const char *sort) { perf_hpp__reset_output_field(&c2c_hists->list); return hpp_list__parse(&c2c_hists->list, output, sort); } static int filter_cb(struct hist_entry *he __maybe_unused) { return 0; } static int resort_cl_cb(struct hist_entry *he) { struct c2c_hist_entry *c2c_he; struct c2c_hists *c2c_hists; c2c_he = container_of(he, struct c2c_hist_entry, he); c2c_hists = c2c_he->hists; if (c2c_hists) { hists__collapse_resort(&c2c_hists->hists, NULL); hists__output_resort_cb(&c2c_hists->hists, NULL, filter_cb); } return 0; } static int perf_c2c__report(int argc, const char **argv) { struct perf_session *session; struct ui_progress prog; struct perf_data_file file = { .mode = PERF_DATA_MODE_READ, }; const struct option c2c_options[] = { OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, "file", "vmlinux pathname"), OPT_INCR('v', "verbose", &verbose, "be more verbose (show counter open errors, etc)"), OPT_STRING('i', "input", &input_name, "file", "the input file to process"), OPT_END() }; int err = 0; argc = parse_options(argc, argv, c2c_options, report_c2c_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (argc) usage_with_options(report_c2c_usage, c2c_options); if (!input_name || !strlen(input_name)) input_name = "perf.data"; file.path = input_name; err = c2c_hists__init(&c2c.hists, "dcacheline"); if (err) { pr_debug("Failed to initialize hists\n"); goto out; } session = perf_session__new(&file, 0, &c2c.tool); if (session == NULL) { pr_debug("No memory for session\n"); goto out; } if (symbol__init(&session->header.env) < 0) goto out_session; /* No pipe support at the moment. */ if (perf_data_file__is_pipe(session->file)) { pr_debug("No pipe support at the moment.\n"); goto out_session; } err = perf_session__process_events(session); if (err) { pr_err("failed to process sample\n"); goto out_session; } ui_progress__init(&prog, c2c.hists.hists.nr_entries, "Sorting..."); hists__collapse_resort(&c2c.hists.hists, NULL); hists__output_resort_cb(&c2c.hists.hists, &prog, resort_cl_cb); ui_progress__finish(); out_session: perf_session__delete(session); out: return err; } static int parse_record_events(const struct option *opt __maybe_unused, const char *str, int unset __maybe_unused) { bool *event_set = (bool *) opt->value; *event_set = true; return perf_mem_events__parse(str); } static const char * const __usage_record[] = { "perf c2c record [] []", "perf c2c record [] -- []", NULL }; static const char * const *record_mem_usage = __usage_record; static int perf_c2c__record(int argc, const char **argv) { int rec_argc, i = 0, j; const char **rec_argv; int ret; bool all_user = false, all_kernel = false; bool event_set = false; struct option options[] = { OPT_CALLBACK('e', "event", &event_set, "event", "event selector. Use 'perf mem record -e list' to list available events", parse_record_events), OPT_INCR('v', "verbose", &verbose, "be more verbose (show counter open errors, etc)"), OPT_BOOLEAN('u', "all-user", &all_user, "collect only user level data"), OPT_BOOLEAN('k', "all-kernel", &all_kernel, "collect only kernel level data"), OPT_UINTEGER('l', "ldlat", &perf_mem_events__loads_ldlat, "setup mem-loads latency"), OPT_END() }; if (perf_mem_events__init()) { pr_err("failed: memory events not supported\n"); return -1; } argc = parse_options(argc, argv, options, record_mem_usage, PARSE_OPT_KEEP_UNKNOWN); rec_argc = argc + 10; /* max number of arguments */ rec_argv = calloc(rec_argc + 1, sizeof(char *)); if (!rec_argv) return -1; rec_argv[i++] = "record"; if (!event_set) { perf_mem_events[PERF_MEM_EVENTS__LOAD].record = true; perf_mem_events[PERF_MEM_EVENTS__STORE].record = true; } if (perf_mem_events[PERF_MEM_EVENTS__LOAD].record) rec_argv[i++] = "-W"; rec_argv[i++] = "-d"; rec_argv[i++] = "--sample-cpu"; for (j = 0; j < PERF_MEM_EVENTS__MAX; j++) { if (!perf_mem_events[j].record) continue; if (!perf_mem_events[j].supported) { pr_err("failed: event '%s' not supported\n", perf_mem_events[j].name); return -1; } rec_argv[i++] = "-e"; rec_argv[i++] = perf_mem_events__name(j); }; if (all_user) rec_argv[i++] = "--all-user"; if (all_kernel) rec_argv[i++] = "--all-kernel"; for (j = 0; j < argc; j++, i++) rec_argv[i] = argv[j]; if (verbose > 0) { pr_debug("calling: "); j = 0; while (rec_argv[j]) { pr_debug("%s ", rec_argv[j]); j++; } pr_debug("\n"); } ret = cmd_record(i, rec_argv, NULL); free(rec_argv); return ret; } int cmd_c2c(int argc, const char **argv, const char *prefix __maybe_unused) { const struct option c2c_options[] = { OPT_INCR('v', "verbose", &verbose, "be more verbose"), OPT_END() }; argc = parse_options(argc, argv, c2c_options, c2c_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (!argc) usage_with_options(c2c_usage, c2c_options); if (!strncmp(argv[0], "rec", 3)) { return perf_c2c__record(argc, argv); } else if (!strncmp(argv[0], "rep", 3)) { return perf_c2c__report(argc, argv); } else { usage_with_options(c2c_usage, c2c_options); } return 0; }