From 77d091bdc8e5d1f11ca7881ebc8170a76f0f76cf Mon Sep 17 00:00:00 2001 From: Simon Cruanes Date: Wed, 27 Dec 2023 01:18:41 -0500 Subject: [PATCH] wip: import-fuchsia tool to import fuchsia traces See: https://fuchsia.dev/fuchsia-src/reference/tracing/trace-format --- import-fuchsia/build/unix/Makefile | 16 + import-fuchsia/build/unix/build.mk | 12 + import-fuchsia/build/unix/debug.mk | 6 + import-fuchsia/build/unix/release.mk | 9 + import-fuchsia/build/win32/import-fuchsia.sln | 25 + .../build/win32/import-fuchsia.vcxproj | 206 ++++++ .../win32/import-fuchsia.vcxproj.filters | 356 ++++++++++ import-fuchsia/src/import-fuchsia.cpp | 653 ++++++++++++++++++ 8 files changed, 1283 insertions(+) create mode 100644 import-fuchsia/build/unix/Makefile create mode 100644 import-fuchsia/build/unix/build.mk create mode 100644 import-fuchsia/build/unix/debug.mk create mode 100644 import-fuchsia/build/unix/release.mk create mode 100644 import-fuchsia/build/win32/import-fuchsia.sln create mode 100644 import-fuchsia/build/win32/import-fuchsia.vcxproj create mode 100644 import-fuchsia/build/win32/import-fuchsia.vcxproj.filters create mode 100644 import-fuchsia/src/import-fuchsia.cpp diff --git a/import-fuchsia/build/unix/Makefile b/import-fuchsia/build/unix/Makefile new file mode 100644 index 00000000..f762d28a --- /dev/null +++ b/import-fuchsia/build/unix/Makefile @@ -0,0 +1,16 @@ +all: release + +debug: + @$(MAKE) -f debug.mk all + +release: + @$(MAKE) -f release.mk all + +clean: + @$(MAKE) -f build.mk clean + +db: clean + @bear -- $(MAKE) -f debug.mk all + @mv -f compile_commands.json ../../../ + +.PHONY: all clean debug release db diff --git a/import-fuchsia/build/unix/build.mk b/import-fuchsia/build/unix/build.mk new file mode 100644 index 00000000..5f62bc17 --- /dev/null +++ b/import-fuchsia/build/unix/build.mk @@ -0,0 +1,12 @@ +CFLAGS += +CXXFLAGS := $(CFLAGS) -std=gnu++17 +DEFINES += -DTRACY_NO_STATISTICS +INCLUDES := $(shell pkg-config --cflags capstone) +LIBS += $(shell pkg-config --libs capstone) -lpthread +PROJECT := import-fuchsia +IMAGE := $(PROJECT)-$(BUILD) + +FILTER := +include ../../../common/src-from-vcxproj.mk + +include ../../../common/unix.mk diff --git a/import-fuchsia/build/unix/debug.mk b/import-fuchsia/build/unix/debug.mk new file mode 100644 index 00000000..a4ec6b6a --- /dev/null +++ b/import-fuchsia/build/unix/debug.mk @@ -0,0 +1,6 @@ +CFLAGS := -g3 -Wall +DEFINES := -DDEBUG +BUILD := debug + +include ../../../common/unix-debug.mk +include build.mk diff --git a/import-fuchsia/build/unix/release.mk b/import-fuchsia/build/unix/release.mk new file mode 100644 index 00000000..ccf07661 --- /dev/null +++ b/import-fuchsia/build/unix/release.mk @@ -0,0 +1,9 @@ +CFLAGS := -O3 +ifndef TRACY_NO_LTO +CFLAGS += -flto +endif +DEFINES := -DNDEBUG +BUILD := release + +include ../../../common/unix-release.mk +include build.mk diff --git a/import-fuchsia/build/win32/import-fuchsia.sln b/import-fuchsia/build/win32/import-fuchsia.sln new file mode 100644 index 00000000..4b38515b --- /dev/null +++ b/import-fuchsia/build/win32/import-fuchsia.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30907.101 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "import-fuchsia", "import-fuchsia.vcxproj", "{447D58BF-94CD-4469-BB90-549C05D03E00}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {447D58BF-94CD-4469-BB90-549C05D03E00}.Debug|x64.ActiveCfg = Debug|x64 + {447D58BF-94CD-4469-BB90-549C05D03E00}.Debug|x64.Build.0 = Debug|x64 + {447D58BF-94CD-4469-BB90-549C05D03E00}.Release|x64.ActiveCfg = Release|x64 + {447D58BF-94CD-4469-BB90-549C05D03E00}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3E51386C-43EA-44AC-9F24-AFAFE4D63ADE} + EndGlobalSection +EndGlobal diff --git a/import-fuchsia/build/win32/import-fuchsia.vcxproj b/import-fuchsia/build/win32/import-fuchsia.vcxproj new file mode 100644 index 00000000..d57f8f84 --- /dev/null +++ b/import-fuchsia/build/win32/import-fuchsia.vcxproj @@ -0,0 +1,206 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {447D58BF-94CD-4469-BB90-549C05D03E00} + import-fuchsia + 10.0 + x64-windows-static + + + + Application + true + v143 + MultiByte + + + Application + false + v143 + true + MultiByte + + + + + + + + + + + + + + + + true + + + + Level3 + Disabled + true + true + true + TRACY_NO_STATISTICS;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32_LEAN_AND_MEAN;NOMINMAX;_USE_MATH_DEFINES;%(PreprocessorDefinitions) + AdvancedVectorExtensions2 + stdcpplatest + $(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\include;$(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\include\capstone;$(VcpkgManifestRoot)\vcpkg_installed\$(VcpkgTriplet)\$(VcpkgTriplet)\include\capstone;$(VcpkgRoot)\installed\$(VcpkgTriplet)\include\capstone + + + ws2_32.lib;capstone.lib;%(AdditionalDependencies) + Console + $(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\debug\lib + + + + + Level3 + MaxSpeed + true + true + true + true + true + TRACY_NO_STATISTICS;NDEBUG;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;WIN32_LEAN_AND_MEAN;NOMINMAX;_USE_MATH_DEFINES;%(PreprocessorDefinitions) + AdvancedVectorExtensions2 + stdcpplatest + $(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\include;$(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\include\capstone;$(VcpkgManifestRoot)\vcpkg_installed\$(VcpkgTriplet)\$(VcpkgTriplet)\include\capstone;$(VcpkgRoot)\installed\$(VcpkgTriplet)\include\capstone + + + true + true + ws2_32.lib;capstone.lib;%(AdditionalDependencies) + Console + $(ProjectDir)..\..\..\vcpkg_installed\$(VcpkgTriplet)\lib + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/import-fuchsia/build/win32/import-fuchsia.vcxproj.filters b/import-fuchsia/build/win32/import-fuchsia.vcxproj.filters new file mode 100644 index 00000000..3efa781a --- /dev/null +++ b/import-fuchsia/build/win32/import-fuchsia.vcxproj.filters @@ -0,0 +1,356 @@ + + + + + {729c80ee-4d26-4a5e-8f1f-6c075783eb56} + + + {cf23ef7b-7694-4154-830b-00cf053350ea} + + + {e39d3623-47cd-4752-8da9-3ea324f964c1} + + + {9ec18988-3ab7-4c05-a9d0-46c0a68037de} + + + {5ee9ba63-2914-4027-997e-e743a294bba6} + + + {a166d032-7be0-4d07-9f85-a8199cc1ec7c} + + + {438fff23-197c-4b6f-91f0-74f8b3878571} + + + {e5c7021a-e0e4-45c2-b461-e806bc036d5f} + + + + + server + + + server + + + src + + + server + + + server + + + server + + + server + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\decompress + + + zstd\decompress + + + zstd\decompress + + + zstd\decompress + + + zstd\dictBuilder + + + zstd\dictBuilder + + + zstd\dictBuilder + + + zstd\dictBuilder + + + common + + + common + + + common + + + common + + + common + + + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + server + + + zstd + + + zstd + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\common + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\compress + + + zstd\decompress + + + zstd\decompress + + + zstd\decompress + + + zstd + + + zstd\dictBuilder + + + zstd\dictBuilder + + + zstd\common + + + zstd\compress + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + + + zstd\decompress + + + diff --git a/import-fuchsia/src/import-fuchsia.cpp b/import-fuchsia/src/import-fuchsia.cpp new file mode 100644 index 00000000..f6762a0e --- /dev/null +++ b/import-fuchsia/src/import-fuchsia.cpp @@ -0,0 +1,653 @@ +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef _MSC_VER +#define stat64 _stat64 +#endif +#if defined __APPLE__ +#define stat64 stat +#endif + +#include "../../server/TracyFileWrite.hpp" +#include "../../server/TracyMmap.hpp" +#include "../../server/TracyWorker.hpp" +#include "../../zstd/zstd.h" + +void Usage() { + printf("Usage: import-fuchsia input.json output.tracy\n\n"); + printf("See: " + "https://fuchsia.dev/fuchsia-src/reference/tracing/trace-format\n\n"); + exit(1); +} + +#define ROUND_TO_WORD(n) ((n) + ((~((n)-1)) & 0x7)) + +struct ThreadRef { + uint64_t pid; + uint64_t tid; +}; + +inline bool operator==(const ThreadRef t1, const ThreadRef t2) { + return t1.pid == t2.pid && t1.tid == t2.tid; +} + +struct Unit {}; + +// arguments +using ArgumentValue = + std::variant; + +struct Argument { + std::string name; + ArgumentValue value; +}; + +// encode a pair of "real pid, real tid" from a trace into a +// pseudo thread ID living in the single namespace of Tracy threads. +struct PidTidEncoder { + ThreadRef thref; + uint64_t pseudo_tid; // fake thread id, unique within Tracy +}; + +// A span into the main buffer +struct Record { + const uint64_t *p; + ; + uint16_t len_word; + uint64_t header; +}; + +struct DecodeState { + std::vector tid_encoders; + std::unordered_map threadNames; + // compressed thread refs + std::unordered_map threadRefs; + // compressed strings + std::unordered_map stringRefs; +}; + +// Append a string representation of `val` to `res` +void appendArgumentValue(std::string &res, ArgumentValue &val) { + if (std::holds_alternative(val)) { + res += std::get(val); + } else if (std::holds_alternative(val)) { + char buf[32]; + snprintf(buf, 31, "%" PRIu64, std::get(val)); + res += buf; + } else if (std::holds_alternative(val)) { + char buf[32]; + snprintf(buf, 31, "%" PRId64, std::get(val)); + res += buf; + } else if (std::holds_alternative(val)) { + res += std::get(val) ? "true" : "false"; + } else if (std::holds_alternative(val)) { + char buf[32]; + snprintf(buf, 31, "%.5f", std::get(val)); + res += buf; + } +} + +// Read input into a local buffer +std::vector read_input(const char *input) { + std::vector buf; + + FILE *f = fopen(input, "rb"); + if (!f) { + fprintf(stderr, "Cannot open input file!\n"); + exit(1); + } + struct stat64 sb; + if (stat64(input, &sb) != 0) { + fprintf(stderr, "Cannot open input file!\n"); + fclose(f); + exit(1); + } + + const auto zsz = sb.st_size; + auto zbuf = (char *)mmap(nullptr, zsz, PROT_READ, MAP_SHARED, fileno(f), 0); + fclose(f); + if (!zbuf) { + fprintf(stderr, "Cannot mmap input file!\n"); + exit(1); + } + + const auto fnsz = strlen(input); + if (fnsz > 4 && memcmp(input + fnsz - 4, ".zst", 4) == 0) { + + auto zctx = ZSTD_createDStream(); + ZSTD_initDStream(zctx); + + enum { tmpSize = 64 * 1024 }; + auto tmp = new char[tmpSize]; + + ZSTD_inBuffer_s zin = {zbuf, (size_t)zsz}; + ZSTD_outBuffer_s zout = {tmp, (size_t)tmpSize}; + + buf.reserve(1024 * 1024); + + while (zin.pos < zin.size) { + const auto res = ZSTD_decompressStream(zctx, &zout, &zin); + if (ZSTD_isError(res)) { + ZSTD_freeDStream(zctx); + delete[] tmp; + fprintf(stderr, "Couldn't decompress input file (%s)!\n", + ZSTD_getErrorName(res)); + exit(1); + } + if (zout.pos > 0) { + const auto bsz = buf.size(); + buf.resize(bsz + zout.pos); + memcpy(buf.data() + bsz, tmp, zout.pos); + zout.pos = 0; + } + } + + ZSTD_freeDStream(zctx); + delete[] tmp; + } else { + // just copy to memory + buf.resize(zsz); + memcpy(buf.data(), zbuf, zsz); + } + + munmap(zbuf, zsz); + return buf; +} + +// read next span starting at `offset` +Record read_next_record(std::vector const &input, size_t &offset) { + uint64_t header = *((uint64_t *)&input[offset]); + uint16_t len_word = (header >> 4) & 0xfff; + Record sp{(uint64_t *)&input[offset], len_word, header}; + offset += 8 * len_word; + return sp; +} + +// there might be multiple processes so we allocate a pseudo-tid +// for each pair (pid, real_tid) +uint64_t getPseudoTid(DecodeState &dec, ThreadRef th) { + for (auto &pair : dec.tid_encoders) { + if (pair.thref == th) + return pair.pseudo_tid; + } + + // not found, invent a new one + assert(th.pid <= std::numeric_limits::max()); + assert(th.tid <= std::numeric_limits::max()); + + const auto pseudo_tid = (th.tid & 0xFFFFFFFF) | (th.pid << 32); + dec.tid_encoders.emplace_back(PidTidEncoder{th, pseudo_tid}); + return pseudo_tid; +} + +// decode thread info +ThreadRef readThread(DecodeState &dec, Record const &r, size_t &offset, + uint8_t ref) { + ThreadRef th; + if (ref == 0) { + // inline + th = {r.p[offset], r.p[offset + 1]}; + offset += 2; + } else { + th = dec.threadRefs[ref]; + } + return th; +} + +// Read a string reference into `res` +void readString(DecodeState &dec, std::string &res, Record const &r, + size_t &offset, uint16_t ref) { + res.clear(); + if (ref == 0) { + } else if ((ref & 0x8000) != 0) { + // inline string + size_t size_name = ref & 0x7fff; + res.resize(size_name + 1); + memcpy(res.data(), (uint8_t *)&r.p[offset], size_name); + res[size_name] = 0; + offset += ROUND_TO_WORD(size_name) >> 3; + } else { + res = dec.stringRefs[ref]; + } +} + +// Skip string reference (just modify offset) +void skipString(size_t &offset, uint16_t ref) { + if (ref != 0 && (ref & 0x80) != 0) { + size_t size = ref & 0x7f; + offset += ROUND_TO_WORD(size) >> 3; + } +} + +// Read a single argument +void readArgument(std::vector &args, DecodeState &dec, + Record const &r, size_t &offset) { + uint64_t header = r.p[offset]; + offset += 1; + + auto ty = (uint8_t)(header & 0xf); + + uint16_t name_ref = (header >> 16) & 0xffff; + std::string name; + readString(dec, name, r, offset, name_ref); + + ArgumentValue value; + switch (ty) { + case 0: + value = (Unit){}; + break; + case 1: { + int32_t i = header >> 32; + value = (int64_t)i; + } break; + case 2: { + uint32_t i = header >> 32; + value = (int64_t)i; + } break; + case 3: { + int64_t i = r.p[offset]; + offset += 1; + value = i; + } break; + case 4: { + uint64_t i = r.p[offset]; + offset += 1; + value = i; + } break; + case 5: { + double i = r.p[offset]; + offset += 1; + value = i; + } break; + case 6: { + uint16_t value_ref = (header >> 32) & 0xffff; + std::string res; + readString(dec, res, r, offset, value_ref); + value = res; + } break; + case 7: + // pointer + case 8: + // koid + { + uint64_t i = r.p[offset]; + offset += 1; + value = i; + } + break; + case 9: { + // bool + bool b = (bool)((header >> 32) & 1); + value = b; + } + + default: + assert(false); + } + + args.push_back({name, value}); +} + +/// Read `n_args` arguments from given offset +void readArguments(std::vector &args, DecodeState &dec, Record r, + size_t &offset, const int n_args) { + args.clear(); + + for (int i = 0; i < n_args; ++i) + readArgument(args, dec, r, offset); +} + +// text made of arguments +void printArgumentsToString(std::string &res, std::vector &args) { + for (auto &kv : args) { + res += kv.name; + res += ": "; + appendArgumentValue(res, kv.value); + res += "\n"; + } +} + +int main(int argc, char **argv) { +#ifdef _WIN32 + if (!AttachConsole(ATTACH_PARENT_PROCESS)) { + AllocConsole(); + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), 0x07); + } +#endif + + tracy::FileWrite::Compression clev = tracy::FileWrite::Compression::Fast; + + if (argc != 3) + Usage(); + + const char *input = argv[1]; + const char *output = argv[2]; + + printf("Loading...\r"); + fflush(stdout); + + std::vector buf = read_input(input); + + printf("\33[2KParsing...\r"); + fflush(stdout); + + std::vector timeline; + std::vector messages; + std::vector plots; + DecodeState dec; + + size_t offset = 0; + int n_records = 0; + std::string name; + std::vector arguments; + + while (offset < buf.size()) { + Record r = read_next_record(buf, offset); + n_records++; + + uint8_t ty = r.header & 0xf; + + switch (ty) { + case 3: { + // thread record + uint8_t th_ref = (r.header >> 16) & 0xff; + uint64_t pid = r.p[1]; + uint64_t tid = r.p[2]; + ThreadRef th{pid, tid}; + dec.threadRefs[th_ref] = th; + break; + } + case 4: { + // event + uint8_t ev_ty = (r.header >> 16) & 0xf; + uint8_t n_args = (r.header >> 20) & 0xf; + + uint64_t timestamp = r.p[1]; + size_t offset = 2; + + // decode thread info + uint8_t th_ref = (r.header >> 24) & 0xff; + ThreadRef th = readThread(dec, r, offset, th_ref); + + // skip category + uint16_t cat_ref = (r.header >> 32) & 0xffff; + skipString(offset, cat_ref); + + // decode name + uint16_t name_ref = (r.header >> 48) & 0xffff; + readString(dec, name, r, offset, name_ref); + + readArguments(arguments, dec, r, offset, n_args); + + std::string locFile; + uint32_t locLine = 0; + + switch (ev_ty) { + case 0: { + // instant + messages.emplace_back(tracy::Worker::ImportEventMessages{ + getPseudoTid(dec, th), timestamp, name}); + break; + } + + case 4: { + // complete duration + const auto tid = getPseudoTid(dec, th); + const auto ts0 = timestamp; + const auto ts1 = r.p[offset]; // end timestamp + std::string zoneText; + printArgumentsToString(zoneText, arguments); + timeline.emplace_back(tracy::Worker::ImportEventTimeline{ + tid, ts0, name, std::move(zoneText), false, std::move(locFile), + locLine}); + timeline.emplace_back( + tracy::Worker::ImportEventTimeline{tid, ts1, "", "", true}); + break; + } + + default: { + } + } + + /* + if( type == "b" || type == "B" ) + { + timeline.emplace_back( tracy::Worker::ImportEventTimeline { + getPseudoTid(v), + uint64_t( v["ts"].get() * 1000. ), + v["name"].get(), + std::move(zoneText), + false, + std::move(locFile), + locLine + } ); + } + */ + + break; + } + + default: { + } + } + } + + printf("read %d records\n", n_records); + fflush(stdout); + + /* + if( j.is_object() && j.contains( "traceEvents" ) ) + { + j = j["traceEvents"]; + } + + if( !j.is_array() ) + { + fprintf( stderr, "Input must be either an array of events or an object + containing an array of events under \"traceEvents\" key.\n" ); exit( 1 ); + } + + for( auto& v : j ) + { + const auto type = v["ph"].get(); + + std::string zoneText = ""; + if( v.contains( "args" ) ) + { + for( auto& kv : v["args"].items() ) + { + const auto val = kv.value(); + const std::string s = val.is_string() ? val.get() : + val.dump(); zoneText += kv.key() + ": " + s + "\n"; + } + } + + std::string locFile; + uint32_t locLine = 0; + if( v.contains( "loc" ) ) + { + auto loc = v["loc"].get(); + const auto lpos = loc.find_last_of( ':' ); + if( lpos == std::string::npos ) + { + std::swap( loc, locFile ); + } + else + { + locFile = loc.substr( 0, lpos ); + locLine = atoi( loc.c_str() + lpos + 1 ); + } + } + + if( type == "b" || type == "B" ) + { + timeline.emplace_back( tracy::Worker::ImportEventTimeline { + getPseudoTid(v), + uint64_t( v["ts"].get() * 1000. ), + v["name"].get(), + std::move(zoneText), + false, + std::move(locFile), + locLine + } ); + } + else if( type == "e" || type == "E" ) + { + timeline.emplace_back( tracy::Worker::ImportEventTimeline { + getPseudoTid(v), + uint64_t( v["ts"].get() * 1000. ), + "", + std::move(zoneText), + true + } ); + } + else if( type == "X" ) + { + const auto tid = getPseudoTid(v); + const auto ts0 = uint64_t( v["ts"].get() * 1000. ); + const auto ts1 = ts0 + uint64_t( v["dur"].get() * 1000. ); + const auto name = v["name"].get(); + timeline.emplace_back( tracy::Worker::ImportEventTimeline { tid, + ts0, name, std::move(zoneText), false, std::move(locFile), locLine } ); + timeline.emplace_back( tracy::Worker::ImportEventTimeline { tid, + ts1, "", "", true } ); + } + else if( type == "i" || type == "I" ) + { + messages.emplace_back( tracy::Worker::ImportEventMessages { + getPseudoTid(v), + uint64_t( v["ts"].get() * 1000. ), + v["name"].get() + } ); + } + else if( type == "C" ) + { + auto timestamp = int64_t( v["ts"].get() * 1000 ); + for( auto& kv : v["args"].items() ) + { + bool plotFound = false; + auto& metricName = kv.key(); + auto dataPoint = std::make_pair( timestamp, + kv.value().get() ); + + // The input file is assumed to have only very few metrics, + // so iterating through plots is not a problem. + for( auto& plot : plots ) + { + if( plot.name == metricName ) + { + plot.data.emplace_back( dataPoint ); + plotFound = true; + break; + } + } + if( !plotFound ) + { + auto formatting = tracy::PlotValueFormatting::Number; + + // NOTE: With C++20 one could say metricName.ends_with( + "_bytes" ) instead of rfind auto metricNameLen = metricName.size(); if ( + metricNameLen >= 6 && metricName.rfind( "_bytes" ) == metricNameLen - 6 ) { + formatting = tracy::PlotValueFormatting::Memory; + } + + plots.emplace_back( tracy::Worker::ImportEventPlots { + std::move( metricName ), + formatting, + { dataPoint } + } ); + } + } + } + else if (type == "M") + { + if (v.contains("name") && v["name"] == "thread_name" && + v.contains("args") && v["args"].is_object() && v["args"].contains("name")) + { + const auto tid = getPseudoTid(v); + threadNames[tid] = v["args"]["name"].get(); + } + } + } + */ + + std::stable_sort( + timeline.begin(), timeline.end(), + [](const auto &l, const auto &r) { return l.timestamp < r.timestamp; }); + std::stable_sort( + messages.begin(), messages.end(), + [](const auto &l, const auto &r) { return l.timestamp < r.timestamp; }); + for (auto &v : plots) + std::stable_sort( + v.data.begin(), v.data.end(), + [](const auto &l, const auto &r) { return l.first < r.first; }); + + uint64_t mts = 0; + if (!timeline.empty()) { + mts = timeline[0].timestamp; + } + if (!messages.empty()) { + if (mts > messages[0].timestamp) + mts = messages[0].timestamp; + } + for (auto &plot : plots) { + if (mts > plot.data[0].first) + mts = plot.data[0].first; + } + for (auto &v : timeline) + v.timestamp -= mts; + for (auto &v : messages) + v.timestamp -= mts; + for (auto &plot : plots) { + for (auto &v : plot.data) + v.first -= mts; + } + + printf("\33[2KProcessing...\r"); + fflush(stdout); + + auto &&getFilename = [](const char *in) { + auto out = in; + while (*out) + ++out; + --out; + while (out > in && (*out != '/' || *out != '\\')) + out--; + return out; + }; + + tracy::Worker worker(getFilename(output), getFilename(input), timeline, + messages, plots, std::move(dec.threadNames)); + + auto w = + std::unique_ptr(tracy::FileWrite::Open(output, clev)); + if (!w) { + fprintf(stderr, "Cannot open output file!\n"); + exit(1); + } + printf("\33[2KSaving...\r"); + fflush(stdout); + worker.Write(*w, false); + + printf("\33[2KCleanup...\n"); + fflush(stdout); + + return 0; +}