Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * JSON export.
0004  *
0005  * Copyright (C) 2021, CodeWeavers Inc. <nfraser@codeweavers.com>
0006  */
0007 
0008 #include "data-convert.h"
0009 
0010 #include <fcntl.h>
0011 #include <inttypes.h>
0012 #include <sys/stat.h>
0013 #include <unistd.h>
0014 
0015 #include "linux/compiler.h"
0016 #include "linux/err.h"
0017 #include "util/auxtrace.h"
0018 #include "util/debug.h"
0019 #include "util/dso.h"
0020 #include "util/event.h"
0021 #include "util/evsel.h"
0022 #include "util/evlist.h"
0023 #include "util/header.h"
0024 #include "util/map.h"
0025 #include "util/session.h"
0026 #include "util/symbol.h"
0027 #include "util/thread.h"
0028 #include "util/tool.h"
0029 
0030 struct convert_json {
0031     struct perf_tool tool;
0032     FILE *out;
0033     bool first;
0034     u64 events_count;
0035 };
0036 
0037 // Outputs a JSON-encoded string surrounded by quotes with characters escaped.
0038 static void output_json_string(FILE *out, const char *s)
0039 {
0040     fputc('"', out);
0041     while (*s) {
0042         switch (*s) {
0043 
0044         // required escapes with special forms as per RFC 8259
0045         case '"':  fputs("\\\"", out); break;
0046         case '\\': fputs("\\\\", out); break;
0047         case '\b': fputs("\\b", out);  break;
0048         case '\f': fputs("\\f", out);  break;
0049         case '\n': fputs("\\n", out);  break;
0050         case '\r': fputs("\\r", out);  break;
0051         case '\t': fputs("\\t", out);  break;
0052 
0053         default:
0054             // all other control characters must be escaped by hex code
0055             if (*s <= 0x1f)
0056                 fprintf(out, "\\u%04x", *s);
0057             else
0058                 fputc(*s, out);
0059             break;
0060         }
0061 
0062         ++s;
0063     }
0064     fputc('"', out);
0065 }
0066 
0067 // Outputs an optional comma, newline and indentation to delimit a new value
0068 // from the previous one in a JSON object or array.
0069 static void output_json_delimiters(FILE *out, bool comma, int depth)
0070 {
0071     int i;
0072 
0073     if (comma)
0074         fputc(',', out);
0075     fputc('\n', out);
0076     for (i = 0; i < depth; ++i)
0077         fputc('\t', out);
0078 }
0079 
0080 // Outputs a printf format string (with delimiter) as a JSON value.
0081 __printf(4, 5)
0082 static void output_json_format(FILE *out, bool comma, int depth, const char *format, ...)
0083 {
0084     va_list args;
0085 
0086     output_json_delimiters(out, comma, depth);
0087     va_start(args, format);
0088     vfprintf(out,  format, args);
0089     va_end(args);
0090 }
0091 
0092 // Outputs a JSON key-value pair where the value is a string.
0093 static void output_json_key_string(FILE *out, bool comma, int depth,
0094         const char *key, const char *value)
0095 {
0096     output_json_delimiters(out, comma, depth);
0097     output_json_string(out, key);
0098     fputs(": ", out);
0099     output_json_string(out, value);
0100 }
0101 
0102 // Outputs a JSON key-value pair where the value is a printf format string.
0103 __printf(5, 6)
0104 static void output_json_key_format(FILE *out, bool comma, int depth,
0105         const char *key, const char *format, ...)
0106 {
0107     va_list args;
0108 
0109     output_json_delimiters(out, comma, depth);
0110     output_json_string(out, key);
0111     fputs(": ", out);
0112     va_start(args, format);
0113     vfprintf(out,  format, args);
0114     va_end(args);
0115 }
0116 
0117 static void output_sample_callchain_entry(struct perf_tool *tool,
0118         u64 ip, struct addr_location *al)
0119 {
0120     struct convert_json *c = container_of(tool, struct convert_json, tool);
0121     FILE *out = c->out;
0122 
0123     output_json_format(out, false, 4, "{");
0124     output_json_key_format(out, false, 5, "ip", "\"0x%" PRIx64 "\"", ip);
0125 
0126     if (al && al->sym && al->sym->namelen) {
0127         fputc(',', out);
0128         output_json_key_string(out, false, 5, "symbol", al->sym->name);
0129 
0130         if (al->map && al->map->dso) {
0131             const char *dso = al->map->dso->short_name;
0132 
0133             if (dso && strlen(dso) > 0) {
0134                 fputc(',', out);
0135                 output_json_key_string(out, false, 5, "dso", dso);
0136             }
0137         }
0138     }
0139 
0140     output_json_format(out, false, 4, "}");
0141 }
0142 
0143 static int process_sample_event(struct perf_tool *tool,
0144                 union perf_event *event __maybe_unused,
0145                 struct perf_sample *sample,
0146                 struct evsel *evsel __maybe_unused,
0147                 struct machine *machine)
0148 {
0149     struct convert_json *c = container_of(tool, struct convert_json, tool);
0150     FILE *out = c->out;
0151     struct addr_location al, tal;
0152     u64 sample_type = __evlist__combined_sample_type(evsel->evlist);
0153     u8 cpumode = PERF_RECORD_MISC_USER;
0154 
0155     if (machine__resolve(machine, &al, sample) < 0) {
0156         pr_err("Sample resolution failed!\n");
0157         return -1;
0158     }
0159 
0160     ++c->events_count;
0161 
0162     if (c->first)
0163         c->first = false;
0164     else
0165         fputc(',', out);
0166     output_json_format(out, false, 2, "{");
0167 
0168     output_json_key_format(out, false, 3, "timestamp", "%" PRIi64, sample->time);
0169     output_json_key_format(out, true, 3, "pid", "%i", al.thread->pid_);
0170     output_json_key_format(out, true, 3, "tid", "%i", al.thread->tid);
0171 
0172     if ((sample_type & PERF_SAMPLE_CPU))
0173         output_json_key_format(out, true, 3, "cpu", "%i", sample->cpu);
0174     else if (al.thread->cpu >= 0)
0175         output_json_key_format(out, true, 3, "cpu", "%i", al.thread->cpu);
0176 
0177     output_json_key_string(out, true, 3, "comm", thread__comm_str(al.thread));
0178 
0179     output_json_key_format(out, true, 3, "callchain", "[");
0180     if (sample->callchain) {
0181         unsigned int i;
0182         bool ok;
0183         bool first_callchain = true;
0184 
0185         for (i = 0; i < sample->callchain->nr; ++i) {
0186             u64 ip = sample->callchain->ips[i];
0187 
0188             if (ip >= PERF_CONTEXT_MAX) {
0189                 switch (ip) {
0190                 case PERF_CONTEXT_HV:
0191                     cpumode = PERF_RECORD_MISC_HYPERVISOR;
0192                     break;
0193                 case PERF_CONTEXT_KERNEL:
0194                     cpumode = PERF_RECORD_MISC_KERNEL;
0195                     break;
0196                 case PERF_CONTEXT_USER:
0197                     cpumode = PERF_RECORD_MISC_USER;
0198                     break;
0199                 default:
0200                     pr_debug("invalid callchain context: %"
0201                             PRId64 "\n", (s64) ip);
0202                     break;
0203                 }
0204                 continue;
0205             }
0206 
0207             if (first_callchain)
0208                 first_callchain = false;
0209             else
0210                 fputc(',', out);
0211 
0212             ok = thread__find_symbol(al.thread, cpumode, ip, &tal);
0213             output_sample_callchain_entry(tool, ip, ok ? &tal : NULL);
0214         }
0215     } else {
0216         output_sample_callchain_entry(tool, sample->ip, &al);
0217     }
0218     output_json_format(out, false, 3, "]");
0219 
0220     output_json_format(out, false, 2, "}");
0221     return 0;
0222 }
0223 
0224 static void output_headers(struct perf_session *session, struct convert_json *c)
0225 {
0226     struct stat st;
0227     struct perf_header *header = &session->header;
0228     int ret;
0229     int fd = perf_data__fd(session->data);
0230     int i;
0231     FILE *out = c->out;
0232 
0233     output_json_key_format(out, false, 2, "header-version", "%u", header->version);
0234 
0235     ret = fstat(fd, &st);
0236     if (ret >= 0) {
0237         time_t stctime = st.st_mtime;
0238         char buf[256];
0239 
0240         strftime(buf, sizeof(buf), "%FT%TZ", gmtime(&stctime));
0241         output_json_key_string(out, true, 2, "captured-on", buf);
0242     } else {
0243         pr_debug("Failed to get mtime of source file, not writing captured-on");
0244     }
0245 
0246     output_json_key_format(out, true, 2, "data-offset", "%" PRIu64, header->data_offset);
0247     output_json_key_format(out, true, 2, "data-size", "%" PRIu64, header->data_size);
0248     output_json_key_format(out, true, 2, "feat-offset", "%" PRIu64, header->feat_offset);
0249 
0250     output_json_key_string(out, true, 2, "hostname", header->env.hostname);
0251     output_json_key_string(out, true, 2, "os-release", header->env.os_release);
0252     output_json_key_string(out, true, 2, "arch", header->env.arch);
0253 
0254     output_json_key_string(out, true, 2, "cpu-desc", header->env.cpu_desc);
0255     output_json_key_string(out, true, 2, "cpuid", header->env.cpuid);
0256     output_json_key_format(out, true, 2, "nrcpus-online", "%u", header->env.nr_cpus_online);
0257     output_json_key_format(out, true, 2, "nrcpus-avail", "%u", header->env.nr_cpus_avail);
0258 
0259     if (header->env.clock.enabled) {
0260         output_json_key_format(out, true, 2, "clockid",
0261                 "%u", header->env.clock.clockid);
0262         output_json_key_format(out, true, 2, "clock-time",
0263                 "%" PRIu64, header->env.clock.clockid_ns);
0264         output_json_key_format(out, true, 2, "real-time",
0265                 "%" PRIu64, header->env.clock.tod_ns);
0266     }
0267 
0268     output_json_key_string(out, true, 2, "perf-version", header->env.version);
0269 
0270     output_json_key_format(out, true, 2, "cmdline", "[");
0271     for (i = 0; i < header->env.nr_cmdline; i++) {
0272         output_json_delimiters(out, i != 0, 3);
0273         output_json_string(c->out, header->env.cmdline_argv[i]);
0274     }
0275     output_json_format(out, false, 2, "]");
0276 }
0277 
0278 int bt_convert__perf2json(const char *input_name, const char *output_name,
0279         struct perf_data_convert_opts *opts __maybe_unused)
0280 {
0281     struct perf_session *session;
0282     int fd;
0283     int ret = -1;
0284 
0285     struct convert_json c = {
0286         .tool = {
0287             .sample         = process_sample_event,
0288             .mmap           = perf_event__process_mmap,
0289             .mmap2          = perf_event__process_mmap2,
0290             .comm           = perf_event__process_comm,
0291             .namespaces     = perf_event__process_namespaces,
0292             .cgroup         = perf_event__process_cgroup,
0293             .exit           = perf_event__process_exit,
0294             .fork           = perf_event__process_fork,
0295             .lost           = perf_event__process_lost,
0296             .tracing_data   = perf_event__process_tracing_data,
0297             .build_id       = perf_event__process_build_id,
0298             .id_index       = perf_event__process_id_index,
0299             .auxtrace_info  = perf_event__process_auxtrace_info,
0300             .auxtrace       = perf_event__process_auxtrace,
0301             .event_update   = perf_event__process_event_update,
0302             .ordered_events = true,
0303             .ordering_requires_timestamps = true,
0304         },
0305         .first = true,
0306         .events_count = 0,
0307     };
0308 
0309     struct perf_data data = {
0310         .mode = PERF_DATA_MODE_READ,
0311         .path = input_name,
0312         .force = opts->force,
0313     };
0314 
0315     if (opts->all) {
0316         pr_err("--all is currently unsupported for JSON output.\n");
0317         goto err;
0318     }
0319     if (opts->tod) {
0320         pr_err("--tod is currently unsupported for JSON output.\n");
0321         goto err;
0322     }
0323 
0324     fd = open(output_name, O_CREAT | O_WRONLY | (opts->force ? O_TRUNC : O_EXCL), 0666);
0325     if (fd == -1) {
0326         if (errno == EEXIST)
0327             pr_err("Output file exists. Use --force to overwrite it.\n");
0328         else
0329             pr_err("Error opening output file!\n");
0330         goto err;
0331     }
0332 
0333     c.out = fdopen(fd, "w");
0334     if (!c.out) {
0335         fprintf(stderr, "Error opening output file!\n");
0336         close(fd);
0337         goto err;
0338     }
0339 
0340     session = perf_session__new(&data, &c.tool);
0341     if (IS_ERR(session)) {
0342         fprintf(stderr, "Error creating perf session!\n");
0343         goto err_fclose;
0344     }
0345 
0346     if (symbol__init(&session->header.env) < 0) {
0347         fprintf(stderr, "Symbol init error!\n");
0348         goto err_session_delete;
0349     }
0350 
0351     // The opening brace is printed manually because it isn't delimited from a
0352     // previous value (i.e. we don't want a leading newline)
0353     fputc('{', c.out);
0354 
0355     // Version number for future-proofing. Most additions should be able to be
0356     // done in a backwards-compatible way so this should only need to be bumped
0357     // if some major breaking change must be made.
0358     output_json_format(c.out, false, 1, "\"linux-perf-json-version\": 1");
0359 
0360     // Output headers
0361     output_json_format(c.out, true, 1, "\"headers\": {");
0362     output_headers(session, &c);
0363     output_json_format(c.out, false, 1, "}");
0364 
0365     // Output samples
0366     output_json_format(c.out, true, 1, "\"samples\": [");
0367     perf_session__process_events(session);
0368     output_json_format(c.out, false, 1, "]");
0369     output_json_format(c.out, false, 0, "}");
0370     fputc('\n', c.out);
0371 
0372     fprintf(stderr,
0373             "[ perf data convert: Converted '%s' into JSON data '%s' ]\n",
0374             data.path, output_name);
0375 
0376     fprintf(stderr,
0377             "[ perf data convert: Converted and wrote %.3f MB (%" PRIu64 " samples) ]\n",
0378             (ftell(c.out)) / 1024.0 / 1024.0, c.events_count);
0379 
0380     ret = 0;
0381 err_session_delete:
0382     perf_session__delete(session);
0383 err_fclose:
0384     fclose(c.out);
0385 err:
0386     return ret;
0387 }