#ifdef _WIN32 # include #endif #include #include #include #include #include #include #include #ifdef _MSC_VER # define stat64 _stat64 #endif #if defined __CYGWIN__ || defined __APPLE__ # define stat64 stat #endif #include "json.hpp" #include "../../server/TracyFileWrite.hpp" #include "../../server/TracyMmap.hpp" #include "../../server/TracyWorker.hpp" #include "../../zstd/zstd.h" using json = nlohmann::json; void Usage() { printf( "Usage: import-chrome input.json output.tracy\n\n" ); exit( 1 ); } 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 ); json j; const auto fnsz = strlen( input ); if( fnsz > 4 && memcmp( input+fnsz-4, ".zst", 4 ) == 0 ) { 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 ); } 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 }; std::vector buf; 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; munmap( zbuf, zsz ); j = json::parse( buf.begin(), buf.end() ); } else { std::ifstream is( input ); if( !is.is_open() ) { fprintf( stderr, "Cannot open input file!\n" ); exit( 1 ); } is >> j; is.close(); } printf( "\33[2KParsing...\r" ); fflush( stdout ); struct PidTid { uint64_t tid; uint64_t pid; uint64_t both; // fake thread id, unique within Tracy }; std::vector tids; std::vector timeline; std::vector messages; std::vector plots; std::unordered_map threadNames; const auto getTid = [&](uint64_t pid, uint64_t tid) -> uint64_t { for ( auto &pair : tids ) { if ( pair.pid == pid && pair.tid == tid ) { return pair.both; } } const auto result = tids.size(); tids.emplace_back(PidTid {.tid=tid, .pid=pid, .both=result}); return result; }; 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"; } } uint64_t pid = 0; if ( v.contains( "pid" ) ) { pid = v["pid"].get(); } if( type == "B" ) { timeline.emplace_back( tracy::Worker::ImportEventTimeline { getTid(pid, v["tid"].get()), uint64_t( v["ts"].get() * 1000. ), v["name"].get(), std::move(zoneText), false } ); } else if( type == "E" ) { timeline.emplace_back( tracy::Worker::ImportEventTimeline { getTid(pid, v["tid"].get()), uint64_t( v["ts"].get() * 1000. ), "", std::move(zoneText), true } ); } else if( type == "X" ) { const auto tid = getTid(pid, v["tid"].get()); 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 } ); timeline.emplace_back( tracy::Worker::ImportEventTimeline { tid, ts1, "", "", true } ); } else if( type == "i" || type == "I" ) { messages.emplace_back( tracy::Worker::ImportEventMessages { getTid(pid, v["tid"].get()), 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 = getTid(pid, v["tid"].get()); 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, 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 ); printf( "\33[2KCleanup...\n" ); fflush( stdout ); return 0; }