linux/tools/perf/util/data-convert-json.c
<<
>>
Prefs
   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}
 385