diff --git a/capture/CMakeLists.txt b/capture/CMakeLists.txt index 1e1e0e7d..df07e179 100644 --- a/capture/CMakeLists.txt +++ b/capture/CMakeLists.txt @@ -19,6 +19,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/../cmake/server.cmake) set(PROGRAM_FILES src/capture.cpp + src/CaptureOutput.cpp ) add_executable(${PROJECT_NAME} ${PROGRAM_FILES} ${COMMON_FILES} ${SERVER_FILES}) diff --git a/capture/src/CaptureOutput.cpp b/capture/src/CaptureOutput.cpp new file mode 100644 index 00000000..5d8c1c21 --- /dev/null +++ b/capture/src/CaptureOutput.cpp @@ -0,0 +1,200 @@ +#ifdef _WIN32 +# include +# include +#else +# include +#endif + +#include +#include +#include +#include +#include +#include + +#include "CaptureOutput.hpp" +#include "../../public/common/TracyProtocol.hpp" +#include "../../public/common/TracyStackFrames.hpp" +#include "../../server/TracyMemory.hpp" +#include "../../server/TracyPrint.hpp" + +static bool s_isTerminal = false; + +void InitTerminalDetection() +{ +#ifdef _WIN32 + s_isTerminal = _isatty( fileno( stdout ) ); +#else + s_isTerminal = isatty( fileno( stdout ) ); +#endif +} + +bool IsTerminal() +{ + return s_isTerminal; +} + +void AnsiPrintf( const char* ansiEscape, const char* format, ... ) +{ + if( IsTerminal() ) + { + 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 + { + va_list args; + va_start( args, format ); + vfprintf( stdout, format, args ); + va_end( args ); + } +} + +int WaitForConnection( tracy::Worker& worker ) +{ + 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 ) ); + } + return 0; +} + +void PrintWorkerFailure( tracy::Worker& worker ) +{ + const auto& failure = worker.GetFailureType(); + if( failure == tracy::Worker::Failure::None ) return; + + 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; f < fsz; f++ ) + { + const auto& frame = frameData->data[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" ); + } + } + } + } + } +} + +void PrintCaptureProgress( tracy::Worker& worker, int64_t firstTime, int64_t memoryLimit ) +{ + if( !IsTerminal() ) return; + + auto& lock = worker.GetMbpsDataLock(); + lock.lock(); + const auto mbps = worker.GetMbpsData().back(); + const auto compRatio = worker.GetCompRatio(); + const auto netTotal = worker.GetDataTransferred(); + const auto queueSize = worker.GetSendQueueSize(); + lock.unlock(); + + 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 ) ); + printf( " | " ); + AnsiPrintf( ANSI_RED ANSI_BOLD, "%s query backlog", tracy::RealToString( queueSize ) ); + fflush( stdout ); +} diff --git a/capture/src/CaptureOutput.hpp b/capture/src/CaptureOutput.hpp new file mode 100644 index 00000000..7fb50676 --- /dev/null +++ b/capture/src/CaptureOutput.hpp @@ -0,0 +1,31 @@ +#ifndef __CAPTUREOUTPUT_HPP__ +#define __CAPTUREOUTPUT_HPP__ + +#include "../../server/TracyWorker.hpp" + +#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" + +void InitTerminalDetection(); +bool IsTerminal(); + +#ifdef __GNUC__ +[[gnu::format( __printf__, 2, 3 )]] +#endif +void AnsiPrintf( const char* ansiEscape, const char* format, ... ); + +int WaitForConnection( tracy::Worker& worker ); + +void PrintWorkerFailure( tracy::Worker& worker ); + +void PrintCaptureProgress( tracy::Worker& worker, int64_t firstTime, int64_t memoryLimit ); + +#endif diff --git a/capture/src/capture.cpp b/capture/src/capture.cpp index d6343879..80c96b62 100644 --- a/capture/src/capture.cpp +++ b/capture/src/capture.cpp @@ -1,29 +1,24 @@ #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" +#include "CaptureOutput.hpp" + #ifdef _WIN32 # include "../../getopt/getopt.h" #endif @@ -38,58 +33,9 @@ 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" ); @@ -106,7 +52,7 @@ int main( int argc, char** argv ) } #endif - InitIsStdoutATerminal(); + InitTerminalDetection(); bool overwrite = false; const char* address = "127.0.0.1"; @@ -165,26 +111,8 @@ int main( int argc, char** argv ) 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 ) ); - } + int result = WaitForConnection( worker ); + if( result != 0 ) return result; printf( "\nTimer resolution: %s\n", tracy::TimeToString( worker.GetResolution() ) ); #ifdef _WIN32 @@ -197,62 +125,18 @@ int main( int argc, char** argv ) #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(); - const auto queueSize = worker.GetSendQueueSize(); - 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 ) ); - printf( " | "); - AnsiPrintf( ANSI_RED ANSI_BOLD, "%s query backlog", tracy::RealToString( queueSize ) ); - fflush( stdout ); - } + PrintCaptureProgress( worker, firstTime, memoryLimit ); std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) ); if( seconds != -1 ) @@ -260,90 +144,13 @@ int main( int argc, char** argv ) 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" ); - } - } - } - } - } - } + PrintWorkerFailure( worker ); 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() ),