| #include <linux/compiler.h> |
| #include <linux/kernel.h> |
| #include "util.h" |
| #include "debug.h" |
| #include "builtin.h" |
| #include <subcmd/parse-options.h> |
| #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_stats stats; |
| }; |
| |
| struct c2c_hist_entry { |
| struct c2c_hists *hists; |
| struct c2c_stats stats; |
| /* |
| * 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 c2c_hists* |
| he__get_c2c_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 = c2c_he->hists = zalloc(sizeof(*hists)); |
| if (!hists) |
| return NULL; |
| |
| ret = c2c_hists__init(hists, sort); |
| if (ret) { |
| free(hists); |
| return NULL; |
| } |
| |
| return 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 c2c_hists *c2c_hists = &c2c.hists; |
| struct c2c_hist_entry *c2c_he; |
| struct c2c_stats stats = { .nr_entries = 0, }; |
| 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; |
| |
| c2c_decode_stats(&stats, mi); |
| |
| he = hists__add_entry_ops(&c2c_hists->hists, &c2c_entry_ops, |
| &al, NULL, NULL, mi, |
| sample, true); |
| if (he == NULL) |
| goto free_mi_dup; |
| |
| c2c_he = container_of(he, struct c2c_hist_entry, he); |
| c2c_add_stats(&c2c_he->stats, &stats); |
| c2c_add_stats(&c2c_hists->stats, &stats); |
| |
| hists__inc_nr_samples(&c2c_hists->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; |
| |
| c2c_hists = he__get_c2c_hists(he, "offset"); |
| if (!c2c_hists) |
| goto free_mi_dup; |
| |
| he = hists__add_entry_ops(&c2c_hists->hists, &c2c_entry_ops, |
| &al, NULL, NULL, mi, |
| sample, true); |
| if (he == NULL) |
| goto free_mi_dup; |
| |
| c2c_he = container_of(he, struct c2c_hist_entry, he); |
| c2c_add_stats(&c2c_he->stats, &stats); |
| c2c_add_stats(&c2c_hists->stats, &stats); |
| |
| hists__inc_nr_samples(&c2c_hists->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); |
| } |
| |
| #define HEADER_LOW(__h) \ |
| { \ |
| .line[1] = { \ |
| .text = __h, \ |
| }, \ |
| } |
| |
| #define HEADER_BOTH(__h0, __h1) \ |
| { \ |
| .line[0] = { \ |
| .text = __h0, \ |
| }, \ |
| .line[1] = { \ |
| .text = __h1, \ |
| }, \ |
| } |
| |
| #define HEADER_SPAN(__h0, __h1, __s) \ |
| { \ |
| .line[0] = { \ |
| .text = __h0, \ |
| .span = __s, \ |
| }, \ |
| .line[1] = { \ |
| .text = __h1, \ |
| }, \ |
| } |
| |
| #define HEADER_SPAN_LOW(__h) \ |
| { \ |
| .line[1] = { \ |
| .text = __h, \ |
| }, \ |
| } |
| |
| 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 [<options>] [<command>]", |
| "perf c2c record [<options>] -- <command> [<options>]", |
| 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; |
| } |