projects/linux·blob master · refs

462 lines · 12.3 KiB

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