[RFC PATCH] perf tools: add on-the-fly ctf conversion

John Ogness
2015-12-15T11:57:22Z

A new argument --format is added to specify an alternate output
format. If perf is compiled with libbabeltrace, support for the
ctf format is available. An example:

perf record --format ctf -e sched:sched_switch ls

Signed-off-by: John Ogness <john.ogness@linutronix.de>
---
Patch against next-20151215.

There definately could be some beautification so the output
is more useful. But I am interested if this approach is
generally how we should do it.

tools/perf/builtin-record.c | 86 ++++++++++++++---
tools/perf/util/data-convert-bt.c | 186 +++++++++++++++++++++++++++++++++++-
tools/perf/util/format-converter.h | 17 ++++
3 files changed, 272 insertions(+), 17 deletions(-)
create mode 100644 tools/perf/util/format-converter.h

diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index 199fc31..49f555c 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -32,6 +32,7 @@
#include "util/parse-branch-options.h"
#include "util/parse-regs-options.h"
#include "util/llvm-utils.h"
+#include "util/format-converter.h"

#include <unistd.h>
#include <sched.h>
@@ -41,6 +42,7 @@
struct record {
struct perf_tool tool;
struct record_opts opts;
+ struct format_converter *format;
u64 bytes_written;
struct perf_data_file file;
struct auxtrace_record *itr;
@@ -53,9 +55,18 @@ struct record {
unsigned long long samples;
};

+
static int record__write(struct record *rec, void *bf, size_t size)
{
- if (perf_data_file__write(rec->session->file, bf, size) < 0) {
+ if (rec->format) {
+ if (rec->format->write &&
+ rec->format->write(rec->evlist, bf, size,
+ rec->format->priv)) {
+ pr_err("failed to write alternate perf data format\n");
+ return -1;
+ }
+
+ } else if (perf_data_file__write(rec->session->file, bf, size) < 0) {
pr_err("failed to write perf data, error: %m\n");
return -1;
}
@@ -153,7 +164,7 @@ static int record__process_auxtrace(struct perf_tool *tool,
size_t padding;
u8 pad[8] = {0};

- if (!perf_data_file__is_pipe(file)) {
+ if (!perf_data_file__is_pipe(file) && !rec->format) {
off_t file_offset;
int fd = perf_data_file__fd(file);
int err;
@@ -497,7 +508,10 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
else
signal(SIGUSR2, SIG_IGN);

- session = perf_session__new(file, false, tool);
+ if (rec->format)
+ session = perf_session__new(NULL, false, tool);
+ else
+ session = perf_session__new(file, false, tool);
if (session == NULL) {
pr_err("Perf session creation failed.\n");
return -1;
@@ -536,7 +550,17 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
if (!rec->evlist->nr_groups)
perf_header__clear_feat(&session->header, HEADER_GROUP_DESC);

- if (file->is_pipe) {
+ if (rec->format) {
+ if (!file->path)
+ file->path = "perf.data";
+ if (rec->format->init &&
+ rec->format->init(rec->evlist, file->path,
+ &rec->format->priv)) {
+ pr_err("Failed to initialize alternate format.\n");
+ err = -1;
+ goto out_child;
+ }
+ } else if (file->is_pipe) {
err = perf_header__write_pipe(fd);
if (err < 0)
goto out_child;
@@ -561,7 +585,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
process_synthesized_event);
if (err < 0) {
pr_err("Couldn't synthesize attrs.\n");
- goto out_child;
+ goto out_format;
}

if (have_tracepoints(&rec->evlist->entries)) {
@@ -577,7 +601,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
process_synthesized_event);
if (err <= 0) {
pr_err("Couldn't record tracing data.\n");
- goto out_child;
+ goto out_format;
}
rec->bytes_written += err;
}
@@ -587,7 +611,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
err = perf_event__synthesize_auxtrace_info(rec->itr, tool,
session, process_synthesized_event);
if (err)
- goto out_delete_session;
+ goto out_format;
}

err = perf_event__synthesize_kernel_mmap(tool, process_synthesized_event,
@@ -613,7 +637,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
process_synthesized_event, opts->sample_address,
opts->proc_map_timeout);
if (err != 0)
- goto out_child;
+ goto out_format;

if (rec->realtime_prio) {
struct sched_param param;
@@ -622,7 +646,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
if (sched_setscheduler(0, SCHED_FIFO, &param)) {
pr_err("Could not set realtime priority.\n");
err = -1;
- goto out_child;
+ goto out_format;
}
}

@@ -673,7 +697,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
if (record__mmap_read_all(rec) < 0) {
auxtrace_snapshot_enabled = 0;
err = -1;
- goto out_child;
+ goto out_format;
}

if (auxtrace_record__snapshot_started) {
@@ -683,7 +707,7 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
if (auxtrace_snapshot_err) {
pr_err("AUX area tracing snapshot failed\n");
err = -1;
- goto out_child;
+ goto out_format;
}
}

@@ -721,12 +745,15 @@ static int __cmd_record(struct record *rec, int argc, const char **argv)
const char *emsg = strerror_r(workload_exec_errno, msg, sizeof(msg));
pr_err("Workload failed: %s\n", emsg);
err = -1;
- goto out_child;
+ goto out_format;
}

if (!quiet)
fprintf(stderr, "[ perf record: Woken up %ld times to write data ]\n", waking);

+out_format:
+ if (rec->format && rec->format->cleanup)
+ rec->format->cleanup(rec->evlist, rec->format->priv);
out_child:
if (forks) {
int exit_status;
@@ -748,7 +775,7 @@ out_child:
/* this will be recalculated during process_buildids() */
rec->samples = 0;

- if (!err && !file->is_pipe) {
+ if (!err && !file->is_pipe && !rec->format) {
rec->session->header.data_size += rec->bytes_written;
file->size = lseek(perf_data_file__fd(file), 0, SEEK_CUR);

@@ -927,6 +954,37 @@ static int parse_clockid(const struct option *opt, const char *str, int unset)
return -1;
}

+static int parse_format(const struct option *opt, const char *str, int unset)
+{
+ struct record *rec = opt->value;
+
+ if (unset) {
+ rec->format = NULL;
+ return 0;
+ }
+
+ /* no arg passed */
+ if (!str) {
+ ui__warning("missing format argument\n");
+ return -1;
+ }
+
+ /* no setting it twice */
+ if (rec->format) {
+ ui__warning("format specified multiple times\n");
+ return -1;
+ }
+
+#ifdef HAVE_LIBBABELTRACE_SUPPORT
+ if (strcasecmp(str, "ctf") == 0) {
+ rec->format = &ctf_format;
+ return 0;
+ }
+#endif
+ ui__warning("unknown format %s, check man page\n", str);
+ return -1;
+}
+
static int record__parse_mmap_pages(const struct option *opt,
const char *str,
int unset __maybe_unused)
@@ -1113,6 +1171,8 @@ struct option __record_options[] = {
"per thread proc mmap processing timeout in ms"),
OPT_BOOLEAN(0, "switch-events", &record.opts.record_switch_events,
"Record context switch events"),
+ OPT_CALLBACK(0, "format", &record, "format", "alternate output format",
+ parse_format),
#ifdef HAVE_LIBBPF_SUPPORT
OPT_STRING(0, "clang-path", &llvm_param.clang_path, "clang path",
"clang binary to use for compiling BPF scriptlets"),
diff --git a/tools/perf/util/data-convert-bt.c b/tools/perf/util/data-convert-bt.c
index 34cd1e4..eb40d74 100644
--- a/tools/perf/util/data-convert-bt.c
+++ b/tools/perf/util/data-convert-bt.c
@@ -7,6 +7,7 @@
* Released under the GPL v2. (and only v2, not any later version)
*/

+#include <sys/utsname.h>
#include <linux/compiler.h>
#include <babeltrace/ctf-writer/writer.h>
#include <babeltrace/ctf-writer/clock.h>
@@ -26,6 +27,7 @@
#include "evlist.h"
#include "evsel.h"
#include "machine.h"
+#include "format-converter.h"

#define pr_N(n, fmt, ...) \
eprintf(n, debug_data_convert, fmt, ##__VA_ARGS__)
@@ -896,6 +898,20 @@ static int ctf_writer__setup_env(struct ctf_writer *cw,
{
struct perf_header *header = &session->header;
struct bt_ctf_writer *writer = cw->writer;
+ struct utsname uts;
+ int ret;
+
+ ret = uname(&uts);
+ if (ret == 0) {
+ if (!header->env.hostname)
+ header->env.hostname = strdup(uts.nodename);
+ if (!header->env.os_release)
+ header->env.os_release = strdup(uts.release);
+ if (!header->env.version)
+ header->env.version = strdup(perf_version_string);
+ if (!header->env.arch)
+ header->env.arch = strdup(uts.machine);
+ }

#define ADD(__n, __v) \
do { \
@@ -903,11 +919,11 @@ do { \
return -1; \
} while (0)

- ADD("host", header->env.hostname);
+ ADD("host", header->env.hostname ?: "unknown");
ADD("sysname", "Linux");
- ADD("release", header->env.os_release);
- ADD("version", header->env.version);
- ADD("machine", header->env.arch);
+ ADD("release", header->env.os_release ?: "unknown");
+ ADD("version", header->env.version ?: "unknown");
+ ADD("machine", header->env.arch ?: "unknown");
ADD("domain", "kernel");
ADD("tracer_name", "perf");

@@ -1183,3 +1199,165 @@ free_writer:
pr_err("Error during conversion setup.\n");
return err;
}
+
+struct ctf_fc {
+ struct convert c;
+
+ void *partial_buf;
+ size_t partial_buf_size;
+
+ void *partial_buf_pos;
+ size_t partial_buf_rem;
+};
+
+static int ctf_fc_init(struct perf_evlist *evlist, const char *path,
+ void **priv)
+{
+ struct perf_session dummy_session;
+ struct ctf_writer *cw;
+ struct ctf_fc *fc;
+ int err = -1;
+
+ fc = calloc(1, sizeof(*fc));
+ if (!fc)
+ goto nomem;
+
+ cw = &fc->c.writer;
+
+ memset(&dummy_session, 0, sizeof(dummy_session));
+
+ if (ctf_writer__init(cw, path))
+ goto free_mem;
+
+ if (ctf_writer__setup_env(cw, &dummy_session))
+ goto free_writer;
+
+ dummy_session.evlist = evlist;
+ if (setup_events(cw, &dummy_session))
+ goto free_writer;
+
+ if (setup_streams(cw, &dummy_session))
+ goto free_writer;
+
+ *priv = fc;
+
+ return 0;
+
+free_writer:
+ ctf_writer__cleanup(cw);
+free_mem:
+ free(fc);
+nomem:
+ pr_err("Error during conversion setup.\n");
+ return err;
+}
+
+static union perf_event *get_next_event(struct ctf_fc *fc, void *buf,
+ size_t size, size_t *inc)
+{
+ union perf_event *event = buf;
+
+ /* deal with existing partial event first */
+ if (fc->partial_buf_pos) {
+ if (size >= fc->partial_buf_rem)
+ size = fc->partial_buf_rem;
+
+ memcpy(fc->partial_buf_pos, buf, size);
+ fc->partial_buf_pos += size;
+ fc->partial_buf_rem -= size;
+ *inc = size;
+
+ /* event still partial */
+ if (fc->partial_buf_rem)
+ return NULL;
+
+ /* we have a full event */
+ fc->partial_buf_pos = NULL;
+ return fc->partial_buf;
+ }
+
+ /* deal with new paritial event */
+ if (size < event->header.size) {
+ /* realloc larger partial buffer if necessary */
+ if (fc->partial_buf_size < event->header.size) {
+ if (fc->partial_buf) {
+ free(fc->partial_buf);
+ fc->partial_buf_size = 0;
+ }
+ fc->partial_buf = malloc(event->header.size);
+ if (!fc->partial_buf) {
+ *inc = 0;
+ return NULL;
+ }
+ fc->partial_buf_size = event->header.size;
+ }
+
+ /* copy over the part of the event we have */
+ memcpy(fc->partial_buf, buf, size);
+ fc->partial_buf_pos = fc->partial_buf + size;
+ fc->partial_buf_rem = event->header.size - size;
+ *inc = size;
+
+ /* we now have a partial event */
+ return NULL;
+ }
+
+ /* full event available */
+ *inc = event->header.size;
+ return event;
+}
+
+static int ctf_fc_write(struct perf_evlist *evlist, void *buf, size_t size,
+ void *priv)
+{
+ struct ctf_fc *fc = priv;
+ struct convert *c = &fc->c;
+ struct perf_sample sample;
+ struct perf_evsel *evsel;
+ union perf_event *event;
+ size_t inc;
+
+ while (size) {
+ event = get_next_event(fc, buf, size, &inc);
+
+ if (!event || event->header.type != PERF_RECORD_SAMPLE)
+ goto skip_event;
+
+ if (perf_evlist__parse_sample(evlist, event, &sample)) {
+ pr_err("Failed to parse event.\n");
+ goto skip_event;
+ }
+
+ evsel = perf_evlist__id2evsel(evlist, sample.id);
+ if (!evsel) {
+ pr_err("Failed to identify event.\n");
+ goto skip_event;
+ }
+
+ if (process_sample_event(&c->tool, event, &sample, evsel, NULL))
+ pr_err("Failed to process event.\n");
+skip_event:
+ size -= inc;
+ buf += inc;
+ }
+
+ return 0;
+}
+
+static int ctf_fc_cleanup(struct perf_evlist *evlist __maybe_unused, void *priv)
+{
+ struct ctf_fc *fc = priv;
+ struct ctf_writer *cw = &fc->c.writer;
+
+ if (ctf_writer__flush_streams(cw))
+ pr_err("Failed to flush events.\n");
+ ctf_writer__cleanup(cw);
+ free(fc);
+ return 0;
+}
+
+struct format_converter ctf_format = {
+ .init = ctf_fc_init,
+ .write = ctf_fc_write,
+ .cleanup = ctf_fc_cleanup,
+};
diff --git a/tools/perf/util/format-converter.h b/tools/perf/util/format-converter.h
new file mode 100644
index 0000000..bb204b1
--- /dev/null
+++ b/tools/perf/util/format-converter.h
@@ -0,0 +1,17 @@
+#ifndef __PERF_FORMAT_CONVERTER_H
+#define __PERF_FORMAT_CONVERTER_H
+
+#include "evlist.h"
+
+struct format_converter {
+ int (*init)(struct perf_evlist *evlist, const char *path, void **priv);
+ int (*write)(struct perf_evlist *evlist, void *buf, size_t size, void *priv);
+ int (*cleanup)(struct perf_evlist *evlist, void *priv);
+ void *priv;
+};
+
+#ifdef HAVE_LIBBABELTRACE_SUPPORT
+extern struct format_converter ctf_format;
+#endif
+
+#endif /* __PERF_FORMAT_CONVERTER_H */
--
1.7.10.4