|
@@ -10,8 +10,14 @@
|
|
|
#include "tool.h"
|
|
|
#include "data.h"
|
|
|
|
|
|
+struct c2c_hists {
|
|
|
+ struct hists hists;
|
|
|
+ struct perf_hpp_list list;
|
|
|
+};
|
|
|
+
|
|
|
struct perf_c2c {
|
|
|
- struct perf_tool tool;
|
|
|
+ struct perf_tool tool;
|
|
|
+ struct c2c_hists hists;
|
|
|
};
|
|
|
|
|
|
static struct perf_c2c c2c;
|
|
@@ -28,6 +34,231 @@ static const char * const __usage_report[] = {
|
|
|
|
|
|
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;
|
|
|
+
|
|
|
+ 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;
|
|
|
+
|
|
|
+ c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
|
|
+ return c2c_fmt->dim->width;
|
|
|
+}
|
|
|
+
|
|
|
+static int c2c_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
|
|
|
+ struct hists *hists __maybe_unused, int line, int *span)
|
|
|
+{
|
|
|
+ struct c2c_fmt *c2c_fmt;
|
|
|
+ struct c2c_dimension *dim;
|
|
|
+ int len = c2c_width(fmt, hpp, hists);
|
|
|
+ const char *text;
|
|
|
+
|
|
|
+ c2c_fmt = container_of(fmt, struct c2c_fmt, fmt);
|
|
|
+ dim = c2c_fmt->dim;
|
|
|
+
|
|
|
+ text = dim->header.line[line].text;
|
|
|
+ if (text == NULL)
|
|
|
+ text = "";
|
|
|
+
|
|
|
+ if (*span) {
|
|
|
+ (*span)--;
|
|
|
+ return 0;
|
|
|
+ } else {
|
|
|
+ *span = dim->header.line[line].span;
|
|
|
+ }
|
|
|
+
|
|
|
+ return scnprintf(hpp->buf, hpp->size, "%*s", len, 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 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->cmp;
|
|
|
+ fmt->sort = dim->cmp;
|
|
|
+ fmt->entry = dim->entry;
|
|
|
+ fmt->header = c2c_header;
|
|
|
+ fmt->width = c2c_width;
|
|
|
+ fmt->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)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ 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)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ 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 perf_c2c__report(int argc, const char **argv)
|
|
|
{
|
|
|
struct perf_session *session;
|
|
@@ -52,6 +283,12 @@ static int perf_c2c__report(int argc, const char **argv)
|
|
|
|
|
|
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");
|