mirror of
https://github.com/wolfpld/tracy.git
synced 2025-03-20 07:40:02 +08:00
Merge 4f769258b4752f236eaacc750b1900640f7b5019 into 873c6ecac8bc324509bbd8ddbf62096b3b8153de
This commit is contained in:
commit
9665db7e90
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -5,6 +5,7 @@
|
|||||||
"${workspaceFolder}/capture",
|
"${workspaceFolder}/capture",
|
||||||
"${workspaceFolder}/csvexport",
|
"${workspaceFolder}/csvexport",
|
||||||
"${workspaceFolder}/import",
|
"${workspaceFolder}/import",
|
||||||
|
"${workspaceFolder}/multiprocess",
|
||||||
"${workspaceFolder}/update",
|
"${workspaceFolder}/update",
|
||||||
"${workspaceFolder}/test",
|
"${workspaceFolder}/test",
|
||||||
"${workspaceFolder}",
|
"${workspaceFolder}",
|
||||||
|
|||||||
@ -17,12 +17,17 @@ include(${CMAKE_CURRENT_LIST_DIR}/../cmake/config.cmake)
|
|||||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/vendor.cmake)
|
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/vendor.cmake)
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/server.cmake)
|
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/server.cmake)
|
||||||
|
|
||||||
set(PROGRAM_FILES
|
|
||||||
|
add_executable(${PROJECT_NAME}
|
||||||
src/capture.cpp
|
src/capture.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME} ${PROGRAM_FILES} ${COMMON_FILES} ${SERVER_FILES})
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE TracyServer TracyGetOpt)
|
target_link_libraries(${PROJECT_NAME} PRIVATE TracyServer TracyGetOpt)
|
||||||
|
|
||||||
|
add_executable(tracy-multicapture
|
||||||
|
src/multicapture.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(tracy-multicapture TracyServer TracyGetOpt)
|
||||||
|
|
||||||
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
||||||
|
|
||||||
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
@ -28,6 +28,8 @@
|
|||||||
# include "../../getopt/getopt.h"
|
# include "../../getopt/getopt.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "lib.cpp" // temporary hack to carefully share code between capture and multicapture
|
||||||
|
|
||||||
|
|
||||||
// This atomic is written by a signal handler (SigInt). Traditionally that would
|
// This atomic is written by a signal handler (SigInt). Traditionally that would
|
||||||
// have had to be `volatile sig_atomic_t`, and annoyingly, `bool` was
|
// have had to be `volatile sig_atomic_t`, and annoyingly, `bool` was
|
||||||
@ -44,52 +46,6 @@ void SigInt( int )
|
|||||||
s_disconnect.store(true, std::memory_order_relaxed);
|
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()
|
[[noreturn]] void Usage()
|
||||||
{
|
{
|
||||||
printf( "Usage: capture -o output.tracy [-a address] [-p port] [-f] [-s seconds] [-m memlimit]\n" );
|
printf( "Usage: capture -o output.tracy [-a address] [-p port] [-f] [-s seconds] [-m memlimit]\n" );
|
||||||
@ -113,7 +69,7 @@ int main( int argc, char** argv )
|
|||||||
const char* output = nullptr;
|
const char* output = nullptr;
|
||||||
int port = 8086;
|
int port = 8086;
|
||||||
int seconds = -1;
|
int seconds = -1;
|
||||||
int64_t memoryLimit = -1;
|
int64_t memoryLimit = tracy::NO_WORKER_MEMORY_LIMIT;
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
while( ( c = getopt( argc, argv, "a:o:p:fs:m:" ) ) != -1 )
|
while( ( c = getopt( argc, argv, "a:o:p:fs:m:" ) ) != -1 )
|
||||||
@ -167,22 +123,13 @@ int main( int argc, char** argv )
|
|||||||
tracy::Worker worker( address, port, memoryLimit );
|
tracy::Worker worker( address, port, memoryLimit );
|
||||||
while( !worker.HasData() )
|
while( !worker.HasData() )
|
||||||
{
|
{
|
||||||
const auto handshake = worker.GetHandshakeStatus();
|
const auto handshake = static_cast<tracy::HandshakeStatus>(worker.GetHandshakeStatus());
|
||||||
if( handshake == tracy::HandshakeProtocolMismatch )
|
int status = checkHandshake(handshake);
|
||||||
|
if( status != 0 )
|
||||||
{
|
{
|
||||||
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 status;
|
||||||
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 ) );
|
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() ) );
|
printf( "\nQueue delay: %s\nTimer resolution: %s\n", tracy::TimeToString( worker.GetDelay() ), tracy::TimeToString( worker.GetResolution() ) );
|
||||||
@ -196,9 +143,6 @@ int main( int argc, char** argv )
|
|||||||
sigaction( SIGINT, &sigint, &oldsigint );
|
sigaction( SIGINT, &sigint, &oldsigint );
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const auto firstTime = worker.GetFirstTime();
|
|
||||||
auto& lock = worker.GetMbpsDataLock();
|
|
||||||
|
|
||||||
const auto t0 = std::chrono::high_resolution_clock::now();
|
const auto t0 = std::chrono::high_resolution_clock::now();
|
||||||
while( worker.IsConnected() )
|
while( worker.IsConnected() )
|
||||||
{
|
{
|
||||||
@ -210,45 +154,14 @@ int main( int argc, char** argv )
|
|||||||
worker.Disconnect();
|
worker.Disconnect();
|
||||||
// Relaxed order is sufficient because only this thread ever reads
|
// Relaxed order is sufficient because only this thread ever reads
|
||||||
// this value.
|
// this value.
|
||||||
s_disconnect.store(false, std::memory_order_relaxed );
|
s_disconnect.store( false, std::memory_order_relaxed );
|
||||||
break;
|
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
|
// 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).
|
// log files (so this is not just about usage of ANSI color codes).
|
||||||
if( IsStdoutATerminal() )
|
if( IsStdoutATerminal() )
|
||||||
{
|
{
|
||||||
const char* unit = "Mbps";
|
printWorkerUpdate( worker, memoryLimit, true, true );
|
||||||
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 ) );
|
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
|
||||||
@ -259,91 +172,16 @@ int main( int argc, char** argv )
|
|||||||
{
|
{
|
||||||
// Relaxed order is sufficient because only this thread ever reads
|
// Relaxed order is sufficient because only this thread ever reads
|
||||||
// this value.
|
// this value.
|
||||||
s_disconnect.store(true, std::memory_order_relaxed );
|
s_disconnect.store( true, std::memory_order_relaxed );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto t1 = std::chrono::high_resolution_clock::now();
|
const auto t1 = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
const auto& failure = worker.GetFailureType();
|
printWorkerFailure( worker, "" );
|
||||||
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; 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" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
printf( "\nFrames: %" PRIu64 "\nTime span: %s\nZones: %s\nElapsed time: %s\nSaving trace...",
|
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() ),
|
worker.GetFrameCount( *worker.GetFramesBase() ), tracy::TimeToString( worker.GetLastTime() - worker.GetFirstTime() ), tracy::RealToString( worker.GetZoneCount() ),
|
||||||
tracy::TimeToString( std::chrono::duration_cast<std::chrono::nanoseconds>( t1 - t0 ).count() ) );
|
tracy::TimeToString( std::chrono::duration_cast<std::chrono::nanoseconds>( t1 - t0 ).count() ) );
|
||||||
fflush( stdout );
|
fflush( stdout );
|
||||||
auto f = std::unique_ptr<tracy::FileWrite>( tracy::FileWrite::Open( output, tracy::FileCompression::Zstd, 3, 4 ) );
|
auto f = std::unique_ptr<tracy::FileWrite>( tracy::FileWrite::Open( output, tracy::FileCompression::Zstd, 3, 4 ) );
|
||||||
|
|||||||
224
capture/src/lib.cpp
Normal file
224
capture/src/lib.cpp
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
// common library file for capture utilities
|
||||||
|
#ifdef _WIN32
|
||||||
|
# include <io.h>
|
||||||
|
# include <windows.h>
|
||||||
|
#else
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "TracyPrint.hpp"
|
||||||
|
#include "TracyStackFrames.hpp"
|
||||||
|
#include "TracyWorker.hpp"
|
||||||
|
|
||||||
|
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"
|
||||||
|
#define ANSI_UP_ONE_LINE "\033[1;A"
|
||||||
|
|
||||||
|
// Like printf, but if stdout is a terminal, prepends the output with
|
||||||
|
// the given `ansiEscape` and appends ANSI_RESET.
|
||||||
|
#ifdef __GNUC__
|
||||||
|
[[gnu::format( __printf__, 2, 3 )]]
|
||||||
|
#endif
|
||||||
|
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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check handshake status
|
||||||
|
// If failure, printf helpful message and return non-zero
|
||||||
|
int checkHandshake( tracy::HandshakeStatus handshake )
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printCurrentMemoryUsage( int64_t memoryLimit )
|
||||||
|
{
|
||||||
|
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 ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printWorkerUpdate( tracy::Worker& worker, int64_t memoryLimit, bool erase, bool memoryUsage )
|
||||||
|
{
|
||||||
|
auto& lock = worker.GetMbpsDataLock();
|
||||||
|
lock.lock();
|
||||||
|
const auto mbps = worker.GetMbpsData().back();
|
||||||
|
const auto compRatio = worker.GetCompRatio();
|
||||||
|
const auto netTotal = worker.GetDataTransferred();
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
|
const char* unit = "Mbps";
|
||||||
|
float unitsPerMbps = 1.f;
|
||||||
|
if( mbps < 0.1f )
|
||||||
|
{
|
||||||
|
unit = "Kbps";
|
||||||
|
unitsPerMbps = 1000.f;
|
||||||
|
}
|
||||||
|
if( erase )
|
||||||
|
{
|
||||||
|
AnsiPrintf( ANSI_ERASE_LINE, "\r" );
|
||||||
|
}
|
||||||
|
AnsiPrintf( ANSI_CYAN ANSI_BOLD, "%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 ) );
|
||||||
|
if( memoryUsage )
|
||||||
|
{
|
||||||
|
printf( " | " );
|
||||||
|
printCurrentMemoryUsage( memoryLimit );
|
||||||
|
}
|
||||||
|
|
||||||
|
printf( " | " );
|
||||||
|
AnsiPrintf( ANSI_RED, "%s", tracy::TimeToString( worker.GetLastTime() - worker.GetFirstTime() ) );
|
||||||
|
fflush( stdout );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool printWorkerFailure( tracy::Worker& worker, char const* prefix )
|
||||||
|
{
|
||||||
|
auto const& failure = worker.GetFailureType();
|
||||||
|
if( failure == tracy::Worker::Failure::None )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AnsiPrintf( ANSI_RED ANSI_BOLD, "\n%s Instrumentation failure: %s", prefix,
|
||||||
|
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" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
706
capture/src/multicapture.cpp
Normal file
706
capture/src/multicapture.cpp
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#ifdef _WIN32
|
||||||
|
# include <windows.h>
|
||||||
|
#else
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "../../getopt/getopt.h" // windows and apple clang don't provide getopt_long
|
||||||
|
|
||||||
|
#include "TracyFileWrite.hpp"
|
||||||
|
#include "TracyPrint.hpp"
|
||||||
|
#include "TracyProtocol.hpp"
|
||||||
|
#include "TracyProtocolServer.hpp"
|
||||||
|
#include "TracySocket.hpp"
|
||||||
|
#include "TracySysUtil.hpp"
|
||||||
|
#include "TracyWorker.hpp"
|
||||||
|
#include "tracy_lz4.hpp"
|
||||||
|
|
||||||
|
#include "lib.cpp"
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
constexpr const char* TRACE_FILE_SUFFIX = ".tracy";
|
||||||
|
|
||||||
|
static std::atomic<bool> s_disconnect{ false };
|
||||||
|
|
||||||
|
void SignalHandler_SigInt( int )
|
||||||
|
{
|
||||||
|
// 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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupDisconnectSignalHandler()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
signal( SIGINT, SignalHandler_SigInt );
|
||||||
|
#else
|
||||||
|
struct sigaction sigint, oldsigint;
|
||||||
|
memset( &sigint, 0, sizeof( sigint ) );
|
||||||
|
sigint.sa_handler = SignalHandler_SigInt;
|
||||||
|
sigaction( SIGINT, &sigint, &oldsigint );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ClientStatus
|
||||||
|
{
|
||||||
|
DISCOVERED,
|
||||||
|
RUNNING,
|
||||||
|
FINISHED,
|
||||||
|
DISABLED
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RunningClient
|
||||||
|
{
|
||||||
|
uint64_t id;
|
||||||
|
ClientStatus status;
|
||||||
|
tracy::BroadcastMessage msg;
|
||||||
|
char addr[tracy::IpAddress::TEXT_SIZE];
|
||||||
|
std::unique_ptr<tracy::Worker> worker = nullptr;
|
||||||
|
bool disconnectSent = false;
|
||||||
|
std::chrono::time_point<std::chrono::high_resolution_clock> detectedAt;
|
||||||
|
std::chrono::time_point<std::chrono::high_resolution_clock> finishedAt;
|
||||||
|
|
||||||
|
RunningClient() = default;
|
||||||
|
|
||||||
|
RunningClient( RunningClient&& in )
|
||||||
|
: id{ in.id }
|
||||||
|
, status{ in.status }
|
||||||
|
, msg{ in.msg }
|
||||||
|
, worker{ std::move( in.worker ) }
|
||||||
|
, disconnectSent{ in.disconnectSent }
|
||||||
|
, detectedAt{ in.detectedAt }
|
||||||
|
, finishedAt{ in.finishedAt }
|
||||||
|
{
|
||||||
|
memcpy( addr, in.addr, tracy::IpAddress::TEXT_SIZE );
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit RunningClient( tracy::BroadcastMessage const& inmsg, tracy::IpAddress const& inaddr )
|
||||||
|
: msg{ inmsg } // copied from profiler/main.cpp TODO: merge put in stdlib
|
||||||
|
, status{ ClientStatus::DISCOVERED }
|
||||||
|
, id{ tracy::ClientUniqueID( inaddr, inmsg.listenPort ) }
|
||||||
|
, detectedAt( std::chrono::high_resolution_clock::now() )
|
||||||
|
{
|
||||||
|
memcpy( addr, inaddr.GetText(), tracy::IpAddress::TEXT_SIZE );
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string saveName( std::string const& prefix ) const
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << prefix << msg.pid << "-" << msg.programName << ".tracy";
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string runtimeName() const
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
oss << "[" << msg.pid << "] " << msg.programName;
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<RunningClient> receive_client_broadcast( tracy::UdpListen* socket )
|
||||||
|
{
|
||||||
|
tracy::IpAddress addr;
|
||||||
|
size_t msgLen;
|
||||||
|
auto msg = socket->Read( msgLen, addr, 0 );
|
||||||
|
if( !msg ) return std::nullopt;
|
||||||
|
auto parsed = tracy::ParseBroadcastMessage( msg, msgLen );
|
||||||
|
if( parsed.has_value() )
|
||||||
|
{
|
||||||
|
auto msg = parsed.value();
|
||||||
|
return RunningClient( msg, addr );
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<( std::ostream& os, RunningClient const& client )
|
||||||
|
{
|
||||||
|
os << client.msg.programName << "@" << client.msg.pid;
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const std::chrono::nanoseconds MULTI_CAPTURE_LOOP_INTERVAL = 100ms;
|
||||||
|
constexpr const std::chrono::nanoseconds PRINT_UPDATE_INTERVAL = 200ms;
|
||||||
|
|
||||||
|
[[noreturn]] void Usage()
|
||||||
|
{
|
||||||
|
printf( "Usage: capture -o <output/prefix> [--multi] [-a address] [-p port] [-s seconds] [-m percent]\n" );
|
||||||
|
|
||||||
|
printf( "Options (a SINGLE tag indicates the option is only accessible in single capture mode):\n" );
|
||||||
|
printf( " -o/--output <str> Output file path (in MULTI mode, it is interpreted as a prefix)\n" );
|
||||||
|
printf( " -f/--force Overwrite existing files\n" );
|
||||||
|
printf( " -M/--multi Enable multi-capture mode\n" );
|
||||||
|
printf( " -v/--verbose Verbose output\n" );
|
||||||
|
|
||||||
|
printf( " -a/--address <ipv4 str> [SINGLE] Target IP address\n" );
|
||||||
|
printf( " -p/--port <int> [SINGLE] Target port\n" );
|
||||||
|
printf( " -s/--stop-after <float> [SINGLE] Stop profiling after this duration (in seconds) \n" );
|
||||||
|
printf( " -m/--memlimit <float> [SINGLE] Set a memory limit (in %% of the total RAM)\n" );
|
||||||
|
|
||||||
|
printf( "\nIn single-capture mode, 'capture' directly connects to the TCP data stream at address:port .\n" );
|
||||||
|
printf( "In multi-capture mode, profiled targets are detected from UDP broadcast packets;\n" );
|
||||||
|
printf( "capture stops once all detected targets disconnect.\n" );
|
||||||
|
printf( "Output files are of the form <prefix>.<client_pid>.tracy\n" );
|
||||||
|
|
||||||
|
exit( 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CaptureArgs
|
||||||
|
{
|
||||||
|
std::filesystem::path outputPath;
|
||||||
|
|
||||||
|
std::filesystem::path outputDirectory; // filled from 'output'
|
||||||
|
std::string outputFileName; // filled from 'output'. In multi mode, contains "<prefix>."
|
||||||
|
|
||||||
|
bool multi = false;
|
||||||
|
std::string address = "127.0.0.1";
|
||||||
|
uint16_t port = tracy::DEFAULT_CLIENT_DATA_TCP_PORT;
|
||||||
|
std::chrono::milliseconds stopAfter = -1ms;
|
||||||
|
bool verbose = false;
|
||||||
|
bool overwrite = false;
|
||||||
|
int64_t memoryLimit = tracy::NO_WORKER_MEMORY_LIMIT;
|
||||||
|
|
||||||
|
// option parsing
|
||||||
|
static CaptureArgs parse( int argc, char* argv[] )
|
||||||
|
{
|
||||||
|
CaptureArgs args;
|
||||||
|
bool setIncompatibleOptionWithMultiCapture = false;
|
||||||
|
int c;
|
||||||
|
const struct option long_options[] = { { "memlimit", required_argument, 0, 'm' },
|
||||||
|
{ "multi", no_argument, 0, 'M' },
|
||||||
|
{ "stop-after", required_argument, 0, 's' },
|
||||||
|
{ "address", required_argument, 0, 'a' },
|
||||||
|
{ "port", required_argument, 0, 'p' },
|
||||||
|
{ "verbose", no_argument, 0, 'v' },
|
||||||
|
{ "output", required_argument, 0, 'o' },
|
||||||
|
{ "force", no_argument, 0, 'f' },
|
||||||
|
{ 0, 0, 0, 0 } };
|
||||||
|
while( ( c = getopt_long( argc, argv, "o:m:a:p:s:Mfv", long_options, nullptr ) ) != -1 )
|
||||||
|
{
|
||||||
|
switch( c )
|
||||||
|
{
|
||||||
|
case 'o':
|
||||||
|
args.outputPath = optarg;
|
||||||
|
break;
|
||||||
|
case 'M':
|
||||||
|
args.multi = true;
|
||||||
|
break;
|
||||||
|
case 'v':
|
||||||
|
args.verbose = true;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
args.overwrite = true;
|
||||||
|
break;
|
||||||
|
case 'm':
|
||||||
|
args.memoryLimit = atol( optarg );
|
||||||
|
args.memoryLimit = std::clamp( atoll( optarg ), 1ll, 999ll ) * tracy::GetPhysicalMemorySize() / 100;
|
||||||
|
setIncompatibleOptionWithMultiCapture = true;
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
args.address = optarg;
|
||||||
|
setIncompatibleOptionWithMultiCapture = true;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
args.port = atoi( optarg );
|
||||||
|
setIncompatibleOptionWithMultiCapture = true;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
{
|
||||||
|
float stopAfterInSeconds = strtof( optarg, nullptr );
|
||||||
|
args.stopAfter = static_cast<int>( stopAfterInSeconds * 1000 ) * 1ms;
|
||||||
|
setIncompatibleOptionWithMultiCapture = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
Usage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( args.outputPath.empty() )
|
||||||
|
{
|
||||||
|
Usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( args.multi and setIncompatibleOptionWithMultiCapture )
|
||||||
|
{
|
||||||
|
std::cout << "ERROR: both --multi mode, and another option incompatible with it were requested\n\n";
|
||||||
|
Usage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// process 'outputPath' argument
|
||||||
|
// - extract directory / file prefix (for multi mode)
|
||||||
|
// - check file existence, fail if --force not given
|
||||||
|
args.outputFileName = args.outputPath.filename().generic_string();
|
||||||
|
|
||||||
|
args.outputDirectory = args.outputPath.parent_path();
|
||||||
|
if( args.outputDirectory.empty() )
|
||||||
|
{
|
||||||
|
args.outputDirectory = ".";
|
||||||
|
}
|
||||||
|
if( not std::filesystem::is_directory( args.outputDirectory ) )
|
||||||
|
{
|
||||||
|
std::cout << "ERROR: target directory " << args.outputDirectory << " does not exist." << std::endl;
|
||||||
|
exit( 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( args.multi )
|
||||||
|
{
|
||||||
|
const size_t SUFFIX_LEN = strlen( TRACE_FILE_SUFFIX );
|
||||||
|
if( args.outputFileName.ends_with( TRACE_FILE_SUFFIX ) )
|
||||||
|
{
|
||||||
|
// convert "<prefix>.tracy" to "<prefix>"
|
||||||
|
args.outputFileName = args.outputFileName.substr( 0, args.outputFileName.size() - SUFFIX_LEN );
|
||||||
|
}
|
||||||
|
// add a final "." to restrict filename matches to the form <prefix>.<pid>.tracy
|
||||||
|
args.outputFileName += ".";
|
||||||
|
|
||||||
|
// we need to check all files starting with the prefix
|
||||||
|
std::cout << "search for existing files matching '" << args.outputDirectory.string() << "/"
|
||||||
|
<< args.outputFileName << "*" << TRACE_FILE_SUFFIX << "'" << std::endl;
|
||||||
|
|
||||||
|
auto foundExisting = false;
|
||||||
|
for( auto const& entry : std::filesystem::directory_iterator( args.outputDirectory ) )
|
||||||
|
{
|
||||||
|
auto filename = entry.path().filename().string();
|
||||||
|
if( filename.starts_with( args.outputFileName ) and filename.ends_with( TRACE_FILE_SUFFIX ) )
|
||||||
|
{
|
||||||
|
if( args.overwrite )
|
||||||
|
{
|
||||||
|
if( args.verbose )
|
||||||
|
{
|
||||||
|
std::cout << "Deleting " << entry.path() << std::endl;
|
||||||
|
}
|
||||||
|
std::filesystem::remove( entry );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "Conflict: file already exist: " << entry.path() << std::endl;
|
||||||
|
foundExisting = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( foundExisting and not args.overwrite )
|
||||||
|
{
|
||||||
|
printf( "Files matching the target prefix already exists! Use -f to clear them all before starting "
|
||||||
|
"capture.\n" );
|
||||||
|
exit( 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( std::filesystem::exists( args.outputDirectory / args.outputFileName ) )
|
||||||
|
{
|
||||||
|
if( args.overwrite )
|
||||||
|
{
|
||||||
|
std::filesystem::remove( args.outputPath );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << "ERROR: Output file " << args.outputPath
|
||||||
|
<< " already exists! Use -f to force overwrite." << std::endl;
|
||||||
|
exit( 4 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug - don't merge
|
||||||
|
void print()
|
||||||
|
{
|
||||||
|
std::cout << "memlimit " << memoryLimit << "\n";
|
||||||
|
std::cout << "output directory " << outputDirectory << "\n";
|
||||||
|
std::cout << "output filename " << outputFileName << "\n";
|
||||||
|
std::cout << "verbose " << verbose << "\n";
|
||||||
|
std::cout << "address " << address << "\n";
|
||||||
|
std::cout << "port " << port << "\n";
|
||||||
|
std::cout << "stopAfter (seconds)"
|
||||||
|
<< std::chrono::duration_cast<std::chrono::duration<float>>( stopAfter ).count() << "\n";
|
||||||
|
std::cout << "multi " << multi << "\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int runCaptureSingle( CaptureArgs const& args )
|
||||||
|
{
|
||||||
|
std::string const outputStr = ( args.outputDirectory / args.outputFileName ).generic_string();
|
||||||
|
char const* output = outputStr.c_str();
|
||||||
|
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...", args.address.c_str(), args.port );
|
||||||
|
fflush( stdout );
|
||||||
|
tracy::Worker worker( args.address.c_str(), args.port, args.memoryLimit );
|
||||||
|
while( !worker.HasData() )
|
||||||
|
{
|
||||||
|
const auto handshake = static_cast<tracy::HandshakeStatus>( worker.GetHandshakeStatus() );
|
||||||
|
int status = checkHandshake( handshake );
|
||||||
|
if( status != 0 )
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
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() ) );
|
||||||
|
|
||||||
|
SetupDisconnectSignalHandler();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
// 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() )
|
||||||
|
{
|
||||||
|
printWorkerUpdate( worker, args.memoryLimit, true, true );
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
|
||||||
|
if( args.stopAfter > 0s )
|
||||||
|
{
|
||||||
|
const auto dur = std::chrono::high_resolution_clock::now() - t0;
|
||||||
|
if( dur >= args.stopAfter )
|
||||||
|
{
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
printWorkerFailure( worker, "" );
|
||||||
|
|
||||||
|
printf( "\nFrames: %" PRIu64 "\nTime span: %s\nZones: %s\nElapsed time: %s\nSaving trace...",
|
||||||
|
worker.GetFrameCount( *worker.GetFramesBase() ),
|
||||||
|
tracy::TimeToString( worker.GetLastTime() - worker.GetFirstTime() ),
|
||||||
|
tracy::RealToString( worker.GetZoneCount() ),
|
||||||
|
tracy::TimeToString( std::chrono::duration_cast<std::chrono::nanoseconds>( t1 - t0 ).count() ) );
|
||||||
|
fflush( stdout );
|
||||||
|
auto f = std::unique_ptr<tracy::FileWrite>( 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int runCaptureMulti( CaptureArgs const& args )
|
||||||
|
{
|
||||||
|
using hr_clock = std::chrono::high_resolution_clock;
|
||||||
|
auto const startCaptureTimestamp = hr_clock::now();
|
||||||
|
|
||||||
|
// configure signal handling early
|
||||||
|
// to ensure the capture loop halts at some point, once we got the order to stop:
|
||||||
|
// - we stop discovering new clients
|
||||||
|
// - we send disconnect signal to existing ones
|
||||||
|
SetupDisconnectSignalHandler();
|
||||||
|
|
||||||
|
tracy::UdpListen broadListen = tracy::UdpListen();
|
||||||
|
if( !broadListen.Listen( tracy::DEFAULT_BROADCAST_UDP_PORT ) )
|
||||||
|
{
|
||||||
|
std::cout << "Failed to listen to UDP broadcast on port" << tracy::DEFAULT_BROADCAST_UDP_PORT << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if( args.verbose )
|
||||||
|
{
|
||||||
|
std::cout << "Listening for client UDP broadcast messages on port " << tracy::DEFAULT_BROADCAST_UDP_PORT
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<RunningClient> knownClients;
|
||||||
|
bool stillRunningClients = false;
|
||||||
|
bool waitingForFirstClient = true; // set to false if we find a client, or if SIGINT is caught
|
||||||
|
auto last_status_print = hr_clock::now();
|
||||||
|
// tracks how many lines to erase before print multi-line status update
|
||||||
|
// if any other message is printed, it must be reset back to 0 to avoid "eating" a line
|
||||||
|
int liveUpdateLines = 0;
|
||||||
|
|
||||||
|
while( stillRunningClients or waitingForFirstClient )
|
||||||
|
{
|
||||||
|
// discover new clients
|
||||||
|
if( not s_disconnect.load( std::memory_order::relaxed ) )
|
||||||
|
{
|
||||||
|
while( true )
|
||||||
|
{
|
||||||
|
auto bcastClientOpt = receive_client_broadcast( &broadListen );
|
||||||
|
if( not bcastClientOpt.has_value() )
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
RunningClient& bcastClient = bcastClientOpt.value();
|
||||||
|
|
||||||
|
// check if client already exists
|
||||||
|
bool matchesExistingClient = false;
|
||||||
|
for( auto const& alreadyKnownClient : knownClients )
|
||||||
|
{
|
||||||
|
if( bcastClient.id == alreadyKnownClient.id )
|
||||||
|
{
|
||||||
|
matchesExistingClient = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if( not matchesExistingClient )
|
||||||
|
{
|
||||||
|
if( bcastClient.msg.protocolVersion != tracy::ProtocolVersion )
|
||||||
|
{
|
||||||
|
AnsiPrintf( ANSI_RED, "Rejecting client %s; bad protocol version\n",
|
||||||
|
bcastClient.runtimeName().c_str() );
|
||||||
|
liveUpdateLines = 0;
|
||||||
|
bcastClient.status = ClientStatus::DISABLED;
|
||||||
|
knownClients.emplace_back( std::move( bcastClient ) );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if( args.verbose )
|
||||||
|
{
|
||||||
|
std::cout << "Detected client:" << "\n"
|
||||||
|
<< "\tName: " << bcastClient.msg.programName << "\n"
|
||||||
|
<< "\tID: " << bcastClient.id << "\n"
|
||||||
|
<< "\tAddress: " << bcastClient.addr << "\n"
|
||||||
|
<< "\tPort: " << bcastClient.msg.listenPort << std::endl;
|
||||||
|
liveUpdateLines = 0;
|
||||||
|
}
|
||||||
|
bcastClient.worker = std::make_unique<tracy::Worker>( bcastClient.addr, bcastClient.msg.listenPort,
|
||||||
|
tracy::NO_WORKER_MEMORY_LIMIT );
|
||||||
|
knownClients.emplace_back( std::move( bcastClient ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// review pending clients - once handsake succeeded we move them to the running client list
|
||||||
|
for( auto& candidate : knownClients )
|
||||||
|
{
|
||||||
|
if( candidate.status != ClientStatus::DISCOVERED )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if data got through, promote it to a running client
|
||||||
|
if( candidate.worker->HasData() )
|
||||||
|
{
|
||||||
|
if( args.verbose )
|
||||||
|
{
|
||||||
|
printf( "Connected to client '%s' (from %s:%d, PID: %lu)\n", candidate.msg.programName,
|
||||||
|
candidate.addr, candidate.msg.listenPort, candidate.msg.pid );
|
||||||
|
liveUpdateLines = 0;
|
||||||
|
}
|
||||||
|
candidate.status = ClientStatus::RUNNING;
|
||||||
|
|
||||||
|
waitingForFirstClient = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if handshake failed - if so, remove the client
|
||||||
|
const auto handshake = static_cast<tracy::HandshakeStatus>( candidate.worker->GetHandshakeStatus() );
|
||||||
|
if( checkHandshake( handshake ) != 0 )
|
||||||
|
{
|
||||||
|
printf( "-> client '%s' (from %s:%d, PID: %lu) was ignored because of failed handshake status\n",
|
||||||
|
candidate.msg.programName, candidate.addr, candidate.msg.listenPort, candidate.msg.pid );
|
||||||
|
liveUpdateLines = 0;
|
||||||
|
candidate.status = ClientStatus::DISABLED;
|
||||||
|
candidate.worker = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// review running clients
|
||||||
|
// If we notice a disconnect, print the failure message if relevant
|
||||||
|
stillRunningClients = false;
|
||||||
|
for( auto& client : knownClients )
|
||||||
|
{
|
||||||
|
if( client.status == ClientStatus::RUNNING )
|
||||||
|
{
|
||||||
|
if( client.worker->IsConnected() )
|
||||||
|
{
|
||||||
|
stillRunningClients = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( printWorkerFailure( *client.worker, client.runtimeName().c_str() ) )
|
||||||
|
{
|
||||||
|
printf( "\n" );
|
||||||
|
liveUpdateLines = 0;
|
||||||
|
}
|
||||||
|
client.status = ClientStatus::FINISHED;
|
||||||
|
client.finishedAt = hr_clock::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if disconnecting, send disconnect to active workers
|
||||||
|
if( s_disconnect.load( std::memory_order::relaxed ) )
|
||||||
|
{
|
||||||
|
waitingForFirstClient = false;
|
||||||
|
for( auto& client : knownClients )
|
||||||
|
{
|
||||||
|
if( client.status == ClientStatus::RUNNING and not client.disconnectSent )
|
||||||
|
{
|
||||||
|
std::cout << "disconnecting " << client << std::endl;
|
||||||
|
client.worker->Disconnect();
|
||||||
|
liveUpdateLines = 0;
|
||||||
|
client.disconnectSent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print status update
|
||||||
|
// Done only if we have a terminal output to avoid bloating log files
|
||||||
|
// we print at regular time intervals, and one last time when there are no clients left
|
||||||
|
auto now = hr_clock::now();
|
||||||
|
if( IsStdoutATerminal() and ( now - last_status_print > PRINT_UPDATE_INTERVAL or not stillRunningClients ) )
|
||||||
|
{
|
||||||
|
for( ; liveUpdateLines > 0; liveUpdateLines-- )
|
||||||
|
{
|
||||||
|
AnsiPrintf( ANSI_UP_ONE_LINE ANSI_ERASE_LINE, "" );
|
||||||
|
}
|
||||||
|
double elapsedSeconds =
|
||||||
|
std::chrono::duration_cast<std::chrono::duration<double>>( now - startCaptureTimestamp ).count();
|
||||||
|
AnsiPrintf( ANSI_YELLOW, "t=%.1lfs", elapsedSeconds );
|
||||||
|
AnsiPrintf( ANSI_RED, " | " );
|
||||||
|
printCurrentMemoryUsage( tracy::NO_WORKER_MEMORY_LIMIT );
|
||||||
|
printf( "\n" );
|
||||||
|
liveUpdateLines++;
|
||||||
|
for( auto const& client : knownClients )
|
||||||
|
{
|
||||||
|
char const* statusColor;
|
||||||
|
switch( client.status )
|
||||||
|
{
|
||||||
|
case ClientStatus::RUNNING:
|
||||||
|
statusColor = ANSI_GREEN;
|
||||||
|
break;
|
||||||
|
case ClientStatus::FINISHED:
|
||||||
|
statusColor = ANSI_BLUE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AnsiPrintf( statusColor, "%s", client.runtimeName().c_str() );
|
||||||
|
printWorkerUpdate( *client.worker, tracy::NO_WORKER_MEMORY_LIMIT, false, false );
|
||||||
|
printf( "\n" );
|
||||||
|
liveUpdateLines++;
|
||||||
|
}
|
||||||
|
last_status_print = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for( MULTI_CAPTURE_LOOP_INTERVAL );
|
||||||
|
}
|
||||||
|
|
||||||
|
// end of main loop
|
||||||
|
// checking if we have something to save, and if so do it
|
||||||
|
auto endCaptureTimestamp = std::chrono::high_resolution_clock::now();
|
||||||
|
bool gotAtLeastOneClient =
|
||||||
|
std::any_of( knownClients.begin(), knownClients.end(), []( auto const& client )
|
||||||
|
{ return client.status == ClientStatus::RUNNING or client.status == ClientStatus::FINISHED; } );
|
||||||
|
if( not gotAtLeastOneClient )
|
||||||
|
{
|
||||||
|
std::cout << "Did not capture data, exiting" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Writing output..." << std::endl;
|
||||||
|
for( auto& client : knownClients )
|
||||||
|
{
|
||||||
|
if( client.status != ClientStatus::FINISHED )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AnsiPrintf( ANSI_BLUE, "- %s\n", client.runtimeName().c_str() );
|
||||||
|
printf( " Frames: %" PRIu64 "\n Time span: %s\n Zones: %s\n Elapsed time: %s\n Saving trace...",
|
||||||
|
client.worker->GetFrameCount( *client.worker->GetFramesBase() ),
|
||||||
|
tracy::TimeToString( client.worker->GetLastTime() - client.worker->GetFirstTime() ),
|
||||||
|
tracy::RealToString( client.worker->GetZoneCount() ),
|
||||||
|
tracy::TimeToString(
|
||||||
|
std::chrono::duration_cast<std::chrono::nanoseconds>( client.finishedAt - client.detectedAt )
|
||||||
|
.count() ) );
|
||||||
|
std::filesystem::path filepath = args.outputDirectory / client.saveName( args.outputFileName );
|
||||||
|
auto f = std::unique_ptr<tracy::FileWrite>(
|
||||||
|
tracy::FileWrite::Open( filepath.generic_string().c_str(), tracy::FileCompression::Zstd, 3, 4 ) );
|
||||||
|
if( f )
|
||||||
|
{
|
||||||
|
client.worker->Write( *f, false );
|
||||||
|
AnsiPrintf( ANSI_GREEN, "done: %s\n", filepath.string().c_str() );
|
||||||
|
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, "failed!\n" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main( int argc, char* argv[] )
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
if( !AttachConsole( ATTACH_PARENT_PROCESS ) )
|
||||||
|
{
|
||||||
|
AllocConsole();
|
||||||
|
SetConsoleMode( GetStdHandle( STD_OUTPUT_HANDLE ), 0x07 );
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
InitIsStdoutATerminal();
|
||||||
|
|
||||||
|
CaptureArgs args = CaptureArgs::parse( argc, argv );
|
||||||
|
args.print(); // remove before merge
|
||||||
|
|
||||||
|
if( args.multi )
|
||||||
|
{
|
||||||
|
return runCaptureMulti( args );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return runCaptureSingle( args );
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,7 @@ set(TRACY_SERVER_SOURCES
|
|||||||
TracyMemory.cpp
|
TracyMemory.cpp
|
||||||
TracyMmap.cpp
|
TracyMmap.cpp
|
||||||
TracyPrint.cpp
|
TracyPrint.cpp
|
||||||
|
TracyProtocolServer.cpp
|
||||||
TracySysUtil.cpp
|
TracySysUtil.cpp
|
||||||
TracyTaskDispatch.cpp
|
TracyTaskDispatch.cpp
|
||||||
TracyTextureCompression.cpp
|
TracyTextureCompression.cpp
|
||||||
|
|||||||
25
multiprocess/CMakeLists.txt
Normal file
25
multiprocess/CMakeLists.txt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
option(NO_ISA_EXTENSIONS "Disable ISA extensions (don't pass -march=native or -mcpu=native to the compiler)" OFF)
|
||||||
|
option(NO_STATISTICS OFF) # we need those to get processed source zone locations
|
||||||
|
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
|
project(
|
||||||
|
tracy-multiprocess
|
||||||
|
LANGUAGES C CXX ASM
|
||||||
|
VERSION ${TRACY_VERSION_STRING}
|
||||||
|
)
|
||||||
|
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/config.cmake)
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/vendor.cmake)
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/server.cmake)
|
||||||
|
|
||||||
|
|
||||||
|
add_executable(tracy-merge
|
||||||
|
src/merger.cpp)
|
||||||
|
target_link_libraries(tracy-merge PRIVATE TracyServer TracyGetOpt)
|
||||||
|
|
||||||
|
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
||||||
337
multiprocess/src/merger.cpp
Normal file
337
multiprocess/src/merger.cpp
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
#include "TracyFileRead.hpp"
|
||||||
|
#include "TracyFileWrite.hpp"
|
||||||
|
#include "TracyWorker.hpp"
|
||||||
|
#include "getopt.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
bool orderImportTimelineEvents( tracy::Worker::ImportEventTimeline const& a,
|
||||||
|
tracy::Worker::ImportEventTimeline const& b )
|
||||||
|
{
|
||||||
|
return a.timestamp < b.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool orderImportMessageEvents( tracy::Worker::ImportEventMessages const& a,
|
||||||
|
tracy::Worker::ImportEventMessages const& b )
|
||||||
|
{
|
||||||
|
return a.timestamp < b.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExportedWorker
|
||||||
|
{
|
||||||
|
std::vector<tracy::Worker::ImportEventTimeline> timeline;
|
||||||
|
std::vector<tracy::Worker::ImportEventMessages> messages;
|
||||||
|
std::vector<tracy::Worker::ImportEventPlots> plots;
|
||||||
|
|
||||||
|
std::unordered_map<uint64_t, std::string> threadNames;
|
||||||
|
std::string name;
|
||||||
|
std::string process;
|
||||||
|
|
||||||
|
static ExportedWorker merge( std::vector<ExportedWorker const*> inputList )
|
||||||
|
{
|
||||||
|
uint64_t nextFreeThreadId = 1;
|
||||||
|
ExportedWorker out;
|
||||||
|
|
||||||
|
// for some data, arbitrarily take the infos from the first trace
|
||||||
|
auto const& firstExport = *inputList[0];
|
||||||
|
out.name = firstExport.name;
|
||||||
|
out.process = firstExport.process;
|
||||||
|
|
||||||
|
// quick pass to allocate output vectors
|
||||||
|
size_t numTimelineEvents = 0, numMessages = 0, numPlots = 0;
|
||||||
|
for( auto const& inputWorker : inputList )
|
||||||
|
{
|
||||||
|
numTimelineEvents += inputWorker->timeline.size();
|
||||||
|
numMessages += inputWorker->messages.size();
|
||||||
|
numPlots += inputWorker->plots.size();
|
||||||
|
}
|
||||||
|
out.timeline.reserve( numTimelineEvents );
|
||||||
|
out.messages.reserve( numMessages );
|
||||||
|
out.plots.reserve( numPlots );
|
||||||
|
|
||||||
|
size_t eventsSortedSoFar = 0;
|
||||||
|
size_t messagesSortedSoFar = 0;
|
||||||
|
|
||||||
|
// keep track of registered threads to avoid overlaps
|
||||||
|
std::unordered_map<uint64_t, uint64_t> localThreadToMultiprocess;
|
||||||
|
|
||||||
|
for( auto exportPtr : inputList )
|
||||||
|
{
|
||||||
|
ExportedWorker const& exported = *exportPtr;
|
||||||
|
|
||||||
|
// rebuild thread mapping
|
||||||
|
// we try to keep original thread IDs intact if possible, falling back to made-up IDs
|
||||||
|
// in case of conflict
|
||||||
|
for( auto const& threadId : exported.threadNames )
|
||||||
|
{
|
||||||
|
uint64_t multiprocessId = threadId.first;
|
||||||
|
if( localThreadToMultiprocess.contains( multiprocessId ) )
|
||||||
|
{
|
||||||
|
// oh-oh, conflict - let's take a random ID instead;
|
||||||
|
multiprocessId = nextFreeThreadId++;
|
||||||
|
}
|
||||||
|
localThreadToMultiprocess[threadId.first] = multiprocessId;
|
||||||
|
out.threadNames[multiprocessId] = threadId.second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// translate all events with the right thread IDs
|
||||||
|
for( auto&& event : exported.timeline )
|
||||||
|
{
|
||||||
|
tracy::Worker::ImportEventTimeline& inserted = out.timeline.emplace_back( event );
|
||||||
|
inserted.tid = localThreadToMultiprocess[inserted.tid];
|
||||||
|
}
|
||||||
|
for( auto&& message : exported.messages )
|
||||||
|
{
|
||||||
|
tracy::Worker::ImportEventMessages& inserted = out.messages.emplace_back( message );
|
||||||
|
inserted.tid = localThreadToMultiprocess[inserted.tid];
|
||||||
|
}
|
||||||
|
for( auto&& plots : exported.plots )
|
||||||
|
{
|
||||||
|
out.plots.emplace_back( plots );
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort timeline and messages events
|
||||||
|
std::inplace_merge( out.timeline.begin(), out.timeline.begin() + eventsSortedSoFar, out.timeline.end(),
|
||||||
|
orderImportTimelineEvents );
|
||||||
|
eventsSortedSoFar += exported.timeline.size();
|
||||||
|
std::inplace_merge( out.messages.begin(), out.messages.begin() + messagesSortedSoFar, out.messages.end(),
|
||||||
|
orderImportMessageEvents );
|
||||||
|
messagesSortedSoFar += exported.messages.size();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<ExportedWorker> fromTracyFile( std::string const& filepath, bool exportPlots )
|
||||||
|
{
|
||||||
|
std::unique_ptr<tracy::FileRead> sourceFile{ tracy::FileRead::Open( ( filepath.c_str() ) ) };
|
||||||
|
if( !sourceFile )
|
||||||
|
{
|
||||||
|
std::cerr << "Could not find file" << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
std::cout << "reading " << filepath << std::endl;
|
||||||
|
|
||||||
|
tracy::Worker worker{ *sourceFile, tracy::EventType::All,
|
||||||
|
true, // otherwise source zones are empty
|
||||||
|
false };
|
||||||
|
while( !worker.AreSourceLocationZonesReady() )
|
||||||
|
{
|
||||||
|
std::cout << "Waiting for source locations" << std::endl;
|
||||||
|
std::this_thread::sleep_for( 1s );
|
||||||
|
}
|
||||||
|
|
||||||
|
ExportedWorker exportedData;
|
||||||
|
exportedData.name = worker.GetCaptureName();
|
||||||
|
exportedData.process = worker.GetCaptureProgram();
|
||||||
|
std::cout << exportedData.name << " (" << exportedData.process << ")" << std::endl;
|
||||||
|
|
||||||
|
std::unordered_set<uint64_t> seenThreadIds;
|
||||||
|
|
||||||
|
auto& sourceLocationZones = worker.GetSourceLocationZones();
|
||||||
|
std::cout << "- " << sourceLocationZones.size() << " events" << std::endl;
|
||||||
|
for( auto&& zone_it : sourceLocationZones )
|
||||||
|
{
|
||||||
|
const tracy::SourceLocation& sourceLoc = worker.GetSourceLocation( zone_it.first );
|
||||||
|
std::string zoneFilePath = worker.GetString( sourceLoc.file );
|
||||||
|
int zoneLine = sourceLoc.line;
|
||||||
|
std::string zoneName = worker.GetZoneName( sourceLoc );
|
||||||
|
|
||||||
|
auto const& zones = zone_it.second;
|
||||||
|
for( auto&& zoneData : zones.zones )
|
||||||
|
{
|
||||||
|
const auto zone_event = zoneData.Zone();
|
||||||
|
const uint64_t threadFullId = worker.DecompressThread( zoneData.Thread() );
|
||||||
|
const auto start = zone_event->Start();
|
||||||
|
const auto end = zone_event->End();
|
||||||
|
seenThreadIds.emplace( threadFullId );
|
||||||
|
|
||||||
|
auto& startEvent = exportedData.timeline.emplace_back();
|
||||||
|
startEvent.locFile = zoneFilePath;
|
||||||
|
startEvent.locLine = zoneLine;
|
||||||
|
startEvent.name = zoneName;
|
||||||
|
startEvent.tid = threadFullId;
|
||||||
|
startEvent.isEnd = false;
|
||||||
|
startEvent.timestamp = zone_event->Start();
|
||||||
|
|
||||||
|
auto& endEvent = exportedData.timeline.emplace_back();
|
||||||
|
endEvent.locFile = zoneFilePath;
|
||||||
|
endEvent.locLine = zoneLine;
|
||||||
|
endEvent.name = zoneName;
|
||||||
|
endEvent.tid = threadFullId;
|
||||||
|
endEvent.isEnd = true;
|
||||||
|
endEvent.timestamp = zone_event->End();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// need to sort because we split 'begin' and 'end' events
|
||||||
|
std::sort( exportedData.timeline.begin(), exportedData.timeline.end(), orderImportTimelineEvents );
|
||||||
|
|
||||||
|
auto const& messages = worker.GetMessages();
|
||||||
|
std::cout << "- " << messages.size() << " messages" << std::endl;
|
||||||
|
for( auto const& messages_it : worker.GetMessages() )
|
||||||
|
{
|
||||||
|
tracy::MessageData const& messageData = *messages_it;
|
||||||
|
tracy::Worker::ImportEventMessages importMessage;
|
||||||
|
uint64_t const threadId = worker.DecompressThread( messageData.thread );
|
||||||
|
importMessage.tid = threadId;
|
||||||
|
importMessage.message = worker.GetString( messageData.ref );
|
||||||
|
importMessage.timestamp = messageData.time;
|
||||||
|
|
||||||
|
exportedData.messages.push_back( importMessage );
|
||||||
|
seenThreadIds.emplace( threadId );
|
||||||
|
}
|
||||||
|
// to be sure, but should not do a lot
|
||||||
|
std::sort( exportedData.messages.begin(), exportedData.messages.end(), orderImportMessageEvents );
|
||||||
|
|
||||||
|
if( exportPlots )
|
||||||
|
{
|
||||||
|
auto const& plots = worker.GetPlots();
|
||||||
|
std::cout << "- " << plots.size() << " plots" << std::endl;
|
||||||
|
for( auto const& plots_it : worker.GetPlots() )
|
||||||
|
{
|
||||||
|
tracy::Worker::ImportEventPlots importPlot;
|
||||||
|
importPlot.name = worker.GetString( plots_it->name );
|
||||||
|
importPlot.format = plots_it->format;
|
||||||
|
|
||||||
|
importPlot.data.resize( plots_it->data.size() );
|
||||||
|
for( auto const& elt : plots_it->data )
|
||||||
|
{
|
||||||
|
std::pair<int64_t, double> dataPoint{ elt.time.Val(), elt.val };
|
||||||
|
importPlot.data.push_back( dataPoint );
|
||||||
|
}
|
||||||
|
exportedData.plots.push_back( importPlot );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for( auto&& tid : seenThreadIds )
|
||||||
|
{
|
||||||
|
std::string name = worker.GetThreadName( tid );
|
||||||
|
exportedData.threadNames[tid] = exportedData.process + "/" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return exportedData;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[[noreturn]] void Usage()
|
||||||
|
{
|
||||||
|
printf( "Usage: merge [-fp] -o output.tracy input1.tracy [input2.tracy]...\n\n" );
|
||||||
|
printf( "Options\n" );
|
||||||
|
printf( " --output/-o <filepath> Output file path\n" );
|
||||||
|
printf( " --force/-f Overwrite output file if it exists\n" );
|
||||||
|
printf( " --export-plots/-p (experimental) Also exports plots\n" );
|
||||||
|
exit( 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Args
|
||||||
|
{
|
||||||
|
std::vector<std::string> inputPaths;
|
||||||
|
std::string outputPath;
|
||||||
|
bool exportPlots = false;
|
||||||
|
|
||||||
|
static Args parse( int argc, char* argv[] )
|
||||||
|
{
|
||||||
|
Args args;
|
||||||
|
// option parsing
|
||||||
|
bool overwrite = false;
|
||||||
|
int c;
|
||||||
|
const struct option long_options[] = {
|
||||||
|
{ "output", required_argument, 0, 'o' },
|
||||||
|
{ "force", no_argument, 0, 'f' },
|
||||||
|
{ "export-plots", no_argument, 0, 'p' },
|
||||||
|
{ 0, 0, 0, 0 },
|
||||||
|
};
|
||||||
|
while( ( c = getopt_long( argc, argv, "o:fp", long_options, nullptr ) ) != -1 )
|
||||||
|
{
|
||||||
|
switch( c )
|
||||||
|
{
|
||||||
|
case 'o':
|
||||||
|
args.outputPath = optarg;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
overwrite = true;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
args.exportPlots = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Usage();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for( int argIndex = optind; argIndex < argc; argIndex++ )
|
||||||
|
{
|
||||||
|
args.inputPaths.push_back( argv[argIndex] );
|
||||||
|
}
|
||||||
|
if( args.inputPaths.size() == 0 or args.outputPath.empty() )
|
||||||
|
{
|
||||||
|
Usage();
|
||||||
|
}
|
||||||
|
if( std::filesystem::exists( args.outputPath ) and not overwrite )
|
||||||
|
{
|
||||||
|
printf( "Output file %s already exists! Use -f to force overwrite.\n", args.outputPath.c_str() );
|
||||||
|
exit( 4 );
|
||||||
|
}
|
||||||
|
for( auto const& input : args.inputPaths )
|
||||||
|
{
|
||||||
|
if( not std::filesystem::exists( input ) )
|
||||||
|
{
|
||||||
|
printf( "Input file %s does not exist!\n", input.c_str() );
|
||||||
|
exit( 4 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main( int argc, char* argv[] )
|
||||||
|
{
|
||||||
|
auto args = Args::parse( argc, argv );
|
||||||
|
|
||||||
|
std::vector<ExportedWorker> exports;
|
||||||
|
for( auto path : args.inputPaths )
|
||||||
|
{
|
||||||
|
auto importedOpt = ExportedWorker::fromTracyFile( path, args.exportPlots );
|
||||||
|
if( not importedOpt.has_value() )
|
||||||
|
{
|
||||||
|
std::cerr << "Error importing " << path << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
exports.push_back( ( importedOpt.value() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ExportedWorker const*> exportRefs;
|
||||||
|
std::transform( exports.cbegin(), exports.cend(), std::back_inserter( exportRefs ),
|
||||||
|
[]( ExportedWorker const& ex ) { return &ex; } );
|
||||||
|
|
||||||
|
auto mergedImport = ExportedWorker::merge( exportRefs );
|
||||||
|
|
||||||
|
{
|
||||||
|
std::cout << "Writing " << args.outputPath << std::endl;
|
||||||
|
auto outputFileWrite = std::unique_ptr<tracy::FileWrite>( tracy::FileWrite::Open( args.outputPath.c_str() ) );
|
||||||
|
if( !outputFileWrite )
|
||||||
|
{
|
||||||
|
fprintf( stderr, "Cannot open output file!\n" );
|
||||||
|
exit( 1 );
|
||||||
|
}
|
||||||
|
tracy::Worker outputWorker( mergedImport.name.c_str(), mergedImport.process.c_str(), mergedImport.timeline,
|
||||||
|
mergedImport.messages, mergedImport.plots, mergedImport.threadNames );
|
||||||
|
outputWorker.Write( *outputFileWrite, false );
|
||||||
|
outputFileWrite->Finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "done" << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -46,6 +46,7 @@
|
|||||||
#include "../../server/tracy_robin_hood.h"
|
#include "../../server/tracy_robin_hood.h"
|
||||||
#include "../../server/TracyFileHeader.hpp"
|
#include "../../server/TracyFileHeader.hpp"
|
||||||
#include "../../server/TracyFileRead.hpp"
|
#include "../../server/TracyFileRead.hpp"
|
||||||
|
#include "../../server/TracyProtocolServer.hpp"
|
||||||
#include "../../server/TracyPrint.hpp"
|
#include "../../server/TracyPrint.hpp"
|
||||||
#include "../../server/TracySysUtil.hpp"
|
#include "../../server/TracySysUtil.hpp"
|
||||||
#include "../../server/TracyWorker.hpp"
|
#include "../../server/TracyWorker.hpp"
|
||||||
@ -87,7 +88,7 @@ enum class ViewShutdown { False, True, Join };
|
|||||||
static tracy::unordered_flat_map<uint64_t, ClientData> clients;
|
static tracy::unordered_flat_map<uint64_t, ClientData> clients;
|
||||||
static std::unique_ptr<tracy::View> view;
|
static std::unique_ptr<tracy::View> view;
|
||||||
static tracy::BadVersionState badVer;
|
static tracy::BadVersionState badVer;
|
||||||
static uint16_t port = 8086;
|
static uint16_t port = tracy::DEFAULT_BROADCAST_UDP_PORT;
|
||||||
static const char* connectTo = nullptr;
|
static const char* connectTo = nullptr;
|
||||||
static char title[128];
|
static char title[128];
|
||||||
static std::thread loadThread, updateThread, updateNotesThread;
|
static std::thread loadThread, updateThread, updateNotesThread;
|
||||||
@ -458,76 +459,15 @@ static void UpdateBroadcastClients()
|
|||||||
{
|
{
|
||||||
auto msg = broadcastListen->Read( len, addr, 0 );
|
auto msg = broadcastListen->Read( len, addr, 0 );
|
||||||
if( !msg ) break;
|
if( !msg ) break;
|
||||||
if( len > sizeof( tracy::BroadcastMessage ) ) continue;
|
auto parsedMessageOpt = tracy::ParseBroadcastMessage(msg, len);
|
||||||
uint16_t broadcastVersion;
|
if (parsedMessageOpt.has_value())
|
||||||
memcpy( &broadcastVersion, msg, sizeof( uint16_t ) );
|
|
||||||
if( broadcastVersion <= tracy::BroadcastVersion )
|
|
||||||
{
|
{
|
||||||
uint32_t protoVer;
|
auto parsedMessage = parsedMessageOpt.value();
|
||||||
char procname[tracy::WelcomeMessageProgramNameSize];
|
|
||||||
int32_t activeTime;
|
|
||||||
uint16_t listenPort;
|
|
||||||
uint64_t pid;
|
|
||||||
|
|
||||||
switch( broadcastVersion )
|
|
||||||
{
|
|
||||||
case 3:
|
|
||||||
{
|
|
||||||
tracy::BroadcastMessage bm;
|
|
||||||
memcpy( &bm, msg, len );
|
|
||||||
protoVer = bm.protocolVersion;
|
|
||||||
strcpy( procname, bm.programName );
|
|
||||||
activeTime = bm.activeTime;
|
|
||||||
listenPort = bm.listenPort;
|
|
||||||
pid = bm.pid;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
{
|
|
||||||
if( len > sizeof( tracy::BroadcastMessage_v2 ) ) continue;
|
|
||||||
tracy::BroadcastMessage_v2 bm;
|
|
||||||
memcpy( &bm, msg, len );
|
|
||||||
protoVer = bm.protocolVersion;
|
|
||||||
strcpy( procname, bm.programName );
|
|
||||||
activeTime = bm.activeTime;
|
|
||||||
listenPort = bm.listenPort;
|
|
||||||
pid = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
{
|
|
||||||
if( len > sizeof( tracy::BroadcastMessage_v1 ) ) continue;
|
|
||||||
tracy::BroadcastMessage_v1 bm;
|
|
||||||
memcpy( &bm, msg, len );
|
|
||||||
protoVer = bm.protocolVersion;
|
|
||||||
strcpy( procname, bm.programName );
|
|
||||||
activeTime = bm.activeTime;
|
|
||||||
listenPort = bm.listenPort;
|
|
||||||
pid = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 0:
|
|
||||||
{
|
|
||||||
if( len > sizeof( tracy::BroadcastMessage_v0 ) ) continue;
|
|
||||||
tracy::BroadcastMessage_v0 bm;
|
|
||||||
memcpy( &bm, msg, len );
|
|
||||||
protoVer = bm.protocolVersion;
|
|
||||||
strcpy( procname, bm.programName );
|
|
||||||
activeTime = bm.activeTime;
|
|
||||||
listenPort = 8086;
|
|
||||||
pid = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
assert( false );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto address = addr.GetText();
|
auto address = addr.GetText();
|
||||||
|
const auto clientId = tracy::ClientUniqueID(addr, parsedMessage.listenPort);
|
||||||
const auto ipNumerical = addr.GetNumber();
|
const auto ipNumerical = addr.GetNumber();
|
||||||
const auto clientId = uint64_t( ipNumerical ) | ( uint64_t( listenPort ) << 32 );
|
|
||||||
auto it = clients.find( clientId );
|
auto it = clients.find( clientId );
|
||||||
if( activeTime >= 0 )
|
if( parsedMessage.activeTime >= 0 )
|
||||||
{
|
{
|
||||||
if( it == clients.end() )
|
if( it == clients.end() )
|
||||||
{
|
{
|
||||||
@ -544,16 +484,16 @@ static void UpdateBroadcastClients()
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
resolvLock.unlock();
|
resolvLock.unlock();
|
||||||
clients.emplace( clientId, ClientData { time, protoVer, activeTime, listenPort, pid, procname, std::move( ip ) } );
|
clients.emplace( clientId, ClientData { time, parsedMessage.protocolVersion, parsedMessage.activeTime, parsedMessage.listenPort, parsedMessage.pid, parsedMessage.programName, std::move( ip ) } );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
it->second.time = time;
|
it->second.time = time;
|
||||||
it->second.activeTime = activeTime;
|
it->second.activeTime = parsedMessage.activeTime;
|
||||||
it->second.port = listenPort;
|
it->second.port = parsedMessage.listenPort;
|
||||||
it->second.pid = pid;
|
it->second.pid = parsedMessage.pid;
|
||||||
it->second.protocolVersion = protoVer;
|
it->second.protocolVersion = parsedMessage.protocolVersion;
|
||||||
if( strcmp( it->second.procName.c_str(), procname ) != 0 ) it->second.procName = procname;
|
if( strcmp( it->second.procName.c_str(), parsedMessage.programName ) != 0 ) it->second.procName = parsedMessage.programName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if( it != clients.end() )
|
else if( it != clients.end() )
|
||||||
|
|||||||
@ -36,7 +36,7 @@ namespace tracy
|
|||||||
double s_time = 0;
|
double s_time = 0;
|
||||||
|
|
||||||
View::View( void(*cbMainThread)(const std::function<void()>&, bool), const char* addr, uint16_t port, ImFont* fixedWidth, ImFont* smallFont, ImFont* bigFont, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, const Config& config, AchievementsMgr* amgr )
|
View::View( void(*cbMainThread)(const std::function<void()>&, bool), const char* addr, uint16_t port, ImFont* fixedWidth, ImFont* smallFont, ImFont* bigFont, SetTitleCallback stcb, SetScaleCallback sscb, AttentionCallback acb, const Config& config, AchievementsMgr* amgr )
|
||||||
: m_worker( addr, port, config.memoryLimit == 0 ? -1 : ( config.memoryLimitPercent * tracy::GetPhysicalMemorySize() / 100 ) )
|
: m_worker( addr, port, config.memoryLimit == 0 ? NO_WORKER_MEMORY_LIMIT : ( config.memoryLimitPercent * tracy::GetPhysicalMemorySize() / 100 ) )
|
||||||
, m_staticView( false )
|
, m_staticView( false )
|
||||||
, m_viewMode( ViewMode::LastFrames )
|
, m_viewMode( ViewMode::LastFrames )
|
||||||
, m_viewModeHeuristicTry( true )
|
, m_viewModeHeuristicTry( true )
|
||||||
|
|||||||
@ -1679,12 +1679,12 @@ void Profiler::Worker()
|
|||||||
auto dataPort = m_userPort != 0 ? m_userPort : TRACY_DATA_PORT;
|
auto dataPort = m_userPort != 0 ? m_userPort : TRACY_DATA_PORT;
|
||||||
#else
|
#else
|
||||||
const bool dataPortSearch = m_userPort == 0;
|
const bool dataPortSearch = m_userPort == 0;
|
||||||
auto dataPort = m_userPort != 0 ? m_userPort : 8086;
|
auto dataPort = m_userPort != 0 ? m_userPort : DEFAULT_CLIENT_DATA_TCP_PORT;
|
||||||
#endif
|
#endif
|
||||||
#ifdef TRACY_BROADCAST_PORT
|
#ifdef TRACY_BROADCAST_PORT
|
||||||
const auto broadcastPort = TRACY_BROADCAST_PORT;
|
const auto broadcastPort = TRACY_BROADCAST_PORT;
|
||||||
#else
|
#else
|
||||||
const auto broadcastPort = 8086;
|
const auto broadcastPort = DEFAULT_BROADCAST_UDP_PORT;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
while( m_timeBegin.load( std::memory_order_relaxed ) == 0 ) std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
|
while( m_timeBegin.load( std::memory_order_relaxed ) == 0 ) std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
|
||||||
|
|||||||
@ -7,6 +7,9 @@
|
|||||||
namespace tracy
|
namespace tracy
|
||||||
{
|
{
|
||||||
|
|
||||||
|
constexpr uint16_t DEFAULT_BROADCAST_UDP_PORT = 8086;
|
||||||
|
constexpr uint16_t DEFAULT_CLIENT_DATA_TCP_PORT = 8086;
|
||||||
|
|
||||||
constexpr unsigned Lz4CompressBound( unsigned isize ) { return isize + ( isize / 255 ) + 16; }
|
constexpr unsigned Lz4CompressBound( unsigned isize ) { return isize + ( isize / 255 ) + 16; }
|
||||||
|
|
||||||
enum : uint32_t { ProtocolVersion = 72 };
|
enum : uint32_t { ProtocolVersion = 72 };
|
||||||
|
|||||||
@ -651,7 +651,7 @@ void IpAddress::Set( const struct sockaddr& addr )
|
|||||||
#else
|
#else
|
||||||
auto ai = (const struct sockaddr_in*)&addr;
|
auto ai = (const struct sockaddr_in*)&addr;
|
||||||
#endif
|
#endif
|
||||||
inet_ntop( AF_INET, &ai->sin_addr, m_text, 17 );
|
inet_ntop( AF_INET, &ai->sin_addr, m_text, TEXT_SIZE );
|
||||||
m_number = ai->sin_addr.s_addr;
|
m_number = ai->sin_addr.s_addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -112,6 +112,8 @@ private:
|
|||||||
class IpAddress
|
class IpAddress
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
static constexpr size_t TEXT_SIZE = 17;
|
||||||
|
|
||||||
IpAddress();
|
IpAddress();
|
||||||
~IpAddress();
|
~IpAddress();
|
||||||
|
|
||||||
@ -127,7 +129,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
uint32_t m_number;
|
uint32_t m_number;
|
||||||
char m_text[17];
|
char m_text[TEXT_SIZE];
|
||||||
};
|
};
|
||||||
|
|
||||||
class UdpListen
|
class UdpListen
|
||||||
|
|||||||
107
server/TracyProtocolServer.cpp
Normal file
107
server/TracyProtocolServer.cpp
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#include "TracyProtocolServer.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace tracy
|
||||||
|
{
|
||||||
|
std::optional<tracy::BroadcastMessage> ParseBroadcastMessage( const char* msg, size_t msgLen )
|
||||||
|
{
|
||||||
|
if( msgLen < sizeof( uint16_t ) )
|
||||||
|
{
|
||||||
|
std::cout << "Received too short broadcast message" << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
uint16_t broadcastVersion;
|
||||||
|
memcpy( &broadcastVersion, msg, sizeof( uint16_t ) );
|
||||||
|
if( broadcastVersion > tracy::BroadcastVersion )
|
||||||
|
{
|
||||||
|
std::cout << "Received broadcast message with unsupported version: " << broadcastVersion << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
switch( broadcastVersion )
|
||||||
|
{
|
||||||
|
case 3:
|
||||||
|
{
|
||||||
|
if( msgLen > sizeof( tracy::BroadcastMessage ) )
|
||||||
|
{
|
||||||
|
std::cout << "Received unexpected size broadcast v3 message" << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
tracy::BroadcastMessage bm;
|
||||||
|
memcpy( &bm, msg, msgLen );
|
||||||
|
return bm;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
{
|
||||||
|
if( msgLen > sizeof( tracy::BroadcastMessage_v2 ) )
|
||||||
|
{
|
||||||
|
std::cout << "Received unexpected size broadcast v2 message" << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
tracy::BroadcastMessage_v2 bm;
|
||||||
|
memcpy( &bm, msg, msgLen );
|
||||||
|
|
||||||
|
tracy::BroadcastMessage out;
|
||||||
|
out.broadcastVersion = broadcastVersion;
|
||||||
|
out.protocolVersion = bm.protocolVersion;
|
||||||
|
out.activeTime = bm.activeTime;
|
||||||
|
out.listenPort = bm.listenPort;
|
||||||
|
strcpy( out.programName, bm.programName );
|
||||||
|
out.pid = 0;
|
||||||
|
return out;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
{
|
||||||
|
if( msgLen > sizeof( tracy::BroadcastMessage_v1 ) )
|
||||||
|
{
|
||||||
|
std::cout << "Received unexpected size broadcast v1 message" << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
tracy::BroadcastMessage_v1 bm;
|
||||||
|
memcpy( &bm, msg, msgLen );
|
||||||
|
|
||||||
|
tracy::BroadcastMessage out;
|
||||||
|
out.broadcastVersion = broadcastVersion;
|
||||||
|
out.protocolVersion = bm.protocolVersion;
|
||||||
|
out.activeTime = bm.activeTime;
|
||||||
|
out.listenPort = bm.listenPort;
|
||||||
|
strcpy( out.programName, bm.programName );
|
||||||
|
out.pid = 0;
|
||||||
|
return out;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
|
if( msgLen > sizeof( tracy::BroadcastMessage_v0 ) )
|
||||||
|
{
|
||||||
|
std::cout << "Received unexpected size broadcast v0 message" << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
tracy::BroadcastMessage_v0 bm;
|
||||||
|
memcpy( &bm, msg, msgLen );
|
||||||
|
|
||||||
|
tracy::BroadcastMessage out;
|
||||||
|
out.broadcastVersion = broadcastVersion;
|
||||||
|
out.protocolVersion = bm.protocolVersion;
|
||||||
|
out.activeTime = bm.activeTime;
|
||||||
|
out.listenPort = tracy::DEFAULT_CLIENT_DATA_TCP_PORT;
|
||||||
|
strcpy( out.programName, bm.programName );
|
||||||
|
out.pid = 0;
|
||||||
|
return out;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
assert( false );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t ClientUniqueID( tracy::IpAddress const& addr, uint16_t port )
|
||||||
|
{
|
||||||
|
return uint64_t( addr.GetNumber() ) | ( uint64_t( port ) << 32 );
|
||||||
|
}
|
||||||
|
}
|
||||||
17
server/TracyProtocolServer.hpp
Normal file
17
server/TracyProtocolServer.hpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// server-side functions supporting the protocol
|
||||||
|
#ifndef __TRACYPROTOCOLSERVER_HPP__
|
||||||
|
#define __TRACYPROTOCOLSERVER_HPP__
|
||||||
|
|
||||||
|
#include "TracyProtocol.hpp"
|
||||||
|
#include "TracySocket.hpp"
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
namespace tracy
|
||||||
|
{
|
||||||
|
// create the latest version of broadcast message, migrating older versions if possible
|
||||||
|
std::optional<tracy::BroadcastMessage> ParseBroadcastMessage( const char* msg, size_t msgLen );
|
||||||
|
// internal unique ID for a client
|
||||||
|
uint64_t ClientUniqueID( tracy::IpAddress const& addr, uint16_t port );
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -313,7 +313,7 @@ Worker::Worker( const char* name, const char* program, const std::vector<ImportE
|
|||||||
, m_buffer( nullptr )
|
, m_buffer( nullptr )
|
||||||
, m_onDemand( false )
|
, m_onDemand( false )
|
||||||
, m_inconsistentSamples( false )
|
, m_inconsistentSamples( false )
|
||||||
, m_memoryLimit( -1 )
|
, m_memoryLimit( NO_WORKER_MEMORY_LIMIT )
|
||||||
, m_traceVersion( CurrentVersion )
|
, m_traceVersion( CurrentVersion )
|
||||||
{
|
{
|
||||||
m_data.sourceLocationExpand.push_back( 0 );
|
m_data.sourceLocationExpand.push_back( 0 );
|
||||||
@ -556,7 +556,7 @@ Worker::Worker( FileRead& f, EventType::Type eventMask, bool bgTasks, bool allow
|
|||||||
, m_stream( nullptr )
|
, m_stream( nullptr )
|
||||||
, m_buffer( nullptr )
|
, m_buffer( nullptr )
|
||||||
, m_inconsistentSamples( false )
|
, m_inconsistentSamples( false )
|
||||||
, m_memoryLimit( -1 )
|
, m_memoryLimit( NO_WORKER_MEMORY_LIMIT )
|
||||||
, m_allowStringModification( allowStringModification )
|
, m_allowStringModification( allowStringModification )
|
||||||
{
|
{
|
||||||
auto loadStart = std::chrono::high_resolution_clock::now();
|
auto loadStart = std::chrono::high_resolution_clock::now();
|
||||||
|
|||||||
@ -52,6 +52,8 @@ namespace EventType
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr int64_t NO_WORKER_MEMORY_LIMIT = -1; // anything <0 would do
|
||||||
|
|
||||||
struct UnsupportedVersion : public std::exception
|
struct UnsupportedVersion : public std::exception
|
||||||
{
|
{
|
||||||
UnsupportedVersion( int version ) : version( version ) {}
|
UnsupportedVersion( int version ) : version( version ) {}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user