From f98c60dae914b11da16fe91152bf6657be8d3812 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 17 Apr 2026 17:39:44 +0000 Subject: [PATCH 01/33] Add basic Tracy memory tracking - Controllable via ENABLE_TELEMETRY_MEMORY_TRACKING, - Set to same value as CCP_TELEMETRY_ENABLED --- CCPMemory.cpp | 27 ++++++++++++++++----------- CcpTelemetry.cpp | 12 ++++++------ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/CCPMemory.cpp b/CCPMemory.cpp index e5bb53b..f266ce1 100644 --- a/CCPMemory.cpp +++ b/CCPMemory.cpp @@ -292,7 +292,7 @@ static inline void* CcpPlatformMalloc( size_t size ) #if ENABLE_TELEMETRY_MEMORY_TRACKING if ( p && CcpTelemetryIsConnected() ) { -// TracySecureAlloc( p, size ); + TracySecureAlloc( p, size ); } #endif return p; @@ -310,7 +310,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) #if ENABLE_TELEMETRY_MEMORY_TRACKING if ( p && CcpTelemetryIsConnected() ) { -// TracySecureAlloc( p, size ); + TracySecureAlloc( p, size ); } #endif return p; @@ -318,6 +318,12 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) static inline void CcpPlatformFree( void* p ) { +#if ENABLE_TELEMETRY_MEMORY_TRACKING + if ( p && CcpTelemetryIsConnected() ) + { + TracySecureFree( p ); + } +#endif UpdateCount( p, false ); HeapFree( s_heap, 0, p ); } @@ -340,7 +346,7 @@ static inline void* CcpPlatformMalloc( size_t size ) #if ENABLE_TELEMETRY_MEMORY_TRACKING if ( CcpTelemetryIsConnected() ) { -// TracySecureAlloc( p, realSize ); + TracySecureAlloc( p, realSize ); } #endif } @@ -365,7 +371,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) #if ENABLE_TELEMETRY_MEMORY_TRACKING if ( CcpTelemetryIsConnected() ) { -// TracySecureAlloc( p, realSize ); + TracySecureAlloc( p, realSize ); } #endif } @@ -376,6 +382,12 @@ static inline void CcpPlatformFree( void* p ) { #if defined(__ANDROID__) p = reinterpret_cast( p ) - 1; +#endif +#if ENABLE_TELEMETRY_MEMORY_TRACKING + if ( p && CcpTelemetryIsConnected() ) + { + TracySecureFree( p ); + } #endif s_memuse -= CCPMSize( p ); free( p ); @@ -542,13 +554,6 @@ void CCPFree( void* p ) { if( p ) { -#if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpTelemetryIsConnected() ) - { -// TracySecureFree( p ); - } -#endif - if( s_guardAllocations ) { CCPFreeWithGuard( p ); diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 5bdf40c..6f3d9b2 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -49,7 +49,7 @@ namespace { uint32_t s_telemetryTick = 0; - CcpTelemetryConfig s_config; + CcpTelemetryConfig s_telemetryConfig; MutexNameMap_t& GetMutexNameMap() { @@ -114,10 +114,10 @@ bool CcpStartTelemetry( const CcpTelemetryConfig& config ) return false; } - s_config = config; + s_telemetryConfig = config; s_telemetryTick = 1; CcpTelemetrySetActiveFiber( "" ); // to ensure that all our look-ups are correctly initialized -// CCP_LOG_CH( s_ch, "Starting profiler - %s - Root fiber is [Fiber %p]", s_config.applicationName.c_str(), t_activeFiber->c_str() ); +// CCP_LOG_CH( s_ch, "Starting profiler - %s - Root fiber is [Fiber %p]", s_telemetryConfig.applicationName.c_str(), t_activeFiber->c_str() ); s_profilerState.store( ProfilerState::StartRequested, std::memory_order_release ); return true; } @@ -145,7 +145,7 @@ void CcpTelemetryTick() if (TracyIsConnected) { CCP_LOG_CH( s_ch, "Telemetry server connected to Profiler" ); - TracySetProgramName( s_config.applicationName.c_str() ); + TracySetProgramName( s_telemetryConfig.applicationName.c_str() ); s_profilerState.store( ProfilerState::Started, std::memory_order_release ); s_profilerStartTime = std::chrono::steady_clock::now(); @@ -186,10 +186,10 @@ void CcpTelemetryTick() } } - if( s_config.captureDuration != std::chrono::milliseconds::zero() ) // Check if we have passed our timed sample time + if( s_telemetryConfig.captureDuration != std::chrono::milliseconds::zero() ) // Check if we have passed our timed sample time { auto timeSinceStart = std::chrono::steady_clock::now() - s_profilerStartTime; - if( timeSinceStart >= s_config.captureDuration ) + if( timeSinceStart >= s_telemetryConfig.captureDuration ) { CCP_LOG_CH( s_ch, "Finalizing timed profiler run" ); CcpStopTelemetry(); From f99f8b7d0b3d51ab4d9ffbbd9125308ed38f4aec Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 27 Apr 2026 09:31:30 +0000 Subject: [PATCH 02/33] Fix memory tracking in v0.11.1 Only rely on Tracy started/connected when sending memory free events --- CCPMemory.cpp | 4 ++-- vcpkg.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CCPMemory.cpp b/CCPMemory.cpp index f266ce1..d4cac7f 100644 --- a/CCPMemory.cpp +++ b/CCPMemory.cpp @@ -319,7 +319,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) static inline void CcpPlatformFree( void* p ) { #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpTelemetryIsConnected() ) + if ( p && TracyIsStarted && TracyIsConnected ) { TracySecureFree( p ); } @@ -384,7 +384,7 @@ static inline void CcpPlatformFree( void* p ) p = reinterpret_cast( p ) - 1; #endif #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpTelemetryIsConnected() ) + if ( p && TracyIsStarted && TracyIsConnected ) { TracySecureFree( p ); } diff --git a/vcpkg.json b/vcpkg.json index ae04e7f..1f80f6c 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,7 +2,7 @@ "dependencies": [ { "name": "tracy", - "version>=": "0.13.1", + "version>=": "0.11.1", "features": [ "fibers", "on-demand", From 06e4be0bd4e487b2ab60c0a8a6756a517eeecfcf Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:48:07 +0000 Subject: [PATCH 03/33] Control memory tracking with a switch --- CCPMemory.cpp | 12 ++++++------ CcpTelemetry.cpp | 11 +++++++++++ include/CcpTelemetry.h | 3 +++ 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/CCPMemory.cpp b/CCPMemory.cpp index d4cac7f..51cd50f 100644 --- a/CCPMemory.cpp +++ b/CCPMemory.cpp @@ -290,7 +290,7 @@ static inline void* CcpPlatformMalloc( size_t size ) void* p = HeapAlloc( s_heap, 0, size ); UpdateCount( size ); #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpTelemetryIsConnected() ) + if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && CcpTelemetryIsConnected() ) { TracySecureAlloc( p, size ); } @@ -308,7 +308,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) void* p = HeapAlloc( s_heap, HEAP_ZERO_MEMORY, bytes ); UpdateCount( bytes ); #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpTelemetryIsConnected() ) + if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && CcpTelemetryIsConnected() ) { TracySecureAlloc( p, size ); } @@ -319,7 +319,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) static inline void CcpPlatformFree( void* p ) { #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && TracyIsStarted && TracyIsConnected ) + if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) { TracySecureFree( p ); } @@ -345,7 +345,7 @@ static inline void* CcpPlatformMalloc( size_t size ) s_memuse += realSize; #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpTelemetryIsConnected() ) { + if ( CcpTelemetryGetConfig().trackMemoryAllocations && CcpTelemetryIsConnected() ) { TracySecureAlloc( p, realSize ); } #endif @@ -369,7 +369,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) s_memuse += realSize; #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpTelemetryIsConnected() ) + if ( CcpTelemetryGetConfig().trackMemoryAllocations && CcpTelemetryIsConnected() ) { TracySecureAlloc( p, realSize ); } @@ -384,7 +384,7 @@ static inline void CcpPlatformFree( void* p ) p = reinterpret_cast( p ) - 1; #endif #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && TracyIsStarted && TracyIsConnected ) + if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) { TracySecureFree( p ); } diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 6f3d9b2..db4be7f 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -229,6 +229,11 @@ uint32_t CcpTelemetryGetTickCount() return s_telemetryTick; } +const CcpTelemetryConfig& CcpTelemetryGetConfig() +{ + return s_telemetryConfig; +} + void CcpRegisterTelemetryEventHandler( CcpOnTelemetryEventHandler handler, void* userData ) { GetEventHandlers().push_back( std::make_pair( handler, userData ) ); @@ -442,6 +447,12 @@ uint32_t CcpTelemetryGetTickCount() return 0; } +const CcpTelemetryConfig& CcpTelemetryGetConfig() +{ + static CcpTelemetryConfig empty; + return empty; +} + void CcpRegisterTelemetryEventHandler( CcpOnTelemetryEventHandler handler, void* userData ) { } diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 0c73691..5dac280 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -44,6 +44,7 @@ struct CcpTelemetryConfig { std::string applicationName; std::chrono::milliseconds captureDuration{}; + bool trackMemoryAllocations{false}; }; [[deprecated( "Use `CcpStartTelemetry( const CcpTelemetryConfig& config ) instead" )]] CARBON_CORE_API bool CcpStartTelemetry( const char* server, int connectionType, uint32_t maxThreadCount ); @@ -52,6 +53,8 @@ CARBON_CORE_API void CcpStopTelemetry(); CARBON_CORE_API void CcpTelemetryTick(); CARBON_CORE_API uint32_t CcpTelemetryGetTickCount(); +const CcpTelemetryConfig& CcpTelemetryGetConfig(); + enum CcpTelemetryEvent { CCP_TELEMETRY_STARTED, From 2dd5320109c25247a8b0b6374b22d74d67f03efe Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 4 May 2026 14:02:18 +0000 Subject: [PATCH 04/33] Only do TracyIsStarted/Connected when tracking memory allocations --- CCPMemory.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CCPMemory.cpp b/CCPMemory.cpp index 51cd50f..956ac64 100644 --- a/CCPMemory.cpp +++ b/CCPMemory.cpp @@ -290,7 +290,7 @@ static inline void* CcpPlatformMalloc( size_t size ) void* p = HeapAlloc( s_heap, 0, size ); UpdateCount( size ); #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && CcpTelemetryIsConnected() ) + if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) { TracySecureAlloc( p, size ); } @@ -308,7 +308,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) void* p = HeapAlloc( s_heap, HEAP_ZERO_MEMORY, bytes ); UpdateCount( bytes ); #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && CcpTelemetryIsConnected() ) + if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) { TracySecureAlloc( p, size ); } @@ -345,7 +345,7 @@ static inline void* CcpPlatformMalloc( size_t size ) s_memuse += realSize; #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpTelemetryGetConfig().trackMemoryAllocations && CcpTelemetryIsConnected() ) { + if ( CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) { TracySecureAlloc( p, realSize ); } #endif @@ -369,7 +369,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) s_memuse += realSize; #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpTelemetryGetConfig().trackMemoryAllocations && CcpTelemetryIsConnected() ) + if ( CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) { TracySecureAlloc( p, realSize ); } From 52fed3637f3f285b700204db7972c9d02899af12 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Tue, 5 May 2026 17:07:25 +0000 Subject: [PATCH 05/33] Add CcpMemoryProfilingIsEnabled() --- CCPMemory.cpp | 12 ++++++------ CcpTelemetry.cpp | 21 ++++++++++----------- include/CcpTelemetry.h | 3 +-- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/CCPMemory.cpp b/CCPMemory.cpp index 956ac64..7496bd7 100644 --- a/CCPMemory.cpp +++ b/CCPMemory.cpp @@ -290,7 +290,7 @@ static inline void* CcpPlatformMalloc( size_t size ) void* p = HeapAlloc( s_heap, 0, size ); UpdateCount( size ); #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) + if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) { TracySecureAlloc( p, size ); } @@ -308,7 +308,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) void* p = HeapAlloc( s_heap, HEAP_ZERO_MEMORY, bytes ); UpdateCount( bytes ); #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) + if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) { TracySecureAlloc( p, size ); } @@ -319,7 +319,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) static inline void CcpPlatformFree( void* p ) { #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) + if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) { TracySecureFree( p ); } @@ -345,7 +345,7 @@ static inline void* CcpPlatformMalloc( size_t size ) s_memuse += realSize; #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) { + if ( CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) { TracySecureAlloc( p, realSize ); } #endif @@ -369,7 +369,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) s_memuse += realSize; #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) + if ( CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) { TracySecureAlloc( p, realSize ); } @@ -384,7 +384,7 @@ static inline void CcpPlatformFree( void* p ) p = reinterpret_cast( p ) - 1; #endif #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpTelemetryGetConfig().trackMemoryAllocations && TracyIsStarted && TracyIsConnected ) + if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) { TracySecureFree( p ); } diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index db4be7f..454563b 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -85,6 +85,11 @@ bool CcpTelemetryIsStarted() return s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Started; } +bool CcpMemoryProfilingIsEnabled() +{ + return s_telemetryConfig.trackMemoryAllocations; +} + void CcpRegisterMutex( class CcpMutex& m, const char* owner, const char* name ) { // Store the name for future Telemetry sessions, even if we're already connected. @@ -229,11 +234,6 @@ uint32_t CcpTelemetryGetTickCount() return s_telemetryTick; } -const CcpTelemetryConfig& CcpTelemetryGetConfig() -{ - return s_telemetryConfig; -} - void CcpRegisterTelemetryEventHandler( CcpOnTelemetryEventHandler handler, void* userData ) { GetEventHandlers().push_back( std::make_pair( handler, userData ) ); @@ -420,6 +420,11 @@ bool CcpTelemetryIsStarted() return false; } +bool CcpMemoryProfilingIsEnabled() +{ + return false; +} + void CcpRegisterThread( CcpThreadId_t threadId, const char* name ) { } @@ -447,12 +452,6 @@ uint32_t CcpTelemetryGetTickCount() return 0; } -const CcpTelemetryConfig& CcpTelemetryGetConfig() -{ - static CcpTelemetryConfig empty; - return empty; -} - void CcpRegisterTelemetryEventHandler( CcpOnTelemetryEventHandler handler, void* userData ) { } diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 5dac280..50146be 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -53,8 +53,6 @@ CARBON_CORE_API void CcpStopTelemetry(); CARBON_CORE_API void CcpTelemetryTick(); CARBON_CORE_API uint32_t CcpTelemetryGetTickCount(); -const CcpTelemetryConfig& CcpTelemetryGetConfig(); - enum CcpTelemetryEvent { CCP_TELEMETRY_STARTED, @@ -69,6 +67,7 @@ CARBON_CORE_API void CcpUnregisterTelemetryEventHandler( CcpOnTelemetryEventHand CARBON_CORE_API bool CcpTelemetryIsConnectionRequested(); CARBON_CORE_API bool CcpTelemetryIsConnected(); CARBON_CORE_API bool CcpTelemetryIsStarted(); +CARBON_CORE_API bool CcpMemoryProfilingIsEnabled(); CARBON_CORE_API void CcpTelemetrySetActiveFiber( const std::string& name ); CARBON_CORE_API const std::string& CcpTelemetryGetActiveFiber(); From 71cf0adee981dd9a60e099e479515b3626559030 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Wed, 6 May 2026 15:27:00 +0000 Subject: [PATCH 06/33] Add a TracyTestClient class for unit tests This unlocks the ability to do proper inspection of our interactions with Tracy. Nota bene: this class was created by Claude Code, and for tracy 0.11.1, so it may need some more TLC before it is fully usable. It might also be a breaking point when updating to newer tracy versions since it relies on Tracy's internal protocol. --- tests/CMakeLists.txt | 1 + tests/TracyTestClient.cpp | 331 ++++++++++++++++++++++++++++++++++++++ tests/TracyTestClient.h | 76 +++++++++ 3 files changed, 408 insertions(+) create mode 100644 tests/TracyTestClient.cpp create mode 100644 tests/TracyTestClient.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e6bc47a..12685c9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable(CcpCoreTest StringConversions.cpp TempFile.cpp CCPLog.cpp + TracyTestClient.cpp ) target_link_libraries(CcpCoreTest PRIVATE CcpCore GTest::gtest GTest::gtest_main) if(APPLE) diff --git a/tests/TracyTestClient.cpp b/tests/TracyTestClient.cpp new file mode 100644 index 0000000..e7a2a07 --- /dev/null +++ b/tests/TracyTestClient.cpp @@ -0,0 +1,331 @@ +// Copyright © 2025 CCP ehf. +#include "TracyTestClient.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +static constexpr int kReadTimeoutMs = 100; + +TracyTestClient::TracyTestClient() + : m_socket( new tracy::Socket() ) + , m_lz4Stream( tracy::LZ4_createStreamDecode() ) + , m_ringBuffer( new char[tracy::TargetFrameSize * 2] ) +{ +} + +TracyTestClient::~TracyTestClient() +{ + Disconnect(); + delete static_cast( m_socket ); + tracy::LZ4_freeStreamDecode( static_cast( m_lz4Stream ) ); + delete[] m_ringBuffer; +} + +bool TracyTestClient::Connect( const char* addr, uint16_t port, int timeoutMs ) +{ + auto& sock = *static_cast( m_socket ); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds( timeoutMs ); + + // Retry until we connect or time out, since the profiler's listen socket may + // not be ready immediately after TracyIsStarted becomes true. + while( std::chrono::steady_clock::now() < deadline ) + { + if( sock.ConnectBlocking( addr, port ) ) + break; + std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) ); + } + if( !sock.IsValid() ) + return false; + + // Send handshake shibboleth and protocol version. + sock.Send( tracy::HandshakeShibboleth, tracy::HandshakeShibbolethSize ); + uint32_t proto = tracy::ProtocolVersion; + sock.Send( &proto, sizeof( proto ) ); + + // Receive handshake status. + tracy::HandshakeStatus status; + if( !sock.ReadRaw( &status, sizeof( status ), 2000 ) || status != tracy::HandshakeWelcome ) + { + sock.Close(); + return false; + } + + // Receive the welcome message. + tracy::WelcomeMessage welcome; + if( !sock.ReadRaw( &welcome, sizeof( welcome ), 5000 ) ) + { + sock.Close(); + return false; + } + + // With TRACY_ON_DEMAND the profiler sends an extra OnDemandPayloadMessage. + tracy::OnDemandPayloadMessage onDemand; + if( !sock.ReadRaw( &onDemand, sizeof( onDemand ), 5000 ) ) + { + sock.Close(); + return false; + } + + // Reset the LZ4 streaming context for the new connection. + tracy::LZ4_setStreamDecode( static_cast( m_lz4Stream ), nullptr, 0 ); + m_bufferOffset = 0; + + m_connected.store( true, std::memory_order_release ); + m_shutdown.store( false, std::memory_order_relaxed ); + m_recvThread = std::thread( &TracyTestClient::RecvLoop, this ); + return true; +} + +void TracyTestClient::Disconnect() +{ + if( !m_connected.load( std::memory_order_acquire ) && !m_recvThread.joinable() ) + return; + + m_shutdown.store( true, std::memory_order_release ); + static_cast( m_socket )->Close(); + + if( m_recvThread.joinable() ) + m_recvThread.join(); + + m_connected.store( false, std::memory_order_release ); +} + +bool TracyTestClient::IsConnected() const +{ + return m_connected.load( std::memory_order_acquire ); +} + +std::vector TracyTestClient::GetZones() const +{ + std::lock_guard lock( m_dataMutex ); + return m_zones; +} + +std::vector TracyTestClient::GetFiberNames() const +{ + std::lock_guard lock( m_dataMutex ); + std::vector names; + names.reserve( m_fiberNames.size() ); + for( const auto& [ptr, name] : m_fiberNames ) + names.push_back( name ); + return names; +} + +// --------------------------------------------------------------------------- +// Private helpers +// --------------------------------------------------------------------------- + +void TracyTestClient::SendQueryLocked( uint8_t queryType, uint64_t ptr, uint32_t extra ) +{ + tracy::ServerQueryPacket pkt; + pkt.type = static_cast( queryType ); + pkt.ptr = ptr; + pkt.extra = extra; + std::lock_guard lock( m_sendMutex ); + static_cast( m_socket )->Send( &pkt, tracy::ServerQueryPacketSize ); +} + +// Receive loop: reads LZ4-compressed frames and decompresses them. +void TracyTestClient::RecvLoop() +{ + auto& sock = *static_cast( m_socket ); + auto* lz4 = static_cast( m_lz4Stream ); + std::unique_ptr lz4Buf( new char[tracy::LZ4Size] ); + + while( !m_shutdown.load( std::memory_order_relaxed ) ) + { + // Each LZ4 frame is prefixed by its compressed size. + tracy::lz4sz_t compressedSz = 0; + if( !sock.ReadRaw( &compressedSz, sizeof( compressedSz ), kReadTimeoutMs ) ) { + continue; + } + + if( compressedSz > static_cast( tracy::LZ4Size ) ) { + fprintf( stderr, "Corrupt frame: %zu\n", static_cast(compressedSz) ); fflush(stderr); + break; // corrupt frame + } + + if( !sock.ReadRaw( lz4Buf.get(), static_cast( compressedSz ), kReadTimeoutMs ) ) { + fprintf(stderr, "ReadRaw failed to read compressed data\n"); fflush(stderr); + break; + } + + // Decompress into the ring buffer using the streaming context so that + // the previous block acts as the LZ4 dictionary. + char* dst = m_ringBuffer + m_bufferOffset; + const int decompressedSz = tracy::LZ4_decompress_safe_continue( + lz4, lz4Buf.get(), dst, static_cast( compressedSz ), tracy::TargetFrameSize ); + if( decompressedSz < 0 ) + break; // decompression error + + ProcessDecompressedData( dst, decompressedSz ); + + m_bufferOffset += decompressedSz; + if( m_bufferOffset > tracy::TargetFrameSize * 2 ) + m_bufferOffset = 0; + } + + m_connected.store( false, std::memory_order_release ); +} + +// Parse the decompressed byte stream and update internal state. +void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) +{ + const char* ptr = data; + const char* const end = data + sz; + + while( ptr < end ) + { + const auto* item = reinterpret_cast( ptr ); + const uint8_t idx = item->hdr.idx; + + if( idx >= static_cast( tracy::QueueType::StringData ) ) + { + // String transfer item: fixed header + QueueStringTransfer, followed by + // a length-prefixed string payload. + if( ptr + sizeof( tracy::QueueHeader ) + sizeof( tracy::QueueStringTransfer ) > end ) + break; + const uint64_t strPtr = item->stringTransfer.ptr; + ptr += sizeof( tracy::QueueHeader ) + sizeof( tracy::QueueStringTransfer ); + + if( item->hdr.type == tracy::QueueType::FrameImageData || + item->hdr.type == tracy::QueueType::SymbolCode || + item->hdr.type == tracy::QueueType::SourceCode ) + { + // Large binary payload with uint32_t length prefix. + if( ptr + sizeof( uint32_t ) > end ) break; + uint32_t strSz = 0; + std::memcpy( &strSz, ptr, sizeof( strSz ) ); + ptr += sizeof( strSz ); + if( ptr + strSz > end ) break; + ptr += strSz; + } + else + { + // Normal string payload with uint16_t length prefix. + if( ptr + sizeof( uint16_t ) > end ) break; + uint16_t strSz = 0; + std::memcpy( &strSz, ptr, sizeof( strSz ) ); + ptr += sizeof( strSz ); + if( ptr + strSz > end ) break; + + switch( item->hdr.type ) + { + case tracy::QueueType::SourceLocationPayload: + { + // The profiler sends this immediately before ZoneBeginAllocSrcLoc. + // Format: [uint32_t color][uint32_t line][function\0][source\0][name] + if( strSz >= 9 ) + { + const char* p = ptr; + p += 4; // skip color + uint32_t line = 0; + std::memcpy( &line, p, 4 ); + p += 4; + const char* function = p; + p += std::strlen( function ) + 1; + const char* source = p; + p += std::strlen( source ) + 1; + const size_t nameLen = static_cast( strSz ) - static_cast( p - ptr ); + + std::lock_guard lock( m_dataMutex ); + m_pendingZone = {}; + m_pendingZone.function = function; + m_pendingZone.source = source; + m_pendingZone.line = line; + if( nameLen > 0 ) + m_pendingZone.name = std::string( p, nameLen ); + m_hasPendingZone = true; + } + break; + } + case tracy::QueueType::FiberName: + { + std::string name( ptr, strSz ); + std::lock_guard lock( m_dataMutex ); + m_fiberNames[strPtr] = std::move( name ); + break; + } + default: + break; + } + + ptr += strSz; + } + } + else + { + // Fixed-size item (or SingleStringData / SecondStringData special cases). + switch( item->hdr.type ) + { + case tracy::QueueType::SingleStringData: + case tracy::QueueType::SecondStringData: + { + ptr += sizeof( tracy::QueueHeader ); + if( ptr + sizeof( uint16_t ) > end ) return; + uint16_t strSz = 0; + std::memcpy( &strSz, ptr, sizeof( strSz ) ); + ptr += sizeof( strSz ); + if( ptr + strSz > end ) return; + ptr += strSz; + break; + } + default: + { + const size_t itemSz = tracy::QueueDataSize[idx]; + if( ptr + itemSz > end ) return; + + switch( item->hdr.type ) + { + case tracy::QueueType::ZoneBeginAllocSrcLoc: + case tracy::QueueType::ZoneBeginAllocSrcLocCallstack: + { + m_zoneBeginCount.fetch_add( 1, std::memory_order_relaxed ); + std::lock_guard lock( m_dataMutex ); + if( m_hasPendingZone ) + { + m_zones.push_back( m_pendingZone ); + m_hasPendingZone = false; + } + break; + } + case tracy::QueueType::ZoneBegin: + case tracy::QueueType::ZoneBeginCallstack: + m_zoneBeginCount.fetch_add( 1, std::memory_order_relaxed ); + break; + + case tracy::QueueType::ZoneEnd: + m_zoneEndCount.fetch_add( 1, std::memory_order_relaxed ); + break; + + case tracy::QueueType::FiberEnter: + { + // Query the fiber name if we haven't already. + const uint64_t fiberPtr = item->fiberEnter.fiber; + std::lock_guard lock( m_dataMutex ); + if( m_queriedFibers.insert( fiberPtr ).second ) + { + SendQueryLocked( + static_cast( tracy::ServerQueryFiberName ), fiberPtr ); + } + break; + } + + default: + break; + } + + ptr += itemSz; + break; + } + } + } + } +} diff --git a/tests/TracyTestClient.h b/tests/TracyTestClient.h new file mode 100644 index 0000000..323afe6 --- /dev/null +++ b/tests/TracyTestClient.h @@ -0,0 +1,76 @@ +// Copyright © 2025 CCP ehf. +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// A minimal Tracy profiler client for use in unit tests. +// Connects to the Tracy profiler embedded in the test executable, +// receives and parses the event stream, and exposes the collected +// data so tests can make assertions about profiler activity. +class TracyTestClient +{ +public: + struct ZoneInfo + { + std::string name; + std::string function; + std::string source; + uint32_t line = 0; + }; + + TracyTestClient(); + ~TracyTestClient(); + + // Try to connect to the Tracy profiler at addr:port. + // Retries until timeoutMs elapses. Returns true on success. + bool Connect( const char* addr = "127.0.0.1", uint16_t port = 8086, int timeoutMs = 5000 ); + void Disconnect(); + bool IsConnected() const; + + int GetZoneBeginCount() const { return m_zoneBeginCount.load( std::memory_order_relaxed ); } + int GetZoneEndCount() const { return m_zoneEndCount.load( std::memory_order_relaxed ); } + + std::vector GetZones() const; + std::vector GetFiberNames() const; + + TracyTestClient( const TracyTestClient& ) = delete; + TracyTestClient& operator=( const TracyTestClient& ) = delete; + +private: + void RecvLoop(); + void ProcessDecompressedData( const char* data, int sz ); + void SendQueryLocked( uint8_t queryType, uint64_t ptr, uint32_t extra = 0 ); + + // Opaque handles to Tracy types, allocated on heap to keep Tracy headers out of this header. + void* m_socket = nullptr; // tracy::Socket* + void* m_lz4Stream = nullptr; // LZ4_streamDecode_t* + + // Ring buffer matching Tracy's decompression scheme: + // must be 2 × TargetFrameSize (= 2 × 256 KiB) to serve as LZ4 dictionary. + char* m_ringBuffer = nullptr; + int m_bufferOffset = 0; + + std::thread m_recvThread; + std::atomic m_connected{ false }; + std::atomic m_shutdown{ false }; + std::atomic m_zoneBeginCount{ 0 }; + std::atomic m_zoneEndCount{ 0 }; + + mutable std::mutex m_dataMutex; + std::mutex m_sendMutex; + + // Source location received from a SourceLocationPayload event, + // to be consumed by the following ZoneBeginAllocSrcLoc event. + ZoneInfo m_pendingZone; + bool m_hasPendingZone = false; + + std::vector m_zones; + std::unordered_map m_fiberNames; // fiber ptr → name + std::unordered_set m_queriedFibers; // ptrs already queried +}; From c4a08374510ff29e97dc7a63264019c1e23bcac3 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Wed, 6 May 2026 15:29:06 +0000 Subject: [PATCH 07/33] Start and stop TracyTestClient as part of the CcpTelemetryTest fixture So that tests can use it. Also provides a `TickTelemetry` wrapper around `CcpTickTelemetry` to deal with the synchronization between the test thread and the TracyTestClient thread. --- tests/CcpTelemetry.cpp | 49 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 91ffbf4..6a6d929 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -2,32 +2,67 @@ #include +#include + #include +#include "TracyTestClient.h" + class CcpTelemetryTest : public ::testing::Test { protected: CcpTelemetryTest() = default; ~CcpTelemetryTest() override = default; - void SetUp() override { + void SetUp() override + { CcpTelemetryConfig conf{ "Telemetry Tests" }; EXPECT_EQ( conf.captureDuration, std::chrono::milliseconds::zero() ); CcpStartTelemetry( conf ); - while ( !TracyIsStarted ) + + // Tick until the profiler's listen socket is up. + while( !TracyIsStarted ) { - CcpTelemetryTick(); - std::this_thread::yield(); + TickTelemetry(); + } + + // Connect on a background thread so this thread can keep ticking Tracy. + // The handshake requires both sides to run concurrently: Tracy's worker + // sends data and may block on Send() until the client reads it. + auto connectFuture = std::async( std::launch::async, [this] { + return m_tracyClient.Connect(); + } ); + + // Tick until CcpTelemetry recognises the connection and enters Started state. + while( !CcpTelemetryIsConnected() ) + { + TickTelemetry(); } + + ASSERT_TRUE( connectFuture.get() ) << "Could not connect to Tracy profiler"; } - void TearDown() override { + void TearDown() override + { + m_tracyClient.Disconnect(); CcpStopTelemetry(); } + void TickTelemetry( std::chrono::milliseconds duration = std::chrono::milliseconds( 500 ) ) + { + const auto deadline = std::chrono::steady_clock::now() + duration; + while( std::chrono::steady_clock::now() < deadline ) + { + CcpTelemetryTick(); + std::this_thread::sleep_for( std::chrono::milliseconds( 5 ) ); + } + } + const std::string expectedNoFiber; - const std::string expectedFiberName{"TestFiber"}; - const std::string expectedFiberName2{"TestFiber"}; + const std::string expectedFiberName{ "TestFiber" }; + const std::string expectedFiberName2{ "TestFiber" }; + + TracyTestClient m_tracyClient; }; TEST_F( CcpTelemetryTest, TestFiberSwitching ) From 896d5cd61c6e15f40bf9e124c298205dd0612106 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Wed, 6 May 2026 15:47:56 +0000 Subject: [PATCH 08/33] Update TracyTestClient to track zones as stacks per fiber and thread Because that is how tracy works underneath the hood as well. Change made by claude code. --- tests/TracyTestClient.cpp | 69 +++++++++++++++++++++++++++++++++++---- tests/TracyTestClient.h | 23 +++++++++++-- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/tests/TracyTestClient.cpp b/tests/TracyTestClient.cpp index e7a2a07..2ba6ab0 100644 --- a/tests/TracyTestClient.cpp +++ b/tests/TracyTestClient.cpp @@ -105,7 +105,36 @@ bool TracyTestClient::IsConnected() const std::vector TracyTestClient::GetZones() const { std::lock_guard lock( m_dataMutex ); - return m_zones; + std::vector result; + for( const auto& [tid, stack] : m_threadZoneStacks ) + result.insert( result.end(), stack.begin(), stack.end() ); + for( const auto& [fptr, stack] : m_fiberZoneStacks ) + result.insert( result.end(), stack.begin(), stack.end() ); + return result; +} + +TracyTestClient::ZoneStack TracyTestClient::GetZonesForThread( uint32_t threadId ) const +{ + std::lock_guard lock( m_dataMutex ); + auto it = m_threadZoneStacks.find( threadId ); + if( it == m_threadZoneStacks.end() ) + return {}; + return it->second; +} + +TracyTestClient::ZoneStack TracyTestClient::GetZonesForFiber( const std::string& fiberName ) const +{ + std::lock_guard lock( m_dataMutex ); + for( const auto& [ptr, name] : m_fiberNames ) + { + if( name == fiberName ) + { + auto it = m_fiberZoneStacks.find( ptr ); + if( it != m_fiberZoneStacks.end() ) + return it->second; + } + } + return {}; } std::vector TracyTestClient::GetFiberNames() const @@ -175,6 +204,14 @@ void TracyTestClient::RecvLoop() m_connected.store( false, std::memory_order_release ); } +TracyTestClient::ZoneStack& TracyTestClient::CurrentStack( uint32_t thread ) +{ + auto fiberIt = m_threadCurrentFiber.find( thread ); + if( fiberIt != m_threadCurrentFiber.end() && fiberIt->second != 0 ) + return m_fiberZoneStacks[fiberIt->second]; + return m_threadZoneStacks[thread]; +} + // Parse the decompressed byte stream and update internal state. void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) { @@ -284,14 +321,19 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) switch( item->hdr.type ) { + case tracy::QueueType::ThreadContext: + m_currentThread = item->threadCtx.thread; + break; + case tracy::QueueType::ZoneBeginAllocSrcLoc: case tracy::QueueType::ZoneBeginAllocSrcLocCallstack: { m_zoneBeginCount.fetch_add( 1, std::memory_order_relaxed ); + const uint32_t thread = m_currentThread; std::lock_guard lock( m_dataMutex ); if( m_hasPendingZone ) { - m_zones.push_back( m_pendingZone ); + CurrentStack( thread ).push_back( m_pendingZone ); m_hasPendingZone = false; } break; @@ -302,19 +344,32 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) break; case tracy::QueueType::ZoneEnd: + { m_zoneEndCount.fetch_add( 1, std::memory_order_relaxed ); + const uint32_t thread = m_currentThread; + std::lock_guard lock( m_dataMutex ); + auto& stack = CurrentStack( thread ); + if( !stack.empty() ) + stack.pop_back(); break; + } case tracy::QueueType::FiberEnter: { - // Query the fiber name if we haven't already. const uint64_t fiberPtr = item->fiberEnter.fiber; + const uint32_t thread = item->fiberEnter.thread; std::lock_guard lock( m_dataMutex ); + m_threadCurrentFiber[thread] = fiberPtr; if( m_queriedFibers.insert( fiberPtr ).second ) - { - SendQueryLocked( - static_cast( tracy::ServerQueryFiberName ), fiberPtr ); - } + SendQueryLocked( static_cast( tracy::ServerQueryFiberName ), fiberPtr ); + break; + } + + case tracy::QueueType::FiberLeave: + { + const uint32_t thread = item->fiberLeave.thread; + std::lock_guard lock( m_dataMutex ); + m_threadCurrentFiber[thread] = 0; break; } diff --git a/tests/TracyTestClient.h b/tests/TracyTestClient.h index 323afe6..410b378 100644 --- a/tests/TracyTestClient.h +++ b/tests/TracyTestClient.h @@ -24,6 +24,8 @@ class TracyTestClient uint32_t line = 0; }; + using ZoneStack = std::vector; + TracyTestClient(); ~TracyTestClient(); @@ -36,7 +38,13 @@ class TracyTestClient int GetZoneBeginCount() const { return m_zoneBeginCount.load( std::memory_order_relaxed ); } int GetZoneEndCount() const { return m_zoneEndCount.load( std::memory_order_relaxed ); } + // Returns all currently open zones across all threads and fibers (flattened). std::vector GetZones() const; + // Returns the zone stack currently open for the given thread (not including fiber zones). + ZoneStack GetZonesForThread( uint32_t threadId ) const; + // Returns the zone stack currently open for the named fiber. + ZoneStack GetZonesForFiber( const std::string& fiberName ) const; + std::vector GetFiberNames() const; TracyTestClient( const TracyTestClient& ) = delete; @@ -47,6 +55,10 @@ class TracyTestClient void ProcessDecompressedData( const char* data, int sz ); void SendQueryLocked( uint8_t queryType, uint64_t ptr, uint32_t extra = 0 ); + // Returns a reference to the zone stack for the current thread/fiber context. + // Must be called with m_dataMutex held. + ZoneStack& CurrentStack( uint32_t thread ); + // Opaque handles to Tracy types, allocated on heap to keep Tracy headers out of this header. void* m_socket = nullptr; // tracy::Socket* void* m_lz4Stream = nullptr; // LZ4_streamDecode_t* @@ -62,6 +74,9 @@ class TracyTestClient std::atomic m_zoneBeginCount{ 0 }; std::atomic m_zoneEndCount{ 0 }; + // Current thread established by ThreadContext events (recv thread only, no mutex needed). + uint32_t m_currentThread = 0; + mutable std::mutex m_dataMutex; std::mutex m_sendMutex; @@ -70,7 +85,9 @@ class TracyTestClient ZoneInfo m_pendingZone; bool m_hasPendingZone = false; - std::vector m_zones; - std::unordered_map m_fiberNames; // fiber ptr → name - std::unordered_set m_queriedFibers; // ptrs already queried + std::unordered_map m_threadCurrentFiber; // thread id → active fiber ptr (0 = none) + std::unordered_map m_threadZoneStacks; // thread id → zone stack + std::unordered_map m_fiberZoneStacks; // fiber ptr → zone stack + std::unordered_map m_fiberNames; // fiber ptr → name + std::unordered_set m_queriedFibers; // ptrs already queried }; From 63fb470be9068ea274d5d32cd2c2b7ff41c4851d Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Wed, 6 May 2026 15:54:22 +0000 Subject: [PATCH 09/33] Add a simple test for entering / leaving a telemetry zone --- tests/CcpTelemetry.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 6a6d929..269a305 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -6,6 +6,27 @@ #include +// How can we test telemetry-related functionality to ensure our bookkeeping +// there is sane? +// The problem is that such tests need `ProfilerState::Started` in order to +// create a valid zone context for testing. This, in turn, needs +// `TracyIsConnected` to be true. +// +// A first thought may be to simply redefine the macro to always return true. +// However, this is not possible because it is set inside the Tracy header. +// +// The next idea, then, would be to mock the `Profiler` class. However, this +// also is not possible because the `Profiler` class is not virtual. +// +// This leads to the next idea of choosing the concrete `Profiler` class type +// based on a template parameter. This is not possible either because the macros +// exposed by Tracy would not honor any such template parameter. +// +// With all this in mind, there is another aspect to consider: +// If we wanted to inspect more of the functionality, then we almost certainly +// want to provide a test implementation of the tracy network protocol. Fortunately, +// tracy itself already provides many of the building blocks for this. So this +// includes the AI-written, but human-reviewed test client. #include "TracyTestClient.h" class CcpTelemetryTest : public ::testing::Test @@ -86,3 +107,30 @@ TEST_F( CcpTelemetryTest, RemovingActiveFiberClearsIt ) CcpTelemetryRemoveFiber( expectedFiberName ); EXPECT_EQ( CcpTelemetryGetActiveFiber(), expectedNoFiber ); } + +TEST_F( CcpTelemetryTest, SimpleZoneTest ) +{ + static int key = 4711; + const std::string zoneName{ "TestZone" }; + EXPECT_TRUE( CcpTelemetryIsConnected() ); + CcpTelemetryEnterZone( &key, zoneName.c_str(), __FILE__, __LINE__ ); + + // Tracy's worker sleeps up to 10 ms between queue flushes, so give it + // time to process and send the zone event before asserting. + TickTelemetry(); + + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); + auto tracyZones = m_tracyClient.GetZones(); + // CcpTelemetryEnterZone passes the zone name as the Tracy "function" field + // (via the 6-param ___tracy_alloc_srcloc), so match against both fields. + auto pred = [&zoneName]( const TracyTestClient::ZoneInfo& elem ) -> bool { + return elem.function == zoneName; + }; + EXPECT_NE( tracyZones.end(), std::find_if( tracyZones.begin(), tracyZones.end(), pred ) ); + + CcpTelemetryLeaveZone( &key ); + TickTelemetry(); + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); + tracyZones = m_tracyClient.GetZones(); + EXPECT_EQ( tracyZones.end(), std::find_if( tracyZones.begin(), tracyZones.end(), pred ) ); +} From 56874edda0ba165a50cf4244a7bc79e2107b7e06 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Wed, 6 May 2026 16:04:37 +0000 Subject: [PATCH 10/33] Fix segmentation fault in `SimpleZoneTest` Interesting one! `SimpleZoneTest` crashes when entering the zone and accessing `t_taskletZoneStore`. This happens because the default value for `t_activeFiber` points at the first element in the `fiberNameStore`. Now, for whatever reason, when starting the telemetry integration set the unnamed root fiber name to trigger initialization of the corresponding zone stack, this does return the same iterator as `t_activeFiber` already contains. As a consequence, the initialization performs an early out, and does not create a relevant TaskletZoneStore. And without such a store, entering a zone clearly accesses invalid memory, causing the segmentation fault. --- CcpTelemetry.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index db4be7f..e82c6cc 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -22,7 +22,7 @@ std::atomic s_profilerState{ProfilerState::Stopped}; FiberNameStore s_fiberNameStore; // Persisted fiber name string store, including the empty "root" fiber name -thread_local FiberNameStore::const_iterator t_activeFiber{ s_fiberNameStore.begin() }; // default to having no fiber +thread_local FiberNameStore::const_iterator t_activeFiber{ s_fiberNameStore.end() }; // default to having no fiber template<> struct std::less @@ -35,7 +35,7 @@ struct std::less typedef std::map> TaskletZoneStore; thread_local TaskletZoneStore t_taskletZoneStore; // Per-thread record of zones instrumented from python -thread_local TaskletZoneStore::iterator t_activeTaskletZoneStore{ t_taskletZoneStore.begin() }; +thread_local TaskletZoneStore::iterator t_activeTaskletZoneStore{ t_taskletZoneStore.end() }; thread_local std::set t_manuallyTrackedZones; // Keep track of zones created through `CcpTelemetryEnterZone` to ensure that we only pop off the zone store's stack when leaving a manually created zone constexpr std::chrono::milliseconds s_cleanupDelay{5000}; From dfedcc74ac71bebbcf6bc668d504ec426ac2e9c0 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Thu, 7 May 2026 11:43:47 +0000 Subject: [PATCH 11/33] Fix a zone leak in CcpTelemetryLeaveZone The internal book-keeping allows for multiple tracy zones sharing the same key (e.g. the `TaskletZoneStore` is a `stack`). However, due to the on-demand profiling book-keeping, there is an additional `set` that tracks whether a zone was manually created with a given key. Since that key is not required to be unique, it may only be removed from the manually tracked set once there are no more zones on that zone store's stack. --- CcpTelemetry.cpp | 4 +++- tests/CcpTelemetry.cpp | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index e82c6cc..ece64cc 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -388,7 +388,9 @@ void CcpTelemetryLeaveZone( void* key ) { t_activeTaskletZoneStore->second.pop(); } - t_manuallyTrackedZones.erase( key ); + if ( t_activeTaskletZoneStore->second.empty() ) { + t_manuallyTrackedZones.erase( key ); + } } } diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 269a305..98e509a 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -134,3 +134,21 @@ TEST_F( CcpTelemetryTest, SimpleZoneTest ) tracyZones = m_tracyClient.GetZones(); EXPECT_EQ( tracyZones.end(), std::find_if( tracyZones.begin(), tracyZones.end(), pred ) ); } + +TEST_F( CcpTelemetryTest, StackedZones ) +{ + // A stacked zone is a zone that has the same key as a previously created zone. + static int key = 4711; + CcpTelemetryEnterZone( &key, "TestZone", __FILE__, __LINE__ ); + CcpTelemetryEnterZone( &key, "TestZone2", __FILE__, __LINE__ ); + TickTelemetry(); + auto tracyZones = m_tracyClient.GetZones(); + EXPECT_EQ( 2, tracyZones.size() ); + CcpTelemetryLeaveZone( &key ); + TickTelemetry(); + tracyZones = m_tracyClient.GetZones(); + EXPECT_EQ( 1, tracyZones.size() ); + CcpTelemetryLeaveZone( &key ); + TickTelemetry(); + EXPECT_TRUE( m_tracyClient.GetZones().empty() ); +} From b9c5317c909cae3ae72df99d2d4d6a9f90181b5b Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 7 May 2026 16:01:21 +0000 Subject: [PATCH 12/33] Fix TracyTestClient when running on Windows Our TracyTestClient needs access to (some of) the source files of Tracy in order to build correctly. --- tests/CMakeLists.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 12685c9..1233e6e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -21,6 +21,23 @@ add_executable(CcpCoreTest CCPLog.cpp TracyTestClient.cpp ) + + +# Find Tracy's source directory +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + find_path(TRACY_COMMON_DIR common/TracySocket.cpp + # Use wildcard for the Tracy version, instead of explicitly setting it to a vX.Y.Z-hash.clean. + # This however is brittle, as the buildtrees folder may have multiple versions available. + PATHS "${CMAKE_SOURCE_DIR}/vendor/github.com/microsoft/vcpkg/buildtrees/tracy/src/*/public" + ) + + target_sources(CcpCoreTest PRIVATE + ${TRACY_COMMON_DIR}/common/TracySocket.cpp + ${TRACY_COMMON_DIR}/common/tracy_lz4.cpp + ) +endif () + + target_link_libraries(CcpCoreTest PRIVATE CcpCore GTest::gtest GTest::gtest_main) if(APPLE) target_link_libraries(CcpCoreTest PRIVATE "-framework CoreFoundation") From 1d06343e951892a4500272e402e6e1d027fd5701 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Thu, 7 May 2026 17:22:06 +0000 Subject: [PATCH 13/33] Update to tracy 0.13.1 --- CMakeLists.txt | 8 ++++++++ vcpkg.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 68a9fe2..281d8ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,14 @@ if(WITH_TELEMETRY) find_package(Tracy CONFIG REQUIRED) target_compile_definitions(CcpCore PUBLIC CCP_TELEMETRY_ENABLED=1 TRACY_IMPORTS TRACY_ENABLE TRACY_FIBERS TRACY_ON_DEMAND TRACY_DELAYED_INIT TRACY_MANUAL_LIFETIME) target_link_libraries(CcpCore PUBLIC Tracy::TracyClient) + # Tracy's vcpkg package is always built as Release (with NDEBUG). CcpTelemetry.cpp + # must also be compiled with NDEBUG to match Tracy's struct layout: in 0.13.1 the + # Profiler class has a debug-only member (std::atomic_bool m_inUse, #ifndef NDEBUG) + # that shifts m_programNameLock and other trailing fields by 8 bytes, causing + # SetProgramName() — inlined from TracyProfiler.hpp — to lock the wrong memory + # address and fail with EINVAL. CCP_ASSERT_ENABLED is set explicitly via CMake so + # CCP_ASSERT macros are unaffected by this NDEBUG definition. + set_source_files_properties(CcpTelemetry.cpp PROPERTIES COMPILE_DEFINITIONS NDEBUG) else() target_compile_definitions(CcpCore PUBLIC CCP_TELEMETRY_ENABLED=0) endif() diff --git a/vcpkg.json b/vcpkg.json index 1f80f6c..ae04e7f 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -2,7 +2,7 @@ "dependencies": [ { "name": "tracy", - "version>=": "0.11.1", + "version>=": "0.13.1", "features": [ "fibers", "on-demand", From 07b7397cbb3e825b40017952295657c910e0f783 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Thu, 7 May 2026 17:49:26 +0000 Subject: [PATCH 14/33] Rewrite `TracyTestClient` to no longer rely on Tracy internal sources Likely still does not work on Windows, but at least it's not because of relying on Tracy internals. --- tests/CMakeLists.txt | 3 +- tests/TracyTestClient.cpp | 447 +++++++++++++++++++++++++++++++------- vcpkg.json | 4 + 3 files changed, 376 insertions(+), 78 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 12685c9..bad2997 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,6 +3,7 @@ enable_testing() find_package(GTest CONFIG REQUIRED) +find_package(lz4 CONFIG REQUIRED) add_executable(CcpCoreTest CcpAtomic.cpp CCPCallstack.cpp @@ -21,7 +22,7 @@ add_executable(CcpCoreTest CCPLog.cpp TracyTestClient.cpp ) -target_link_libraries(CcpCoreTest PRIVATE CcpCore GTest::gtest GTest::gtest_main) +target_link_libraries(CcpCoreTest PRIVATE CcpCore GTest::gtest GTest::gtest_main lz4::lz4) if(APPLE) target_link_libraries(CcpCoreTest PRIVATE "-framework CoreFoundation") endif() diff --git a/tests/TracyTestClient.cpp b/tests/TracyTestClient.cpp index 2ba6ab0..c96c8fb 100644 --- a/tests/TracyTestClient.cpp +++ b/tests/TracyTestClient.cpp @@ -1,36 +1,338 @@ // Copyright © 2025 CCP ehf. #include "TracyTestClient.h" +#include #include #include #include +#include +#include +#include +#include #include - -#include -#include -#include -#include +#include static constexpr int kReadTimeoutMs = 100; +// --------------------------------------------------------------------------- +// POSIX TCP socket +// --------------------------------------------------------------------------- + +namespace { + +struct TcpSocket +{ + int fd = -1; + + bool ConnectBlocking( const char* addr, uint16_t port ) + { + fd = ::socket( AF_INET, SOCK_STREAM, 0 ); + if( fd < 0 ) return false; + struct sockaddr_in sa{}; + sa.sin_family = AF_INET; + sa.sin_port = htons( port ); + if( ::inet_pton( AF_INET, addr, &sa.sin_addr ) != 1 ) + { + ::close( fd ); fd = -1; return false; + } + if( ::connect( fd, reinterpret_cast( &sa ), sizeof( sa ) ) != 0 ) + { + ::close( fd ); fd = -1; return false; + } + return true; + } + + void Send( const void* buf, int len ) + { + ::send( fd, buf, static_cast( len ), 0 ); + } + + bool ReadRaw( void* buf, int len, int timeoutMs ) + { + auto* p = static_cast( buf ); + while( len > 0 ) + { + fd_set fds; + FD_ZERO( &fds ); + FD_SET( fd, &fds ); + struct timeval tv{}; + tv.tv_sec = timeoutMs / 1000; + tv.tv_usec = ( timeoutMs % 1000 ) * 1000; + if( ::select( fd + 1, &fds, nullptr, nullptr, &tv ) <= 0 ) return false; + const int n = static_cast( ::recv( fd, p, static_cast( len ), 0 ) ); + if( n <= 0 ) return false; + p += n; + len -= n; + } + return true; + } + + void Close() + { + if( fd >= 0 ) { ::close( fd ); fd = -1; } + } + + bool IsValid() const { return fd >= 0; } +}; + +// --------------------------------------------------------------------------- +// Tracy wire-protocol constants and types (Tracy 0.13.1) +// Derived from TracyProtocol.hpp / TracyQueue.hpp — no Tracy headers needed. +// --------------------------------------------------------------------------- + +static constexpr uint32_t kProtocolVersion = 76; +static constexpr uint32_t kTargetFrameSize = 256 * 1024; +static constexpr unsigned kLZ4Size = kTargetFrameSize + ( kTargetFrameSize / 255 ) + 16; +static constexpr int kHandshakeShibbolethSize = 8; +static const char kHandshakeShibboleth[kHandshakeShibbolethSize] = { 'T', 'r', 'a', 'c', 'y', 'P', 'r', 'f' }; + +enum HandshakeStatus : uint8_t +{ + HandshakePending = 0, + HandshakeWelcome = 1, + HandshakeProtocolMismatch = 2, + HandshakeNotAvailable = 3, + HandshakeDropped = 4, +}; + +#pragma pack( push, 1 ) + +struct WelcomeMessage +{ + double timerMul; + int64_t initBegin; + int64_t initEnd; + uint64_t resolution; + uint64_t epoch; + uint64_t exectime; + uint64_t pid; + int64_t samplingPeriod; + uint8_t flags; + uint8_t cpuArch; + char cpuManufacturer[12]; + uint32_t cpuId; + char programName[64]; + char hostInfo[1024]; +}; + +struct OnDemandPayloadMessage +{ + uint64_t frames; + uint64_t currentTime; +}; + +// Only the server-query value we actually emit. +static constexpr uint8_t kServerQueryFiberName = 7; + +struct ServerQueryPacket +{ + uint8_t type; + uint64_t ptr; + uint32_t extra; +}; + +// Minimal queue-item structs — only fields accessed in ProcessDecompressedData. +struct QueueHeader { uint8_t idx; }; +struct QueueThreadContext { uint32_t thread; }; + +struct QueueFiberEnter +{ + int64_t time; + uint64_t fiber; + uint32_t thread; + int32_t groupHint; +}; + +struct QueueFiberLeave +{ + int64_t time; + uint32_t thread; +}; + +struct QueueStringTransfer { uint64_t ptr; }; + +// QueueItem matches Tracy's 32-byte packed union layout. +struct QueueItem +{ + QueueHeader hdr; + union { + QueueThreadContext threadCtx; + QueueFiberEnter fiberEnter; + QueueFiberLeave fiberLeave; + QueueStringTransfer stringTransfer; + char _pad[31]; + }; +}; +static_assert( sizeof( QueueItem ) == 32, "QueueItem size mismatch" ); + +#pragma pack( pop ) + +// QueueType index constants (QueueType enum : uint8_t from TracyQueue.hpp). +static constexpr uint8_t kQueueZoneBeginAllocSrcLoc = 7; +static constexpr uint8_t kQueueZoneBeginAllocSrcLocCallstack = 8; +static constexpr uint8_t kQueueZoneBegin = 15; +static constexpr uint8_t kQueueZoneBeginCallstack = 16; +static constexpr uint8_t kQueueZoneEnd = 17; +static constexpr uint8_t kQueueFiberEnter = 58; +static constexpr uint8_t kQueueFiberLeave = 59; +static constexpr uint8_t kQueueThreadContext = 62; +static constexpr uint8_t kQueueSingleStringData = 99; +static constexpr uint8_t kQueueSecondStringData = 100; +static constexpr uint8_t kQueueStringDataFirst = 104; // indices >= this carry QueueStringTransfer +static constexpr uint8_t kQueueSourceLocationPayload = 107; +static constexpr uint8_t kQueueFrameImageData = 111; +static constexpr uint8_t kQueueSymbolCode = 114; +static constexpr uint8_t kQueueSourceCode = 115; +static constexpr uint8_t kQueueFiberName = 116; +static constexpr uint8_t kQueueNumTypes = 117; + +// Wire-format byte size of each queue item indexed by QueueType. +// Mirrors QueueDataSize[] from TracyQueue.hpp with #pragma pack(1) struct sizes. +static const size_t kQueueDataSize[kQueueNumTypes] = { + 1, // 0 ZoneText + 1, // 1 ZoneName + 9, // 2 Message {int64_t} + 12, // 3 MessageColor {int64_t, 3×uint8_t} + 9, // 4 MessageCallstack + 12, // 5 MessageColorCallstack + 9, // 6 MessageAppInfo + 9, // 7 ZoneBeginAllocSrcLoc {int64_t} + 9, // 8 ZoneBeginAllocSrcLocCallstack + 1, // 9 CallstackSerial + 1, // 10 Callstack + 1, // 11 CallstackAlloc + 13, // 12 CallstackSample {int64_t, uint32_t} + 13, // 13 CallstackSampleContextSwitch + 10, // 14 FrameImage {uint32_t, uint16_t, uint16_t, uint8_t} + 17, // 15 ZoneBegin {int64_t, uint64_t} + 17, // 16 ZoneBeginCallstack + 9, // 17 ZoneEnd {int64_t} + 17, // 18 LockWait {uint32_t, uint32_t, int64_t} + 17, // 19 LockObtain + 13, // 20 LockRelease {uint32_t, int64_t} + 17, // 21 LockSharedWait + 17, // 22 LockSharedObtain + 17, // 23 LockSharedRelease {uint32_t, int64_t, uint32_t} + 5, // 24 LockName {uint32_t} + 27, // 25 MemAlloc {int64_t, uint32_t, uint64_t, char[6]} + 27, // 26 MemAllocNamed + 21, // 27 MemFree {int64_t, uint32_t, uint64_t} + 21, // 28 MemFreeNamed + 27, // 29 MemAllocCallstack + 27, // 30 MemAllocCallstackNamed + 21, // 31 MemFreeCallstack + 21, // 32 MemFreeCallstackNamed + 21, // 33 MemDiscard + 21, // 34 MemDiscardCallstack + 24, // 35 GpuZoneBegin {int64_t, uint32_t, uint16_t, uint8_t, uint64_t} + 24, // 36 GpuZoneBeginCallstack + 16, // 37 GpuZoneBeginAllocSrcLoc {int64_t, uint32_t, uint16_t, uint8_t} + 16, // 38 GpuZoneBeginAllocSrcLocCallstack + 16, // 39 GpuZoneEnd + 24, // 40 GpuZoneBeginSerial + 24, // 41 GpuZoneBeginCallstackSerial + 16, // 42 GpuZoneBeginAllocSrcLocSerial + 16, // 43 GpuZoneBeginAllocSrcLocCallstackSerial + 16, // 44 GpuZoneEndSerial + 25, // 45 PlotDataInt {uint64_t, int64_t, int64_t} + 21, // 46 PlotDataFloat {uint64_t, int64_t, float} + 25, // 47 PlotDataDouble {uint64_t, int64_t, double} + 23, // 48 ContextSwitch {int64_t, 2×uint32_t, 4×uint8_t, 2×int8_t} + 16, // 49 ThreadWakeup {int64_t, uint32_t, uint8_t, 2×int8_t} + 12, // 50 GpuTime {int64_t, uint16_t, uint8_t} + 2, // 51 GpuContextName {uint8_t} + 10, // 52 GpuAnnotationName {int64_t, uint8_t} + 10, // 53 CallstackFrameSize {uint64_t, uint8_t} + 13, // 54 SymbolInformation {uint32_t, uint64_t} + 1, // 55 ExternalNameMetadata (not wire-transferred) + 1, // 56 SymbolCodeMetadata (not wire-transferred) + 1, // 57 SourceCodeMetadata (not wire-transferred) + 25, // 58 FiberEnter {int64_t, uint64_t, uint32_t, int32_t} + 13, // 59 FiberLeave {int64_t, uint32_t} + 1, // 60 Terminate + 1, // 61 KeepAlive + 5, // 62 ThreadContext {uint32_t} + 26, // 63 GpuCalibration {int64_t, int64_t, int64_t, uint8_t} + 18, // 64 GpuTimeSync {int64_t, int64_t, uint8_t} + 1, // 65 Crash + 17, // 66 CrashReport {int64_t, uint64_t} + 5, // 67 ZoneValidation {uint32_t} + 4, // 68 ZoneColor {3×uint8_t} + 9, // 69 ZoneValue {uint64_t} + 17, // 70 FrameMarkMsg {int64_t, uint64_t} + 17, // 71 FrameMarkMsgStart + 17, // 72 FrameMarkMsgEnd + 13, // 73 FrameVsync {int64_t, uint32_t} + 32, // 74 SourceLocation {3×uint64_t, uint32_t, 3×uint8_t} + 22, // 75 LockAnnounce {uint32_t, int64_t, uint64_t, uint8_t} + 13, // 76 LockTerminate {uint32_t, int64_t} + 17, // 77 LockMark {2×uint32_t, uint64_t} + 17, // 78 MessageLiteral {int64_t, uint64_t} + 20, // 79 MessageLiteralColor {int64_t, 3×uint8_t, uint64_t} + 17, // 80 MessageLiteralCallstack + 20, // 81 MessageLiteralColorCallstack + 28, // 82 GpuNewContext {2×int64_t, uint32_t, float, 3×uint8_t} + 17, // 83 CallstackFrame {uint32_t, uint64_t, uint32_t} + 13, // 84 SysTimeReport {int64_t, float} + 25, // 85 SysPowerReport {3×int64_t/uint64_t} + 17, // 86 TidToPid {2×uint64_t} + 17, // 87 HwSampleCpuCycle {uint64_t, int64_t} + 17, // 88 HwSampleInstructionRetired + 17, // 89 HwSampleCacheReference + 17, // 90 HwSampleCacheMiss + 17, // 91 HwSampleBranchRetired + 17, // 92 HwSampleBranchMiss + 16, // 93 PlotConfig {uint64_t, 3×uint8_t, uint32_t} + 18, // 94 ParamSetup {uint32_t, uint64_t, uint8_t, int32_t} + 1, // 95 AckServerQueryNoop + 5, // 96 AckSourceCodeNotAvailable {uint32_t} + 1, // 97 AckSymbolCodeNotAvailable + 17, // 98 CpuTopology {4×uint32_t} + 1, // 99 SingleStringData (variable-length; header only in fixed table) + 1, // 100 SecondStringData (variable-length; header only in fixed table) + 9, // 101 MemNamePayload {uint64_t} + 9, // 102 ThreadGroupHint {uint32_t, int32_t} + 24, // 103 GpuZoneAnnotation {int64_t, double, uint32_t, uint16_t, uint8_t} + // indices >= kQueueStringDataFirst carry QueueStringTransfer + variable string + 9, // 104 StringData {QueueStringTransfer} + 9, // 105 ThreadName + 9, // 106 PlotName + 9, // 107 SourceLocationPayload + 9, // 108 CallstackPayload + 9, // 109 CallstackAllocPayload + 9, // 110 FrameName + 9, // 111 FrameImageData + 9, // 112 ExternalName + 9, // 113 ExternalThreadName + 9, // 114 SymbolCode + 9, // 115 SourceCode + 9, // 116 FiberName +}; + +} // anonymous namespace + +// --------------------------------------------------------------------------- +// TracyTestClient +// --------------------------------------------------------------------------- + TracyTestClient::TracyTestClient() - : m_socket( new tracy::Socket() ) - , m_lz4Stream( tracy::LZ4_createStreamDecode() ) - , m_ringBuffer( new char[tracy::TargetFrameSize * 2] ) + : m_socket( new TcpSocket() ) + , m_lz4Stream( LZ4_createStreamDecode() ) + , m_ringBuffer( new char[kTargetFrameSize * 2] ) { } TracyTestClient::~TracyTestClient() { Disconnect(); - delete static_cast( m_socket ); - tracy::LZ4_freeStreamDecode( static_cast( m_lz4Stream ) ); + delete static_cast( m_socket ); + LZ4_freeStreamDecode( static_cast( m_lz4Stream ) ); delete[] m_ringBuffer; } bool TracyTestClient::Connect( const char* addr, uint16_t port, int timeoutMs ) { - auto& sock = *static_cast( m_socket ); + auto& sock = *static_cast( m_socket ); const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds( timeoutMs ); // Retry until we connect or time out, since the profiler's listen socket may @@ -45,20 +347,20 @@ bool TracyTestClient::Connect( const char* addr, uint16_t port, int timeoutMs ) return false; // Send handshake shibboleth and protocol version. - sock.Send( tracy::HandshakeShibboleth, tracy::HandshakeShibbolethSize ); - uint32_t proto = tracy::ProtocolVersion; + sock.Send( kHandshakeShibboleth, kHandshakeShibbolethSize ); + uint32_t proto = kProtocolVersion; sock.Send( &proto, sizeof( proto ) ); // Receive handshake status. - tracy::HandshakeStatus status; - if( !sock.ReadRaw( &status, sizeof( status ), 2000 ) || status != tracy::HandshakeWelcome ) + HandshakeStatus status; + if( !sock.ReadRaw( &status, sizeof( status ), 2000 ) || status != HandshakeWelcome ) { sock.Close(); return false; } // Receive the welcome message. - tracy::WelcomeMessage welcome; + WelcomeMessage welcome; if( !sock.ReadRaw( &welcome, sizeof( welcome ), 5000 ) ) { sock.Close(); @@ -66,7 +368,7 @@ bool TracyTestClient::Connect( const char* addr, uint16_t port, int timeoutMs ) } // With TRACY_ON_DEMAND the profiler sends an extra OnDemandPayloadMessage. - tracy::OnDemandPayloadMessage onDemand; + OnDemandPayloadMessage onDemand; if( !sock.ReadRaw( &onDemand, sizeof( onDemand ), 5000 ) ) { sock.Close(); @@ -74,7 +376,7 @@ bool TracyTestClient::Connect( const char* addr, uint16_t port, int timeoutMs ) } // Reset the LZ4 streaming context for the new connection. - tracy::LZ4_setStreamDecode( static_cast( m_lz4Stream ), nullptr, 0 ); + LZ4_setStreamDecode( static_cast( m_lz4Stream ), nullptr, 0 ); m_bufferOffset = 0; m_connected.store( true, std::memory_order_release ); @@ -89,7 +391,7 @@ void TracyTestClient::Disconnect() return; m_shutdown.store( true, std::memory_order_release ); - static_cast( m_socket )->Close(); + static_cast( m_socket )->Close(); if( m_recvThread.joinable() ) m_recvThread.join(); @@ -153,51 +455,53 @@ std::vector TracyTestClient::GetFiberNames() const void TracyTestClient::SendQueryLocked( uint8_t queryType, uint64_t ptr, uint32_t extra ) { - tracy::ServerQueryPacket pkt; - pkt.type = static_cast( queryType ); - pkt.ptr = ptr; + ServerQueryPacket pkt; + pkt.type = queryType; + pkt.ptr = ptr; pkt.extra = extra; std::lock_guard lock( m_sendMutex ); - static_cast( m_socket )->Send( &pkt, tracy::ServerQueryPacketSize ); + static_cast( m_socket )->Send( &pkt, static_cast( sizeof( pkt ) ) ); } // Receive loop: reads LZ4-compressed frames and decompresses them. void TracyTestClient::RecvLoop() { - auto& sock = *static_cast( m_socket ); - auto* lz4 = static_cast( m_lz4Stream ); - std::unique_ptr lz4Buf( new char[tracy::LZ4Size] ); + auto& sock = *static_cast( m_socket ); + auto* lz4 = static_cast( m_lz4Stream ); + std::unique_ptr lz4Buf( new char[kLZ4Size] ); while( !m_shutdown.load( std::memory_order_relaxed ) ) { // Each LZ4 frame is prefixed by its compressed size. - tracy::lz4sz_t compressedSz = 0; - if( !sock.ReadRaw( &compressedSz, sizeof( compressedSz ), kReadTimeoutMs ) ) { + uint32_t compressedSz = 0; + if( !sock.ReadRaw( &compressedSz, sizeof( compressedSz ), kReadTimeoutMs ) ) continue; - } - if( compressedSz > static_cast( tracy::LZ4Size ) ) { - fprintf( stderr, "Corrupt frame: %zu\n", static_cast(compressedSz) ); fflush(stderr); - break; // corrupt frame + if( compressedSz > static_cast( kLZ4Size ) ) + { + fprintf( stderr, "Corrupt frame: %zu\n", static_cast( compressedSz ) ); fflush( stderr ); + break; } - if( !sock.ReadRaw( lz4Buf.get(), static_cast( compressedSz ), kReadTimeoutMs ) ) { - fprintf(stderr, "ReadRaw failed to read compressed data\n"); fflush(stderr); + if( !sock.ReadRaw( lz4Buf.get(), static_cast( compressedSz ), kReadTimeoutMs ) ) + { + fprintf( stderr, "ReadRaw failed to read compressed data\n" ); fflush( stderr ); break; } // Decompress into the ring buffer using the streaming context so that // the previous block acts as the LZ4 dictionary. char* dst = m_ringBuffer + m_bufferOffset; - const int decompressedSz = tracy::LZ4_decompress_safe_continue( - lz4, lz4Buf.get(), dst, static_cast( compressedSz ), tracy::TargetFrameSize ); + const int decompressedSz = LZ4_decompress_safe_continue( + lz4, lz4Buf.get(), dst, + static_cast( compressedSz ), static_cast( kTargetFrameSize ) ); if( decompressedSz < 0 ) break; // decompression error ProcessDecompressedData( dst, decompressedSz ); m_bufferOffset += decompressedSz; - if( m_bufferOffset > tracy::TargetFrameSize * 2 ) + if( m_bufferOffset > static_cast( kTargetFrameSize * 2 ) ) m_bufferOffset = 0; } @@ -220,21 +524,21 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) while( ptr < end ) { - const auto* item = reinterpret_cast( ptr ); + const auto* item = reinterpret_cast( ptr ); const uint8_t idx = item->hdr.idx; - if( idx >= static_cast( tracy::QueueType::StringData ) ) + if( idx >= kQueueStringDataFirst ) { // String transfer item: fixed header + QueueStringTransfer, followed by // a length-prefixed string payload. - if( ptr + sizeof( tracy::QueueHeader ) + sizeof( tracy::QueueStringTransfer ) > end ) + if( ptr + sizeof( QueueHeader ) + sizeof( QueueStringTransfer ) > end ) break; const uint64_t strPtr = item->stringTransfer.ptr; - ptr += sizeof( tracy::QueueHeader ) + sizeof( tracy::QueueStringTransfer ); + ptr += sizeof( QueueHeader ) + sizeof( QueueStringTransfer ); - if( item->hdr.type == tracy::QueueType::FrameImageData || - item->hdr.type == tracy::QueueType::SymbolCode || - item->hdr.type == tracy::QueueType::SourceCode ) + if( idx == kQueueFrameImageData || + idx == kQueueSymbolCode || + idx == kQueueSourceCode ) { // Large binary payload with uint32_t length prefix. if( ptr + sizeof( uint32_t ) > end ) break; @@ -253,9 +557,7 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) ptr += sizeof( strSz ); if( ptr + strSz > end ) break; - switch( item->hdr.type ) - { - case tracy::QueueType::SourceLocationPayload: + if( idx == kQueueSourceLocationPayload ) { // The profiler sends this immediately before ZoneBeginAllocSrcLoc. // Format: [uint32_t color][uint32_t line][function\0][source\0][name] @@ -273,25 +575,20 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) const size_t nameLen = static_cast( strSz ) - static_cast( p - ptr ); std::lock_guard lock( m_dataMutex ); - m_pendingZone = {}; + m_pendingZone = {}; m_pendingZone.function = function; - m_pendingZone.source = source; - m_pendingZone.line = line; + m_pendingZone.source = source; + m_pendingZone.line = line; if( nameLen > 0 ) m_pendingZone.name = std::string( p, nameLen ); m_hasPendingZone = true; } - break; } - case tracy::QueueType::FiberName: + else if( idx == kQueueFiberName ) { std::string name( ptr, strSz ); std::lock_guard lock( m_dataMutex ); m_fiberNames[strPtr] = std::move( name ); - break; - } - default: - break; } ptr += strSz; @@ -300,33 +597,30 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) else { // Fixed-size item (or SingleStringData / SecondStringData special cases). - switch( item->hdr.type ) + if( idx == kQueueSingleStringData || idx == kQueueSecondStringData ) { - case tracy::QueueType::SingleStringData: - case tracy::QueueType::SecondStringData: - { - ptr += sizeof( tracy::QueueHeader ); + ptr += sizeof( QueueHeader ); if( ptr + sizeof( uint16_t ) > end ) return; uint16_t strSz = 0; std::memcpy( &strSz, ptr, sizeof( strSz ) ); ptr += sizeof( strSz ); if( ptr + strSz > end ) return; ptr += strSz; - break; } - default: + else { - const size_t itemSz = tracy::QueueDataSize[idx]; + if( idx >= kQueueNumTypes ) return; + const size_t itemSz = kQueueDataSize[idx]; if( ptr + itemSz > end ) return; - switch( item->hdr.type ) + switch( idx ) { - case tracy::QueueType::ThreadContext: + case kQueueThreadContext: m_currentThread = item->threadCtx.thread; break; - case tracy::QueueType::ZoneBeginAllocSrcLoc: - case tracy::QueueType::ZoneBeginAllocSrcLocCallstack: + case kQueueZoneBeginAllocSrcLoc: + case kQueueZoneBeginAllocSrcLocCallstack: { m_zoneBeginCount.fetch_add( 1, std::memory_order_relaxed ); const uint32_t thread = m_currentThread; @@ -338,12 +632,13 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) } break; } - case tracy::QueueType::ZoneBegin: - case tracy::QueueType::ZoneBeginCallstack: + + case kQueueZoneBegin: + case kQueueZoneBeginCallstack: m_zoneBeginCount.fetch_add( 1, std::memory_order_relaxed ); break; - case tracy::QueueType::ZoneEnd: + case kQueueZoneEnd: { m_zoneEndCount.fetch_add( 1, std::memory_order_relaxed ); const uint32_t thread = m_currentThread; @@ -354,18 +649,18 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) break; } - case tracy::QueueType::FiberEnter: + case kQueueFiberEnter: { const uint64_t fiberPtr = item->fiberEnter.fiber; - const uint32_t thread = item->fiberEnter.thread; + const uint32_t thread = item->fiberEnter.thread; std::lock_guard lock( m_dataMutex ); m_threadCurrentFiber[thread] = fiberPtr; if( m_queriedFibers.insert( fiberPtr ).second ) - SendQueryLocked( static_cast( tracy::ServerQueryFiberName ), fiberPtr ); + SendQueryLocked( kServerQueryFiberName, fiberPtr ); break; } - case tracy::QueueType::FiberLeave: + case kQueueFiberLeave: { const uint32_t thread = item->fiberLeave.thread; std::lock_guard lock( m_dataMutex ); @@ -378,8 +673,6 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) } ptr += itemSz; - break; - } } } } diff --git a/vcpkg.json b/vcpkg.json index ae04e7f..df7405b 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -15,6 +15,10 @@ "name": "gtest", "version>=": "1.16.0" }, + { + "name": "lz4", + "version>=": "1.9.4" + }, { "name": "python3", "version>=": "3.12.9#1", From e69d2dbed4467663e83b445edd6fcbf2c3dfbc71 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Thu, 7 May 2026 17:55:25 +0000 Subject: [PATCH 15/33] Add preliminary Windows support to TracyTestClient Untested on Windows, written by claude on macOS. --- tests/TracyTestClient.cpp | 52 ++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/tests/TracyTestClient.cpp b/tests/TracyTestClient.cpp index c96c8fb..4520ece 100644 --- a/tests/TracyTestClient.cpp +++ b/tests/TracyTestClient.cpp @@ -1,50 +1,62 @@ // Copyright © 2025 CCP ehf. #include "TracyTestClient.h" -#include #include #include #include #include -#include -#include -#include #include -#include + +#ifdef _WIN32 +# include +# include + using socket_t = SOCKET; + static constexpr socket_t kInvalidSocket = INVALID_SOCKET; +# define sock_close( s ) ::closesocket( s ) +#else +# include +# include +# include +# include +# include + using socket_t = int; + static constexpr socket_t kInvalidSocket = -1; +# define sock_close( s ) ::close( s ) +#endif static constexpr int kReadTimeoutMs = 100; // --------------------------------------------------------------------------- -// POSIX TCP socket +// TCP socket (POSIX + Winsock) // --------------------------------------------------------------------------- namespace { struct TcpSocket { - int fd = -1; + socket_t fd = kInvalidSocket; bool ConnectBlocking( const char* addr, uint16_t port ) { fd = ::socket( AF_INET, SOCK_STREAM, 0 ); - if( fd < 0 ) return false; + if( fd == kInvalidSocket ) return false; struct sockaddr_in sa{}; sa.sin_family = AF_INET; sa.sin_port = htons( port ); if( ::inet_pton( AF_INET, addr, &sa.sin_addr ) != 1 ) { - ::close( fd ); fd = -1; return false; + sock_close( fd ); fd = kInvalidSocket; return false; } if( ::connect( fd, reinterpret_cast( &sa ), sizeof( sa ) ) != 0 ) { - ::close( fd ); fd = -1; return false; + sock_close( fd ); fd = kInvalidSocket; return false; } return true; } void Send( const void* buf, int len ) { - ::send( fd, buf, static_cast( len ), 0 ); + ::send( fd, static_cast( buf ), len, 0 ); } bool ReadRaw( void* buf, int len, int timeoutMs ) @@ -58,8 +70,13 @@ struct TcpSocket struct timeval tv{}; tv.tv_sec = timeoutMs / 1000; tv.tv_usec = ( timeoutMs % 1000 ) * 1000; + // nfds is ignored on Windows; on POSIX it must be fd + 1. +#ifdef _WIN32 + if( ::select( 0, &fds, nullptr, nullptr, &tv ) <= 0 ) return false; +#else if( ::select( fd + 1, &fds, nullptr, nullptr, &tv ) <= 0 ) return false; - const int n = static_cast( ::recv( fd, p, static_cast( len ), 0 ) ); +#endif + const int n = static_cast( ::recv( fd, p, len, 0 ) ); if( n <= 0 ) return false; p += n; len -= n; @@ -69,10 +86,10 @@ struct TcpSocket void Close() { - if( fd >= 0 ) { ::close( fd ); fd = -1; } + if( fd != kInvalidSocket ) { sock_close( fd ); fd = kInvalidSocket; } } - bool IsValid() const { return fd >= 0; } + bool IsValid() const { return fd != kInvalidSocket; } }; // --------------------------------------------------------------------------- @@ -320,6 +337,10 @@ TracyTestClient::TracyTestClient() , m_lz4Stream( LZ4_createStreamDecode() ) , m_ringBuffer( new char[kTargetFrameSize * 2] ) { +#ifdef _WIN32 + WSADATA wsaData; + WSAStartup( MAKEWORD( 2, 2 ), &wsaData ); +#endif } TracyTestClient::~TracyTestClient() @@ -328,6 +349,9 @@ TracyTestClient::~TracyTestClient() delete static_cast( m_socket ); LZ4_freeStreamDecode( static_cast( m_lz4Stream ) ); delete[] m_ringBuffer; +#ifdef _WIN32 + WSACleanup(); +#endif } bool TracyTestClient::Connect( const char* addr, uint16_t port, int timeoutMs ) From 2c15aaf6b66e85922d94e9b6ab8dd3a616cdd1e9 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 11 May 2026 13:18:29 +0000 Subject: [PATCH 16/33] Add ws2_32 to test project when linking against Windows --- tests/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bad2997..17f117f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,6 +23,9 @@ add_executable(CcpCoreTest TracyTestClient.cpp ) target_link_libraries(CcpCoreTest PRIVATE CcpCore GTest::gtest GTest::gtest_main lz4::lz4) +if(WIN32) + target_link_libraries(CcpCoreTest PRIVATE ws2_32) +endif() if(APPLE) target_link_libraries(CcpCoreTest PRIVATE "-framework CoreFoundation") endif() From ca46f8620dbf87e94106645bf79b4107ee41dcfd Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 11 May 2026 15:54:43 +0000 Subject: [PATCH 17/33] Use CcpTelemetryIsConnected() for all Tracy memory alloc/dealloc operations. CcpTelemetryIsConnected() has the check for: - TracyIsStarted - TracyIsConnected - checks s_profilerState == Started This means that any multiple start/stops within the same profiling session (in Tracy GUI visualizer) may fail. That's because it may try to allocate memory to the same memory address as previously done, but without registering the free event for that memory address. Because it got the free event while in a Tracy stopped state. --- CCPMemory.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CCPMemory.cpp b/CCPMemory.cpp index 7496bd7..072dfb8 100644 --- a/CCPMemory.cpp +++ b/CCPMemory.cpp @@ -290,7 +290,7 @@ static inline void* CcpPlatformMalloc( size_t size ) void* p = HeapAlloc( s_heap, 0, size ); UpdateCount( size ); #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) + if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) { TracySecureAlloc( p, size ); } @@ -308,7 +308,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) void* p = HeapAlloc( s_heap, HEAP_ZERO_MEMORY, bytes ); UpdateCount( bytes ); #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) + if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) { TracySecureAlloc( p, size ); } @@ -319,7 +319,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) static inline void CcpPlatformFree( void* p ) { #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) + if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) { TracySecureFree( p ); } @@ -345,7 +345,7 @@ static inline void* CcpPlatformMalloc( size_t size ) s_memuse += realSize; #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) { + if ( CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) { TracySecureAlloc( p, realSize ); } #endif @@ -369,7 +369,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) s_memuse += realSize; #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) + if ( CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) { TracySecureAlloc( p, realSize ); } @@ -384,7 +384,7 @@ static inline void CcpPlatformFree( void* p ) p = reinterpret_cast( p ) - 1; #endif #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsStarted() ) + if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) { TracySecureFree( p ); } From da7f4921853f900192b512b08d162956bb058b94 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Tue, 12 May 2026 16:50:24 +0000 Subject: [PATCH 18/33] Create first draft of ReconnectAfterStop test. - Additionally sprinkle the code with fprintf statements, for debug purposes. To be removed later. --- CcpTelemetry.cpp | 11 +++++++ tests/CcpTelemetry.cpp | 63 ++++++++++++++++++++++++++++++++++++++- tests/TracyTestClient.cpp | 1 + 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 192248c..b3ca771 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -113,6 +113,7 @@ bool CcpStartTelemetry( const char* serverOrDumpPath, int connectionType, uint32 bool CcpStartTelemetry( const CcpTelemetryConfig& config ) { + fprintf( stderr, "***** CcpStartTelemetry() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this if( s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Started || s_profilerState.load( std::memory_order_acquire ) == ProfilerState::StartRequested ) { CCP_LOGERR_CH( s_ch, "Cannot start profiler - already started" ); @@ -124,11 +125,13 @@ bool CcpStartTelemetry( const CcpTelemetryConfig& config ) CcpTelemetrySetActiveFiber( "" ); // to ensure that all our look-ups are correctly initialized // CCP_LOG_CH( s_ch, "Starting profiler - %s - Root fiber is [Fiber %p]", s_telemetryConfig.applicationName.c_str(), t_activeFiber->c_str() ); s_profilerState.store( ProfilerState::StartRequested, std::memory_order_release ); + fprintf( stderr, "***** CcpStartTelemetry() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this return true; } void CcpStopTelemetry() { + fprintf( stderr, "***** CcpStopTelemetry() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this if( s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Stopped || s_profilerState.load( std::memory_order_acquire ) == ProfilerState::StopRequested ) { return; @@ -136,6 +139,7 @@ void CcpStopTelemetry() CCP_LOG_CH( s_ch, "Profiler stop requested" ); s_profilerState.store( ProfilerState::StopRequested, std::memory_order_release ); + fprintf( stderr, "***** CcpStopTelemetry() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this } void CcpTelemetryTick() @@ -146,9 +150,11 @@ void CcpTelemetryTick() { if (TracyIsStarted) { + fprintf( stderr, "***** CcpTelemetryTick() - StartRequested - if (TracyIsStarted)\n" ); fflush( stderr ); // TODO: Debug info, remove this // CCP_LOG_CH( s_ch, "Telemetry server started, waiting for connection..." ); if (TracyIsConnected) { + fprintf( stderr, "***** CcpTelemetryTick() - StartRequested - if (TracyIsConnected)\n" ); fflush( stderr ); // TODO: Debug info, remove this CCP_LOG_CH( s_ch, "Telemetry server connected to Profiler" ); TracySetProgramName( s_telemetryConfig.applicationName.c_str() ); s_profilerState.store( ProfilerState::Started, std::memory_order_release ); @@ -165,6 +171,7 @@ void CcpTelemetryTick() { CCP_LOG_CH( s_ch, "Starting Telemetry Server" ); #ifdef TRACY_MANUAL_LIFETIME + fprintf( stderr, "***** CcpTelemetryTick() - StartRequested - tracy::StartupProfiler()\n" ); fflush( stderr ); // TODO: Debug info, remove this tracy::StartupProfiler(); #endif // TRACY_MANUAL_LIFETIME } @@ -181,6 +188,7 @@ void CcpTelemetryTick() // the underlying string if ( !s_fiberEraseMap.empty() ) { + fprintf( stderr, "***** CcpTelemetryTick() - Started - if ( !s_fiberEraseMap.empty() )\n" ); fflush( stderr ); // TODO: Debug info, remove this auto now = std::chrono::steady_clock::now(); auto elem = s_fiberEraseMap.front(); while ( !s_fiberEraseMap.empty() && elem.second >= now ) @@ -204,12 +212,14 @@ void CcpTelemetryTick() else { CCP_LOG_CH( s_ch, "Disconnected from profiler" ); + fprintf( stderr, "***** CcpTelemetryTick() - Started - CcpStopTelemetry()\n" ); fflush( stderr ); // TODO: Debug info, remove this CcpStopTelemetry(); } break; } case ProfilerState::StopRequested: { + fprintf( stderr, "***** CcpTelemetryTick() - StopRequested - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this CCP_LOG_CH( s_ch, "Stopping Telemetry Server" ); FrameMark; ++s_telemetryTick; @@ -219,6 +229,7 @@ void CcpTelemetryTick() { ( *handler.first )( CCP_TELEMETRY_STOPPED, handler.second ); } + fprintf( stderr, "***** CcpTelemetryTick() - StopRequested - End\n" ); fflush( stderr ); // TODO: Debug info, remove this } case ProfilerState::Stopped: // Nothing to do diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 98e509a..141b0b9 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -37,6 +37,7 @@ class CcpTelemetryTest : public ::testing::Test void SetUp() override { + fprintf( stderr, "CcpTelemetryTest::SetUp() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this CcpTelemetryConfig conf{ "Telemetry Tests" }; EXPECT_EQ( conf.captureDuration, std::chrono::milliseconds::zero() ); CcpStartTelemetry( conf ); @@ -44,6 +45,7 @@ class CcpTelemetryTest : public ::testing::Test // Tick until the profiler's listen socket is up. while( !TracyIsStarted ) { + fprintf( stderr, "CcpTelemetryTest::SetUp() - while( !TracyIsStarted ) - TickTelemetry()\n" ); fflush( stderr ); // TODO: Debug info, remove this TickTelemetry(); } @@ -57,16 +59,20 @@ class CcpTelemetryTest : public ::testing::Test // Tick until CcpTelemetry recognises the connection and enters Started state. while( !CcpTelemetryIsConnected() ) { + fprintf( stderr, "CcpTelemetryTest::SetUp() - while( !CcpTelemetryIsConnected ) - TickTelemetry()\n" ); fflush( stderr ); // TODO: Debug info, remove this TickTelemetry(); } ASSERT_TRUE( connectFuture.get() ) << "Could not connect to Tracy profiler"; + fprintf( stderr, "CcpTelemetryTest::SetUp() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this } void TearDown() override { - m_tracyClient.Disconnect(); + fprintf( stderr, "CcpTelemetryTest::TearDown() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this + // m_tracyClient.Disconnect(); // TODO: Removed the explicit call to Disconnect. CcpStopTelemetry(); + fprintf( stderr, "CcpTelemetryTest::TearDown() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this } void TickTelemetry( std::chrono::milliseconds duration = std::chrono::milliseconds( 500 ) ) @@ -152,3 +158,58 @@ TEST_F( CcpTelemetryTest, StackedZones ) TickTelemetry(); EXPECT_TRUE( m_tracyClient.GetZones().empty() ); } + +TEST_F( CcpTelemetryTest, ReconnectAfterStop ) +{ + // Setup takes care of connecting to the TracyTestClient + EXPECT_TRUE( m_tracyClient.IsConnected() ); + + static int key1 = 1001; + const std::string zoneName1{ "FirstZone" }; + CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); + fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After CcpTelemetryEnterZone(key1)\n" ); fflush( stderr ); // TODO: Debug info, remove this + + TickTelemetry(); + fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After TickTelemetry() #1\n" ); fflush( stderr ); // TODO: Debug info, remove this + auto tracyZones = m_tracyClient.GetZones(); + auto pred = [&zoneName1]( const TracyTestClient::ZoneInfo& elem ) -> bool { + return elem.function == zoneName1; + }; + EXPECT_NE( tracyZones.end(), std::find_if( tracyZones.begin(), tracyZones.end(), pred ) ); + EXPECT_EQ( 1, tracyZones.size() ); + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); + EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); + + CcpTelemetryLeaveZone( &key1 ); + fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After CcpTelemetryLeaveZone(key1)\n" ); fflush( stderr ); // TODO: Debug info, remove this + + TickTelemetry(); + fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After TickTelemetry() #2\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_TRUE( m_tracyClient.GetZones().empty() ); + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); + EXPECT_TRUE( CcpTelemetryIsConnected() ); + EXPECT_TRUE( m_tracyClient.IsConnected() ); + + // Now simulate "Stop Telemetry" operation, NOT by disconnecting, but only signaling profiler Stop + CcpStopTelemetry(); + fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After CcpStopTelemetry()\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_TRUE( m_tracyClient.IsConnected() ) << "Connection should still be true at this point as we've only signaled stop"; + EXPECT_TRUE( TracyIsStarted ) << "Until we Tick, this should still be true"; + EXPECT_TRUE( TracyIsConnected ) << "Tracy should still consider itself connected until the client disconnects"; + EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: Started"; + + TickTelemetry(); + fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After TickTelemetry() #3\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_TRUE( m_tracyClient.IsConnected() ) << "Tick should NOT have made this false"; + EXPECT_FALSE( CcpTelemetryIsConnected() ) << "At least the TracyIsStarted condition should be false"; + EXPECT_FALSE( TracyIsStarted ) << "Tracy should have fully stopped by now BUT IT IS STILL returning TRUE. Focus on this one!!! <<<<==============="; + EXPECT_TRUE( TracyIsConnected ) << "Tracy should still consider itself connected until the client disconnects"; + EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: Started"; + + // TODO: Finish the test once we figure out why TracyIsStarted is not returning FALSE (as expected) + + // Now simulate "Start Telemetry" operation again... + + // Enter and Leave a different zone... +} \ No newline at end of file diff --git a/tests/TracyTestClient.cpp b/tests/TracyTestClient.cpp index 4520ece..b485b7f 100644 --- a/tests/TracyTestClient.cpp +++ b/tests/TracyTestClient.cpp @@ -529,6 +529,7 @@ void TracyTestClient::RecvLoop() m_bufferOffset = 0; } + fprintf( stderr, "TracyTestClient::RecvLoop() - ending, about to set m_connected = false\n" ); fflush( stderr ); // TODO: Debug info, remove this m_connected.store( false, std::memory_order_release ); } From 602111530d2f4f471a973b4b06bcac966f4e46a9 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 18 May 2026 15:58:15 +0000 Subject: [PATCH 19/33] Handle kQueueTerminate in TracyTestClient - This sets the m_shutdown boolean which controls the RecvLoop. - Also sprinkle in some debug fprintf statements (to be removed later) --- tests/TracyTestClient.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/TracyTestClient.cpp b/tests/TracyTestClient.cpp index b485b7f..5c1f7f9 100644 --- a/tests/TracyTestClient.cpp +++ b/tests/TracyTestClient.cpp @@ -192,6 +192,7 @@ static constexpr uint8_t kQueueZoneBeginCallstack = 16; static constexpr uint8_t kQueueZoneEnd = 17; static constexpr uint8_t kQueueFiberEnter = 58; static constexpr uint8_t kQueueFiberLeave = 59; +static constexpr uint8_t kQueueTerminate = 60; static constexpr uint8_t kQueueThreadContext = 62; static constexpr uint8_t kQueueSingleStringData = 99; static constexpr uint8_t kQueueSecondStringData = 100; @@ -411,16 +412,25 @@ bool TracyTestClient::Connect( const char* addr, uint16_t port, int timeoutMs ) void TracyTestClient::Disconnect() { + fprintf( stderr, "TracyTestClient::Disconnect() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this if( !m_connected.load( std::memory_order_acquire ) && !m_recvThread.joinable() ) + { + fprintf( stderr, "TracyTestClient::Disconnect() - #0\n" ); fflush( stderr ); // TODO: Debug info, remove this return; + } + fprintf( stderr, "TracyTestClient::Disconnect() - #1\n" ); fflush( stderr ); // TODO: Debug info, remove this m_shutdown.store( true, std::memory_order_release ); static_cast( m_socket )->Close(); if( m_recvThread.joinable() ) + { + fprintf( stderr, "TracyTestClient::Disconnect() - #2\n" ); fflush( stderr ); // TODO: Debug info, remove this m_recvThread.join(); + } m_connected.store( false, std::memory_order_release ); + fprintf( stderr, "TracyTestClient::Disconnect() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this } bool TracyTestClient::IsConnected() const @@ -693,6 +703,12 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) break; } + case kQueueTerminate: + { + m_shutdown.store( true, std::memory_order_release ); + break; + } + default: break; } From f779df3e06050917764d5539cf95cce0400e2980 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 18 May 2026 16:54:07 +0000 Subject: [PATCH 20/33] Add handling for ShutdownProfiler - Call RequestShutdown() when StopRequested - Call ShutdownProfiler() when Stopped and safe to do so - Cleanup fiber names store and erease map, along with thread local zones stores - Make sure not to emit data for zones if telemetry is not connected. - Change indent of temp debug fprintf statements (all debug info to be removed later) --- CcpTelemetry.cpp | 66 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index b3ca771..a68e308 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -113,7 +113,7 @@ bool CcpStartTelemetry( const char* serverOrDumpPath, int connectionType, uint32 bool CcpStartTelemetry( const CcpTelemetryConfig& config ) { - fprintf( stderr, "***** CcpStartTelemetry() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, " ***** CcpStartTelemetry() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this if( s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Started || s_profilerState.load( std::memory_order_acquire ) == ProfilerState::StartRequested ) { CCP_LOGERR_CH( s_ch, "Cannot start profiler - already started" ); @@ -125,13 +125,13 @@ bool CcpStartTelemetry( const CcpTelemetryConfig& config ) CcpTelemetrySetActiveFiber( "" ); // to ensure that all our look-ups are correctly initialized // CCP_LOG_CH( s_ch, "Starting profiler - %s - Root fiber is [Fiber %p]", s_telemetryConfig.applicationName.c_str(), t_activeFiber->c_str() ); s_profilerState.store( ProfilerState::StartRequested, std::memory_order_release ); - fprintf( stderr, "***** CcpStartTelemetry() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, " ***** CcpStartTelemetry() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this return true; } void CcpStopTelemetry() { - fprintf( stderr, "***** CcpStopTelemetry() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, " ***** CcpStopTelemetry() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this if( s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Stopped || s_profilerState.load( std::memory_order_acquire ) == ProfilerState::StopRequested ) { return; @@ -139,7 +139,7 @@ void CcpStopTelemetry() CCP_LOG_CH( s_ch, "Profiler stop requested" ); s_profilerState.store( ProfilerState::StopRequested, std::memory_order_release ); - fprintf( stderr, "***** CcpStopTelemetry() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, " ***** CcpStopTelemetry() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this } void CcpTelemetryTick() @@ -150,11 +150,11 @@ void CcpTelemetryTick() { if (TracyIsStarted) { - fprintf( stderr, "***** CcpTelemetryTick() - StartRequested - if (TracyIsStarted)\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, " ***** CcpTelemetryTick() - StartRequested - if (TracyIsStarted)\n" ); fflush( stderr ); // TODO: Debug info, remove this // CCP_LOG_CH( s_ch, "Telemetry server started, waiting for connection..." ); if (TracyIsConnected) { - fprintf( stderr, "***** CcpTelemetryTick() - StartRequested - if (TracyIsConnected)\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, " ***** CcpTelemetryTick() - StartRequested - if (TracyIsConnected)\n" ); fflush( stderr ); // TODO: Debug info, remove this CCP_LOG_CH( s_ch, "Telemetry server connected to Profiler" ); TracySetProgramName( s_telemetryConfig.applicationName.c_str() ); s_profilerState.store( ProfilerState::Started, std::memory_order_release ); @@ -171,7 +171,7 @@ void CcpTelemetryTick() { CCP_LOG_CH( s_ch, "Starting Telemetry Server" ); #ifdef TRACY_MANUAL_LIFETIME - fprintf( stderr, "***** CcpTelemetryTick() - StartRequested - tracy::StartupProfiler()\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, " ***** CcpTelemetryTick() - StartRequested - tracy::StartupProfiler()\n" ); fflush( stderr ); // TODO: Debug info, remove this tracy::StartupProfiler(); #endif // TRACY_MANUAL_LIFETIME } @@ -188,7 +188,7 @@ void CcpTelemetryTick() // the underlying string if ( !s_fiberEraseMap.empty() ) { - fprintf( stderr, "***** CcpTelemetryTick() - Started - if ( !s_fiberEraseMap.empty() )\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, " ***** CcpTelemetryTick() - Started - if ( !s_fiberEraseMap.empty() )\n" ); fflush( stderr ); // TODO: Debug info, remove this auto now = std::chrono::steady_clock::now(); auto elem = s_fiberEraseMap.front(); while ( !s_fiberEraseMap.empty() && elem.second >= now ) @@ -212,14 +212,14 @@ void CcpTelemetryTick() else { CCP_LOG_CH( s_ch, "Disconnected from profiler" ); - fprintf( stderr, "***** CcpTelemetryTick() - Started - CcpStopTelemetry()\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, " ***** CcpTelemetryTick() - Started - CcpStopTelemetry()\n" ); fflush( stderr ); // TODO: Debug info, remove this CcpStopTelemetry(); } break; } case ProfilerState::StopRequested: { - fprintf( stderr, "***** CcpTelemetryTick() - StopRequested - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, " ***** CcpTelemetryTick() - StopRequested - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this CCP_LOG_CH( s_ch, "Stopping Telemetry Server" ); FrameMark; ++s_telemetryTick; @@ -229,10 +229,41 @@ void CcpTelemetryTick() { ( *handler.first )( CCP_TELEMETRY_STOPPED, handler.second ); } - fprintf( stderr, "***** CcpTelemetryTick() - StopRequested - End\n" ); fflush( stderr ); // TODO: Debug info, remove this + + // Signal to the Tracy client (GUI or TracyTestClient) that we want to stop and close the connection to the Profiling application (server) + if ( TracyIsStarted ) + { + CCP_LOG_CH( s_ch, "Tracy profiler shutdown requested, waiting for completion..." ); + fprintf( stderr, " ***** CcpTelemetryTick() - StopRequested - Before tracy::GetProfiler().RequestShutdown()\n" ); fflush( stderr ); // TODO: Debug info, remove this + tracy::GetProfiler().RequestShutdown(); + fprintf( stderr, " ***** CcpTelemetryTick() - StopRequested - After tracy::GetProfiler().RequestShutdown()\n" ); fflush( stderr ); // TODO: Debug info, remove this + } + + // Clear any pending tasklet zones to prevent stale zone-end events from bleeding into the next profiling session + t_taskletZoneStore.clear(); + t_activeTaskletZoneStore = t_taskletZoneStore.end(); // TODO: Discuss with Thomas: I should probably set this to .end (as that's the initial state of it) instead of using .begin() + t_manuallyTrackedZones.clear(); + + // Also clear both the fiberEraseQueue and the fiberNamesStore as they should be empty in case we start again + while ( !s_fiberEraseMap.empty() ) + { + s_fiberEraseMap.pop(); + } + s_fiberNameStore.clear(); + + fprintf( stderr, " ***** CcpTelemetryTick() - StopRequested - End\n" ); fflush( stderr ); // TODO: Debug info, remove this + break; } case ProfilerState::Stopped: - // Nothing to do +#ifdef TRACY_MANUAL_LIFETIME + if ( TracyIsStarted && tracy::GetProfiler().HasShutdownFinished() ) + { + CCP_LOG_CH( s_ch, "Shutting down Tracy profiler" ); + fprintf( stderr, " ***** CcpTelemetryTick() - Stopped - Before tracy::ShutdownProfiler()\n" ); fflush( stderr ); // TODO: Debug info, remove this + tracy::ShutdownProfiler(); + fprintf( stderr, " ***** CcpTelemetryTick() - Stopped - After tracy::ShutdownProfiler()\n" ); fflush( stderr ); // TODO: Debug info, remove this + } +#endif break; default: CCP_LOGERR_CH( s_ch, "Unhandled profiler state %d", s_profilerState.load(std::memory_order_acquire)); @@ -290,7 +321,9 @@ void CcpTelemetrySetActiveFiber( FiberNameStore::const_iterator elem ) if ( existing != t_taskletZoneStore.end() && ! ( t_taskletZoneStore.key_comp()( t_activeFiber, existing->first ) ) ) { t_activeTaskletZoneStore = existing; - } else { + } + else + { t_activeTaskletZoneStore = t_taskletZoneStore.emplace_hint( existing, t_activeFiber, std::stack() ); } // CCP_LOG_CH( s_ch, "[Fiber %p] [Store %p] Setting active tasklet zone store", t_activeFiber, t_activeTaskletZoneStore ); @@ -363,6 +396,13 @@ TelemetryZone::~TelemetryZone() return; } + // Only emit zone-end if Tracy is still started AND we're in an active profiling session + if( !CcpTelemetryIsConnected() ) + { + m_telemetryContext.reset(); + return; + } + // Zones need to end on the same fiber they were started from, so do a little song and dance to ensure that auto previous = t_activeFiber; CcpTelemetrySetActiveFiber( m_fiber ); From 367593929f3a65962486fc3524e72bd31b780cdf Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 18 May 2026 17:07:57 +0000 Subject: [PATCH 21/33] Add some StartStopConnectDisconnect profiler tests Added skeleton code for the following tests: - StartConnectStopDisconnectProfiling - StartConnectStopProfiling - StartConnectDisconnectProfiling - StartConnectDisconnectStopProfiling - ReconnectAfterStop --- tests/CcpTelemetry.cpp | 121 +++++++++++++++++++++++++++++++++++------ 1 file changed, 105 insertions(+), 16 deletions(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 141b0b9..312833f 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -159,7 +159,7 @@ TEST_F( CcpTelemetryTest, StackedZones ) EXPECT_TRUE( m_tracyClient.GetZones().empty() ); } -TEST_F( CcpTelemetryTest, ReconnectAfterStop ) +TEST_F( CcpTelemetryTest, StartConnectStopDisconnectProfiling ) { // Setup takes care of connecting to the TracyTestClient EXPECT_TRUE( m_tracyClient.IsConnected() ); @@ -167,10 +167,8 @@ TEST_F( CcpTelemetryTest, ReconnectAfterStop ) static int key1 = 1001; const std::string zoneName1{ "FirstZone" }; CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); - fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After CcpTelemetryEnterZone(key1)\n" ); fflush( stderr ); // TODO: Debug info, remove this TickTelemetry(); - fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After TickTelemetry() #1\n" ); fflush( stderr ); // TODO: Debug info, remove this auto tracyZones = m_tracyClient.GetZones(); auto pred = [&zoneName1]( const TracyTestClient::ZoneInfo& elem ) -> bool { return elem.function == zoneName1; @@ -181,34 +179,125 @@ TEST_F( CcpTelemetryTest, ReconnectAfterStop ) EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); CcpTelemetryLeaveZone( &key1 ); - fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After CcpTelemetryLeaveZone(key1)\n" ); fflush( stderr ); // TODO: Debug info, remove this TickTelemetry(); - fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After TickTelemetry() #2\n" ); fflush( stderr ); // TODO: Debug info, remove this EXPECT_TRUE( m_tracyClient.GetZones().empty() ); EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); EXPECT_TRUE( CcpTelemetryIsConnected() ); EXPECT_TRUE( m_tracyClient.IsConnected() ); - // Now simulate "Stop Telemetry" operation, NOT by disconnecting, but only signaling profiler Stop + // Now simulate "Stop Telemetry" operation. NOTE we're not disconnecting the TracyTestClient, only signaling the profiler to Stop CcpStopTelemetry(); - fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After CcpStopTelemetry()\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_TRUE( m_tracyClient.IsConnected() ) << "Connection should still be true at this point as we've only signaled stop"; - EXPECT_TRUE( TracyIsStarted ) << "Until we Tick, this should still be true"; - EXPECT_TRUE( TracyIsConnected ) << "Tracy should still consider itself connected until the client disconnects"; + EXPECT_TRUE( m_tracyClient.IsConnected() ) << "Connection should still be true at this point because the TracyTestClient hasn't been disconnected"; + EXPECT_TRUE( TracyIsStarted ) << "Until we Tick TWICE, this should still be true (first for StopRequested, then for Stopped)"; + EXPECT_TRUE( TracyNoop ) << "Tracy should have a profiler available"; + EXPECT_TRUE( TracyIsConnected ) << "TracyTestClient should still be connected at this point"; + EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: Started, it should be StopRequested"; + + TickTelemetry(); // This should process the "StopRequested" state. + EXPECT_FALSE( m_tracyClient.IsConnected() ) << "Now that the TracyTestClient is handling the 'kQueueTerminate' and setting m_shutdown=true, this should be FALSE"; //"Connection should still be true at this point because the TracyTestClient hasn't been disconnected"; + EXPECT_TRUE( TracyIsStarted ) << "Until we Tick ONE MORE TIME, this should still be true (this is for StopRequested, next Tick is for Stopped)"; + EXPECT_TRUE( TracyNoop ) << "Tracy should have a profiler available"; + EXPECT_TRUE( TracyIsConnected ) << "TracyTestClient should still be connected at this point"; + EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: StopRequested, it should have transitioned to Stopped"; + + // TODO: Figure out why TracyIsStarted only becomes FALSE if m_tracyClient.Disconnect() is called, but NOT just because we called tracy::ShutdownProfiler() + + m_tracyClient.Disconnect(); // Disconnect the TracyTestClient, meaning TracyNoop should return FALSE (and TracyIsConnected a segfault because tracy::GetProfiler() is invalid) + + fprintf( stderr, "CC0\n" ); fflush( stderr ); // TODO: Debug info, remove this + TickTelemetry(); // This should process the "Stopped" state (2nd part of calling Stop Telemetry) + fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After TickTelemetry() #4\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_FALSE( m_tracyClient.IsConnected() ) << "By disconnecting the TracyTestClient, this should return false"; + fprintf( stderr, "CC1\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_FALSE( TracyNoop ) << "TracyNoop is returning false here, NOT because we called tracy::ShutdownProfiler(), but because we called m_tracyClient.Disconnect(). This is a bit surprising. <<<===="; + fprintf( stderr, "CC2\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_FALSE( TracyIsStarted ) << "Tracy should have fully stopped by now WHICH SHOULD have happened because of a call to tracy::ShutdownProfiler(), BUT is probably because of m_tracyClient.Disconnect() <<<===="; + fprintf( stderr, "CC3\n" ); fflush( stderr ); // TODO: Debug info, remove this + //EXPECT_FALSE( TracyIsConnected ) << "Tracy should NOT be connected, because we've called m_tracyClient.Disconnect()"; + fprintf( stderr, "CC4 - We can't call TracyIsConnected here, because it will end up with a SEH exception\n" ); fflush( stderr ); // TODO: Debug info, remove this EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: Started"; + fprintf( stderr, "CC5\n" ); fflush( stderr ); // TODO: Debug info, remove this + +} + +TEST_F( CcpTelemetryTest, StartConnectStopProfiling ) +{ + // Setup takes care of connecting to the TracyTestClient + EXPECT_TRUE( m_tracyClient.IsConnected() ); + + static int key1 = 1001; + const std::string zoneName1{ "FirstZone" }; + CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); + + TickTelemetry(); + auto tracyZones = m_tracyClient.GetZones(); + auto pred = [&zoneName1]( const TracyTestClient::ZoneInfo& elem ) -> bool { + return elem.function == zoneName1; + }; + EXPECT_NE( tracyZones.end(), std::find_if( tracyZones.begin(), tracyZones.end(), pred ) ); + EXPECT_EQ( 1, tracyZones.size() ); + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); + EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); + + CcpTelemetryLeaveZone( &key1 ); TickTelemetry(); - fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After TickTelemetry() #3\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_TRUE( m_tracyClient.IsConnected() ) << "Tick should NOT have made this false"; - EXPECT_FALSE( CcpTelemetryIsConnected() ) << "At least the TracyIsStarted condition should be false"; - EXPECT_FALSE( TracyIsStarted ) << "Tracy should have fully stopped by now BUT IT IS STILL returning TRUE. Focus on this one!!! <<<<==============="; - EXPECT_TRUE( TracyIsConnected ) << "Tracy should still consider itself connected until the client disconnects"; + EXPECT_TRUE( m_tracyClient.GetZones().empty() ); + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); + EXPECT_TRUE( CcpTelemetryIsConnected() ); + EXPECT_TRUE( m_tracyClient.IsConnected() ); + + // Now simulate "Stop Telemetry" operation. NOTE we're not disconnecting the TracyTestClient, only signaling the profiler to Stop + CcpStopTelemetry(); + EXPECT_TRUE( m_tracyClient.IsConnected() ) << "Connection should still be true at this point because the TracyTestClient hasn't been disconnected"; + EXPECT_TRUE( TracyIsStarted ) << "Until we Tick TWICE, this should still be true (first for StopRequested, then for Stopped)"; + EXPECT_TRUE( TracyNoop ) << "Tracy should have a profiler available"; + EXPECT_TRUE( TracyIsConnected ) << "TracyTestClient should still be connected at this point"; + EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: Started, it should be StopRequested"; + + TickTelemetry(); // This should process the "StopRequested" state. + EXPECT_FALSE( m_tracyClient.IsConnected() ) << "Now that the TracyTestClient is handling the 'kQueueTerminate' and setting m_shutdown=true, this should be FALSE"; //"Connection should still be true at this point because the TracyTestClient hasn't been disconnected"; + EXPECT_TRUE( TracyIsStarted ) << "Until we Tick ONE MORE TIME, this should still be true (this is for StopRequested, next Tick is for Stopped)"; + EXPECT_TRUE( TracyNoop ) << "Tracy should have a profiler available"; + EXPECT_TRUE( TracyIsConnected ) << "TracyTestClient should still be connected at this point"; + EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: StopRequested, it should have transitioned to Stopped"; + + // TODO: Figure out why TracyIsStarted only becomes FALSE if m_tracyClient.Disconnect() is called, but NOT just because we called tracy::ShutdownProfiler() + + // m_tracyClient.Disconnect(); // NOT calling m_tracyClient.Disconnect() here on purpose in the TracyTestClient. + // We should be able to Stop Profiling, by calling tracy::ShutdownProfiler(), without having to disconnect the Tracy GUI or TracyTestClient first + + fprintf( stderr, "CC0\n" ); fflush( stderr ); // TODO: Debug info, remove this + TickTelemetry(std::chrono::milliseconds(1000)); // This should process the "Stopped" state (2nd part of calling Stop Telemetry) + TickTelemetry(std::chrono::milliseconds(1000)); // Giving it more time to flush the queue. + fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After TickTelemetry() #4\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_FALSE( m_tracyClient.IsConnected() ) << "By disconnecting the TracyTestClient (because of the 'kQueueTerminate' handling setting m_shutdown=true), this should return false"; + fprintf( stderr, "CC1\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_TRUE( TracyNoop ) << "For some reason, even though we've Stopped Telemetry, Tracy is still available, this is ODD. <<<===="; + fprintf( stderr, "CC2\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_FALSE( TracyIsStarted ) << "I would have thought that this should be FALSE here, because we've called tracy::GetProfiler().RequestShutdown() -> which in turn should on the next tick process Stopped state and call ShutdownProfiler(). BUT this never becomes true [TracyIsStarted && tracy::GetProfiler().HasShutdownFinished()] <<<====="; + fprintf( stderr, "CC3\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_FALSE( TracyIsConnected ) << "Tracy should NOT be connected, because the TracyTestClient should have been disconnected because of kQueueTerminate"; + fprintf( stderr, "CC4\n" ); fflush( stderr ); // TODO: Debug info, remove this EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: Started"; + fprintf( stderr, "CC5\n" ); fflush( stderr ); // TODO: Debug info, remove this +} + +TEST_F( CcpTelemetryTest, StartConnectDisconnectProfiling ) +{ + +} + +TEST_F( CcpTelemetryTest, StartConnectDisconnectStopProfiling ) +{ - // TODO: Finish the test once we figure out why TracyIsStarted is not returning FALSE (as expected) +} +TEST_F( CcpTelemetryTest, ReconnectAfterStop ) +{ // Now simulate "Start Telemetry" operation again... // Enter and Leave a different zone... From a0a4e2821d916aac087216f7a4847e59debc04eb Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Tue, 19 May 2026 15:35:24 +0000 Subject: [PATCH 22/33] Use Tracy with option 'no-crash-handler' This maps onto TRACY_NO_CRASH_HANDLER, which should disable the internal Tracy crash handler. The no-crash-handler feature is specific to the carbonengine/vcpkg-registry port of Tracy. It replaces the default crash-handler INVERTED FEATURE in the official vcpkg registry. --- CMakeLists.txt | 2 +- vcpkg.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 281d8ae..a07633e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,7 +103,7 @@ target_link_libraries(CcpCore PUBLIC $<$:-framework\ AppKit> if(WITH_TELEMETRY) message(STATUS "Enabling profiler integration") find_package(Tracy CONFIG REQUIRED) - target_compile_definitions(CcpCore PUBLIC CCP_TELEMETRY_ENABLED=1 TRACY_IMPORTS TRACY_ENABLE TRACY_FIBERS TRACY_ON_DEMAND TRACY_DELAYED_INIT TRACY_MANUAL_LIFETIME) + target_compile_definitions(CcpCore PUBLIC CCP_TELEMETRY_ENABLED=1 TRACY_IMPORTS TRACY_ENABLE TRACY_FIBERS TRACY_ON_DEMAND TRACY_NO_CALLSTACK TRACY_MANUAL_LIFETIME TRACY_DELAYED_INIT TRACY_NO_CRASH_HANDLER) target_link_libraries(CcpCore PUBLIC Tracy::TracyClient) # Tracy's vcpkg package is always built as Release (with NDEBUG). CcpTelemetry.cpp # must also be compiled with NDEBUG to match Tracy's struct layout: in 0.13.1 the diff --git a/vcpkg.json b/vcpkg.json index df7405b..f0ae7e4 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -8,7 +8,8 @@ "on-demand", "no-callstack", "manual-lifetime", - "delayed-init" + "delayed-init", + "no-crash-handler" ] }, { From 7072e13ac6cef557bffae54f7d90fce8ad789f25 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 26 May 2026 15:00:46 +0000 Subject: [PATCH 23/33] Fix disconnect logic in `TracyTestClient` Previously, the socket was only closed when `TracyTestClient::Disconnect` was called. However, it also needs to be closed when the profiler itself signals a disconnect. --- tests/TracyTestClient.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/TracyTestClient.cpp b/tests/TracyTestClient.cpp index 5c1f7f9..e7fa898 100644 --- a/tests/TracyTestClient.cpp +++ b/tests/TracyTestClient.cpp @@ -412,25 +412,15 @@ bool TracyTestClient::Connect( const char* addr, uint16_t port, int timeoutMs ) void TracyTestClient::Disconnect() { - fprintf( stderr, "TracyTestClient::Disconnect() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this if( !m_connected.load( std::memory_order_acquire ) && !m_recvThread.joinable() ) - { - fprintf( stderr, "TracyTestClient::Disconnect() - #0\n" ); fflush( stderr ); // TODO: Debug info, remove this return; - } - fprintf( stderr, "TracyTestClient::Disconnect() - #1\n" ); fflush( stderr ); // TODO: Debug info, remove this m_shutdown.store( true, std::memory_order_release ); - static_cast( m_socket )->Close(); if( m_recvThread.joinable() ) - { - fprintf( stderr, "TracyTestClient::Disconnect() - #2\n" ); fflush( stderr ); // TODO: Debug info, remove this m_recvThread.join(); - } m_connected.store( false, std::memory_order_release ); - fprintf( stderr, "TracyTestClient::Disconnect() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this } bool TracyTestClient::IsConnected() const @@ -539,7 +529,11 @@ void TracyTestClient::RecvLoop() m_bufferOffset = 0; } - fprintf( stderr, "TracyTestClient::RecvLoop() - ending, about to set m_connected = false\n" ); fflush( stderr ); // TODO: Debug info, remove this + // Close the socket so Tracy's worker thread sees the connection drop and + // can finish its own shutdown sequence. This is necessary whether we exit + // because kQueueTerminate was received or because Disconnect() set m_shutdown. + // TcpSocket::Close() is idempotent, so a double-close from Disconnect() is safe. + sock.Close(); m_connected.store( false, std::memory_order_release ); } From 8de74dee2fa9a4357a8badef7303d8b0280eaed1 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 27 May 2026 11:03:23 +0000 Subject: [PATCH 24/33] Revert ShutdownProfiler() changes Changes include: - Remove explicit call to tracy::ShutdownProfiler() - Revert Zone state bookkeeping changes/cleanup associated with earlier ShutdownProfiler change --- CcpTelemetry.cpp | 40 +--------------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index a68e308..e06ce5d 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -229,41 +229,10 @@ void CcpTelemetryTick() { ( *handler.first )( CCP_TELEMETRY_STOPPED, handler.second ); } - - // Signal to the Tracy client (GUI or TracyTestClient) that we want to stop and close the connection to the Profiling application (server) - if ( TracyIsStarted ) - { - CCP_LOG_CH( s_ch, "Tracy profiler shutdown requested, waiting for completion..." ); - fprintf( stderr, " ***** CcpTelemetryTick() - StopRequested - Before tracy::GetProfiler().RequestShutdown()\n" ); fflush( stderr ); // TODO: Debug info, remove this - tracy::GetProfiler().RequestShutdown(); - fprintf( stderr, " ***** CcpTelemetryTick() - StopRequested - After tracy::GetProfiler().RequestShutdown()\n" ); fflush( stderr ); // TODO: Debug info, remove this - } - - // Clear any pending tasklet zones to prevent stale zone-end events from bleeding into the next profiling session - t_taskletZoneStore.clear(); - t_activeTaskletZoneStore = t_taskletZoneStore.end(); // TODO: Discuss with Thomas: I should probably set this to .end (as that's the initial state of it) instead of using .begin() - t_manuallyTrackedZones.clear(); - - // Also clear both the fiberEraseQueue and the fiberNamesStore as they should be empty in case we start again - while ( !s_fiberEraseMap.empty() ) - { - s_fiberEraseMap.pop(); - } - s_fiberNameStore.clear(); - - fprintf( stderr, " ***** CcpTelemetryTick() - StopRequested - End\n" ); fflush( stderr ); // TODO: Debug info, remove this break; } case ProfilerState::Stopped: -#ifdef TRACY_MANUAL_LIFETIME - if ( TracyIsStarted && tracy::GetProfiler().HasShutdownFinished() ) - { - CCP_LOG_CH( s_ch, "Shutting down Tracy profiler" ); - fprintf( stderr, " ***** CcpTelemetryTick() - Stopped - Before tracy::ShutdownProfiler()\n" ); fflush( stderr ); // TODO: Debug info, remove this - tracy::ShutdownProfiler(); - fprintf( stderr, " ***** CcpTelemetryTick() - Stopped - After tracy::ShutdownProfiler()\n" ); fflush( stderr ); // TODO: Debug info, remove this - } -#endif + // Nothing to do break; default: CCP_LOGERR_CH( s_ch, "Unhandled profiler state %d", s_profilerState.load(std::memory_order_acquire)); @@ -396,13 +365,6 @@ TelemetryZone::~TelemetryZone() return; } - // Only emit zone-end if Tracy is still started AND we're in an active profiling session - if( !CcpTelemetryIsConnected() ) - { - m_telemetryContext.reset(); - return; - } - // Zones need to end on the same fiber they were started from, so do a little song and dance to ensure that auto previous = t_activeFiber; CcpTelemetrySetActiveFiber( m_fiber ); From 4ac288edaa610e6a37e68bcdb4c8516801556e2f Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 27 May 2026 11:09:32 +0000 Subject: [PATCH 25/33] Remove debug printf statements in CcpTelemetry.cpp --- CcpTelemetry.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index e06ce5d..89dd8b0 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -113,7 +113,6 @@ bool CcpStartTelemetry( const char* serverOrDumpPath, int connectionType, uint32 bool CcpStartTelemetry( const CcpTelemetryConfig& config ) { - fprintf( stderr, " ***** CcpStartTelemetry() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this if( s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Started || s_profilerState.load( std::memory_order_acquire ) == ProfilerState::StartRequested ) { CCP_LOGERR_CH( s_ch, "Cannot start profiler - already started" ); @@ -125,13 +124,11 @@ bool CcpStartTelemetry( const CcpTelemetryConfig& config ) CcpTelemetrySetActiveFiber( "" ); // to ensure that all our look-ups are correctly initialized // CCP_LOG_CH( s_ch, "Starting profiler - %s - Root fiber is [Fiber %p]", s_telemetryConfig.applicationName.c_str(), t_activeFiber->c_str() ); s_profilerState.store( ProfilerState::StartRequested, std::memory_order_release ); - fprintf( stderr, " ***** CcpStartTelemetry() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this return true; } void CcpStopTelemetry() { - fprintf( stderr, " ***** CcpStopTelemetry() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this if( s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Stopped || s_profilerState.load( std::memory_order_acquire ) == ProfilerState::StopRequested ) { return; @@ -139,7 +136,6 @@ void CcpStopTelemetry() CCP_LOG_CH( s_ch, "Profiler stop requested" ); s_profilerState.store( ProfilerState::StopRequested, std::memory_order_release ); - fprintf( stderr, " ***** CcpStopTelemetry() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this } void CcpTelemetryTick() @@ -150,11 +146,9 @@ void CcpTelemetryTick() { if (TracyIsStarted) { - fprintf( stderr, " ***** CcpTelemetryTick() - StartRequested - if (TracyIsStarted)\n" ); fflush( stderr ); // TODO: Debug info, remove this // CCP_LOG_CH( s_ch, "Telemetry server started, waiting for connection..." ); if (TracyIsConnected) { - fprintf( stderr, " ***** CcpTelemetryTick() - StartRequested - if (TracyIsConnected)\n" ); fflush( stderr ); // TODO: Debug info, remove this CCP_LOG_CH( s_ch, "Telemetry server connected to Profiler" ); TracySetProgramName( s_telemetryConfig.applicationName.c_str() ); s_profilerState.store( ProfilerState::Started, std::memory_order_release ); @@ -171,7 +165,6 @@ void CcpTelemetryTick() { CCP_LOG_CH( s_ch, "Starting Telemetry Server" ); #ifdef TRACY_MANUAL_LIFETIME - fprintf( stderr, " ***** CcpTelemetryTick() - StartRequested - tracy::StartupProfiler()\n" ); fflush( stderr ); // TODO: Debug info, remove this tracy::StartupProfiler(); #endif // TRACY_MANUAL_LIFETIME } @@ -188,7 +181,6 @@ void CcpTelemetryTick() // the underlying string if ( !s_fiberEraseMap.empty() ) { - fprintf( stderr, " ***** CcpTelemetryTick() - Started - if ( !s_fiberEraseMap.empty() )\n" ); fflush( stderr ); // TODO: Debug info, remove this auto now = std::chrono::steady_clock::now(); auto elem = s_fiberEraseMap.front(); while ( !s_fiberEraseMap.empty() && elem.second >= now ) @@ -212,14 +204,12 @@ void CcpTelemetryTick() else { CCP_LOG_CH( s_ch, "Disconnected from profiler" ); - fprintf( stderr, " ***** CcpTelemetryTick() - Started - CcpStopTelemetry()\n" ); fflush( stderr ); // TODO: Debug info, remove this CcpStopTelemetry(); } break; } case ProfilerState::StopRequested: { - fprintf( stderr, " ***** CcpTelemetryTick() - StopRequested - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this CCP_LOG_CH( s_ch, "Stopping Telemetry Server" ); FrameMark; ++s_telemetryTick; From 8fbbc0b755e7dd24285f80cda6bf164e46b668d4 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 27 May 2026 11:39:30 +0000 Subject: [PATCH 26/33] Rename s_telemetryConfig back to s_config --- CcpTelemetry.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 89dd8b0..3788f0e 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -49,7 +49,7 @@ namespace { uint32_t s_telemetryTick = 0; - CcpTelemetryConfig s_telemetryConfig; + CcpTelemetryConfig s_config; MutexNameMap_t& GetMutexNameMap() { @@ -87,7 +87,7 @@ bool CcpTelemetryIsStarted() bool CcpMemoryProfilingIsEnabled() { - return s_telemetryConfig.trackMemoryAllocations; + return s_config.trackMemoryAllocations; } void CcpRegisterMutex( class CcpMutex& m, const char* owner, const char* name ) @@ -119,10 +119,10 @@ bool CcpStartTelemetry( const CcpTelemetryConfig& config ) return false; } - s_telemetryConfig = config; + s_config = config; s_telemetryTick = 1; CcpTelemetrySetActiveFiber( "" ); // to ensure that all our look-ups are correctly initialized -// CCP_LOG_CH( s_ch, "Starting profiler - %s - Root fiber is [Fiber %p]", s_telemetryConfig.applicationName.c_str(), t_activeFiber->c_str() ); +// CCP_LOG_CH( s_ch, "Starting profiler - %s - Root fiber is [Fiber %p]", s_config.applicationName.c_str(), t_activeFiber->c_str() ); s_profilerState.store( ProfilerState::StartRequested, std::memory_order_release ); return true; } @@ -150,7 +150,7 @@ void CcpTelemetryTick() if (TracyIsConnected) { CCP_LOG_CH( s_ch, "Telemetry server connected to Profiler" ); - TracySetProgramName( s_telemetryConfig.applicationName.c_str() ); + TracySetProgramName( s_config.applicationName.c_str() ); s_profilerState.store( ProfilerState::Started, std::memory_order_release ); s_profilerStartTime = std::chrono::steady_clock::now(); @@ -191,10 +191,10 @@ void CcpTelemetryTick() } } - if( s_telemetryConfig.captureDuration != std::chrono::milliseconds::zero() ) // Check if we have passed our timed sample time + if( s_config.captureDuration != std::chrono::milliseconds::zero() ) // Check if we have passed our timed sample time { auto timeSinceStart = std::chrono::steady_clock::now() - s_profilerStartTime; - if( timeSinceStart >= s_telemetryConfig.captureDuration ) + if( timeSinceStart >= s_config.captureDuration ) { CCP_LOG_CH( s_ch, "Finalizing timed profiler run" ); CcpStopTelemetry(); @@ -490,4 +490,4 @@ void CcpTelemetryZoneAddText( void* key, const char* text ) { } -#endif // CCP_TELEMETRY_ENABLED +#endif // CCP_TELEMETRY_ENABLED \ No newline at end of file From 41fdf6a3f60627c601bc5ab7c4827cec50bce6bc Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 27 May 2026 11:45:24 +0000 Subject: [PATCH 27/33] Fix wrong size being sent to Tracy in CcpPlatformCalloc --- CCPMemory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CCPMemory.cpp b/CCPMemory.cpp index 072dfb8..236a4d9 100644 --- a/CCPMemory.cpp +++ b/CCPMemory.cpp @@ -310,7 +310,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) #if ENABLE_TELEMETRY_MEMORY_TRACKING if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) { - TracySecureAlloc( p, size ); + TracySecureAlloc( p, bytes ); } #endif return p; From 52a428476eca7e4b333392e1dc68b3746088953f Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:44:55 +0000 Subject: [PATCH 28/33] Fix multiple `-Wformat-security` instances Not sure why the tests weren't able to correctly deduce that it's a string literal without `constexpr`. The occurrence in `Throw` was a valid concern, as that just blindly trusts the input, which the compiler cannot assume as always being a string literal. --- include/CCPLog.h | 2 +- tests/CCPLog.cpp | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/CCPLog.h b/include/CCPLog.h index a508db8..0727cee 100644 --- a/include/CCPLog.h +++ b/include/CCPLog.h @@ -187,7 +187,7 @@ CARBON_CORE_API const char* GetLastErrorMessage(); // Throws a std exception with string 'message' and logs out the 'message' inline void Throw( const char* message ) { - CCP_LOGERR( message ); + CCP_LOGERR( "%s", message ); CCP_LOGWARN( "Exception thrown" ); throw std::runtime_error( message ); } diff --git a/tests/CCPLog.cpp b/tests/CCPLog.cpp index eb48528..4fbfd67 100644 --- a/tests/CCPLog.cpp +++ b/tests/CCPLog.cpp @@ -42,7 +42,7 @@ class CCPLog : public ::testing::Test TEST_F ( CCPLog, TestCanLogSingleLine ) { CCP::RegisterLogEcho( LogTracker, CCP::LOGTYPE_INFO, true ); - const char *s = "One line"; + constexpr const char *s = "One line"; CCP_LOG(s); EXPECT_EQ( 1, logstack.size() ); @@ -52,7 +52,7 @@ TEST_F ( CCPLog, TestCanLogSingleLine ) TEST_F ( CCPLog, TestDefaultLogLevelIsInfo ) { CCP::RegisterLogEcho( LogTracker, CCP::LOGTYPE_INFO, true ); - const char *s = "One line"; + constexpr const char *s = "One line"; CCP_LOG(s); EXPECT_EQ( 1, logstack.size() ); @@ -116,15 +116,15 @@ TEST_F ( CCPLog, CanUnregisterCallbackHandler ) TEST_F ( CCPLog, GetLastErrorMessageReturnsLastError ) { - const char* expected = "Something has gone horribly wrong."; - CCP_LOGERR(expected); + constexpr const char* expected = "Something has gone horribly wrong."; + CCP_LOGERR( expected ); const char* actual = CCP::GetLastErrorMessage(); EXPECT_STREQ( expected, actual ); } TEST_F ( CCPLog, ThrowLastErrorThrowsAnError ) { - const char* error_str = "Something has gone horribly wrong."; + constexpr const char* error_str = "Something has gone horribly wrong."; CCP_LOGERR( error_str ); EXPECT_ANY_THROW( {CCP::ThrowLastError();} ); } @@ -133,7 +133,7 @@ TEST_F ( CCPLog, LogEnormousLine ) { CCP::RegisterLogEcho( LogTracker, CCP::LOGTYPE_INFO, true ); CCP::SetLogMainThreadId(); - const char* enormous_line = + constexpr const char* enormous_line = "Testing a really long log line. The log server can't handle log lines\n" "that are longer than 253 characters, so we need a test case for it.\n" "Somewhere along the line we need to split the log message into several\n" From 1434367e2f97a59b479c661eefe595af4150cb6a Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:47:35 +0000 Subject: [PATCH 29/33] Wrap Tracy memory API calls behind CcpTelemetry methods This keeps usage of the underlying profiling framework isolated. --- CCPMemory.cpp | 29 ++++++----------------------- CcpTelemetry.cpp | 17 ++++++++++++++++- include/CcpTelemetry.h | 2 ++ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/CCPMemory.cpp b/CCPMemory.cpp index 236a4d9..ab7ec90 100644 --- a/CCPMemory.cpp +++ b/CCPMemory.cpp @@ -290,10 +290,7 @@ static inline void* CcpPlatformMalloc( size_t size ) void* p = HeapAlloc( s_heap, 0, size ); UpdateCount( size ); #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) - { - TracySecureAlloc( p, size ); - } + CcpTelemetryTrackAllocation( p, size ); #endif return p; } @@ -308,10 +305,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) void* p = HeapAlloc( s_heap, HEAP_ZERO_MEMORY, bytes ); UpdateCount( bytes ); #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) - { - TracySecureAlloc( p, bytes ); - } + CcpTelemetryTrackAllocation( p, bytes ); #endif return p; } @@ -319,10 +313,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) static inline void CcpPlatformFree( void* p ) { #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) - { - TracySecureFree( p ); - } + CcpTelemetryTrackDeallocation( p ); #endif UpdateCount( p, false ); HeapFree( s_heap, 0, p ); @@ -345,9 +336,7 @@ static inline void* CcpPlatformMalloc( size_t size ) s_memuse += realSize; #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) { - TracySecureAlloc( p, realSize ); - } + CcpTelemetryTrackAllocation( p, realSize ); #endif } #if defined(__ANDROID__) @@ -369,10 +358,7 @@ static inline void* CcpPlatformCalloc( size_t items, size_t size ) s_memuse += realSize; #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) - { - TracySecureAlloc( p, realSize ); - } + CcpTelemetryTrackAllocation( p, realSize ); #endif } return p; @@ -384,10 +370,7 @@ static inline void CcpPlatformFree( void* p ) p = reinterpret_cast( p ) - 1; #endif #if ENABLE_TELEMETRY_MEMORY_TRACKING - if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) - { - TracySecureFree( p ); - } + CcpTelemetryTrackDeallocation( p ); #endif s_memuse -= CCPMSize( p ); free( p ); diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 3788f0e..5a68b2a 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -230,6 +230,21 @@ void CcpTelemetryTick() } } +void CcpTelemetryTrackAllocation( void* p, size_t size ) +{ + if ( CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) { + TracySecureAlloc( p, size ); + } +} + +void CcpTelemetryTrackDeallocation( void* p ) +{ + if ( p && CcpMemoryProfilingIsEnabled() && CcpTelemetryIsConnected() ) + { + TracySecureFree( p ); + } +} + uint32_t CcpTelemetryGetTickCount() { return s_telemetryTick; @@ -490,4 +505,4 @@ void CcpTelemetryZoneAddText( void* key, const char* text ) { } -#endif // CCP_TELEMETRY_ENABLED \ No newline at end of file +#endif // CCP_TELEMETRY_ENABLED diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 50146be..e229faf 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -97,4 +97,6 @@ CARBON_CORE_API void CcpTelemetryEnterZone( void* key, const char* name, const c CARBON_CORE_API void CcpTelemetryLeaveZone( void* key ); CARBON_CORE_API void CcpTelemetryZoneAddText( void* key, const char* text ); +void CcpTelemetryTrackAllocation( void*, size_t ); +void CcpTelemetryTrackDeallocation( void* ); #endif From b852e0f1f56f14ce2379ca0903c5259ca9008d2a Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 3 Jun 2026 12:08:04 +0000 Subject: [PATCH 30/33] Add ReStartAfterStop test Also: - Cleaned up unused/wrong tests - Added overload for SetUp() that keeps existing connection to TracyTestClient - Removed TODO statements --- tests/CcpTelemetry.cpp | 149 ++++++++++------------------------------- 1 file changed, 37 insertions(+), 112 deletions(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 312833f..5b90ba9 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -37,7 +37,12 @@ class CcpTelemetryTest : public ::testing::Test void SetUp() override { - fprintf( stderr, "CcpTelemetryTest::SetUp() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this + SetUp(true); + } + + void SetUp(bool doTestClientConnect) + { + fprintf( stderr, "CcpTelemetryTest::SetUp(%s) - Begin\n", doTestClientConnect ? "true" : "false" ); fflush( stderr ); CcpTelemetryConfig conf{ "Telemetry Tests" }; EXPECT_EQ( conf.captureDuration, std::chrono::milliseconds::zero() ); CcpStartTelemetry( conf ); @@ -45,34 +50,34 @@ class CcpTelemetryTest : public ::testing::Test // Tick until the profiler's listen socket is up. while( !TracyIsStarted ) { - fprintf( stderr, "CcpTelemetryTest::SetUp() - while( !TracyIsStarted ) - TickTelemetry()\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, "CcpTelemetryTest::SetUp(%s) - while( !TracyIsStarted ) - TickTelemetry()\n", doTestClientConnect ? "true" : "false" ); fflush( stderr ); TickTelemetry(); } // Connect on a background thread so this thread can keep ticking Tracy. // The handshake requires both sides to run concurrently: Tracy's worker // sends data and may block on Send() until the client reads it. - auto connectFuture = std::async( std::launch::async, [this] { - return m_tracyClient.Connect(); - } ); + auto connectFuture = doTestClientConnect + ? std::async( std::launch::async, [this] { return m_tracyClient.Connect(); } ) + : std::async( std::launch::deferred, [] { return true; } ); // Tick until CcpTelemetry recognises the connection and enters Started state. while( !CcpTelemetryIsConnected() ) { - fprintf( stderr, "CcpTelemetryTest::SetUp() - while( !CcpTelemetryIsConnected ) - TickTelemetry()\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, "CcpTelemetryTest::SetUp(%s) - while( !CcpTelemetryIsConnected ) - TickTelemetry()\n", doTestClientConnect ? "true" : "false" ); fflush( stderr ); TickTelemetry(); } ASSERT_TRUE( connectFuture.get() ) << "Could not connect to Tracy profiler"; - fprintf( stderr, "CcpTelemetryTest::SetUp() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, "CcpTelemetryTest::SetUp(%s) - End\n", doTestClientConnect ? "true" : "false" ); fflush( stderr ); } void TearDown() override { - fprintf( stderr, "CcpTelemetryTest::TearDown() - Begin\n" ); fflush( stderr ); // TODO: Debug info, remove this - // m_tracyClient.Disconnect(); // TODO: Removed the explicit call to Disconnect. + fprintf( stderr, "CcpTelemetryTest::TearDown() - Begin\n" ); fflush( stderr ); + // m_tracyClient.Disconnect(); // Remove explicit call to Disconnect() because current implementation does NOT call tracy::ShutdownProfiler(). CcpStopTelemetry(); - fprintf( stderr, "CcpTelemetryTest::TearDown() - End\n" ); fflush( stderr ); // TODO: Debug info, remove this + fprintf( stderr, "CcpTelemetryTest::TearDown() - End\n" ); fflush( stderr ); } void TickTelemetry( std::chrono::milliseconds duration = std::chrono::milliseconds( 500 ) ) @@ -159,7 +164,7 @@ TEST_F( CcpTelemetryTest, StackedZones ) EXPECT_TRUE( m_tracyClient.GetZones().empty() ); } -TEST_F( CcpTelemetryTest, StartConnectStopDisconnectProfiling ) +TEST_F( CcpTelemetryTest, ReStartAfterStop ) { // Setup takes care of connecting to the TracyTestClient EXPECT_TRUE( m_tracyClient.IsConnected() ); @@ -183,122 +188,42 @@ TEST_F( CcpTelemetryTest, StartConnectStopDisconnectProfiling ) TickTelemetry(); EXPECT_TRUE( m_tracyClient.GetZones().empty() ); EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); - EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); EXPECT_TRUE( CcpTelemetryIsConnected() ); EXPECT_TRUE( m_tracyClient.IsConnected() ); - // Now simulate "Stop Telemetry" operation. NOTE we're not disconnecting the TracyTestClient, only signaling the profiler to Stop + // Now simulate "Stop Telemetry" operation and Tick until we're in "Stopped" state CcpStopTelemetry(); + TickTelemetry(); // This processes the "StopRequested" state. + TickTelemetry(); // This processes the "Stopped" state. EXPECT_TRUE( m_tracyClient.IsConnected() ) << "Connection should still be true at this point because the TracyTestClient hasn't been disconnected"; - EXPECT_TRUE( TracyIsStarted ) << "Until we Tick TWICE, this should still be true (first for StopRequested, then for Stopped)"; - EXPECT_TRUE( TracyNoop ) << "Tracy should have a profiler available"; - EXPECT_TRUE( TracyIsConnected ) << "TracyTestClient should still be connected at this point"; - EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: Started, it should be StopRequested"; - - TickTelemetry(); // This should process the "StopRequested" state. - EXPECT_FALSE( m_tracyClient.IsConnected() ) << "Now that the TracyTestClient is handling the 'kQueueTerminate' and setting m_shutdown=true, this should be FALSE"; //"Connection should still be true at this point because the TracyTestClient hasn't been disconnected"; - EXPECT_TRUE( TracyIsStarted ) << "Until we Tick ONE MORE TIME, this should still be true (this is for StopRequested, next Tick is for Stopped)"; - EXPECT_TRUE( TracyNoop ) << "Tracy should have a profiler available"; - EXPECT_TRUE( TracyIsConnected ) << "TracyTestClient should still be connected at this point"; - EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: StopRequested, it should have transitioned to Stopped"; - - // TODO: Figure out why TracyIsStarted only becomes FALSE if m_tracyClient.Disconnect() is called, but NOT just because we called tracy::ShutdownProfiler() - - m_tracyClient.Disconnect(); // Disconnect the TracyTestClient, meaning TracyNoop should return FALSE (and TracyIsConnected a segfault because tracy::GetProfiler() is invalid) - - fprintf( stderr, "CC0\n" ); fflush( stderr ); // TODO: Debug info, remove this - TickTelemetry(); // This should process the "Stopped" state (2nd part of calling Stop Telemetry) - fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After TickTelemetry() #4\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_FALSE( m_tracyClient.IsConnected() ) << "By disconnecting the TracyTestClient, this should return false"; - fprintf( stderr, "CC1\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_FALSE( TracyNoop ) << "TracyNoop is returning false here, NOT because we called tracy::ShutdownProfiler(), but because we called m_tracyClient.Disconnect(). This is a bit surprising. <<<===="; - fprintf( stderr, "CC2\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_FALSE( TracyIsStarted ) << "Tracy should have fully stopped by now WHICH SHOULD have happened because of a call to tracy::ShutdownProfiler(), BUT is probably because of m_tracyClient.Disconnect() <<<===="; - fprintf( stderr, "CC3\n" ); fflush( stderr ); // TODO: Debug info, remove this - //EXPECT_FALSE( TracyIsConnected ) << "Tracy should NOT be connected, because we've called m_tracyClient.Disconnect()"; - fprintf( stderr, "CC4 - We can't call TracyIsConnected here, because it will end up with a SEH exception\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: Started"; - fprintf( stderr, "CC5\n" ); fflush( stderr ); // TODO: Debug info, remove this + EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should have changed: Started->StopRequested->Stopped"; -} - -TEST_F( CcpTelemetryTest, StartConnectStopProfiling ) -{ - // Setup takes care of connecting to the TracyTestClient - EXPECT_TRUE( m_tracyClient.IsConnected() ); + // Simulate a new call to StartTelemetry + SetUp( false ); + EXPECT_TRUE( m_tracyClient.IsConnected() ) << "Connection should still be true because the TracyTestClient hasn't never been disconnected"; + EXPECT_TRUE( CcpTelemetryIsStarted() ) << "Internal profiler state should have changed: Started->StopRequested->Stopped"; - static int key1 = 1001; - const std::string zoneName1{ "FirstZone" }; - CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); + // Emit a new Zone, on the 2nd Start and validate + static int key2 = 1002; + const std::string zoneName2{ "SecondZone" }; + CcpTelemetryEnterZone( &key2, zoneName2.c_str(), __FILE__, __LINE__ ); TickTelemetry(); - auto tracyZones = m_tracyClient.GetZones(); - auto pred = [&zoneName1]( const TracyTestClient::ZoneInfo& elem ) -> bool { - return elem.function == zoneName1; + auto tracyZones2ndStart = m_tracyClient.GetZones(); + auto pred2nd = [&zoneName2]( const TracyTestClient::ZoneInfo& elem ) -> bool { + return elem.function == zoneName2; }; - EXPECT_NE( tracyZones.end(), std::find_if( tracyZones.begin(), tracyZones.end(), pred ) ); - EXPECT_EQ( 1, tracyZones.size() ); - EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); - EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); + EXPECT_NE( tracyZones2ndStart.end(), std::find_if( tracyZones2ndStart.begin(), tracyZones2ndStart.end(), pred2nd ) ); + EXPECT_EQ( 1, tracyZones2ndStart.size() ); + EXPECT_EQ( 2, m_tracyClient.GetZoneBeginCount() ) << "The total Begin Zone count should be 2, even after Stop/Start"; + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "The total End Zone count should be 1 at this point, because of the FirstZone has ended"; - CcpTelemetryLeaveZone( &key1 ); + CcpTelemetryLeaveZone( &key2 ); TickTelemetry(); EXPECT_TRUE( m_tracyClient.GetZones().empty() ); - EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); - EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); + EXPECT_EQ( 2, m_tracyClient.GetZoneEndCount() ) << "The total End Zone count should be 2, FirstZone (before the Stop) and SecondZone from after the Stop/Start";; EXPECT_TRUE( CcpTelemetryIsConnected() ); EXPECT_TRUE( m_tracyClient.IsConnected() ); - - // Now simulate "Stop Telemetry" operation. NOTE we're not disconnecting the TracyTestClient, only signaling the profiler to Stop - CcpStopTelemetry(); - EXPECT_TRUE( m_tracyClient.IsConnected() ) << "Connection should still be true at this point because the TracyTestClient hasn't been disconnected"; - EXPECT_TRUE( TracyIsStarted ) << "Until we Tick TWICE, this should still be true (first for StopRequested, then for Stopped)"; - EXPECT_TRUE( TracyNoop ) << "Tracy should have a profiler available"; - EXPECT_TRUE( TracyIsConnected ) << "TracyTestClient should still be connected at this point"; - EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: Started, it should be StopRequested"; - - TickTelemetry(); // This should process the "StopRequested" state. - EXPECT_FALSE( m_tracyClient.IsConnected() ) << "Now that the TracyTestClient is handling the 'kQueueTerminate' and setting m_shutdown=true, this should be FALSE"; //"Connection should still be true at this point because the TracyTestClient hasn't been disconnected"; - EXPECT_TRUE( TracyIsStarted ) << "Until we Tick ONE MORE TIME, this should still be true (this is for StopRequested, next Tick is for Stopped)"; - EXPECT_TRUE( TracyNoop ) << "Tracy should have a profiler available"; - EXPECT_TRUE( TracyIsConnected ) << "TracyTestClient should still be connected at this point"; - EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: StopRequested, it should have transitioned to Stopped"; - - // TODO: Figure out why TracyIsStarted only becomes FALSE if m_tracyClient.Disconnect() is called, but NOT just because we called tracy::ShutdownProfiler() - - // m_tracyClient.Disconnect(); // NOT calling m_tracyClient.Disconnect() here on purpose in the TracyTestClient. - // We should be able to Stop Profiling, by calling tracy::ShutdownProfiler(), without having to disconnect the Tracy GUI or TracyTestClient first - - fprintf( stderr, "CC0\n" ); fflush( stderr ); // TODO: Debug info, remove this - TickTelemetry(std::chrono::milliseconds(1000)); // This should process the "Stopped" state (2nd part of calling Stop Telemetry) - TickTelemetry(std::chrono::milliseconds(1000)); // Giving it more time to flush the queue. - fprintf( stderr, "CcpTelemetryTest::ReconnectAfterStop() - After TickTelemetry() #4\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_FALSE( m_tracyClient.IsConnected() ) << "By disconnecting the TracyTestClient (because of the 'kQueueTerminate' handling setting m_shutdown=true), this should return false"; - fprintf( stderr, "CC1\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_TRUE( TracyNoop ) << "For some reason, even though we've Stopped Telemetry, Tracy is still available, this is ODD. <<<===="; - fprintf( stderr, "CC2\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_FALSE( TracyIsStarted ) << "I would have thought that this should be FALSE here, because we've called tracy::GetProfiler().RequestShutdown() -> which in turn should on the next tick process Stopped state and call ShutdownProfiler(). BUT this never becomes true [TracyIsStarted && tracy::GetProfiler().HasShutdownFinished()] <<<====="; - fprintf( stderr, "CC3\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_FALSE( TracyIsConnected ) << "Tracy should NOT be connected, because the TracyTestClient should have been disconnected because of kQueueTerminate"; - fprintf( stderr, "CC4\n" ); fflush( stderr ); // TODO: Debug info, remove this - EXPECT_FALSE( CcpTelemetryIsStarted() ) << "Internal profiler state should no longer be: Started"; - fprintf( stderr, "CC5\n" ); fflush( stderr ); // TODO: Debug info, remove this } -TEST_F( CcpTelemetryTest, StartConnectDisconnectProfiling ) -{ - -} - -TEST_F( CcpTelemetryTest, StartConnectDisconnectStopProfiling ) -{ - -} - -TEST_F( CcpTelemetryTest, ReconnectAfterStop ) -{ - // Now simulate "Start Telemetry" operation again... - - // Enter and Leave a different zone... -} \ No newline at end of file From 4ccb20067321184821c5ae0569f084071b30fbdd Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 3 Jun 2026 12:36:46 +0000 Subject: [PATCH 31/33] Remove fprintf statements from tests/CcpTelemetry.cpp --- tests/CcpTelemetry.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 5b90ba9..9467f26 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -42,7 +42,6 @@ class CcpTelemetryTest : public ::testing::Test void SetUp(bool doTestClientConnect) { - fprintf( stderr, "CcpTelemetryTest::SetUp(%s) - Begin\n", doTestClientConnect ? "true" : "false" ); fflush( stderr ); CcpTelemetryConfig conf{ "Telemetry Tests" }; EXPECT_EQ( conf.captureDuration, std::chrono::milliseconds::zero() ); CcpStartTelemetry( conf ); @@ -50,7 +49,6 @@ class CcpTelemetryTest : public ::testing::Test // Tick until the profiler's listen socket is up. while( !TracyIsStarted ) { - fprintf( stderr, "CcpTelemetryTest::SetUp(%s) - while( !TracyIsStarted ) - TickTelemetry()\n", doTestClientConnect ? "true" : "false" ); fflush( stderr ); TickTelemetry(); } @@ -64,20 +62,16 @@ class CcpTelemetryTest : public ::testing::Test // Tick until CcpTelemetry recognises the connection and enters Started state. while( !CcpTelemetryIsConnected() ) { - fprintf( stderr, "CcpTelemetryTest::SetUp(%s) - while( !CcpTelemetryIsConnected ) - TickTelemetry()\n", doTestClientConnect ? "true" : "false" ); fflush( stderr ); TickTelemetry(); } ASSERT_TRUE( connectFuture.get() ) << "Could not connect to Tracy profiler"; - fprintf( stderr, "CcpTelemetryTest::SetUp(%s) - End\n", doTestClientConnect ? "true" : "false" ); fflush( stderr ); } void TearDown() override { - fprintf( stderr, "CcpTelemetryTest::TearDown() - Begin\n" ); fflush( stderr ); // m_tracyClient.Disconnect(); // Remove explicit call to Disconnect() because current implementation does NOT call tracy::ShutdownProfiler(). CcpStopTelemetry(); - fprintf( stderr, "CcpTelemetryTest::TearDown() - End\n" ); fflush( stderr ); } void TickTelemetry( std::chrono::milliseconds duration = std::chrono::milliseconds( 500 ) ) From 0ed5a38b9f4bafbe62cc06c6aa632887e41a8b36 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:01:38 +0000 Subject: [PATCH 32/33] Add calls to base Test class SetUp() and TearDown() functions. --- tests/CcpTelemetry.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 9467f26..54b9cc8 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -37,6 +37,7 @@ class CcpTelemetryTest : public ::testing::Test void SetUp() override { + ::testing::Test::SetUp(); SetUp(true); } @@ -72,6 +73,7 @@ class CcpTelemetryTest : public ::testing::Test { // m_tracyClient.Disconnect(); // Remove explicit call to Disconnect() because current implementation does NOT call tracy::ShutdownProfiler(). CcpStopTelemetry(); + ::testing::Test::TearDown(); } void TickTelemetry( std::chrono::milliseconds duration = std::chrono::milliseconds( 500 ) ) From bbe02bbc00fd36368c0f7302e1695f197f53a331 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:13:24 +0000 Subject: [PATCH 33/33] Use distinct FiberNames in TestFiberSwitching test --- tests/CcpTelemetry.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 54b9cc8..27b166c 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -87,31 +87,37 @@ class CcpTelemetryTest : public ::testing::Test } const std::string expectedNoFiber; - const std::string expectedFiberName{ "TestFiber" }; - const std::string expectedFiberName2{ "TestFiber" }; + const std::string expectedFiberName1{ "TestFiber1" }; + const std::string expectedFiberName2{ "TestFiber2" }; TracyTestClient m_tracyClient; }; TEST_F( CcpTelemetryTest, TestFiberSwitching ) { - CcpTelemetrySetActiveFiber( expectedFiberName ); + CcpTelemetrySetActiveFiber( expectedFiberName1 ); const auto& observedFiberName1 = CcpTelemetryGetActiveFiber(); - EXPECT_EQ( observedFiberName1, expectedFiberName ); + EXPECT_EQ( observedFiberName1, expectedFiberName1 ); + + // Switching to a new name CcpTelemetrySetActiveFiber( expectedFiberName2 ); const auto& observedFiberName2 = CcpTelemetryGetActiveFiber(); EXPECT_EQ( observedFiberName2, expectedFiberName2 ); + + // Switching back to the original name + CcpTelemetrySetActiveFiber( expectedFiberName1 ); const auto& observedFiberName3 = CcpTelemetryGetActiveFiber(); - CcpTelemetrySetActiveFiber( expectedFiberName ); EXPECT_EQ( observedFiberName1.c_str(), observedFiberName3.c_str() ); + + // Switching to the "Root name" (no name) CcpTelemetrySetActiveFiber( "" ); EXPECT_EQ( CcpTelemetryGetActiveFiber(), expectedNoFiber ); } TEST_F( CcpTelemetryTest, RemovingActiveFiberClearsIt ) { - CcpTelemetrySetActiveFiber( expectedFiberName ); - CcpTelemetryRemoveFiber( expectedFiberName ); + CcpTelemetrySetActiveFiber( expectedFiberName1 ); + CcpTelemetryRemoveFiber( expectedFiberName1 ); EXPECT_EQ( CcpTelemetryGetActiveFiber(), expectedNoFiber ); }