diff --git a/src/xrt/targets/cli/CMakeLists.txt b/src/xrt/targets/cli/CMakeLists.txt index 4f1a78cec..2d44964b5 100644 --- a/src/xrt/targets/cli/CMakeLists.txt +++ b/src/xrt/targets/cli/CMakeLists.txt @@ -13,6 +13,13 @@ set(SOURCE_FILES cli_main.c ) +if(NOT WIN32) + # Lots of linux assumptions in this file. + list(APPEND SOURCE_FILES + cli_cmd_trace.c + ) +endif() + if(NOT WIN32) # No getline on Windows, so until we have a portable impl list(APPEND SOURCE_FILES diff --git a/src/xrt/targets/cli/cli_cmd_trace.c b/src/xrt/targets/cli/cli_cmd_trace.c new file mode 100644 index 000000000..caebf8724 --- /dev/null +++ b/src/xrt/targets/cli/cli_cmd_trace.c @@ -0,0 +1,334 @@ +// Copyright 2021, Collabora, Ltd. +// SPDX-License-Identifier: BSL-1.0 +/*! + * @file + * @brief Trace marker parsing and coversion code. + * @author Jakob Bornecrantz + */ + +#include "xrt/xrt_compiler.h" + +#include "util/u_misc.h" +#include "util/u_trace_marker.h" + +#include "cli_common.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define P(...) fprintf(stderr, __VA_ARGS__) + +#define TRACE_PIPE_FILENAME "/sys/kernel/tracing/trace_pipe" +#define TRACE_MARKER_FILENAME "/sys/kernel/tracing/trace_marker" +#define BUF_SIZE (1024 * 8) + + +struct trace +{ + int fd; + FILE *file; + + bool running; + + char buffer[BUF_SIZE]; +}; + +static struct trace t = {0}; + + +/* + * + * JSON writing. + * + */ + +static void +json_w_header() +{ + fprintf(t.file, + "{\n" + "\t\"displayTimeUnit\": \"ms\",\n" + "\t\"traceEvents\": [\n" + "\t\t{\n" + "\t\t\t\"#\": \"This is to avoid having to deal with ',' all over the code.\"\n" + "\t\t}"); +} + +static void +json_w_end() +{ + fprintf(t.file, + "\n" + "\t]\n" + "}\n"); +} + + +/* + * + * Functions. + * + */ + +static int +open_file_fd(const char *filename, int flags) +{ + int fd = open(filename, flags); + if (fd < 0) { + fprintf(stderr, " :: Failed to open the file: '%s'\n", filename); + fprintf(stderr, " See command help!\n"); + } + + return fd; +} + +static int +open_fd() +{ + int check_fd = open_file_fd(TRACE_MARKER_FILENAME, O_WRONLY); + if (check_fd < 0) { + return -1; + } + close(check_fd); + + fprintf(stderr, " :: Checked '%s'\n", TRACE_MARKER_FILENAME); + + t.fd = open_file_fd(TRACE_PIPE_FILENAME, O_RDONLY); + + fprintf(stderr, " :: Opened '%s'\n", TRACE_PIPE_FILENAME); + + return 0; +} + +static void +handle_data(const char *rest_of_line, size_t len) +{ + int type = 0; + int data_length = 0; + int consumed = 0; + int scan = sscanf(rest_of_line, "%i %i %n", &type, &data_length, &consumed); + char data[1024]; + + if ((int)data_length * 2 != (int)len - (int)consumed) { + return; + } + + if ((size_t)data_length > sizeof(data)) { + return; + } + + rest_of_line = rest_of_line + consumed; + len = len - (size_t)consumed; + + + for (int i = 0; i < data_length; i++) { + int tmp = 0; + scan = sscanf(rest_of_line + (i * 2), "%2x", &tmp); + if (scan < 1) { + return; + } + + data[i] = tmp; + } + + switch (type) { + case U_TRACE_DATA_TYPE_TIMING_FRAME: u_timing_frame_write_json(t.file, (void *)data); break; + default: fprintf(stderr, "%.*s\n", (int)len, rest_of_line); break; + } +} + +static void +handle_line(const char *line, size_t len) +{ + int pid = 0, tid = 0; + int secs = 0, usecs = 0; + char cmd = '\0'; + int consumed = 0; + char function[256] = {0}; + + int scan = sscanf( // + line, // + " <...>-%i %*s %*s %d.%d: tracing_mark_write: %1s %i %n", // + &tid, // + &secs, // + &usecs, // + &cmd, // + &pid, // + &consumed); // + + if (scan < 5) { + fprintf(stderr, "%.*s\n", (int)len, line); + return; + } + + switch (cmd) { + case 'E': + case 'B': + scan = sscanf(line + consumed, "%256s", function); + if (scan < 1) { + fprintf(stderr, "%.*s\n", (int)len, line); + return; + } + + fprintf(t.file, + ",\n" + "\t\t{\n" + "\t\t\t\"ph\": \"%.1s\",\n" + "\t\t\t\"name\": \"%s\",\n" + "\t\t\t\"cat\": \"%s\",\n" + "\t\t\t\"ts\": %u%06u,\n" + "\t\t\t\"pid\": %u,\n" + "\t\t\t\"tid\": %u\n" + "\t\t}", + &cmd, function, "func", secs, usecs, pid, tid); + break; + case 'r': handle_data(line + consumed, len - (size_t)consumed); break; + default: fprintf(stderr, "%.*s\n", (int)len, line); break; + } +} + +static int +loop() +{ + while (t.running) { + ssize_t ret = read(t.fd, t.buffer, BUF_SIZE); + if (ret < 0) { + return 1; + } + if (ret == 0) { + continue; + } + + size_t pos = 0; + size_t count = 0; + while (count < (size_t)ret) { + if (t.buffer[count] != '\n') { + count++; + continue; + } + + assert(t.buffer[pos] != '\n'); + + size_t length = count - pos; + handle_line(t.buffer + pos, length); + + // Point *after* the newline. + count++; + pos = count; + } + + fflush(t.file); + } + + return 0; +} + +void +signal_handler(int signum, siginfo_t *info, void *ptr) +{ + t.running = false; + + // Since we are doing a clean shutdown ^C be on it's own line. + write(STDERR_FILENO, "\n", 1); +} + +void +catch_sigterm() +{ + static struct sigaction _sigact; + + memset(&_sigact, 0, sizeof(_sigact)); + _sigact.sa_sigaction = signal_handler; + _sigact.sa_flags = SA_SIGINFO; + + sigaction(SIGTERM, &_sigact, NULL); + sigaction(SIGINT, &_sigact, NULL); +} + +static int +trace_pipe(int argc, const char **argv) +{ + catch_sigterm(); + + t.file = stdout; + t.running = true; + + int ret = open_fd(); + if (ret < 0) { + return 1; + } + + json_w_header(); + + u_timing_frame_write_json_metadata(t.file); + + P(" :: Looping\n"); + + ret = loop(); + if (ret < 0) { + return 1; + } + + json_w_end(); + + P(" :: Clean shutdown\n"); + + return 0; +} + +static void +print_help(int argc, const char **argv) +{ + if (argc >= 3) { + P("Unknown trace command '%s'\n\n", argv[2]); + } + + P("Usage %s trace \n", argv[0]); + P("\n"); + P("Commands:\n"); + P(" pipe - Read the trace_pipe stream and convert into json outputted to stdout.\n"); + P("\n"); + P("Example:\n"); + P(" $ %s trace pipe 1> /tmp/chrome_tracing.json\n", argv[0]); + P("\n"); + P("Make sure your user has access to the files:\n"); + P(" '%s'.\n", TRACE_PIPE_FILENAME); + P(" '%s'.\n", TRACE_MARKER_FILENAME); + P("\n"); + P("The reference clocks needs to be the same in Monado and the tracing framework.\n"); + P(" $ echo mono | sudo dd of=/sys/kernel/tracing/trace_clock\n"); + P("This command is very unsecure but will make things work.\n"); + P(" $ sudo chown -R : /sys/kernel/tracing\n"); + P("\n"); + P("See https://lwn.net/Articles/366796/\n"); +} + +int +cli_cmd_trace(int argc, const char **argv) +{ + if (argc <= 2) { + print_help(argc, argv); + return 1; + } + + if (strcmp(argv[2], "help") == 0) { + print_help(2, argv); + return 0; + } + + if (strcmp(argv[2], "pipe") == 0) { + return trace_pipe(argc, argv); + } + + print_help(argc, argv); + + return 1; +} diff --git a/src/xrt/targets/cli/cli_common.h b/src/xrt/targets/cli/cli_common.h index af97c1542..ea7e8c34f 100644 --- a/src/xrt/targets/cli/cli_common.h +++ b/src/xrt/targets/cli/cli_common.h @@ -9,6 +9,11 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + + int cli_cmd_calibrate(int argc, const char **argv); @@ -20,3 +25,11 @@ cli_cmd_probe(int argc, const char **argv); int cli_cmd_test(int argc, const char **argv); + +int +cli_cmd_trace(int argc, const char **argv); + + +#ifdef __cplusplus +} +#endif diff --git a/src/xrt/targets/cli/cli_main.c b/src/xrt/targets/cli/cli_main.c index 1cc7bccca..8ffc05a3e 100644 --- a/src/xrt/targets/cli/cli_main.c +++ b/src/xrt/targets/cli/cli_main.c @@ -30,8 +30,8 @@ cli_print_help(int argc, const char **argv) P(" test - List found devices, for prober testing.\n"); P(" probe - Just probe and then exit.\n"); P(" lighthouse - Control the power of lighthouses [on|off].\n"); - P(" calibrate - Calibrate a camera and save config (not implemented " - "yet).\n"); + P(" calibrate - Calibrate a camera and save config (not implemented yet).\n"); + P(" trace - Tracing functionality.\n"); return 1; } @@ -57,5 +57,10 @@ main(int argc, const char **argv) if (strcmp(argv[1], "lighthouse") == 0) { return cli_cmd_lighthouse(argc, argv); } +#ifndef XRT_OS_WINDOWS + if (strcmp(argv[1], "trace") == 0) { + return cli_cmd_trace(argc, argv); + } +#endif // !XRT_OS_WINDOWS return cli_print_help(argc, argv); } diff --git a/src/xrt/targets/cli/meson.build b/src/xrt/targets/cli/meson.build index a8ce05bb5..4a470464f 100644 --- a/src/xrt/targets/cli/meson.build +++ b/src/xrt/targets/cli/meson.build @@ -8,6 +8,7 @@ cli = executable( 'cli_cmd_lighthouse.c', 'cli_cmd_probe.c', 'cli_cmd_test.c', + 'cli_cmd_trace.c', 'cli_common.h', 'cli_main.c', ),