cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

data-convert-json.c (10679B)


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