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