#ifdef _WIN32 # include # include #else # include #endif #include #include #include #include #include #include #include #include #include #include #include "../../public/common/TracyProtocol.hpp" #include "../../public/common/TracyStackFrames.hpp" #include "../../server/TracyFileWrite.hpp" #include "../../server/TracyMemory.hpp" #include "../../server/TracyPrint.hpp" #include "../../server/TracySysUtil.hpp" #include "../../server/TracyWorker.hpp" #ifdef _WIN32 # include "../../getopt/getopt.h" #endif // This atomic is written by a signal handler (SigInt). Traditionally that would // have had to be `volatile sig_atomic_t`, and annoyingly, `bool` was // technically not allowed there, even though in practice it would work. // The good thing with C++11 atomics is that we can use atomic instead // here and be on the actually supported path. static std::atomic s_disconnect { false }; void SigInt( int ) { // Relaxed order is closest to a traditional `volatile` write. // We don't need stronger ordering since this signal handler doesn't do // anything else that would need to be ordered relatively to this. s_disconnect.store(true, std::memory_order_relaxed); } static bool s_isStdoutATerminal = false; void InitIsStdoutATerminal() { #ifdef _WIN32 s_isStdoutATerminal = _isatty( fileno( stdout ) ); #else s_isStdoutATerminal = isatty( fileno( stdout ) ); #endif } bool IsStdoutATerminal() { return s_isStdoutATerminal; } #define ANSI_RESET "\033[0m" #define ANSI_BOLD "\033[1m" #define ANSI_BLACK "\033[30m" #define ANSI_RED "\033[31m" #define ANSI_GREEN "\033[32m" #define ANSI_YELLOW "\033[33m" #define ANSI_BLUE "\033[34m" #define ANSI_MAGENTA "\033[35m" #define ANSI_CYAN "\033[36m" #define ANSI_ERASE_LINE "\033[2K" // Like printf, but if stdout is a terminal, prepends the output with // the given `ansiEscape` and appends ANSI_RESET. void AnsiPrintf( const char* ansiEscape, const char* format, ... ) { if( IsStdoutATerminal() ) { // Prepend ansiEscape and append ANSI_RESET. char buf[256]; va_list args; va_start( args, format ); vsnprintf( buf, sizeof buf, format, args ); va_end( args ); printf( "%s%s" ANSI_RESET, ansiEscape, buf ); } else { // Just a normal printf. va_list args; va_start( args, format ); vfprintf( stdout, format, args ); va_end( args ); } } [[noreturn]] void Usage() { printf( "Usage: capture -o output.tracy [-a address] [-p port] [-f] [-s seconds] [-m memlimit]\n" ); exit( 1 ); } int main( int argc, char** argv ) { #ifdef _WIN32 if( !AttachConsole( ATTACH_PARENT_PROCESS ) ) { AllocConsole(); SetConsoleMode( GetStdHandle( STD_OUTPUT_HANDLE ), 0x07 ); } #endif InitIsStdoutATerminal(); bool overwrite = false; const char* address = "127.0.0.1"; const char* output = nullptr; int port = 8086; int seconds = -1; int64_t memoryLimit = -1; int c; while( ( c = getopt( argc, argv, "a:o:p:fs:m:" ) ) != -1 ) { switch( c ) { case 'a': address = optarg; break; case 'o': output = optarg; break; case 'p': port = atoi( optarg ); break; case 'f': overwrite = true; break; case 's': seconds = atoi(optarg); break; case 'm': memoryLimit = std::clamp( atoll( optarg ), 1ll, 999ll ) * tracy::GetPhysicalMemorySize() / 100; break; default: Usage(); break; } } if( !address || !output ) Usage(); struct stat st; if( stat( output, &st ) == 0 && !overwrite ) { printf( "Output file %s already exists! Use -f to force overwrite.\n", output ); return 4; } FILE* test = fopen( output, "wb" ); if( !test ) { printf( "Cannot open output file %s for writing!\n", output ); return 5; } fclose( test ); unlink( output ); printf( "Connecting to %s:%i...", address, port ); fflush( stdout ); tracy::Worker worker( address, port, memoryLimit ); while( !worker.HasData() ) { const auto handshake = worker.GetHandshakeStatus(); if( handshake == tracy::HandshakeProtocolMismatch ) { printf( "\nThe client you are trying to connect to uses incompatible protocol version.\nMake sure you are using the same Tracy version on both client and server.\n" ); return 1; } if( handshake == tracy::HandshakeNotAvailable ) { printf( "\nThe client you are trying to connect to is no longer able to sent profiling data,\nbecause another server was already connected to it.\nYou can do the following:\n\n 1. Restart the client application.\n 2. Rebuild the client application with on-demand mode enabled.\n" ); return 2; } if( handshake == tracy::HandshakeDropped ) { printf( "\nThe client you are trying to connect to has disconnected during the initial\nconnection handshake. Please check your network configuration.\n" ); return 3; } std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); } printf( "\nQueue delay: %s\nTimer resolution: %s\n", tracy::TimeToString( worker.GetDelay() ), tracy::TimeToString( worker.GetResolution() ) ); #ifdef _WIN32 signal( SIGINT, SigInt ); #else struct sigaction sigint, oldsigint; memset( &sigint, 0, sizeof( sigint ) ); sigint.sa_handler = SigInt; sigaction( SIGINT, &sigint, &oldsigint ); #endif const auto firstTime = worker.GetFirstTime(); auto& lock = worker.GetMbpsDataLock(); const auto t0 = std::chrono::high_resolution_clock::now(); while( worker.IsConnected() ) { // Relaxed order is sufficient here because `s_disconnect` is only ever // set by this thread or by the SigInt handler, and that handler does // nothing else than storing `s_disconnect`. if( s_disconnect.load( std::memory_order_relaxed ) ) { worker.Disconnect(); // Relaxed order is sufficient because only this thread ever reads // this value. s_disconnect.store(false, std::memory_order_relaxed ); break; } lock.lock(); const auto mbps = worker.GetMbpsData().back(); const auto compRatio = worker.GetCompRatio(); const auto netTotal = worker.GetDataTransferred(); lock.unlock(); // Output progress info only if destination is a TTY to avoid bloating // log files (so this is not just about usage of ANSI color codes). if( IsStdoutATerminal() ) { const char* unit = "Mbps"; float unitsPerMbps = 1.f; if( mbps < 0.1f ) { unit = "Kbps"; unitsPerMbps = 1000.f; } AnsiPrintf( ANSI_ERASE_LINE ANSI_CYAN ANSI_BOLD, "\r%7.2f %s", mbps * unitsPerMbps, unit ); printf( " /"); AnsiPrintf( ANSI_CYAN ANSI_BOLD, "%5.1f%%", compRatio * 100.f ); printf( " ="); AnsiPrintf( ANSI_YELLOW ANSI_BOLD, "%7.2f Mbps", mbps / compRatio ); printf( " | "); AnsiPrintf( ANSI_YELLOW, "Tx: "); AnsiPrintf( ANSI_GREEN, "%s", tracy::MemSizeToString( netTotal ) ); printf( " | "); AnsiPrintf( ANSI_RED ANSI_BOLD, "%s", tracy::MemSizeToString( tracy::memUsage.load( std::memory_order_relaxed ) ) ); if( memoryLimit > 0 ) { printf( " / " ); AnsiPrintf( ANSI_BLUE ANSI_BOLD, "%s", tracy::MemSizeToString( memoryLimit ) ); } printf( " | "); AnsiPrintf( ANSI_RED, "%s", tracy::TimeToString( worker.GetLastTime() - firstTime ) ); fflush( stdout ); } std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); if( seconds != -1 ) { const auto dur = std::chrono::high_resolution_clock::now() - t0; if( std::chrono::duration_cast(dur).count() >= seconds ) { // Relaxed order is sufficient because only this thread ever reads // this value. s_disconnect.store(true, std::memory_order_relaxed ); } } } const auto t1 = std::chrono::high_resolution_clock::now(); const auto& failure = worker.GetFailureType(); if( failure != tracy::Worker::Failure::None ) { AnsiPrintf( ANSI_RED ANSI_BOLD, "\nInstrumentation failure: %s", tracy::Worker::GetFailureString( failure ) ); auto& fd = worker.GetFailureData(); if( !fd.message.empty() ) { printf( "\nContext: %s", fd.message.c_str() ); } if( fd.callstack != 0 ) { AnsiPrintf( ANSI_BOLD, "\nFailure callstack:\n" ); auto& cs = worker.GetCallstack( fd.callstack ); int fidx = 0; for( auto& entry : cs ) { auto frameData = worker.GetCallstackFrame( entry ); if( !frameData ) { printf( "%3i. %p\n", fidx++, (void*)worker.GetCanonicalPointer( entry ) ); } else { const auto fsz = frameData->size; for( uint8_t f=0; fdata[f]; auto txt = worker.GetString( frame.name ); if( fidx == 0 && f != fsz-1 ) { auto test = tracy::s_tracyStackFrames; bool match = false; do { if( strcmp( txt, *test ) == 0 ) { match = true; break; } } while( *++test ); if( match ) continue; } if( f == fsz-1 ) { printf( "%3i. ", fidx++ ); } else { AnsiPrintf( ANSI_BLACK ANSI_BOLD, "inl. " ); } AnsiPrintf( ANSI_CYAN, "%s ", txt ); txt = worker.GetString( frame.file ); if( frame.line == 0 ) { AnsiPrintf( ANSI_YELLOW, "(%s)", txt ); } else { AnsiPrintf( ANSI_YELLOW, "(%s:%" PRIu32 ")", txt, frame.line ); } if( frameData->imageName.Active() ) { AnsiPrintf( ANSI_MAGENTA, " %s\n", worker.GetString( frameData->imageName ) ); } else { printf( "\n" ); } } } } } } printf( "\nFrames: %" PRIu64 "\nTime span: %s\nZones: %s\nElapsed time: %s\nSaving trace...", worker.GetFrameCount( *worker.GetFramesBase() ), tracy::TimeToString( worker.GetLastTime() - firstTime ), tracy::RealToString( worker.GetZoneCount() ), tracy::TimeToString( std::chrono::duration_cast( t1 - t0 ).count() ) ); fflush( stdout ); auto f = std::unique_ptr( tracy::FileWrite::Open( output, tracy::FileCompression::Zstd, 3, 4 ) ); if( f ) { worker.Write( *f, false ); AnsiPrintf( ANSI_GREEN ANSI_BOLD, " done!\n" ); f->Finish(); const auto stats = f->GetCompressionStatistics(); printf( "Trace size %s (%.2f%% ratio)\n", tracy::MemSizeToString( stats.second ), 100.f * stats.second / stats.first ); } else { AnsiPrintf( ANSI_RED ANSI_BOLD, " failed!\n"); } return 0; }