422 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * JSON export.4 *5 * Copyright (C) 2021, CodeWeavers Inc. <nfraser@codeweavers.com>6 */7 8#include "data-convert.h"9 10#include <fcntl.h>11#include <inttypes.h>12#include <sys/stat.h>13#include <unistd.h>14 15#include "linux/compiler.h"16#include "linux/err.h"17#include "util/auxtrace.h"18#include "util/debug.h"19#include "util/dso.h"20#include "util/event.h"21#include "util/evsel.h"22#include "util/evlist.h"23#include "util/header.h"24#include "util/map.h"25#include "util/session.h"26#include "util/symbol.h"27#include "util/thread.h"28#include "util/tool.h"29 30#ifdef HAVE_LIBTRACEEVENT31#include <traceevent/event-parse.h>32#endif33 34struct convert_json {35 struct perf_tool tool;36 FILE *out;37 bool first;38 u64 events_count;39};40 41// Outputs a JSON-encoded string surrounded by quotes with characters escaped.42static void output_json_string(FILE *out, const char *s)43{44 fputc('"', out);45 while (*s) {46 switch (*s) {47 48 // required escapes with special forms as per RFC 825949 case '"': fputs("\\\"", out); break;50 case '\\': fputs("\\\\", out); break;51 case '\b': fputs("\\b", out); break;52 case '\f': fputs("\\f", out); break;53 case '\n': fputs("\\n", out); break;54 case '\r': fputs("\\r", out); break;55 case '\t': fputs("\\t", out); break;56 57 default:58 // all other control characters must be escaped by hex code59 if (*s <= 0x1f)60 fprintf(out, "\\u%04x", *s);61 else62 fputc(*s, out);63 break;64 }65 66 ++s;67 }68 fputc('"', out);69}70 71// Outputs an optional comma, newline and indentation to delimit a new value72// from the previous one in a JSON object or array.73static void output_json_delimiters(FILE *out, bool comma, int depth)74{75 int i;76 77 if (comma)78 fputc(',', out);79 fputc('\n', out);80 for (i = 0; i < depth; ++i)81 fputc('\t', out);82}83 84// Outputs a printf format string (with delimiter) as a JSON value.85__printf(4, 5)86static void output_json_format(FILE *out, bool comma, int depth, const char *format, ...)87{88 va_list args;89 90 output_json_delimiters(out, comma, depth);91 va_start(args, format);92 vfprintf(out, format, args);93 va_end(args);94}95 96// Outputs a JSON key-value pair where the value is a string.97static void output_json_key_string(FILE *out, bool comma, int depth,98 const char *key, const char *value)99{100 output_json_delimiters(out, comma, depth);101 output_json_string(out, key);102 fputs(": ", out);103 output_json_string(out, value);104}105 106// Outputs a JSON key-value pair where the value is a printf format string.107__printf(5, 6)108static void output_json_key_format(FILE *out, bool comma, int depth,109 const char *key, const char *format, ...)110{111 va_list args;112 113 output_json_delimiters(out, comma, depth);114 output_json_string(out, key);115 fputs(": ", out);116 va_start(args, format);117 vfprintf(out, format, args);118 va_end(args);119}120 121static void output_sample_callchain_entry(const struct perf_tool *tool,122 u64 ip, struct addr_location *al)123{124 struct convert_json *c = container_of(tool, struct convert_json, tool);125 FILE *out = c->out;126 127 output_json_format(out, false, 4, "{");128 output_json_key_format(out, false, 5, "ip", "\"0x%" PRIx64 "\"", ip);129 130 if (al && al->sym && al->sym->namelen) {131 struct dso *dso = al->map ? map__dso(al->map) : NULL;132 133 fputc(',', out);134 output_json_key_string(out, false, 5, "symbol", al->sym->name);135 136 if (dso) {137 const char *dso_name = dso__short_name(dso);138 139 if (dso_name && strlen(dso_name) > 0) {140 fputc(',', out);141 output_json_key_string(out, false, 5, "dso", dso_name);142 }143 }144 }145 146 output_json_format(out, false, 4, "}");147}148 149static int process_sample_event(const struct perf_tool *tool,150 union perf_event *event __maybe_unused,151 struct perf_sample *sample,152 struct evsel *evsel __maybe_unused,153 struct machine *machine)154{155 struct convert_json *c = container_of(tool, struct convert_json, tool);156 FILE *out = c->out;157 struct addr_location al;158 u64 sample_type = __evlist__combined_sample_type(evsel->evlist);159 u8 cpumode = PERF_RECORD_MISC_USER;160 161 addr_location__init(&al);162 if (machine__resolve(machine, &al, sample) < 0) {163 pr_err("Sample resolution failed!\n");164 addr_location__exit(&al);165 return -1;166 }167 168 ++c->events_count;169 170 if (c->first)171 c->first = false;172 else173 fputc(',', out);174 output_json_format(out, false, 2, "{");175 176 output_json_key_format(out, false, 3, "timestamp", "%" PRIi64, sample->time);177 output_json_key_format(out, true, 3, "pid", "%i", thread__pid(al.thread));178 output_json_key_format(out, true, 3, "tid", "%i", thread__tid(al.thread));179 180 if ((sample_type & PERF_SAMPLE_CPU))181 output_json_key_format(out, true, 3, "cpu", "%i", sample->cpu);182 else if (thread__cpu(al.thread) >= 0)183 output_json_key_format(out, true, 3, "cpu", "%i", thread__cpu(al.thread));184 185 output_json_key_string(out, true, 3, "comm", thread__comm_str(al.thread));186 187 output_json_key_format(out, true, 3, "callchain", "[");188 if (sample->callchain) {189 unsigned int i;190 bool ok;191 bool first_callchain = true;192 193 for (i = 0; i < sample->callchain->nr; ++i) {194 u64 ip = sample->callchain->ips[i];195 struct addr_location tal;196 197 if (ip >= PERF_CONTEXT_MAX) {198 switch (ip) {199 case PERF_CONTEXT_HV:200 cpumode = PERF_RECORD_MISC_HYPERVISOR;201 break;202 case PERF_CONTEXT_KERNEL:203 cpumode = PERF_RECORD_MISC_KERNEL;204 break;205 case PERF_CONTEXT_USER:206 cpumode = PERF_RECORD_MISC_USER;207 break;208 default:209 pr_debug("invalid callchain context: %"210 PRId64 "\n", (s64) ip);211 break;212 }213 continue;214 }215 216 if (first_callchain)217 first_callchain = false;218 else219 fputc(',', out);220 221 addr_location__init(&tal);222 ok = thread__find_symbol(al.thread, cpumode, ip, &tal);223 output_sample_callchain_entry(tool, ip, ok ? &tal : NULL);224 addr_location__exit(&tal);225 }226 } else {227 output_sample_callchain_entry(tool, sample->ip, &al);228 }229 output_json_format(out, false, 3, "]");230 231#ifdef HAVE_LIBTRACEEVENT232 if (sample->raw_data) {233 int i;234 struct tep_format_field **fields;235 236 fields = tep_event_fields(evsel->tp_format);237 if (fields) {238 i = 0;239 while (fields[i]) {240 struct trace_seq s;241 242 trace_seq_init(&s);243 tep_print_field(&s, sample->raw_data, fields[i]);244 output_json_key_string(out, true, 3, fields[i]->name, s.buffer);245 246 i++;247 }248 free(fields);249 }250 }251#endif252 output_json_format(out, false, 2, "}");253 addr_location__exit(&al);254 return 0;255}256 257static void output_headers(struct perf_session *session, struct convert_json *c)258{259 struct stat st;260 struct perf_header *header = &session->header;261 int ret;262 int fd = perf_data__fd(session->data);263 int i;264 FILE *out = c->out;265 266 output_json_key_format(out, false, 2, "header-version", "%u", header->version);267 268 ret = fstat(fd, &st);269 if (ret >= 0) {270 time_t stctime = st.st_mtime;271 char buf[256];272 273 strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&stctime));274 output_json_key_string(out, true, 2, "captured-on", buf);275 } else {276 pr_debug("Failed to get mtime of source file, not writing captured-on");277 }278 279 output_json_key_format(out, true, 2, "data-offset", "%" PRIu64, header->data_offset);280 output_json_key_format(out, true, 2, "data-size", "%" PRIu64, header->data_size);281 output_json_key_format(out, true, 2, "feat-offset", "%" PRIu64, header->feat_offset);282 283 output_json_key_string(out, true, 2, "hostname", header->env.hostname);284 output_json_key_string(out, true, 2, "os-release", header->env.os_release);285 output_json_key_string(out, true, 2, "arch", header->env.arch);286 287 if (header->env.cpu_desc)288 output_json_key_string(out, true, 2, "cpu-desc", header->env.cpu_desc);289 290 output_json_key_string(out, true, 2, "cpuid", header->env.cpuid);291 output_json_key_format(out, true, 2, "nrcpus-online", "%u", header->env.nr_cpus_online);292 output_json_key_format(out, true, 2, "nrcpus-avail", "%u", header->env.nr_cpus_avail);293 294 if (header->env.clock.enabled) {295 output_json_key_format(out, true, 2, "clockid",296 "%u", header->env.clock.clockid);297 output_json_key_format(out, true, 2, "clock-time",298 "%" PRIu64, header->env.clock.clockid_ns);299 output_json_key_format(out, true, 2, "real-time",300 "%" PRIu64, header->env.clock.tod_ns);301 }302 303 output_json_key_string(out, true, 2, "perf-version", header->env.version);304 305 output_json_key_format(out, true, 2, "cmdline", "[");306 for (i = 0; i < header->env.nr_cmdline; i++) {307 output_json_delimiters(out, i != 0, 3);308 output_json_string(c->out, header->env.cmdline_argv[i]);309 }310 output_json_format(out, false, 2, "]");311}312 313int bt_convert__perf2json(const char *input_name, const char *output_name,314 struct perf_data_convert_opts *opts __maybe_unused)315{316 struct perf_session *session;317 int fd;318 int ret = -1;319 struct convert_json c = {320 .first = true,321 .events_count = 0,322 };323 struct perf_data data = {324 .mode = PERF_DATA_MODE_READ,325 .path = input_name,326 .force = opts->force,327 };328 329 perf_tool__init(&c.tool, /*ordered_events=*/true);330 c.tool.sample = process_sample_event;331 c.tool.mmap = perf_event__process_mmap;332 c.tool.mmap2 = perf_event__process_mmap2;333 c.tool.comm = perf_event__process_comm;334 c.tool.namespaces = perf_event__process_namespaces;335 c.tool.cgroup = perf_event__process_cgroup;336 c.tool.exit = perf_event__process_exit;337 c.tool.fork = perf_event__process_fork;338 c.tool.lost = perf_event__process_lost;339#ifdef HAVE_LIBTRACEEVENT340 c.tool.tracing_data = perf_event__process_tracing_data;341#endif342 c.tool.build_id = perf_event__process_build_id;343 c.tool.id_index = perf_event__process_id_index;344 c.tool.auxtrace_info = perf_event__process_auxtrace_info;345 c.tool.auxtrace = perf_event__process_auxtrace;346 c.tool.event_update = perf_event__process_event_update;347 c.tool.ordering_requires_timestamps = true;348 349 if (opts->all) {350 pr_err("--all is currently unsupported for JSON output.\n");351 goto err;352 }353 if (opts->tod) {354 pr_err("--tod is currently unsupported for JSON output.\n");355 goto err;356 }357 358 fd = open(output_name, O_CREAT | O_WRONLY | (opts->force ? O_TRUNC : O_EXCL), 0666);359 if (fd == -1) {360 if (errno == EEXIST)361 pr_err("Output file exists. Use --force to overwrite it.\n");362 else363 pr_err("Error opening output file!\n");364 goto err;365 }366 367 c.out = fdopen(fd, "w");368 if (!c.out) {369 fprintf(stderr, "Error opening output file!\n");370 close(fd);371 goto err;372 }373 374 session = perf_session__new(&data, &c.tool);375 if (IS_ERR(session)) {376 fprintf(stderr, "Error creating perf session!\n");377 goto err_fclose;378 }379 380 if (symbol__init(&session->header.env) < 0) {381 fprintf(stderr, "Symbol init error!\n");382 goto err_session_delete;383 }384 385 // The opening brace is printed manually because it isn't delimited from a386 // previous value (i.e. we don't want a leading newline)387 fputc('{', c.out);388 389 // Version number for future-proofing. Most additions should be able to be390 // done in a backwards-compatible way so this should only need to be bumped391 // if some major breaking change must be made.392 output_json_format(c.out, false, 1, "\"linux-perf-json-version\": 1");393 394 // Output headers395 output_json_format(c.out, true, 1, "\"headers\": {");396 output_headers(session, &c);397 output_json_format(c.out, false, 1, "}");398 399 // Output samples400 output_json_format(c.out, true, 1, "\"samples\": [");401 perf_session__process_events(session);402 output_json_format(c.out, false, 1, "]");403 output_json_format(c.out, false, 0, "}");404 fputc('\n', c.out);405 406 fprintf(stderr,407 "[ perf data convert: Converted '%s' into JSON data '%s' ]\n",408 data.path, output_name);409 410 fprintf(stderr,411 "[ perf data convert: Converted and wrote %.3f MB (%" PRIu64 " samples) ]\n",412 (ftell(c.out)) / 1024.0 / 1024.0, c.events_count);413 414 ret = 0;415err_session_delete:416 perf_session__delete(session);417err_fclose:418 fclose(c.out);419err:420 return ret;421}422