Received: by 2002:a05:6a10:9848:0:0:0:0 with SMTP id x8csp324446pxf; Wed, 24 Mar 2021 06:09:12 -0700 (PDT) X-Google-Smtp-Source: ABdhPJzSDnpHmaACKCX1dDe/Pu/6zt0X8YJAbyYcf20GnjVKRMvQEpjsCUWgSVGGP3bKvI4LRg3e X-Received: by 2002:a17:907:2b03:: with SMTP id gc3mr3630626ejc.448.1616591351817; Wed, 24 Mar 2021 06:09:11 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1616591351; cv=none; d=google.com; s=arc-20160816; b=cLu+TNV5OBPwCZlBiC13533aMuBfT3S2r86VqyEaEsSrb4k0nVSrWSGvQJIN9MFtTz VJxIFoPWtr+zQJ3TUhZSV0heTEeaW3Ipga+ynmDwY5C6eOVKSzA/IgZnDexRYu6MutgL /424yg9tEroQStme72axy5xK1vdNwIo3ZUOjFS8RnJ8Huv18FFI5A8QyJhREn5AroGUq RdGGdbyr+OsnZR7/zWMebbwBMHVVJlJRrkcFqmbSdCBTCetL9Y7FbuvZuVQAqODmviS6 WZ6Fhkej7NwTa95Jx0Ax7A0xdpFIaKWNOVIUm/ZkKASEF3ib3HVNAhE6GWM86gR0RgHn JEzg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:content-language :mime-version:user-agent:date:message-id:to:subject:from :dkim-signature; bh=7G8nLDTDzPAMf/bvFkOWAqqMkcbLMjpmpKRXerw14yU=; b=tymzYZTsaGcAX2746ESMMkKZSmKdqt+sJlmJFf1wGGRuafyoYXaECUdQ66/7BJPleZ JMo5fg0feRBqilpUEcWWMmcRjj4wVQEjA0tO0twf/kYxB+qvATaWyfZxMF+ZktgX8A26 LKumtWjk6LuvuBhaCezaBJLrJeRMJyHbp9EFqy0r+ovfZdEgWW172UswLhFDB2b9glWL yaeoxTaZWlFYcfE8WsnG+e8ktPKguNpKIQNxfIl4bthVbVgWiKQE1cKl9vH1M4Okbn4F 9y6e1KgxWXNWxHTRuCC/HA7RPTteMMbZbCoOMBsuErF/ewHcE3i8NpYy5WTtEsYZL7pz L9VQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=fail (test mode) header.i=@codeweavers.com header.s=6377696661 header.b=F3imPyBH; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=codeweavers.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id bx20si1758429edb.215.2021.03.24.06.08.46; Wed, 24 Mar 2021 06:09:11 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=fail (test mode) header.i=@codeweavers.com header.s=6377696661 header.b=F3imPyBH; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=codeweavers.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S234497AbhCXNHP (ORCPT + 99 others); Wed, 24 Mar 2021 09:07:15 -0400 Received: from mail.codeweavers.com ([50.203.203.244]:40350 "EHLO mail.codeweavers.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234381AbhCXNG6 (ORCPT ); Wed, 24 Mar 2021 09:06:58 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=codeweavers.com; s=6377696661; h=Content-Transfer-Encoding:Content-Type: MIME-Version:Date:Message-ID:To:Subject:From:Sender:Reply-To:Cc:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:In-Reply-To:References:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=7G8nLDTDzPAMf/bvFkOWAqqMkcbLMjpmpKRXerw14yU=; b=F3imPyBHLOJ9CUqwBeVtvzJ4UQ qptziXgtIorStMCfczVEvEndI9nsruqdYt6Ifll30fuI5lRmQhasZi0A2+AxMr/dnZ2wtu4G3Y91P gkfPZ5XSGlTYuFlMWcGma8FbFT48h/nvYHUe5X4O2E3gnMZfhKFpQ2ofF5Ung3yRxS2A=; Received: from [10.69.141.136] by mail.codeweavers.com with esmtpsa (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1lP3Do-0006Ha-AE; Wed, 24 Mar 2021 08:06:54 -0500 From: Nicholas Fraser Subject: [PATCH] perf data: export to JSON To: Peter Zijlstra , Ingo Molnar , Arnaldo Carvalho de Melo , Mark Rutland , Alexander Shishkin , Jiri Olsa , Namhyung Kim , Nicholas Fraser , Ian Rogers , Stephane Eranian , Tan Xiaojun , linux-kernel@vger.kernel.org Message-ID: <4687bbe5-4ff3-af3a-fcec-06d8bfe5591c@codeweavers.com> Date: Wed, 24 Mar 2021 09:06:50 -0400 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.8.1 MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Language: en-US Content-Transfer-Encoding: 7bit Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This adds preliminary support to dump the contents of a perf.data file to human-readable JSON. The "perf data" command currently only supports exporting to Common Trace Format and it doesn't do symbol resolution among other things. Dumping to JSON means the data can be trivially parsed by anything without any dependencies (besides a JSON parser.) We use this to import the data into a tool on Windows where integrating perf or libbabeltrace is impractical. The JSON is encoded using some trivial fprintf() commands; there is no dependency on any JSON library. It currently only outputs samples. Other stuff like processes and mappings could easily be added as needed. The output is of course huge but it compresses well enough. Use it like this: perf data convert --to-json out.json Here's what the output looks like: { "linux-perf-json-version": 1, "samples": [ { "timestamp": 3074717308597, "pid": 8604, "tid": 8604, "comm": "sh", "callchain": [ { "ip": "0x7f1e0deb2d36", "symbol": "__strcmp_avx2", "dso": "libc-2.33.so" }, { "ip": "0x7f1e0dd7f49f", "symbol": "__gconv_find_transform", "dso": "libc-2.33.so" }, { "ip": "0x7f1e0de0b71c", "symbol": "__wcsmbs_load_conv", "dso": "libc-2.33.so" } ] }, ... ] } Signed-off-by: Nicholas Fraser --- tools/perf/Documentation/perf-data.txt | 5 +- tools/perf/builtin-data.c | 39 ++++- tools/perf/util/Build | 1 + tools/perf/util/data-convert-json.c | 228 +++++++++++++++++++++++++ tools/perf/util/data-convert-json.h | 9 + tools/perf/util/data-convert.h | 2 + 6 files changed, 276 insertions(+), 8 deletions(-) create mode 100644 tools/perf/util/data-convert-json.c create mode 100644 tools/perf/util/data-convert-json.h diff --git a/tools/perf/Documentation/perf-data.txt b/tools/perf/Documentation/perf-data.txt index 726b9bc9e1a7..417bf17e265c 100644 --- a/tools/perf/Documentation/perf-data.txt +++ b/tools/perf/Documentation/perf-data.txt @@ -17,7 +17,7 @@ Data file related processing. COMMANDS -------- convert:: - Converts perf data file into another format (only CTF [1] format is support by now). + Converts perf data file into another format. It's possible to set data-convert debug variable to get debug messages from conversion, like: perf --debug data-convert data convert ... @@ -27,6 +27,9 @@ OPTIONS for 'convert' --to-ctf:: Triggers the CTF conversion, specify the path of CTF data directory. +--to-json:: + Triggers JSON conversion. Specify the JSON filename to output. + --tod:: Convert time to wall clock time. diff --git a/tools/perf/builtin-data.c b/tools/perf/builtin-data.c index 8d23b8d6ee8e..64546ba517a5 100644 --- a/tools/perf/builtin-data.c +++ b/tools/perf/builtin-data.c @@ -8,6 +8,7 @@ #include #include "data-convert.h" #include "data-convert-bt.h" +#include "data-convert-json.h" typedef int (*data_cmd_fn_t)(int argc, const char **argv); @@ -55,7 +56,8 @@ static const char * const data_convert_usage[] = { static int cmd_data_convert(int argc, const char **argv) { - const char *to_ctf = NULL; + const char *to_json = NULL; + const char *to_ctf = NULL; struct perf_data_convert_opts opts = { .force = false, .all = false, @@ -63,6 +65,7 @@ static int cmd_data_convert(int argc, const char **argv) const struct option options[] = { OPT_INCR('v', "verbose", &verbose, "be more verbose"), OPT_STRING('i', "input", &input_name, "file", "input file name"), + OPT_STRING(0, "to-json", &to_json, NULL, "Convert to JSON format"), #ifdef HAVE_LIBBABELTRACE_SUPPORT OPT_STRING(0, "to-ctf", &to_ctf, NULL, "Convert to CTF format"), OPT_BOOLEAN(0, "tod", &opts.tod, "Convert time to wall clock time"), @@ -72,11 +75,6 @@ static int cmd_data_convert(int argc, const char **argv) OPT_END() }; -#ifndef HAVE_LIBBABELTRACE_SUPPORT - pr_err("No conversion support compiled in. perf should be compiled with environment variables LIBBABELTRACE=1 and LIBBABELTRACE_DIR=/path/to/libbabeltrace/\n"); - return -1; -#endif - argc = parse_options(argc, argv, options, data_convert_usage, 0); if (argc) { @@ -84,11 +82,38 @@ static int cmd_data_convert(int argc, const char **argv) return -1; } + if (to_json && to_ctf) { + pr_err("You cannot specify both --to-ctf and --to-json.\n"); + return -1; + } + if (!to_json && !to_ctf) { + pr_err("You must specify one of --to-ctf or --to-json.\n"); + return -1; + } + + if (to_json) { + if (opts.all) { + pr_err("--all is currently unsupported for JSON output.\n"); + return -1; + } + if (opts.tod) { + pr_err("--tod is currently unsupported for JSON output.\n"); + return -1; + } + if (opts.force) { + pr_err("--force is currently unsupported for JSON output.\n"); + return -1; + } + return bt_convert__perf2json(input_name, to_json, &opts); + } + if (to_ctf) { #ifdef HAVE_LIBBABELTRACE_SUPPORT return bt_convert__perf2ctf(input_name, to_ctf, &opts); #else - pr_err("The libbabeltrace support is not compiled in.\n"); + pr_err("The libbabeltrace support is not compiled in. perf should be " + "compiled with environment variables LIBBABELTRACE=1 and " + "LIBBABELTRACE_DIR=/path/to/libbabeltrace/\n"); return -1; #endif } diff --git a/tools/perf/util/Build b/tools/perf/util/Build index e2563d0154eb..de9ac182b25a 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -163,6 +163,7 @@ perf-$(CONFIG_LIBUNWIND_X86) += libunwind/x86_32.o perf-$(CONFIG_LIBUNWIND_AARCH64) += libunwind/arm64.o perf-$(CONFIG_LIBBABELTRACE) += data-convert-bt.o +perf-y += data-convert-json.o perf-y += scripting-engines/ diff --git a/tools/perf/util/data-convert-json.c b/tools/perf/util/data-convert-json.c new file mode 100644 index 000000000000..b19674a9f2b8 --- /dev/null +++ b/tools/perf/util/data-convert-json.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * JSON export. + * + * Copyright (C) 2021, CodeWeavers Inc. + */ + +#include "data-convert-json.h" + +#include +#include + +#include "linux/compiler.h" +#include "linux/err.h" +#include "util/auxtrace.h" +#include "util/debug.h" +#include "util/dso.h" +#include "util/event.h" +#include "util/evsel.h" +#include "util/header.h" +#include "util/map.h" +#include "util/session.h" +#include "util/symbol.h" +#include "util/thread.h" +#include "util/tool.h" + +struct convert_json { + struct perf_tool tool; + FILE *out; + bool first; +}; + +static void output_json_string(FILE *out, const char *s) +{ + fputc('"', out); + while (*s) { + switch (*s) { + + // required escapes with special forms as per RFC 8259 + case '"': fprintf(out, "\\\""); break; + case '\\': fprintf(out, "\\\\"); break; + case '/': fprintf(out, "\\/"); break; + case '\b': fprintf(out, "\\b"); break; + case '\f': fprintf(out, "\\f"); break; + case '\n': fprintf(out, "\\n"); break; + case '\r': fprintf(out, "\\r"); break; + case '\t': fprintf(out, "\\t"); break; + + default: + // all other control characters must be escaped by hex code + if (*s <= 0x1f) { + fprintf(out, "\\u%04x", *s); + } else { + fputc(*s, out); + } + break; + } + + ++s; + } + fputc('"', out); +} + +static void output_sample_callchain_entry(struct perf_tool *tool, + u64 ip, struct addr_location *al) +{ + struct convert_json *c = container_of(tool, struct convert_json, tool); + FILE *out = c->out; + + fprintf(out, "\n\t\t\t\t{"); + fprintf(out, "\n\t\t\t\t\t\"ip\": \"0x%" PRIx64 "\"", ip); + + if (al && al->sym && al->sym->name && strlen(al->sym->name) > 0) { + fprintf(out, ",\n\t\t\t\t\t\"symbol\": "); + output_json_string(out, al->sym->name); + + if (al->map && al->map->dso) { + const char *dso = al->map->dso->short_name; + if (dso && strlen(dso) > 0) { + fprintf(out, ",\n\t\t\t\t\t\"dso\": "); + output_json_string(out, dso); + } + } + } + + fprintf(out, "\n\t\t\t\t}"); +} + +static int process_sample_event(struct perf_tool *tool, + union perf_event *event __maybe_unused, + struct perf_sample *sample, + struct evsel *evsel __maybe_unused, + struct machine *machine) +{ + struct convert_json *c = container_of(tool, struct convert_json, tool); + FILE *out = c->out; + struct addr_location al, tal; + u8 cpumode = PERF_RECORD_MISC_USER; + + if (machine__resolve(machine, &al, sample) < 0) { + return 0; + } + + if (c->first) { + c->first = false; + } else { + fprintf(out, ","); + } + fprintf(out, "\n\t\t{"); + + fprintf(out, "\n\t\t\t\"timestamp\": %" PRIi64, sample->time); + fprintf(out, ",\n\t\t\t\"pid\": %i", al.thread->pid_); + fprintf(out, ",\n\t\t\t\"tid\": %i", al.thread->tid); + + if (al.thread->cpu >= 0) { + fprintf(out, ",\n\t\t\t\"cpu\": %i", al.thread->cpu); + } + + fprintf(out, ",\n\t\t\t\"comm\": "); + output_json_string(out, thread__comm_str(al.thread)); + + fprintf(out, ",\n\t\t\t\"callchain\": ["); + if (sample->callchain) { + unsigned int i; + bool ok; + bool first_callchain = true; + + for (i = 0; i < sample->callchain->nr; ++i) { + u64 ip = sample->callchain->ips[i]; + + if (ip >= PERF_CONTEXT_MAX) { + switch (ip) { + case PERF_CONTEXT_HV: + cpumode = PERF_RECORD_MISC_HYPERVISOR; + break; + case PERF_CONTEXT_KERNEL: + cpumode = PERF_RECORD_MISC_KERNEL; + break; + case PERF_CONTEXT_USER: + cpumode = PERF_RECORD_MISC_USER; + break; + default: + pr_debug("invalid callchain context: " + "%"PRId64"\n", (s64) ip); + break; + } + continue; + } + + if (first_callchain) { + first_callchain = false; + } else { + fprintf(out, ","); + } + + ok = thread__find_symbol(al.thread, cpumode, ip, &tal); + output_sample_callchain_entry(tool, ip, ok ? &tal : NULL); + } + } else { + output_sample_callchain_entry(tool, sample->ip, &al); + } + fprintf(out, "\n\t\t\t]"); + + fprintf(out, "\n\t\t}"); + return 0; +} + +int bt_convert__perf2json(const char *input_name, const char *output_name, + struct perf_data_convert_opts *opts __maybe_unused) +{ + struct perf_session *session; + + struct convert_json c = { + .tool = { + .sample = process_sample_event, + .mmap = perf_event__process_mmap, + .mmap2 = perf_event__process_mmap2, + .comm = perf_event__process_comm, + .namespaces = perf_event__process_namespaces, + .cgroup = perf_event__process_cgroup, + .exit = perf_event__process_exit, + .fork = perf_event__process_fork, + .lost = perf_event__process_lost, + .tracing_data = perf_event__process_tracing_data, + .build_id = perf_event__process_build_id, + .id_index = perf_event__process_id_index, + .auxtrace_info = perf_event__process_auxtrace_info, + .auxtrace = perf_event__process_auxtrace, + .event_update = perf_event__process_event_update, + .ordered_events = true, + .ordering_requires_timestamps = true, + }, + .first = true, + }; + + struct perf_data data = { + .mode = PERF_DATA_MODE_READ, + .path = input_name, + }; + + c.out = fopen(output_name, "w"); + if (!c.out) { + fprintf(stderr, "error opening output file!\n"); + return -1; + } + + session = perf_session__new(&data, false, &c.tool); + if (IS_ERR(session)) { + fprintf(stderr, "error creating perf session!\n"); + return -1; + } + + if (symbol__init(&session->header.env) < 0) { + fprintf(stderr, "symbol init error!\n"); + return -1; + } + + // Version number for future-proofing. Most additions should be able to be + // done in a backwards-compatible way so this should only need to be bumped + // if some major breaking change must be made. + fprintf(c.out, "{\n\t\"linux-perf-json-version\": 1,"); + + fprintf(c.out, "\n\t\"samples\": ["); + perf_session__process_events(session); + fprintf(c.out, "\n\t]\n}\n"); + + return 0; +} diff --git a/tools/perf/util/data-convert-json.h b/tools/perf/util/data-convert-json.h new file mode 100644 index 000000000000..1fcac5ce3ec1 --- /dev/null +++ b/tools/perf/util/data-convert-json.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DATA_CONVERT_JSON_H +#define __DATA_CONVERT_JSON_H +#include "data-convert.h" + +int bt_convert__perf2json(const char *input_name, const char *to_ctf, + struct perf_data_convert_opts *opts); + +#endif /* __DATA_CONVERT_JSON_H */ diff --git a/tools/perf/util/data-convert.h b/tools/perf/util/data-convert.h index feab5f114e37..17c35eb6ab4f 100644 --- a/tools/perf/util/data-convert.h +++ b/tools/perf/util/data-convert.h @@ -2,6 +2,8 @@ #ifndef __DATA_CONVERT_H #define __DATA_CONVERT_H +#include + struct perf_data_convert_opts { bool force; bool all; -- 2.31.0