All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH] perf tools: add on-the-fly ctf conversion
@ 2015-12-15 11:57 John Ogness
  0 siblings, 0 replies; only message in thread
From: John Ogness @ 2015-12-15 11:57 UTC (permalink / raw)
  To: peterz; +Cc: mingo, acme, linux-kernel

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

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2015-12-15 11:57 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-12-15 11:57 [RFC PATCH] perf tools: add on-the-fly ctf conversion John Ogness

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.