1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
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_LIBTRACEEVENT |
31 | #include <traceevent/event-parse.h> |
32 | #endif |
33 | |
34 | struct 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. |
42 | static 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 8259 |
49 | 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 code |
59 | if (*s <= 0x1f) |
60 | fprintf(out, "\\u%04x" , *s); |
61 | else |
62 | 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 value |
72 | // from the previous one in a JSON object or array. |
73 | static 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) |
86 | static 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. |
97 | static 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) |
108 | static 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 | |
121 | static void output_sample_callchain_entry(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; |
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 | |
149 | static int process_sample_event(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 | else |
173 | 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 | else |
219 | 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, al: ok ? &tal : NULL); |
224 | addr_location__exit(&tal); |
225 | } |
226 | } else { |
227 | output_sample_callchain_entry(tool, ip: sample->ip, al: &al); |
228 | } |
229 | output_json_format(out, false, 3, "]" ); |
230 | |
231 | #ifdef HAVE_LIBTRACEEVENT |
232 | 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 | #endif |
252 | output_json_format(out, false, 2, "}" ); |
253 | addr_location__exit(&al); |
254 | return 0; |
255 | } |
256 | |
257 | static void (struct perf_session *session, struct convert_json *c) |
258 | { |
259 | struct stat st; |
260 | struct * = &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 | |
313 | int 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 | |
320 | struct convert_json c = { |
321 | .tool = { |
322 | .sample = process_sample_event, |
323 | .mmap = perf_event__process_mmap, |
324 | .mmap2 = perf_event__process_mmap2, |
325 | .comm = perf_event__process_comm, |
326 | .namespaces = perf_event__process_namespaces, |
327 | .cgroup = perf_event__process_cgroup, |
328 | .exit = perf_event__process_exit, |
329 | .fork = perf_event__process_fork, |
330 | .lost = perf_event__process_lost, |
331 | #ifdef HAVE_LIBTRACEEVENT |
332 | .tracing_data = perf_event__process_tracing_data, |
333 | #endif |
334 | .build_id = perf_event__process_build_id, |
335 | .id_index = perf_event__process_id_index, |
336 | .auxtrace_info = perf_event__process_auxtrace_info, |
337 | .auxtrace = perf_event__process_auxtrace, |
338 | .event_update = perf_event__process_event_update, |
339 | .ordered_events = true, |
340 | .ordering_requires_timestamps = true, |
341 | }, |
342 | .first = true, |
343 | .events_count = 0, |
344 | }; |
345 | |
346 | struct perf_data data = { |
347 | .mode = PERF_DATA_MODE_READ, |
348 | .path = input_name, |
349 | .force = opts->force, |
350 | }; |
351 | |
352 | if (opts->all) { |
353 | pr_err("--all is currently unsupported for JSON output.\n" ); |
354 | goto err; |
355 | } |
356 | if (opts->tod) { |
357 | pr_err("--tod is currently unsupported for JSON output.\n" ); |
358 | goto err; |
359 | } |
360 | |
361 | fd = open(output_name, O_CREAT | O_WRONLY | (opts->force ? O_TRUNC : O_EXCL), 0666); |
362 | if (fd == -1) { |
363 | if (errno == EEXIST) |
364 | pr_err("Output file exists. Use --force to overwrite it.\n" ); |
365 | else |
366 | pr_err("Error opening output file!\n" ); |
367 | goto err; |
368 | } |
369 | |
370 | c.out = fdopen(fd, "w" ); |
371 | if (!c.out) { |
372 | fprintf(stderr, "Error opening output file!\n" ); |
373 | close(fd); |
374 | goto err; |
375 | } |
376 | |
377 | session = perf_session__new(&data, &c.tool); |
378 | if (IS_ERR(ptr: session)) { |
379 | fprintf(stderr, "Error creating perf session!\n" ); |
380 | goto err_fclose; |
381 | } |
382 | |
383 | if (symbol__init(&session->header.env) < 0) { |
384 | fprintf(stderr, "Symbol init error!\n" ); |
385 | goto err_session_delete; |
386 | } |
387 | |
388 | // The opening brace is printed manually because it isn't delimited from a |
389 | // previous value (i.e. we don't want a leading newline) |
390 | fputc('{', c.out); |
391 | |
392 | // Version number for future-proofing. Most additions should be able to be |
393 | // done in a backwards-compatible way so this should only need to be bumped |
394 | // if some major breaking change must be made. |
395 | output_json_format(c.out, false, 1, "\"linux-perf-json-version\": 1" ); |
396 | |
397 | // Output headers |
398 | output_json_format(c.out, true, 1, "\"headers\": {" ); |
399 | output_headers(session, c: &c); |
400 | output_json_format(c.out, false, 1, "}" ); |
401 | |
402 | // Output samples |
403 | output_json_format(c.out, true, 1, "\"samples\": [" ); |
404 | perf_session__process_events(session); |
405 | output_json_format(c.out, false, 1, "]" ); |
406 | output_json_format(c.out, false, 0, "}" ); |
407 | fputc('\n', c.out); |
408 | |
409 | fprintf(stderr, |
410 | "[ perf data convert: Converted '%s' into JSON data '%s' ]\n" , |
411 | data.path, output_name); |
412 | |
413 | fprintf(stderr, |
414 | "[ perf data convert: Converted and wrote %.3f MB (%" PRIu64 " samples) ]\n" , |
415 | (ftell(c.out)) / 1024.0 / 1024.0, c.events_count); |
416 | |
417 | ret = 0; |
418 | err_session_delete: |
419 | perf_session__delete(session); |
420 | err_fclose: |
421 | fclose(c.out); |
422 | err: |
423 | return ret; |
424 | } |
425 | |