diff --git a/tools/meson.build b/tools/meson.build index b0b17b5e..7743dd91 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -6,6 +6,12 @@ sysprof_cli = executable('sysprof-cli', install: true, ) +sysprof_cat = executable('sysprof-cat', 'sysprof-cat.c', + dependencies: libsysprof_capture_dep, + c_args: exe_c_args, + link_args: exe_link_args, +) + sysprof_dump = executable('sysprof-dump', 'sysprof-dump.c', dependencies: libsysprof_capture_dep, diff --git a/tools/sysprof-cat.c b/tools/sysprof-cat.c new file mode 100644 index 00000000..f7a1e129 --- /dev/null +++ b/tools/sysprof-cat.c @@ -0,0 +1,423 @@ +/* sysprof-cat.c + * + * Copyright © 2018 Christian Hergert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include +#include +#include + +#include "capture/sp-capture-reader.h" +#include "capture/sp-capture-writer.h" +#include "util/sp-platform.h" + +typedef struct +{ + guint64 src; + guint64 dst; +} TranslateItem; + +enum { + TRANSLATE_ADDR, + TRANSLATE_CTR, + N_TRANSLATE +}; + +static GArray *translate_table[N_TRANSLATE]; + +static void +translate_table_clear (guint table) +{ + g_clear_pointer (&translate_table[table], g_array_unref); +} + +static gint +compare_by_src (gconstpointer a, + gconstpointer b) +{ + const TranslateItem *itema = a; + const TranslateItem *itemb = a; + + if (itema->src < itemb->src) + return -1; + else if (itema->src > itemb->src) + return 1; + else + return 0; +} + +static void +translate_table_sort (guint table) +{ + if (translate_table[table]) + g_array_sort (translate_table[table], compare_by_src); +} + +static void +translate_table_add (guint table, + guint64 src, + guint64 dst) +{ + TranslateItem item = { src, dst }; + + if (translate_table[table] == NULL) + translate_table[table] = g_array_new (FALSE, FALSE, sizeof (TranslateItem)); + + g_array_append_val (translate_table[table], item); +} + +static guint64 +translate_table_translate (guint table, + guint64 src) +{ + const TranslateItem *item; + TranslateItem key = { src, 0 }; + + if (!translate_table[table]) + return src; + + item = bsearch (&key, + translate_table[table]->data, + translate_table[table]->len, + sizeof (TranslateItem), + compare_by_src); + + return item != NULL ? item->dst : src; +} + +gint +main (gint argc, + gchar *argv[]) +{ + g_autoptr(SpCaptureWriter) writer = NULL; + g_autofree gchar *contents = NULL; + g_autofree gchar *tmpname = NULL; + gint64 first_start_time = G_MAXINT64; + gint64 end_time = -1; + gsize len; + gint fd; + + if (argc == 1) + return 0; + + for (guint i = 1; i < argc; i++) + { + if (!g_file_test (argv[i], G_FILE_TEST_IS_REGULAR)) + { + g_printerr ("\"%s\" is not a regular file.\n", argv[i]); + return EXIT_FAILURE; + } + } + + if (-1 == (fd = g_file_open_tmp (".sysprof-cat-XXXXXX", &tmpname, NULL))) + { + g_printerr ("Failed to create memfd for capture file.\n"); + return EXIT_FAILURE; + } + + writer = sp_capture_writer_new_from_fd (fd, 4096*4); + + for (guint i = 1; i < argc; i++) + { + g_autoptr(GError) error = NULL; + g_autoptr(SpCaptureReader) reader = sp_capture_reader_new (argv[i], &error); + SpCaptureFrameType type; + gint64 start_time; + + if (reader == NULL) + { + g_printerr ("Failed to create reader for \"%s\": %s\n", + argv[i], error->message); + return EXIT_FAILURE; + } + + translate_table_clear (TRANSLATE_CTR); + translate_table_clear (TRANSLATE_ADDR); + + start_time = sp_capture_reader_get_start_time (reader); + + if (start_time < first_start_time) + first_start_time = start_time; + + while (sp_capture_reader_peek_type (reader, &type)) + { + SpCaptureFrame fr; + + if (sp_capture_reader_peek_frame (reader, &fr)) + { + if (fr.time > end_time) + end_time = fr.time; + } + + switch (type) + { + case SP_CAPTURE_FRAME_TIMESTAMP: + { + const SpCaptureTimestamp *frame; + + if (!(frame = sp_capture_reader_read_timestamp (reader))) + goto panic; + + sp_capture_writer_add_timestamp (writer, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid); + break; + } + + case SP_CAPTURE_FRAME_MAP: + { + const SpCaptureMap *frame; + + if (!(frame = sp_capture_reader_read_map (reader))) + goto panic; + + sp_capture_writer_add_map (writer, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid, + frame->start, + frame->end, + frame->offset, + frame->inode, + frame->filename); + break; + } + + case SP_CAPTURE_FRAME_MARK: + { + const SpCaptureMark *frame; + + if (!(frame = sp_capture_reader_read_mark (reader))) + goto panic; + + sp_capture_writer_add_mark (writer, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid, + frame->duration, + frame->group, + frame->name, + frame->message); + + if (frame->frame.time + frame->duration > end_time) + end_time = frame->frame.time + frame->duration; + + break; + } + + case SP_CAPTURE_FRAME_PROCESS: + { + const SpCaptureProcess *frame; + + if (!(frame = sp_capture_reader_read_process (reader))) + goto panic; + + sp_capture_writer_add_process (writer, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid, + frame->cmdline); + break; + } + + case SP_CAPTURE_FRAME_FORK: + { + const SpCaptureFork *frame; + + if (!(frame = sp_capture_reader_read_fork (reader))) + goto panic; + + sp_capture_writer_add_fork (writer, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid, + frame->child_pid); + break; + } + + case SP_CAPTURE_FRAME_EXIT: + { + const SpCaptureExit *frame; + + if (!(frame = sp_capture_reader_read_exit (reader))) + goto panic; + + sp_capture_writer_add_exit (writer, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid); + break; + } + + case SP_CAPTURE_FRAME_JITMAP: + { + GHashTable *jitmap; + GHashTableIter iter; + const gchar *name; + guint64 addr; + + if (!(jitmap = sp_capture_reader_read_jitmap (reader))) + goto panic; + + g_hash_table_iter_init (&iter, jitmap); + while (g_hash_table_iter_next (&iter, (gpointer *)&addr, (gpointer *)&name)) + { + guint64 replace = sp_capture_writer_add_jitmap (writer, name); + /* We need to keep a table of replacement addresses so that + * we can translate the samples into the destination address + * space that we synthesized for the address identifier. + */ + translate_table_add (TRANSLATE_ADDR, addr, replace); + } + + translate_table_sort (TRANSLATE_ADDR); + + g_hash_table_unref (jitmap); + + break; + } + + case SP_CAPTURE_FRAME_SAMPLE: + { + const SpCaptureSample *frame; + + if (!(frame = sp_capture_reader_read_sample (reader))) + goto panic; + + { + SpCaptureAddress addrs[frame->n_addrs]; + + for (guint z = 0; z < frame->n_addrs; z++) + addrs[z] = translate_table_translate (TRANSLATE_ADDR, frame->addrs[z]); + + sp_capture_writer_add_sample (writer, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid, + addrs, + frame->n_addrs); + } + + break; + } + + case SP_CAPTURE_FRAME_CTRDEF: + { + const SpCaptureFrameCounterDefine *frame; + + if (!(frame = sp_capture_reader_read_counter_define (reader))) + goto panic; + + { + g_autoptr(GArray) counter = g_array_new (FALSE, FALSE, sizeof (SpCaptureCounter)); + + for (guint z = 0; z < frame->n_counters; z++) + { + SpCaptureCounter c = frame->counters[z]; + guint src = c.id; + + c.id = sp_capture_writer_request_counter (writer, 1); + + if (c.id != src) + translate_table_add (TRANSLATE_CTR, src, c.id); + + g_array_append_val (counter, c); + } + + sp_capture_writer_define_counters (writer, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid, + (gpointer)counter->data, + counter->len); + + translate_table_sort (TRANSLATE_CTR); + } + + break; + } + + case SP_CAPTURE_FRAME_CTRSET: + { + const SpCaptureFrameCounterSet *frame; + + if (!(frame = sp_capture_reader_read_counter_set (reader))) + goto panic; + + { + g_autoptr(GArray) ids = g_array_new (FALSE, FALSE, sizeof (guint)); + g_autoptr(GArray) values = g_array_new (FALSE, FALSE, sizeof (SpCaptureCounterValue)); + + for (guint z = 0; z < frame->n_values; z++) + { + const SpCaptureCounterValues *v = &frame->values[z]; + + for (guint y = 0; y < G_N_ELEMENTS (v->ids); y++) + { + if (v->ids[y]) + { + guint dst = translate_table_translate (TRANSLATE_CTR, v->ids[y]); + SpCaptureCounterValue value = v->values[y]; + + g_array_append_val (ids, dst); + g_array_append_val (values, value); + } + } + } + + g_assert (ids->len == values->len); + + sp_capture_writer_set_counters (writer, + frame->frame.time, + frame->frame.cpu, + frame->frame.pid, + (const guint *)(gpointer)ids->data, + (const SpCaptureCounterValue *)(gpointer)values->data, + ids->len); + } + + break; + } + + default: + break; + } + } + } + + sp_capture_writer_flush (writer); + + /* do this after flushing as it uses pwrite() to replace data */ + _sp_capture_writer_set_time_range (writer, first_start_time, end_time); + + if (g_file_get_contents (tmpname, &contents, &len, NULL)) + write (STDOUT_FILENO, contents, len); + + g_unlink (tmpname); + + return EXIT_SUCCESS; + +panic: + g_printerr ("There was a failure to read the stream.\n"); + + if (tmpname) + g_unlink (tmpname); + + return EXIT_FAILURE; +}