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 <[email protected]> --- 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, ¶m)) { 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 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/

