2018-03-10 00:29:27 +00:00
# ifdef _WIN32
# include <windows.h>
2022-04-08 20:10:33 +00:00
# include <io.h>
2020-09-20 18:11:19 +00:00
# else
# include <unistd.h>
2018-03-10 00:29:27 +00:00
# endif
2022-04-12 17:33:47 +00:00
# include <atomic>
2018-03-10 00:29:27 +00:00
# include <chrono>
2018-03-10 01:25:29 +00:00
# include <inttypes.h>
2018-03-10 00:29:27 +00:00
# include <mutex>
2019-01-24 18:13:09 +00:00
# include <signal.h>
2022-04-08 20:10:33 +00:00
# include <stdarg.h>
2018-03-10 01:25:29 +00:00
# include <stdint.h>
2018-03-10 00:29:27 +00:00
# include <stdio.h>
# include <stdlib.h>
2020-09-20 18:04:43 +00:00
# include <sys/stat.h>
2018-03-10 00:29:27 +00:00
2022-07-17 11:41:40 +00:00
# include "../../public/common/TracyProtocol.hpp"
# include "../../public/common/TracyStackFrames.hpp"
2018-03-10 00:29:27 +00:00
# include "../../server/TracyFileWrite.hpp"
# include "../../server/TracyMemory.hpp"
2019-06-18 18:43:28 +00:00
# include "../../server/TracyPrint.hpp"
2018-03-10 00:29:27 +00:00
# include "../../server/TracyWorker.hpp"
2021-01-26 23:32:38 +00:00
# ifdef _WIN32
# include ".. / .. / getopt / getopt.h"
# endif
2018-03-10 00:29:27 +00:00
2020-04-03 00:00:07 +00:00
2022-04-12 17:33:47 +00:00
// 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<bool> instead
// here and be on the actually supported path.
2022-04-12 17:47:14 +00:00
static std : : atomic < bool > s_disconnect { false } ;
2019-01-24 18:13:09 +00:00
void SigInt ( int )
{
2022-04-12 17:33:47 +00:00
// 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.
2022-04-12 17:45:33 +00:00
s_disconnect . store ( true , std : : memory_order_relaxed ) ;
2019-01-24 18:13:09 +00:00
}
2018-03-10 01:25:29 +00:00
2022-04-08 20:10:33 +00:00
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_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 , . . . ) {
2022-04-12 20:36:08 +00:00
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 ) ;
}
2022-04-08 20:10:33 +00:00
}
2020-09-20 18:08:39 +00:00
[[noreturn]] void Usage ( )
2018-03-10 00:29:27 +00:00
{
2021-08-26 05:22:58 +00:00
printf ( " Usage: capture -o output.tracy [-a address] [-p port] [-f] [-s seconds] \n " ) ;
2018-03-10 00:29:27 +00:00
exit ( 1 ) ;
}
int main ( int argc , char * * argv )
{
# ifdef _WIN32
if ( ! AttachConsole ( ATTACH_PARENT_PROCESS ) )
{
AllocConsole ( ) ;
SetConsoleMode ( GetStdHandle ( STD_OUTPUT_HANDLE ) , 0x07 ) ;
}
# endif
2022-04-08 20:10:33 +00:00
InitIsStdoutATerminal ( ) ;
2020-09-20 18:04:43 +00:00
bool overwrite = false ;
2020-09-23 14:38:20 +00:00
const char * address = " 127.0.0.1 " ;
2018-03-10 00:29:27 +00:00
const char * output = nullptr ;
2019-09-21 13:43:01 +00:00
int port = 8086 ;
2021-08-26 05:22:58 +00:00
int seconds = - 1 ;
2024-05-05 19:12:50 +00:00
int64_t memoryLimit = - 1 ;
2018-03-10 00:29:27 +00:00
int c ;
2021-08-26 05:22:58 +00:00
while ( ( c = getopt ( argc , argv , " a:o:p:fs: " ) ) ! = - 1 )
2018-03-10 00:29:27 +00:00
{
switch ( c )
{
case ' a ' :
address = optarg ;
break ;
case ' o ' :
output = optarg ;
break ;
2019-09-21 13:43:01 +00:00
case ' p ' :
port = atoi ( optarg ) ;
break ;
2020-09-20 18:04:43 +00:00
case ' f ' :
overwrite = true ;
break ;
2021-08-26 05:22:58 +00:00
case ' s ' :
seconds = atoi ( optarg ) ;
break ;
2018-03-10 00:29:27 +00:00
default :
Usage ( ) ;
break ;
}
}
if ( ! address | | ! output ) Usage ( ) ;
2020-09-20 18:04:43 +00:00
struct stat st ;
if ( stat ( output , & st ) = = 0 & & ! overwrite )
{
printf ( " Output file %s already exists! Use -f to force overwrite. \n " , output ) ;
return 4 ;
}
2020-09-20 18:11:19 +00:00
FILE * test = fopen ( output , " wb " ) ;
if ( ! test )
{
printf ( " Cannot open output file %s for writing! \n " , output ) ;
return 5 ;
}
fclose ( test ) ;
unlink ( output ) ;
2019-09-21 13:43:01 +00:00
printf ( " Connecting to %s:%i... " , address , port ) ;
2018-03-10 00:29:27 +00:00
fflush ( stdout ) ;
2024-05-05 19:12:50 +00:00
tracy : : Worker worker ( address , port , memoryLimit ) ;
2023-01-25 05:32:19 +00:00
while ( ! worker . HasData ( ) )
2018-09-09 17:28:53 +00:00
{
const auto handshake = worker . GetHandshakeStatus ( ) ;
if ( handshake = = tracy : : HandshakeProtocolMismatch )
{
printf ( " \n The client you are trying to connect to uses incompatible protocol version. \n Make sure you are using the same Tracy version on both client and server. \n " ) ;
return 1 ;
}
2018-09-09 17:42:06 +00:00
if ( handshake = = tracy : : HandshakeNotAvailable )
{
printf ( " \n The client you are trying to connect to is no longer able to sent profiling data, \n because another server was already connected to it. \n You 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 ;
}
2019-02-12 10:13:53 +00:00
if ( handshake = = tracy : : HandshakeDropped )
{
printf ( " \n The client you are trying to connect to has disconnected during the initial \n connection handshake. Please check your network configuration. \n " ) ;
return 3 ;
}
2022-10-19 20:00:40 +00:00
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 100 ) ) ;
2018-09-09 17:28:53 +00:00
}
2019-06-18 18:43:28 +00:00
printf ( " \n Queue delay: %s \n Timer resolution: %s \n " , tracy : : TimeToString ( worker . GetDelay ( ) ) , tracy : : TimeToString ( worker . GetResolution ( ) ) ) ;
2018-03-10 00:29:27 +00:00
2020-04-03 00:00:07 +00:00
# ifdef _WIN32
signal ( SIGINT , SigInt ) ;
# else
struct sigaction sigint , oldsigint ;
2019-01-24 18:13:09 +00:00
memset ( & sigint , 0 , sizeof ( sigint ) ) ;
sigint . sa_handler = SigInt ;
sigaction ( SIGINT , & sigint , & oldsigint ) ;
2019-01-24 19:04:08 +00:00
# endif
2019-01-24 18:13:09 +00:00
2023-03-03 21:51:04 +00:00
const auto firstTime = worker . GetFirstTime ( ) ;
2018-03-10 00:29:27 +00:00
auto & lock = worker . GetMbpsDataLock ( ) ;
2019-11-07 00:51:45 +00:00
const auto t0 = std : : chrono : : high_resolution_clock : : now ( ) ;
2018-03-10 00:29:27 +00:00
while ( worker . IsConnected ( ) )
{
2022-04-12 17:45:33 +00:00
// Relaxed order is sufficient here because `s_disconnect` is only ever
2022-04-12 17:33:47 +00:00
// set by this thread or by the SigInt handler, and that handler does
2022-04-12 17:45:33 +00:00
// nothing else than storing `s_disconnect`.
if ( s_disconnect . load ( std : : memory_order_relaxed ) )
2019-01-24 18:13:09 +00:00
{
worker . Disconnect ( ) ;
2022-04-12 17:33:47 +00:00
// Relaxed order is sufficient because only this thread ever reads
// this value.
2022-04-12 17:45:33 +00:00
s_disconnect . store ( false , std : : memory_order_relaxed ) ;
2021-08-26 05:22:58 +00:00
break ;
2019-01-24 18:13:09 +00:00
}
2018-03-10 00:29:27 +00:00
lock . lock ( ) ;
const auto mbps = worker . GetMbpsData ( ) . back ( ) ;
const auto compRatio = worker . GetCompRatio ( ) ;
2019-10-26 14:14:18 +00:00
const auto netTotal = worker . GetDataTransferred ( ) ;
2018-03-10 00:29:27 +00:00
lock . unlock ( ) ;
2022-04-08 20:10:33 +00:00
// 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 ( ) )
2018-03-10 00:29:27 +00:00
{
2022-04-08 20:10:33 +00:00
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 ( " | " ) ;
2024-05-04 12:28:55 +00:00
AnsiPrintf ( ANSI_RED ANSI_BOLD , " %s " , tracy : : MemSizeToString ( tracy : : memUsage . load ( std : : memory_order_relaxed ) ) ) ;
2022-04-08 20:10:33 +00:00
printf ( " | " ) ;
2023-03-03 21:51:04 +00:00
AnsiPrintf ( ANSI_RED , " %s " , tracy : : TimeToString ( worker . GetLastTime ( ) - firstTime ) ) ;
2022-04-08 20:10:33 +00:00
fflush ( stdout ) ;
2018-03-10 00:29:27 +00:00
}
std : : this_thread : : sleep_for ( std : : chrono : : milliseconds ( 100 ) ) ;
2021-08-26 05:22:58 +00:00
if ( seconds ! = - 1 )
{
const auto dur = std : : chrono : : high_resolution_clock : : now ( ) - t0 ;
if ( std : : chrono : : duration_cast < std : : chrono : : seconds > ( dur ) . count ( ) > = seconds )
{
2022-04-12 17:33:47 +00:00
// Relaxed order is sufficient because only this thread ever reads
// this value.
2022-04-12 17:45:33 +00:00
s_disconnect . store ( true , std : : memory_order_relaxed ) ;
2021-08-26 05:22:58 +00:00
}
}
2018-03-10 00:29:27 +00:00
}
2019-11-07 00:51:45 +00:00
const auto t1 = std : : chrono : : high_resolution_clock : : now ( ) ;
2018-03-10 00:29:27 +00:00
2019-01-14 22:52:38 +00:00
const auto & failure = worker . GetFailureType ( ) ;
if ( failure ! = tracy : : Worker : : Failure : : None )
{
2022-04-08 20:10:33 +00:00
AnsiPrintf ( ANSI_RED ANSI_BOLD , " \n Instrumentation failure: %s " , tracy : : Worker : : GetFailureString ( failure ) ) ;
2020-10-06 12:50:55 +00:00
auto & fd = worker . GetFailureData ( ) ;
2021-10-10 12:52:30 +00:00
if ( ! fd . message . empty ( ) )
{
printf ( " \n Context: %s " , fd . message . c_str ( ) ) ;
}
2020-10-06 12:50:55 +00:00
if ( fd . callstack ! = 0 )
{
2022-04-08 20:10:33 +00:00
AnsiPrintf ( ANSI_BOLD , " \n %sFailure callstack:%s \n " ) ;
2020-10-06 12:50:55 +00:00
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
{
2022-04-08 20:10:33 +00:00
AnsiPrintf ( ANSI_BLACK ANSI_BOLD , " inl. " ) ;
2020-10-06 12:50:55 +00:00
}
2022-04-08 20:10:33 +00:00
AnsiPrintf ( ANSI_CYAN , " %s " , txt ) ;
2020-10-06 12:50:55 +00:00
txt = worker . GetString ( frame . file ) ;
if ( frame . line = = 0 )
{
2022-04-08 20:10:33 +00:00
AnsiPrintf ( ANSI_YELLOW , " (%s) " , txt ) ;
2020-10-06 12:50:55 +00:00
}
else
{
2022-04-08 20:10:33 +00:00
AnsiPrintf ( ANSI_YELLOW , " (%s:% " PRIu32 " ) " , txt , frame . line ) ;
2020-10-06 12:50:55 +00:00
}
if ( frameData - > imageName . Active ( ) )
{
2022-04-08 20:10:33 +00:00
AnsiPrintf ( ANSI_MAGENTA , " %s \n " , worker . GetString ( frameData - > imageName ) ) ;
2020-10-06 12:50:55 +00:00
}
else
{
2022-04-08 20:10:33 +00:00
printf ( " \n " ) ;
2020-10-06 12:50:55 +00:00
}
}
}
}
}
2019-01-14 22:52:38 +00:00
}
2019-11-07 00:51:45 +00:00
printf ( " \n Frames: % " PRIu64 " \n Time span: %s \n Zones: %s \n Elapsed time: %s \n Saving trace... " ,
2023-03-03 21:51:04 +00:00
worker . GetFrameCount ( * worker . GetFramesBase ( ) ) , tracy : : TimeToString ( worker . GetLastTime ( ) - firstTime ) , tracy : : RealToString ( worker . GetZoneCount ( ) ) ,
2019-11-07 00:51:45 +00:00
tracy : : TimeToString ( std : : chrono : : duration_cast < std : : chrono : : nanoseconds > ( t1 - t0 ) . count ( ) ) ) ;
2018-03-10 00:29:27 +00:00
fflush ( stdout ) ;
2018-03-28 23:11:54 +00:00
auto f = std : : unique_ptr < tracy : : FileWrite > ( tracy : : FileWrite : : Open ( output ) ) ;
2018-03-10 00:29:27 +00:00
if ( f )
{
2021-05-15 13:50:20 +00:00
worker . Write ( * f , false ) ;
2022-04-08 20:10:33 +00:00
AnsiPrintf ( ANSI_GREEN ANSI_BOLD , " done! \n " ) ;
2020-02-08 12:10:41 +00:00
f - > Finish ( ) ;
const auto stats = f - > GetCompressionStatistics ( ) ;
printf ( " Trace size %s (%.2f%% ratio) \n " , tracy : : MemSizeToString ( stats . second ) , 100.f * stats . second / stats . first ) ;
2018-03-10 00:29:27 +00:00
}
else
{
2022-04-08 20:10:33 +00:00
AnsiPrintf ( ANSI_RED ANSI_BOLD , " failed! \n " ) ;
2018-03-10 00:29:27 +00:00
}
return 0 ;
}