From cf74c37192f919f676b2c7c669f3f4bcc82dfd5b Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 9 Jul 2026 16:32:08 +0000 Subject: [PATCH 01/40] Add RegisterCaptureMask() functionality Change contains: - Ability to call RegisterCaptureMask() for a given name and an optional Telemetry Zone display color - Automatically assign a color if one is not provided Part of: https://fenriscreations.atlassian.net/browse/PLAT-11474 --- CMakeLists.txt | 1 + CcpColorConstants.cpp | 152 +++++++++++++++++++++++++ CcpTelemetry.cpp | 102 +++++++++++++++++ include/CcpColorConstants.h | 214 ++++++++++++++++++++++++++++++++++++ include/CcpTelemetry.h | 13 +++ tests/CMakeLists.txt | 1 + tests/CcpColorConstants.cpp | 60 ++++++++++ tests/CcpTelemetry.cpp | 48 ++++++++ 8 files changed, 591 insertions(+) create mode 100644 CcpColorConstants.cpp create mode 100644 tests/CcpColorConstants.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c0ec58a..e642bba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ set(SRC_FILES CcpCore.cpp CCPAssert.cpp CCPCallstack.cpp + CcpColorConstants.cpp CCPDefines.cpp CcpFileUtils.cpp CCPHash.cpp diff --git a/CcpColorConstants.cpp b/CcpColorConstants.cpp new file mode 100644 index 0000000..8202498 --- /dev/null +++ b/CcpColorConstants.cpp @@ -0,0 +1,152 @@ +// Copyright © 2026 Fenris Creations. + +#include "include/CcpColorConstants.h" + +std::string CcpColorToString( Color color ) +{ + // For colors sharing the same RGB value (Aqua/Cyan, Fuchsia/Magenta, and the + // various *Gray/*Grey pairs), the first name declared in the enum wins. + switch( color ) + { + case Color::AliceBlue: return "AliceBlue"; + case Color::AntiqueWhite: return "AntiqueWhite"; + case Color::Aqua: return "Aqua"; // == Cyan + case Color::Aquamarine: return "Aquamarine"; + case Color::Azure: return "Azure"; + case Color::Beige: return "Beige"; + case Color::Bisque: return "Bisque"; + case Color::Black: return "Black"; + case Color::BlanchedAlmond: return "BlanchedAlmond"; + case Color::Blue: return "Blue"; + case Color::BlueViolet: return "BlueViolet"; + case Color::Brown: return "Brown"; + case Color::BurlyWood: return "BurlyWood"; + case Color::CadetBlue: return "CadetBlue"; + case Color::Chartreuse: return "Chartreuse"; + case Color::Chocolate: return "Chocolate"; + case Color::Coral: return "Coral"; + case Color::CornflowerBlue: return "CornflowerBlue"; + case Color::Cornsilk: return "Cornsilk"; + case Color::Crimson: return "Crimson"; + case Color::DarkBlue: return "DarkBlue"; + case Color::DarkCyan: return "DarkCyan"; + case Color::DarkGoldenrod: return "DarkGoldenrod"; + case Color::DarkGray: return "DarkGray"; // == DarkGrey + case Color::DarkGreen: return "DarkGreen"; + case Color::DarkKhaki: return "DarkKhaki"; + case Color::DarkMagenta: return "DarkMagenta"; + case Color::DarkOliveGreen: return "DarkOliveGreen"; + case Color::DarkOrange: return "DarkOrange"; + case Color::DarkOrchid: return "DarkOrchid"; + case Color::DarkRed: return "DarkRed"; + case Color::DarkSalmon: return "DarkSalmon"; + case Color::DarkSeaGreen: return "DarkSeaGreen"; + case Color::DarkSlateBlue: return "DarkSlateBlue"; + case Color::DarkSlateGray: return "DarkSlateGray"; // == DarkSlateGrey + case Color::DarkTurquoise: return "DarkTurquoise"; + case Color::DarkViolet: return "DarkViolet"; + case Color::DeepPink: return "DeepPink"; + case Color::DeepSkyBlue: return "DeepSkyBlue"; + case Color::DimGray: return "DimGray"; // == DimGrey + case Color::DodgerBlue: return "DodgerBlue"; + case Color::FireBrick: return "FireBrick"; + case Color::FloralWhite: return "FloralWhite"; + case Color::ForestGreen: return "ForestGreen"; + case Color::Fuchsia: return "Fuchsia"; // == Magenta + case Color::Gainsboro: return "Gainsboro"; + case Color::GhostWhite: return "GhostWhite"; + case Color::Gold: return "Gold"; + case Color::Goldenrod: return "Goldenrod"; + case Color::Gray: return "Gray"; // == Grey + case Color::Green: return "Green"; + case Color::GreenYellow: return "GreenYellow"; + case Color::Honeydew: return "Honeydew"; + case Color::HotPink: return "HotPink"; + case Color::IndianRed: return "IndianRed"; + case Color::Indigo: return "Indigo"; + case Color::Ivory: return "Ivory"; + case Color::Khaki: return "Khaki"; + case Color::Lavender: return "Lavender"; + case Color::LavenderBlush: return "LavenderBlush"; + case Color::LawnGreen: return "LawnGreen"; + case Color::LemonChiffon: return "LemonChiffon"; + case Color::LightBlue: return "LightBlue"; + case Color::LightCoral: return "LightCoral"; + case Color::LightCyan: return "LightCyan"; + case Color::LightGoldenrodYellow: return "LightGoldenrodYellow"; + case Color::LightGray: return "LightGray"; // == LightGrey + case Color::LightGreen: return "LightGreen"; + case Color::LightPink: return "LightPink"; + case Color::LightSalmon: return "LightSalmon"; + case Color::LightSeaGreen: return "LightSeaGreen"; + case Color::LightSkyBlue: return "LightSkyBlue"; + case Color::LightSlateGray: return "LightSlateGray"; // == LightSlateGrey + case Color::LightSteelBlue: return "LightSteelBlue"; + case Color::LightYellow: return "LightYellow"; + case Color::Lime: return "Lime"; + case Color::LimeGreen: return "LimeGreen"; + case Color::Linen: return "Linen"; + case Color::Maroon: return "Maroon"; + case Color::MediumAquamarine: return "MediumAquamarine"; + case Color::MediumBlue: return "MediumBlue"; + case Color::MediumOrchid: return "MediumOrchid"; + case Color::MediumPurple: return "MediumPurple"; + case Color::MediumSeaGreen: return "MediumSeaGreen"; + case Color::MediumSlateBlue: return "MediumSlateBlue"; + case Color::MediumSpringGreen: return "MediumSpringGreen"; + case Color::MediumTurquoise: return "MediumTurquoise"; + case Color::MediumVioletRed: return "MediumVioletRed"; + case Color::MidnightBlue: return "MidnightBlue"; + case Color::MintCream: return "MintCream"; + case Color::MistyRose: return "MistyRose"; + case Color::Moccasin: return "Moccasin"; + case Color::NavajoWhite: return "NavajoWhite"; + case Color::Navy: return "Navy"; + case Color::OldLace: return "OldLace"; + case Color::Olive: return "Olive"; + case Color::OliveDrab: return "OliveDrab"; + case Color::Orange: return "Orange"; + case Color::OrangeRed: return "OrangeRed"; + case Color::Orchid: return "Orchid"; + case Color::PaleGoldenrod: return "PaleGoldenrod"; + case Color::PaleGreen: return "PaleGreen"; + case Color::PaleTurquoise: return "PaleTurquoise"; + case Color::PaleVioletRed: return "PaleVioletRed"; + case Color::PapayaWhip: return "PapayaWhip"; + case Color::PeachPuff: return "PeachPuff"; + case Color::Peru: return "Peru"; + case Color::Pink: return "Pink"; + case Color::Plum: return "Plum"; + case Color::PowderBlue: return "PowderBlue"; + case Color::Purple: return "Purple"; + case Color::RebeccaPurple: return "RebeccaPurple"; + case Color::Red: return "Red"; + case Color::RosyBrown: return "RosyBrown"; + case Color::RoyalBlue: return "RoyalBlue"; + case Color::SaddleBrown: return "SaddleBrown"; + case Color::Salmon: return "Salmon"; + case Color::SandyBrown: return "SandyBrown"; + case Color::SeaGreen: return "SeaGreen"; + case Color::SeaShell: return "SeaShell"; + case Color::Sienna: return "Sienna"; + case Color::Silver: return "Silver"; + case Color::SkyBlue: return "SkyBlue"; + case Color::SlateBlue: return "SlateBlue"; + case Color::SlateGray: return "SlateGray"; // == SlateGrey + case Color::Snow: return "Snow"; + case Color::SpringGreen: return "SpringGreen"; + case Color::SteelBlue: return "SteelBlue"; + case Color::Tan: return "Tan"; + case Color::Teal: return "Teal"; + case Color::Thistle: return "Thistle"; + case Color::Tomato: return "Tomato"; + case Color::Turquoise: return "Turquoise"; + case Color::Violet: return "Violet"; + case Color::Wheat: return "Wheat"; + case Color::White: return "White"; + case Color::WhiteSmoke: return "WhiteSmoke"; + case Color::Yellow: return "Yellow"; + case Color::YellowGreen: return "YellowGreen"; + default: return "ColorNotFound"; + } +} diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 72a761d..f3fa30d 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -1,9 +1,11 @@ // Copyright © 2013 CCP ehf. +#include #include #include #include "include/CCPAssert.h" +#include "include/CcpMutex.h" #include "include/CcpTelemetry.h" #include "include/CcpTime.h" @@ -60,6 +62,17 @@ typedef TrackableStdMap> MutexName typedef TrackableStdMap ThreadNameMap_t; typedef TrackableStdVector> EventHandlerVector_t; +// ------------------------------- +// CaptureMask specifics: +// ------------------------------- +struct CaptureMaskEntry +{ + uint64_t maskBit{0}; + Color color{Color::White}; +}; + +typedef TrackableStdMap CaptureMaskMap_t; + namespace { uint32_t s_telemetryTick = 0; @@ -83,6 +96,85 @@ namespace static EventHandlerVector_t s_eventHandlers( "CcpTelemetry/s_eventHandlers" ); return s_eventHandlers; } + + // ------------------------------- + // CaptureMask specifics: + // ------------------------------- + CcpMutex s_captureMaskMutex( "CcpTelemetry", "CaptureMaskMutex" ); + + uint64_t s_allocatedCaptureMaskBits = 0; + + uint64_t RegisterCaptureMask( const std::string& name, std::optional color ) + { + static CaptureMaskMap_t s_captureMasks( "CcpTelemetry/s_captureMasks" ); + + // Make sure to guard write access to both s_captureMasks and s_allocatedCaptureMaskBits + CcpAutoMutex lock( s_captureMaskMutex ); + + if( s_allocatedCaptureMaskBits == 0 ) + { + // The default capture masks (used by components that haven't added CaptureMask support yet) + // must be registered and available from the start. + s_captureMasks["general"] = CaptureMaskEntry{ TMCM_GENERAL, Color::SteelBlue }; + s_captureMasks["cpp"] = CaptureMaskEntry{ TMCM_CPP, Color::Yellow }; + s_allocatedCaptureMaskBits = TMCM_GENERAL | TMCM_CPP; + } + + if( name.empty() ) + { + CCP_LOGERR_CH( s_ch, "Cannot register a capture mask without a name" ); + return 0; + } + + std::string lowerName( name ); + std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); + + // Explicitly allow change of color on an existing captureMask entry + auto existing = s_captureMasks.find( lowerName ); + if( existing != s_captureMasks.end() ) + { + if( color ) + { + existing->second.color = *color; + } + return existing->second.maskBit; + } + + if( ~s_allocatedCaptureMaskBits == 0 ) + { + CCP_LOGERR_CH( s_ch, "Cannot register capture mask '%s' - all 64 capture mask bits are already in use", lowerName.c_str() ); + return 0; + } + + // Allocate the lowest free bit + const uint64_t maskBit = ~s_allocatedCaptureMaskBits & ( s_allocatedCaptureMaskBits + 1 ); + + if( !color ) + { + std::vector existingColors; + existingColors.reserve( s_captureMasks.size() ); + for( const auto& entry : s_captureMasks ) + { + existingColors.push_back( entry.second.color ); + } + color = CcpColor::PickDistinctColor( existingColors ); + } + + s_captureMasks[lowerName] = CaptureMaskEntry{ maskBit, *color }; + s_allocatedCaptureMaskBits |= maskBit; + CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", lowerName.c_str(), static_cast( maskBit ), CcpColorToString( *color ).c_str() ); + return maskBit; + } +} + +uint64_t CcpRegisterCaptureMask( const std::string& name ) +{ + return RegisterCaptureMask( name, std::nullopt ); +} + +uint64_t CcpRegisterCaptureMask( const std::string& name, Color color ) +{ + return RegisterCaptureMask( name, color ); } bool CcpTelemetryIsConnected() @@ -481,6 +573,16 @@ void CcpRegisterThread( CcpThreadId_t threadId, const char* name ) { } +uint64_t CcpRegisterCaptureMask( const std::string& ) +{ + return 0; +} + +uint64_t CcpRegisterCaptureMask( const std::string&, Color ) +{ + return 0; +} + bool CcpStartTelemetry( const char* server, int connectionType, uint32_t maxThreadCount ) { return false; diff --git a/include/CcpColorConstants.h b/include/CcpColorConstants.h index 219625f..acdddee 100644 --- a/include/CcpColorConstants.h +++ b/include/CcpColorConstants.h @@ -1,6 +1,15 @@ +// Copyright © 2026 Fenris Creations. + +#pragma once #ifndef CCP_COLOR_CONSTANTS_H #define CCP_COLOR_CONSTANTS_H +#include +#include +#include + +#include "carbon_core_export.h" + // Useful RGB color constants // Initially based on the type of CSS standard, see https://www.w3.org/TR/css-color-4/#named-colors. enum class Color : uint32_t @@ -155,4 +164,209 @@ enum class Color : uint32_t YellowGreen = 0x9acd32, }; +// Return the name of the color. +// For colors with the same RGB value (e.g. Aqua/Cyan, Gray/Grey), +// the first name declared in Color enum class is returned. +CARBON_CORE_API std::string CcpColorToString( Color color ); + +namespace CcpColor +{ +// Decompose a Color into its 8-bit red, green and blue components. +static inline void ToRgb( Color color, int& r, int& g, int& b ) +{ + const uint32_t value = static_cast( color ); + r = static_cast( ( value >> 16 ) & 0xff ); + g = static_cast( ( value >> 8 ) & 0xff ); + b = static_cast( value & 0xff ); +} + +// Find the squared Euclidean distance between two colors in RGB space. +static inline int64_t GetSquaredDistance( Color lhs, Color rhs ) +{ + int r1, g1, b1; + int r2, g2, b2; + ToRgb( lhs, r1, g1, b1 ); + ToRgb( rhs, r2, g2, b2 ); + const int64_t dr = r1 - r2; + const int64_t dg = g1 - g2; + const int64_t db = b1 - b2; + return dr * dr + dg * dg + db * db; +} + +// All named colors, used as candidates when picking a display color. +// NOTE: +// - Remove a color from this list if you don't want it to be selected +// when automatically picking a display color for a new CaptureMask entry. +static constexpr Color s_awailableNamedColors[] = { + //Color::AliceBlue, + Color::AntiqueWhite, + Color::Aqua /* == Cyan */, + Color::Aquamarine, + //Color::Azure, + Color::Beige, + Color::Bisque, + //Color::Black, + Color::BlanchedAlmond, + Color::Blue, + Color::BlueViolet, + Color::Brown, + Color::BurlyWood, + Color::CadetBlue, + Color::Chartreuse, + Color::Chocolate, + Color::Coral, + Color::CornflowerBlue, + Color::Cornsilk, + Color::Crimson, + Color::DarkBlue, + Color::DarkCyan, + Color::DarkGoldenrod, + //Color::DarkGray /* == DarkGrey */, + Color::DarkGreen, + Color::DarkKhaki, + Color::DarkMagenta, + Color::DarkOliveGreen, + Color::DarkOrange, + Color::DarkOrchid, + Color::DarkRed, + Color::DarkSalmon, + Color::DarkSeaGreen, + Color::DarkSlateBlue, + Color::DarkSlateGray /* == DarkSlateGrey */, + Color::DarkTurquoise, + Color::DarkViolet, + Color::DeepPink, + Color::DeepSkyBlue, + //Color::DimGray /* == DimGrey */, + Color::DodgerBlue, + Color::FireBrick, + //Color::FloralWhite, + Color::ForestGreen, + Color::Fuchsia /* == Magenta */, + Color::Gainsboro, + //Color::GhostWhite, + Color::Gold, + Color::Goldenrod, + //Color::Gray /* == Grey */, + Color::Green, + Color::GreenYellow, + Color::Honeydew, + Color::HotPink, + Color::IndianRed, + Color::Indigo, + //Color::Ivory, + Color::Khaki, + Color::Lavender, + Color::LavenderBlush, + Color::LawnGreen, + Color::LemonChiffon, + Color::LightBlue, + Color::LightCoral, + Color::LightCyan, + Color::LightGoldenrodYellow, + Color::LightGray /* == LightGrey */, + Color::LightGreen, + Color::LightPink, + Color::LightSalmon, + Color::LightSeaGreen, + Color::LightSkyBlue, + Color::LightSlateGray /* == LightSlateGrey */, + Color::LightSteelBlue, + Color::LightYellow, + Color::Lime, + Color::LimeGreen, + Color::Linen, + Color::Maroon, + Color::MediumAquamarine, + Color::MediumBlue, + Color::MediumOrchid, + Color::MediumPurple, + Color::MediumSeaGreen, + Color::MediumSlateBlue, + Color::MediumSpringGreen, + Color::MediumTurquoise, + Color::MediumVioletRed, + Color::MidnightBlue, + //Color::MintCream, + Color::MistyRose, + Color::Moccasin, + Color::NavajoWhite, + Color::Navy, + Color::OldLace, + Color::Olive, + Color::OliveDrab, + Color::Orange, + Color::OrangeRed, + Color::Orchid, + Color::PaleGoldenrod, + Color::PaleGreen, + Color::PaleTurquoise, + Color::PaleVioletRed, + Color::PapayaWhip, + Color::PeachPuff, + Color::Peru, + Color::Pink, + Color::Plum, + Color::PowderBlue, + Color::Purple, + Color::RebeccaPurple, + Color::Red, + Color::RosyBrown, + Color::RoyalBlue, + Color::SaddleBrown, + Color::Salmon, + Color::SandyBrown, + Color::SeaGreen, + //Color::SeaShell, + Color::Sienna, + Color::Silver, + Color::SkyBlue, + Color::SlateBlue, + Color::SlateGray /* == SlateGrey */, + //Color::Snow, + Color::SpringGreen, + Color::SteelBlue, + Color::Tan, + Color::Teal, + Color::Thistle, + Color::Tomato, + Color::Turquoise, + Color::Violet, + Color::Wheat, + //Color::White, + //Color::WhiteSmoke, + Color::Yellow, + Color::YellowGreen, +}; + +// Pick a display color from the s_awailableNamedColors list above +// that are reasonably distinct from incoming existingColors. +// Candidates are scored by their distance to existingColors and +// the color furthest away wins. +static inline Color PickDistinctColor( const std::vector& existingColors ) +{ + Color bestColor = Color::SteelBlue; + int64_t bestScore = -1; + for( const Color candidate : s_awailableNamedColors ) + { + int64_t minDistance = INT64_MAX; + for( const Color existing : existingColors ) + { + const int64_t dist = GetSquaredDistance( candidate, existing ); + if( dist < minDistance ) + { + minDistance = dist; + } + } + + if( minDistance > bestScore ) + { + bestScore = minDistance; + bestColor = candidate; + } + } + return bestColor; +} +} + #endif diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 4d1cbeb..4cffac0 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -33,6 +33,19 @@ CARBON_CORE_API void CcpRegisterThread( CcpThreadId_t threadId, const char* name ); +// --------------------------------------------------------------------------- +// CaptureMasks: +// - CaptureMask(s) are used to determine if a given Zone should be emitted to +// Telemetry tracking or not based on its origin, i.e. carbon component. +// - Each carbon component, core/blue/scheduler/etc..., will register for a +// CaptureMask with their chosen "component display name" and be assigned an +// available CaptureMask bit from a 64bit integer mask. +// - A Telemetry display color is also associated with the chosen CaptureMask bit, +// either manually or automatically allocated based on "best available fit". +// --------------------------------------------------------------------------- +CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name ); +CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, Color color ); + struct CcpTelemetryConfig { std::string applicationName; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 293faf5..3ddef7f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,6 +7,7 @@ find_package(lz4 CONFIG REQUIRED) add_executable(CcpCoreTest CcpAtomic.cpp CCPCallstack.cpp + CcpColorConstants.cpp CcpCoreTest.cpp CCPHash.cpp CcpFileUtils.cpp diff --git a/tests/CcpColorConstants.cpp b/tests/CcpColorConstants.cpp new file mode 100644 index 0000000..9f11d48 --- /dev/null +++ b/tests/CcpColorConstants.cpp @@ -0,0 +1,60 @@ +// Copyright © 2026 Fenris Creations. + + +#include + +#include +#include +#include + +#include + + +TEST( CcpColorConstantsTest, PickedColorIsUnused ) +{ + std::vector existing{ Color::SteelBlue, Color::Yellow }; + for( int i = 0; i < 62; ++i ) + { + const Color picked = CcpColor::PickDistinctColor( existing ); + EXPECT_EQ( existing.end(), std::find( existing.begin(), existing.end(), picked ) ); + existing.push_back( picked ); + + // TODO: Remove this once visually validated. + // Print out selected colors for manual checking + std::fprintf( stdout, + "[%2d]: %s - (0x%06x)\n", + i, + CcpColorToString( picked ).c_str(), + static_cast( picked ) ); + } + std::fflush( stdout ); +} + +TEST( CcpColorConstantsTest, PickedColorIsDistinctFromExisting ) +{ + // With a single existing color, the pick should be far away from it in RGB space, + // not merely a slightly different shade. + const std::vector existingRed{ Color::Red }; + const Color pickedVsRed = CcpColor::PickDistinctColor( existingRed ); + auto distPickedVsRed = CcpColor::GetSquaredDistance( pickedVsRed, Color::Red ); + + std::fprintf( stdout, + "[0x%06x] '%s' vs Picked color: [0x%06x] '%s' - distance = %lld \n", + Color::Red, CcpColorToString(Color::Red).c_str(), + pickedVsRed, CcpColorToString( pickedVsRed ).c_str(), + distPickedVsRed ); + std::fflush( stdout ); + EXPECT_GT( distPickedVsRed, 128 * 128 ); + + const std::vector existingBlue{ Color::Blue }; + const Color pickedVsBlue = CcpColor::PickDistinctColor( existingBlue ); + auto distPickedVsBlue = CcpColor::GetSquaredDistance( pickedVsBlue, Color::Blue ); + + std::fprintf( stdout, + "[0x%06x] '%s' vs Picked color: [0x%06x] '%s' - distance = %lld \n", + Color::Blue, CcpColorToString(Color::Blue).c_str(), + pickedVsBlue, CcpColorToString( pickedVsBlue ).c_str(), + distPickedVsBlue ); + std::fflush( stdout ); + EXPECT_GT( distPickedVsBlue, 128 * 128 ); +} diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index c3448db..cca7f15 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -539,3 +539,51 @@ TEST_F( CcpTelemetryTest, CcpSemaphoreTimedWaitTimesOut ) EXPECT_EQ( 1, lockInfo.obtainCount ); EXPECT_EQ( 0, lockInfo.releaseCount ); } + +// --------------------------------------------------------------------------- +// CaptureMask tests: +// --------------------------------------------------------------------------- +bool IsSCaptureMaskSingleBit( uint64_t mask ) +{ + return mask != 0 && ( mask & ( mask - 1 ) ) == 0; +} + +TEST_F( CcpTelemetryTest, CaptureMaskRegisterAllocatesSingleFreeBit ) +{ + const uint64_t maskBit = CcpRegisterCaptureMask( "capturemasktest_component_a" ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBit ) ); + // TMCM_GENERAL and TMCM_CPP are registered as the default CaptureMasks, so their + // bits must never be handed out to other components. + EXPECT_EQ( 0, maskBit & ( TMCM_GENERAL | TMCM_CPP ) ); +} + +TEST_F( CcpTelemetryTest, CaptureMaskRegisterIsCaseInsensitive ) +{ + const uint64_t firstBit = CcpRegisterCaptureMask( "CaptureMaskTest_Component_B" ); + const uint64_t secondBit = CcpRegisterCaptureMask( "capturemasktest_component_b" ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( firstBit ) ); + EXPECT_EQ( firstBit, secondBit ); +} + +TEST_F( CcpTelemetryTest, CaptureMaskRegisterDistinctBitPerName ) +{ + const uint64_t maskBitC = CcpRegisterCaptureMask( "capturemasktest_component_c" ); + const uint64_t maskBitD = CcpRegisterCaptureMask( "capturemasktest_component_d", Color::Tomato ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBitC ) ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBitD ) ); + EXPECT_NE( maskBitC, maskBitD ); +} + +TEST_F( CcpTelemetryTest, CaptureMaskRegisterRejectEmpty ) +{ + EXPECT_EQ( 0, CcpRegisterCaptureMask( "" ) ); +} + +TEST_F( CcpTelemetryTest, CaptureMaskDefaultsAreRegistered ) +{ + // TODO: Change this test to check values from the GetCaptureMasks() function once it's ready + EXPECT_EQ( static_cast( TMCM_GENERAL ), CcpRegisterCaptureMask( "general", Color::Black ) ); + EXPECT_EQ( static_cast( TMCM_CPP ), CcpRegisterCaptureMask( "cpp" ) ); + EXPECT_EQ( static_cast( TMCM_GENERAL ), CcpRegisterCaptureMask( "GENERAL", Color::White ) ); +} + From d45f9eeec98982444e58644d54a618f55990dd84 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:32:37 +0000 Subject: [PATCH 02/40] Fix errors after Color=>CcpColor name change Changed: - enum class Color => CcpColor - namespace CcpColor => ColorUtil --- CcpColorConstants.cpp | 280 ++++++++++++++++----------------- CcpTelemetry.cpp | 14 +- include/CcpColorConstants.h | 300 ++++++++++++++++++------------------ include/CcpTelemetry.h | 2 +- tests/CcpColorConstants.cpp | 20 +-- tests/CcpTelemetry.cpp | 6 +- 6 files changed, 311 insertions(+), 311 deletions(-) diff --git a/CcpColorConstants.cpp b/CcpColorConstants.cpp index 8202498..eacbd4b 100644 --- a/CcpColorConstants.cpp +++ b/CcpColorConstants.cpp @@ -2,151 +2,151 @@ #include "include/CcpColorConstants.h" -std::string CcpColorToString( Color color ) +std::string CcpColorToString( CcpColor color ) { // For colors sharing the same RGB value (Aqua/Cyan, Fuchsia/Magenta, and the // various *Gray/*Grey pairs), the first name declared in the enum wins. switch( color ) { - case Color::AliceBlue: return "AliceBlue"; - case Color::AntiqueWhite: return "AntiqueWhite"; - case Color::Aqua: return "Aqua"; // == Cyan - case Color::Aquamarine: return "Aquamarine"; - case Color::Azure: return "Azure"; - case Color::Beige: return "Beige"; - case Color::Bisque: return "Bisque"; - case Color::Black: return "Black"; - case Color::BlanchedAlmond: return "BlanchedAlmond"; - case Color::Blue: return "Blue"; - case Color::BlueViolet: return "BlueViolet"; - case Color::Brown: return "Brown"; - case Color::BurlyWood: return "BurlyWood"; - case Color::CadetBlue: return "CadetBlue"; - case Color::Chartreuse: return "Chartreuse"; - case Color::Chocolate: return "Chocolate"; - case Color::Coral: return "Coral"; - case Color::CornflowerBlue: return "CornflowerBlue"; - case Color::Cornsilk: return "Cornsilk"; - case Color::Crimson: return "Crimson"; - case Color::DarkBlue: return "DarkBlue"; - case Color::DarkCyan: return "DarkCyan"; - case Color::DarkGoldenrod: return "DarkGoldenrod"; - case Color::DarkGray: return "DarkGray"; // == DarkGrey - case Color::DarkGreen: return "DarkGreen"; - case Color::DarkKhaki: return "DarkKhaki"; - case Color::DarkMagenta: return "DarkMagenta"; - case Color::DarkOliveGreen: return "DarkOliveGreen"; - case Color::DarkOrange: return "DarkOrange"; - case Color::DarkOrchid: return "DarkOrchid"; - case Color::DarkRed: return "DarkRed"; - case Color::DarkSalmon: return "DarkSalmon"; - case Color::DarkSeaGreen: return "DarkSeaGreen"; - case Color::DarkSlateBlue: return "DarkSlateBlue"; - case Color::DarkSlateGray: return "DarkSlateGray"; // == DarkSlateGrey - case Color::DarkTurquoise: return "DarkTurquoise"; - case Color::DarkViolet: return "DarkViolet"; - case Color::DeepPink: return "DeepPink"; - case Color::DeepSkyBlue: return "DeepSkyBlue"; - case Color::DimGray: return "DimGray"; // == DimGrey - case Color::DodgerBlue: return "DodgerBlue"; - case Color::FireBrick: return "FireBrick"; - case Color::FloralWhite: return "FloralWhite"; - case Color::ForestGreen: return "ForestGreen"; - case Color::Fuchsia: return "Fuchsia"; // == Magenta - case Color::Gainsboro: return "Gainsboro"; - case Color::GhostWhite: return "GhostWhite"; - case Color::Gold: return "Gold"; - case Color::Goldenrod: return "Goldenrod"; - case Color::Gray: return "Gray"; // == Grey - case Color::Green: return "Green"; - case Color::GreenYellow: return "GreenYellow"; - case Color::Honeydew: return "Honeydew"; - case Color::HotPink: return "HotPink"; - case Color::IndianRed: return "IndianRed"; - case Color::Indigo: return "Indigo"; - case Color::Ivory: return "Ivory"; - case Color::Khaki: return "Khaki"; - case Color::Lavender: return "Lavender"; - case Color::LavenderBlush: return "LavenderBlush"; - case Color::LawnGreen: return "LawnGreen"; - case Color::LemonChiffon: return "LemonChiffon"; - case Color::LightBlue: return "LightBlue"; - case Color::LightCoral: return "LightCoral"; - case Color::LightCyan: return "LightCyan"; - case Color::LightGoldenrodYellow: return "LightGoldenrodYellow"; - case Color::LightGray: return "LightGray"; // == LightGrey - case Color::LightGreen: return "LightGreen"; - case Color::LightPink: return "LightPink"; - case Color::LightSalmon: return "LightSalmon"; - case Color::LightSeaGreen: return "LightSeaGreen"; - case Color::LightSkyBlue: return "LightSkyBlue"; - case Color::LightSlateGray: return "LightSlateGray"; // == LightSlateGrey - case Color::LightSteelBlue: return "LightSteelBlue"; - case Color::LightYellow: return "LightYellow"; - case Color::Lime: return "Lime"; - case Color::LimeGreen: return "LimeGreen"; - case Color::Linen: return "Linen"; - case Color::Maroon: return "Maroon"; - case Color::MediumAquamarine: return "MediumAquamarine"; - case Color::MediumBlue: return "MediumBlue"; - case Color::MediumOrchid: return "MediumOrchid"; - case Color::MediumPurple: return "MediumPurple"; - case Color::MediumSeaGreen: return "MediumSeaGreen"; - case Color::MediumSlateBlue: return "MediumSlateBlue"; - case Color::MediumSpringGreen: return "MediumSpringGreen"; - case Color::MediumTurquoise: return "MediumTurquoise"; - case Color::MediumVioletRed: return "MediumVioletRed"; - case Color::MidnightBlue: return "MidnightBlue"; - case Color::MintCream: return "MintCream"; - case Color::MistyRose: return "MistyRose"; - case Color::Moccasin: return "Moccasin"; - case Color::NavajoWhite: return "NavajoWhite"; - case Color::Navy: return "Navy"; - case Color::OldLace: return "OldLace"; - case Color::Olive: return "Olive"; - case Color::OliveDrab: return "OliveDrab"; - case Color::Orange: return "Orange"; - case Color::OrangeRed: return "OrangeRed"; - case Color::Orchid: return "Orchid"; - case Color::PaleGoldenrod: return "PaleGoldenrod"; - case Color::PaleGreen: return "PaleGreen"; - case Color::PaleTurquoise: return "PaleTurquoise"; - case Color::PaleVioletRed: return "PaleVioletRed"; - case Color::PapayaWhip: return "PapayaWhip"; - case Color::PeachPuff: return "PeachPuff"; - case Color::Peru: return "Peru"; - case Color::Pink: return "Pink"; - case Color::Plum: return "Plum"; - case Color::PowderBlue: return "PowderBlue"; - case Color::Purple: return "Purple"; - case Color::RebeccaPurple: return "RebeccaPurple"; - case Color::Red: return "Red"; - case Color::RosyBrown: return "RosyBrown"; - case Color::RoyalBlue: return "RoyalBlue"; - case Color::SaddleBrown: return "SaddleBrown"; - case Color::Salmon: return "Salmon"; - case Color::SandyBrown: return "SandyBrown"; - case Color::SeaGreen: return "SeaGreen"; - case Color::SeaShell: return "SeaShell"; - case Color::Sienna: return "Sienna"; - case Color::Silver: return "Silver"; - case Color::SkyBlue: return "SkyBlue"; - case Color::SlateBlue: return "SlateBlue"; - case Color::SlateGray: return "SlateGray"; // == SlateGrey - case Color::Snow: return "Snow"; - case Color::SpringGreen: return "SpringGreen"; - case Color::SteelBlue: return "SteelBlue"; - case Color::Tan: return "Tan"; - case Color::Teal: return "Teal"; - case Color::Thistle: return "Thistle"; - case Color::Tomato: return "Tomato"; - case Color::Turquoise: return "Turquoise"; - case Color::Violet: return "Violet"; - case Color::Wheat: return "Wheat"; - case Color::White: return "White"; - case Color::WhiteSmoke: return "WhiteSmoke"; - case Color::Yellow: return "Yellow"; - case Color::YellowGreen: return "YellowGreen"; + case CcpColor::AliceBlue: return "AliceBlue"; + case CcpColor::AntiqueWhite: return "AntiqueWhite"; + case CcpColor::Aqua: return "Aqua"; // == Cyan + case CcpColor::Aquamarine: return "Aquamarine"; + case CcpColor::Azure: return "Azure"; + case CcpColor::Beige: return "Beige"; + case CcpColor::Bisque: return "Bisque"; + case CcpColor::Black: return "Black"; + case CcpColor::BlanchedAlmond: return "BlanchedAlmond"; + case CcpColor::Blue: return "Blue"; + case CcpColor::BlueViolet: return "BlueViolet"; + case CcpColor::Brown: return "Brown"; + case CcpColor::BurlyWood: return "BurlyWood"; + case CcpColor::CadetBlue: return "CadetBlue"; + case CcpColor::Chartreuse: return "Chartreuse"; + case CcpColor::Chocolate: return "Chocolate"; + case CcpColor::Coral: return "Coral"; + case CcpColor::CornflowerBlue: return "CornflowerBlue"; + case CcpColor::Cornsilk: return "Cornsilk"; + case CcpColor::Crimson: return "Crimson"; + case CcpColor::DarkBlue: return "DarkBlue"; + case CcpColor::DarkCyan: return "DarkCyan"; + case CcpColor::DarkGoldenrod: return "DarkGoldenrod"; + case CcpColor::DarkGray: return "DarkGray"; // == DarkGrey + case CcpColor::DarkGreen: return "DarkGreen"; + case CcpColor::DarkKhaki: return "DarkKhaki"; + case CcpColor::DarkMagenta: return "DarkMagenta"; + case CcpColor::DarkOliveGreen: return "DarkOliveGreen"; + case CcpColor::DarkOrange: return "DarkOrange"; + case CcpColor::DarkOrchid: return "DarkOrchid"; + case CcpColor::DarkRed: return "DarkRed"; + case CcpColor::DarkSalmon: return "DarkSalmon"; + case CcpColor::DarkSeaGreen: return "DarkSeaGreen"; + case CcpColor::DarkSlateBlue: return "DarkSlateBlue"; + case CcpColor::DarkSlateGray: return "DarkSlateGray"; // == DarkSlateGrey + case CcpColor::DarkTurquoise: return "DarkTurquoise"; + case CcpColor::DarkViolet: return "DarkViolet"; + case CcpColor::DeepPink: return "DeepPink"; + case CcpColor::DeepSkyBlue: return "DeepSkyBlue"; + case CcpColor::DimGray: return "DimGray"; // == DimGrey + case CcpColor::DodgerBlue: return "DodgerBlue"; + case CcpColor::FireBrick: return "FireBrick"; + case CcpColor::FloralWhite: return "FloralWhite"; + case CcpColor::ForestGreen: return "ForestGreen"; + case CcpColor::Fuchsia: return "Fuchsia"; // == Magenta + case CcpColor::Gainsboro: return "Gainsboro"; + case CcpColor::GhostWhite: return "GhostWhite"; + case CcpColor::Gold: return "Gold"; + case CcpColor::Goldenrod: return "Goldenrod"; + case CcpColor::Gray: return "Gray"; // == Grey + case CcpColor::Green: return "Green"; + case CcpColor::GreenYellow: return "GreenYellow"; + case CcpColor::Honeydew: return "Honeydew"; + case CcpColor::HotPink: return "HotPink"; + case CcpColor::IndianRed: return "IndianRed"; + case CcpColor::Indigo: return "Indigo"; + case CcpColor::Ivory: return "Ivory"; + case CcpColor::Khaki: return "Khaki"; + case CcpColor::Lavender: return "Lavender"; + case CcpColor::LavenderBlush: return "LavenderBlush"; + case CcpColor::LawnGreen: return "LawnGreen"; + case CcpColor::LemonChiffon: return "LemonChiffon"; + case CcpColor::LightBlue: return "LightBlue"; + case CcpColor::LightCoral: return "LightCoral"; + case CcpColor::LightCyan: return "LightCyan"; + case CcpColor::LightGoldenrodYellow: return "LightGoldenrodYellow"; + case CcpColor::LightGray: return "LightGray"; // == LightGrey + case CcpColor::LightGreen: return "LightGreen"; + case CcpColor::LightPink: return "LightPink"; + case CcpColor::LightSalmon: return "LightSalmon"; + case CcpColor::LightSeaGreen: return "LightSeaGreen"; + case CcpColor::LightSkyBlue: return "LightSkyBlue"; + case CcpColor::LightSlateGray: return "LightSlateGray"; // == LightSlateGrey + case CcpColor::LightSteelBlue: return "LightSteelBlue"; + case CcpColor::LightYellow: return "LightYellow"; + case CcpColor::Lime: return "Lime"; + case CcpColor::LimeGreen: return "LimeGreen"; + case CcpColor::Linen: return "Linen"; + case CcpColor::Maroon: return "Maroon"; + case CcpColor::MediumAquamarine: return "MediumAquamarine"; + case CcpColor::MediumBlue: return "MediumBlue"; + case CcpColor::MediumOrchid: return "MediumOrchid"; + case CcpColor::MediumPurple: return "MediumPurple"; + case CcpColor::MediumSeaGreen: return "MediumSeaGreen"; + case CcpColor::MediumSlateBlue: return "MediumSlateBlue"; + case CcpColor::MediumSpringGreen: return "MediumSpringGreen"; + case CcpColor::MediumTurquoise: return "MediumTurquoise"; + case CcpColor::MediumVioletRed: return "MediumVioletRed"; + case CcpColor::MidnightBlue: return "MidnightBlue"; + case CcpColor::MintCream: return "MintCream"; + case CcpColor::MistyRose: return "MistyRose"; + case CcpColor::Moccasin: return "Moccasin"; + case CcpColor::NavajoWhite: return "NavajoWhite"; + case CcpColor::Navy: return "Navy"; + case CcpColor::OldLace: return "OldLace"; + case CcpColor::Olive: return "Olive"; + case CcpColor::OliveDrab: return "OliveDrab"; + case CcpColor::Orange: return "Orange"; + case CcpColor::OrangeRed: return "OrangeRed"; + case CcpColor::Orchid: return "Orchid"; + case CcpColor::PaleGoldenrod: return "PaleGoldenrod"; + case CcpColor::PaleGreen: return "PaleGreen"; + case CcpColor::PaleTurquoise: return "PaleTurquoise"; + case CcpColor::PaleVioletRed: return "PaleVioletRed"; + case CcpColor::PapayaWhip: return "PapayaWhip"; + case CcpColor::PeachPuff: return "PeachPuff"; + case CcpColor::Peru: return "Peru"; + case CcpColor::Pink: return "Pink"; + case CcpColor::Plum: return "Plum"; + case CcpColor::PowderBlue: return "PowderBlue"; + case CcpColor::Purple: return "Purple"; + case CcpColor::RebeccaPurple: return "RebeccaPurple"; + case CcpColor::Red: return "Red"; + case CcpColor::RosyBrown: return "RosyBrown"; + case CcpColor::RoyalBlue: return "RoyalBlue"; + case CcpColor::SaddleBrown: return "SaddleBrown"; + case CcpColor::Salmon: return "Salmon"; + case CcpColor::SandyBrown: return "SandyBrown"; + case CcpColor::SeaGreen: return "SeaGreen"; + case CcpColor::SeaShell: return "SeaShell"; + case CcpColor::Sienna: return "Sienna"; + case CcpColor::Silver: return "Silver"; + case CcpColor::SkyBlue: return "SkyBlue"; + case CcpColor::SlateBlue: return "SlateBlue"; + case CcpColor::SlateGray: return "SlateGray"; // == SlateGrey + case CcpColor::Snow: return "Snow"; + case CcpColor::SpringGreen: return "SpringGreen"; + case CcpColor::SteelBlue: return "SteelBlue"; + case CcpColor::Tan: return "Tan"; + case CcpColor::Teal: return "Teal"; + case CcpColor::Thistle: return "Thistle"; + case CcpColor::Tomato: return "Tomato"; + case CcpColor::Turquoise: return "Turquoise"; + case CcpColor::Violet: return "Violet"; + case CcpColor::Wheat: return "Wheat"; + case CcpColor::White: return "White"; + case CcpColor::WhiteSmoke: return "WhiteSmoke"; + case CcpColor::Yellow: return "Yellow"; + case CcpColor::YellowGreen: return "YellowGreen"; default: return "ColorNotFound"; } } diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 5c45396..94eb1e9 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -68,7 +68,7 @@ typedef TrackableStdVector> EventHa struct CaptureMaskEntry { uint64_t maskBit{0}; - Color color{Color::White}; + CcpColor color{CcpColor::White}; }; typedef TrackableStdMap CaptureMaskMap_t; @@ -104,7 +104,7 @@ namespace uint64_t s_allocatedCaptureMaskBits = 0; - uint64_t RegisterCaptureMask( const std::string& name, std::optional color ) + uint64_t RegisterCaptureMask( const std::string& name, std::optional color ) { static CaptureMaskMap_t s_captureMasks( "CcpTelemetry/s_captureMasks" ); @@ -115,8 +115,8 @@ namespace { // The default capture masks (used by components that haven't added CaptureMask support yet) // must be registered and available from the start. - s_captureMasks["general"] = CaptureMaskEntry{ TMCM_GENERAL, Color::SteelBlue }; - s_captureMasks["cpp"] = CaptureMaskEntry{ TMCM_CPP, Color::Yellow }; + s_captureMasks["general"] = CaptureMaskEntry{ TMCM_GENERAL, CcpColor::SteelBlue }; + s_captureMasks["cpp"] = CaptureMaskEntry{ TMCM_CPP, CcpColor::Yellow }; s_allocatedCaptureMaskBits = TMCM_GENERAL | TMCM_CPP; } @@ -151,13 +151,13 @@ namespace if( !color ) { - std::vector existingColors; + std::vector existingColors; existingColors.reserve( s_captureMasks.size() ); for( const auto& entry : s_captureMasks ) { existingColors.push_back( entry.second.color ); } - color = CcpColor::PickDistinctColor( existingColors ); + color = ColorUtil::PickDistinctColor( existingColors ); } s_captureMasks[lowerName] = CaptureMaskEntry{ maskBit, *color }; @@ -172,7 +172,7 @@ uint64_t CcpRegisterCaptureMask( const std::string& name ) return RegisterCaptureMask( name, std::nullopt ); } -uint64_t CcpRegisterCaptureMask( const std::string& name, Color color ) +uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color ) { return RegisterCaptureMask( name, color ); } diff --git a/include/CcpColorConstants.h b/include/CcpColorConstants.h index 30e78f0..220ef10 100644 --- a/include/CcpColorConstants.h +++ b/include/CcpColorConstants.h @@ -166,13 +166,13 @@ enum class CcpColor : uint32_t // Return the name of the color. // For colors with the same RGB value (e.g. Aqua/Cyan, Gray/Grey), -// the first name declared in Color enum class is returned. -CARBON_CORE_API std::string CcpColorToString( Color color ); +// the first name declared in CcpColor enum class is returned. +CARBON_CORE_API std::string CcpColorToString( CcpColor color ); -namespace CcpColor +namespace ColorUtil { -// Decompose a Color into its 8-bit red, green and blue components. -static inline void ToRgb( Color color, int& r, int& g, int& b ) +// Decompose a CcpColor into its 8-bit red, green and blue components. +static inline void ToRgb( CcpColor color, int& r, int& g, int& b ) { const uint32_t value = static_cast( color ); r = static_cast( ( value >> 16 ) & 0xff ); @@ -181,7 +181,7 @@ static inline void ToRgb( Color color, int& r, int& g, int& b ) } // Find the squared Euclidean distance between two colors in RGB space. -static inline int64_t GetSquaredDistance( Color lhs, Color rhs ) +static inline int64_t GetSquaredDistance( CcpColor lhs, CcpColor rhs ) { int r1, g1, b1; int r2, g2, b2; @@ -197,160 +197,160 @@ static inline int64_t GetSquaredDistance( Color lhs, Color rhs ) // NOTE: // - Remove a color from this list if you don't want it to be selected // when automatically picking a display color for a new CaptureMask entry. -static constexpr Color s_awailableNamedColors[] = { - //Color::AliceBlue, - Color::AntiqueWhite, - Color::Aqua /* == Cyan */, - Color::Aquamarine, - //Color::Azure, - Color::Beige, - Color::Bisque, - //Color::Black, - Color::BlanchedAlmond, - Color::Blue, - Color::BlueViolet, - Color::Brown, - Color::BurlyWood, - Color::CadetBlue, - Color::Chartreuse, - Color::Chocolate, - Color::Coral, - Color::CornflowerBlue, - Color::Cornsilk, - Color::Crimson, - Color::DarkBlue, - Color::DarkCyan, - Color::DarkGoldenrod, - //Color::DarkGray /* == DarkGrey */, - Color::DarkGreen, - Color::DarkKhaki, - Color::DarkMagenta, - Color::DarkOliveGreen, - Color::DarkOrange, - Color::DarkOrchid, - Color::DarkRed, - Color::DarkSalmon, - Color::DarkSeaGreen, - Color::DarkSlateBlue, - Color::DarkSlateGray /* == DarkSlateGrey */, - Color::DarkTurquoise, - Color::DarkViolet, - Color::DeepPink, - Color::DeepSkyBlue, - //Color::DimGray /* == DimGrey */, - Color::DodgerBlue, - Color::FireBrick, - //Color::FloralWhite, - Color::ForestGreen, - Color::Fuchsia /* == Magenta */, - Color::Gainsboro, - //Color::GhostWhite, - Color::Gold, - Color::Goldenrod, - //Color::Gray /* == Grey */, - Color::Green, - Color::GreenYellow, - Color::Honeydew, - Color::HotPink, - Color::IndianRed, - Color::Indigo, - //Color::Ivory, - Color::Khaki, - Color::Lavender, - Color::LavenderBlush, - Color::LawnGreen, - Color::LemonChiffon, - Color::LightBlue, - Color::LightCoral, - Color::LightCyan, - Color::LightGoldenrodYellow, - Color::LightGray /* == LightGrey */, - Color::LightGreen, - Color::LightPink, - Color::LightSalmon, - Color::LightSeaGreen, - Color::LightSkyBlue, - Color::LightSlateGray /* == LightSlateGrey */, - Color::LightSteelBlue, - Color::LightYellow, - Color::Lime, - Color::LimeGreen, - Color::Linen, - Color::Maroon, - Color::MediumAquamarine, - Color::MediumBlue, - Color::MediumOrchid, - Color::MediumPurple, - Color::MediumSeaGreen, - Color::MediumSlateBlue, - Color::MediumSpringGreen, - Color::MediumTurquoise, - Color::MediumVioletRed, - Color::MidnightBlue, - //Color::MintCream, - Color::MistyRose, - Color::Moccasin, - Color::NavajoWhite, - Color::Navy, - Color::OldLace, - Color::Olive, - Color::OliveDrab, - Color::Orange, - Color::OrangeRed, - Color::Orchid, - Color::PaleGoldenrod, - Color::PaleGreen, - Color::PaleTurquoise, - Color::PaleVioletRed, - Color::PapayaWhip, - Color::PeachPuff, - Color::Peru, - Color::Pink, - Color::Plum, - Color::PowderBlue, - Color::Purple, - Color::RebeccaPurple, - Color::Red, - Color::RosyBrown, - Color::RoyalBlue, - Color::SaddleBrown, - Color::Salmon, - Color::SandyBrown, - Color::SeaGreen, - //Color::SeaShell, - Color::Sienna, - Color::Silver, - Color::SkyBlue, - Color::SlateBlue, - Color::SlateGray /* == SlateGrey */, - //Color::Snow, - Color::SpringGreen, - Color::SteelBlue, - Color::Tan, - Color::Teal, - Color::Thistle, - Color::Tomato, - Color::Turquoise, - Color::Violet, - Color::Wheat, - //Color::White, - //Color::WhiteSmoke, - Color::Yellow, - Color::YellowGreen, +static constexpr CcpColor s_awailableNamedColors[] = { + //CcpColor::AliceBlue, + CcpColor::AntiqueWhite, + CcpColor::Aqua /* == Cyan */, + CcpColor::Aquamarine, + //CcpColor::Azure, + CcpColor::Beige, + CcpColor::Bisque, + //CcpColor::Black, + CcpColor::BlanchedAlmond, + CcpColor::Blue, + CcpColor::BlueViolet, + CcpColor::Brown, + CcpColor::BurlyWood, + CcpColor::CadetBlue, + CcpColor::Chartreuse, + CcpColor::Chocolate, + CcpColor::Coral, + CcpColor::CornflowerBlue, + CcpColor::Cornsilk, + CcpColor::Crimson, + CcpColor::DarkBlue, + CcpColor::DarkCyan, + CcpColor::DarkGoldenrod, + //CcpColor::DarkGray /* == DarkGrey */, + CcpColor::DarkGreen, + CcpColor::DarkKhaki, + CcpColor::DarkMagenta, + CcpColor::DarkOliveGreen, + CcpColor::DarkOrange, + CcpColor::DarkOrchid, + CcpColor::DarkRed, + CcpColor::DarkSalmon, + CcpColor::DarkSeaGreen, + CcpColor::DarkSlateBlue, + CcpColor::DarkSlateGray /* == DarkSlateGrey */, + CcpColor::DarkTurquoise, + CcpColor::DarkViolet, + CcpColor::DeepPink, + CcpColor::DeepSkyBlue, + //CcpColor::DimGray /* == DimGrey */, + CcpColor::DodgerBlue, + CcpColor::FireBrick, + //CcpColor::FloralWhite, + CcpColor::ForestGreen, + CcpColor::Fuchsia /* == Magenta */, + CcpColor::Gainsboro, + //CcpColor::GhostWhite, + CcpColor::Gold, + CcpColor::Goldenrod, + //CcpColor::Gray /* == Grey */, + CcpColor::Green, + CcpColor::GreenYellow, + CcpColor::Honeydew, + CcpColor::HotPink, + CcpColor::IndianRed, + CcpColor::Indigo, + //CcpColor::Ivory, + CcpColor::Khaki, + CcpColor::Lavender, + CcpColor::LavenderBlush, + CcpColor::LawnGreen, + CcpColor::LemonChiffon, + CcpColor::LightBlue, + CcpColor::LightCoral, + CcpColor::LightCyan, + CcpColor::LightGoldenrodYellow, + CcpColor::LightGray /* == LightGrey */, + CcpColor::LightGreen, + CcpColor::LightPink, + CcpColor::LightSalmon, + CcpColor::LightSeaGreen, + CcpColor::LightSkyBlue, + CcpColor::LightSlateGray /* == LightSlateGrey */, + CcpColor::LightSteelBlue, + CcpColor::LightYellow, + CcpColor::Lime, + CcpColor::LimeGreen, + CcpColor::Linen, + CcpColor::Maroon, + CcpColor::MediumAquamarine, + CcpColor::MediumBlue, + CcpColor::MediumOrchid, + CcpColor::MediumPurple, + CcpColor::MediumSeaGreen, + CcpColor::MediumSlateBlue, + CcpColor::MediumSpringGreen, + CcpColor::MediumTurquoise, + CcpColor::MediumVioletRed, + CcpColor::MidnightBlue, + //CcpColor::MintCream, + CcpColor::MistyRose, + CcpColor::Moccasin, + CcpColor::NavajoWhite, + CcpColor::Navy, + CcpColor::OldLace, + CcpColor::Olive, + CcpColor::OliveDrab, + CcpColor::Orange, + CcpColor::OrangeRed, + CcpColor::Orchid, + CcpColor::PaleGoldenrod, + CcpColor::PaleGreen, + CcpColor::PaleTurquoise, + CcpColor::PaleVioletRed, + CcpColor::PapayaWhip, + CcpColor::PeachPuff, + CcpColor::Peru, + CcpColor::Pink, + CcpColor::Plum, + CcpColor::PowderBlue, + CcpColor::Purple, + CcpColor::RebeccaPurple, + CcpColor::Red, + CcpColor::RosyBrown, + CcpColor::RoyalBlue, + CcpColor::SaddleBrown, + CcpColor::Salmon, + CcpColor::SandyBrown, + CcpColor::SeaGreen, + //CcpColor::SeaShell, + CcpColor::Sienna, + CcpColor::Silver, + CcpColor::SkyBlue, + CcpColor::SlateBlue, + CcpColor::SlateGray /* == SlateGrey */, + //CcpColor::Snow, + CcpColor::SpringGreen, + CcpColor::SteelBlue, + CcpColor::Tan, + CcpColor::Teal, + CcpColor::Thistle, + CcpColor::Tomato, + CcpColor::Turquoise, + CcpColor::Violet, + CcpColor::Wheat, + //CcpColor::White, + //CcpColor::WhiteSmoke, + CcpColor::Yellow, + CcpColor::YellowGreen, }; // Pick a display color from the s_awailableNamedColors list above // that are reasonably distinct from incoming existingColors. // Candidates are scored by their distance to existingColors and // the color furthest away wins. -static inline Color PickDistinctColor( const std::vector& existingColors ) +static inline CcpColor PickDistinctColor( const std::vector& existingColors ) { - Color bestColor = Color::SteelBlue; + CcpColor bestColor = CcpColor::SteelBlue; int64_t bestScore = -1; - for( const Color candidate : s_awailableNamedColors ) + for( const CcpColor candidate : s_awailableNamedColors ) { int64_t minDistance = INT64_MAX; - for( const Color existing : existingColors ) + for( const CcpColor existing : existingColors ) { const int64_t dist = GetSquaredDistance( candidate, existing ); if( dist < minDistance ) diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 4d617e9..c02fb5f 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -44,7 +44,7 @@ CARBON_CORE_API void CcpRegisterThread( CcpThreadId_t threadId, const char* name // either manually or automatically allocated based on "best available fit". // --------------------------------------------------------------------------- CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name ); -CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, Color color ); +CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color ); struct CcpTelemetryConfig { diff --git a/tests/CcpColorConstants.cpp b/tests/CcpColorConstants.cpp index 9f11d48..3bd3516 100644 --- a/tests/CcpColorConstants.cpp +++ b/tests/CcpColorConstants.cpp @@ -12,10 +12,10 @@ TEST( CcpColorConstantsTest, PickedColorIsUnused ) { - std::vector existing{ Color::SteelBlue, Color::Yellow }; + std::vector existing{ CcpColor::SteelBlue, CcpColor::Yellow }; for( int i = 0; i < 62; ++i ) { - const Color picked = CcpColor::PickDistinctColor( existing ); + const CcpColor picked = ColorUtil::PickDistinctColor( existing ); EXPECT_EQ( existing.end(), std::find( existing.begin(), existing.end(), picked ) ); existing.push_back( picked ); @@ -34,25 +34,25 @@ TEST( CcpColorConstantsTest, PickedColorIsDistinctFromExisting ) { // With a single existing color, the pick should be far away from it in RGB space, // not merely a slightly different shade. - const std::vector existingRed{ Color::Red }; - const Color pickedVsRed = CcpColor::PickDistinctColor( existingRed ); - auto distPickedVsRed = CcpColor::GetSquaredDistance( pickedVsRed, Color::Red ); + const std::vector existingRed{ CcpColor::Red }; + const CcpColor pickedVsRed = ColorUtil::PickDistinctColor( existingRed ); + auto distPickedVsRed = ColorUtil::GetSquaredDistance( pickedVsRed, CcpColor::Red ); std::fprintf( stdout, "[0x%06x] '%s' vs Picked color: [0x%06x] '%s' - distance = %lld \n", - Color::Red, CcpColorToString(Color::Red).c_str(), + CcpColor::Red, CcpColorToString(CcpColor::Red).c_str(), pickedVsRed, CcpColorToString( pickedVsRed ).c_str(), distPickedVsRed ); std::fflush( stdout ); EXPECT_GT( distPickedVsRed, 128 * 128 ); - const std::vector existingBlue{ Color::Blue }; - const Color pickedVsBlue = CcpColor::PickDistinctColor( existingBlue ); - auto distPickedVsBlue = CcpColor::GetSquaredDistance( pickedVsBlue, Color::Blue ); + const std::vector existingBlue{ CcpColor::Blue }; + const CcpColor pickedVsBlue = ColorUtil::PickDistinctColor( existingBlue ); + auto distPickedVsBlue = ColorUtil::GetSquaredDistance( pickedVsBlue, CcpColor::Blue ); std::fprintf( stdout, "[0x%06x] '%s' vs Picked color: [0x%06x] '%s' - distance = %lld \n", - Color::Blue, CcpColorToString(Color::Blue).c_str(), + CcpColor::Blue, CcpColorToString(CcpColor::Blue).c_str(), pickedVsBlue, CcpColorToString( pickedVsBlue ).c_str(), distPickedVsBlue ); std::fflush( stdout ); diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index cca7f15..7b76f76 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -568,7 +568,7 @@ TEST_F( CcpTelemetryTest, CaptureMaskRegisterIsCaseInsensitive ) TEST_F( CcpTelemetryTest, CaptureMaskRegisterDistinctBitPerName ) { const uint64_t maskBitC = CcpRegisterCaptureMask( "capturemasktest_component_c" ); - const uint64_t maskBitD = CcpRegisterCaptureMask( "capturemasktest_component_d", Color::Tomato ); + const uint64_t maskBitD = CcpRegisterCaptureMask( "capturemasktest_component_d", CcpColor::Tomato ); EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBitC ) ); EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBitD ) ); EXPECT_NE( maskBitC, maskBitD ); @@ -582,8 +582,8 @@ TEST_F( CcpTelemetryTest, CaptureMaskRegisterRejectEmpty ) TEST_F( CcpTelemetryTest, CaptureMaskDefaultsAreRegistered ) { // TODO: Change this test to check values from the GetCaptureMasks() function once it's ready - EXPECT_EQ( static_cast( TMCM_GENERAL ), CcpRegisterCaptureMask( "general", Color::Black ) ); + EXPECT_EQ( static_cast( TMCM_GENERAL ), CcpRegisterCaptureMask( "general", CcpColor::Black ) ); EXPECT_EQ( static_cast( TMCM_CPP ), CcpRegisterCaptureMask( "cpp" ) ); - EXPECT_EQ( static_cast( TMCM_GENERAL ), CcpRegisterCaptureMask( "GENERAL", Color::White ) ); + EXPECT_EQ( static_cast( TMCM_GENERAL ), CcpRegisterCaptureMask( "GENERAL", CcpColor::White ) ); } From f44da09df7f71353a1a3802b143738347905f316 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:11:29 +0000 Subject: [PATCH 03/40] Remove static from ColorUtil inline helper functions --- include/CcpColorConstants.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/CcpColorConstants.h b/include/CcpColorConstants.h index 220ef10..8d390f4 100644 --- a/include/CcpColorConstants.h +++ b/include/CcpColorConstants.h @@ -172,7 +172,7 @@ CARBON_CORE_API std::string CcpColorToString( CcpColor color ); namespace ColorUtil { // Decompose a CcpColor into its 8-bit red, green and blue components. -static inline void ToRgb( CcpColor color, int& r, int& g, int& b ) +inline void ToRgb( CcpColor color, int& r, int& g, int& b ) { const uint32_t value = static_cast( color ); r = static_cast( ( value >> 16 ) & 0xff ); @@ -181,7 +181,7 @@ static inline void ToRgb( CcpColor color, int& r, int& g, int& b ) } // Find the squared Euclidean distance between two colors in RGB space. -static inline int64_t GetSquaredDistance( CcpColor lhs, CcpColor rhs ) +inline int64_t GetSquaredDistance( CcpColor lhs, CcpColor rhs ) { int r1, g1, b1; int r2, g2, b2; @@ -197,7 +197,7 @@ static inline int64_t GetSquaredDistance( CcpColor lhs, CcpColor rhs ) // NOTE: // - Remove a color from this list if you don't want it to be selected // when automatically picking a display color for a new CaptureMask entry. -static constexpr CcpColor s_awailableNamedColors[] = { +inline constexpr CcpColor s_awailableNamedColors[] = { //CcpColor::AliceBlue, CcpColor::AntiqueWhite, CcpColor::Aqua /* == Cyan */, @@ -343,7 +343,7 @@ static constexpr CcpColor s_awailableNamedColors[] = { // that are reasonably distinct from incoming existingColors. // Candidates are scored by their distance to existingColors and // the color furthest away wins. -static inline CcpColor PickDistinctColor( const std::vector& existingColors ) +inline CcpColor PickDistinctColor( const std::vector& existingColors ) { CcpColor bestColor = CcpColor::SteelBlue; int64_t bestScore = -1; From 0ec85dc2c27e49e01e0c3968e09e6c9cfe067bae Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:03:29 +0000 Subject: [PATCH 04/40] Add CcpGetRegisteredCaptureMasks() support Change includes: - Expose CcpGetRegisteredCaptureMasks() from core - Add/change test coverage for CaptureMasks tests - Rename existing CaptureMasks variables to state they are for "registered" CaptureMasks Part of: https://fenriscreations.atlassian.net/browse/PLAT-11475 --- CcpTelemetry.cpp | 80 +++++++++++++++++++++++++++------------- include/CcpTelemetry.h | 10 +++++ tests/CcpTelemetry.cpp | 84 ++++++++++++++++++++++++++++++++---------- 3 files changed, 129 insertions(+), 45 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 94eb1e9..27d0167 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -71,7 +71,7 @@ struct CaptureMaskEntry CcpColor color{CcpColor::White}; }; -typedef TrackableStdMap CaptureMaskMap_t; +typedef TrackableStdMap RegisteredCaptureMaskMap_t; namespace { @@ -100,38 +100,46 @@ namespace // ------------------------------- // CaptureMask specifics: // ------------------------------- - CcpMutex s_captureMaskMutex( "CcpTelemetry", "CaptureMaskMutex" ); + CcpMutex s_captureMaskRegisterMutex( "CcpTelemetry", "CaptureMaskRegisterMutex" ); - uint64_t s_allocatedCaptureMaskBits = 0; + uint64_t s_registeredCaptureMaskBits = 0; - uint64_t RegisterCaptureMask( const std::string& name, std::optional color ) + // Callers must hold the s_captureMaskRegisterMutex before calling this function + RegisteredCaptureMaskMap_t& GetRegisteredCaptureMasks() { - static CaptureMaskMap_t s_captureMasks( "CcpTelemetry/s_captureMasks" ); - - // Make sure to guard write access to both s_captureMasks and s_allocatedCaptureMaskBits - CcpAutoMutex lock( s_captureMaskMutex ); + static RegisteredCaptureMaskMap_t s_registeredCaptureMasks( "CcpTelemetry/s_registeredCaptureMasks" ); - if( s_allocatedCaptureMaskBits == 0 ) + if( s_registeredCaptureMaskBits == 0 ) { - // The default capture masks (used by components that haven't added CaptureMask support yet) - // must be registered and available from the start. - s_captureMasks["general"] = CaptureMaskEntry{ TMCM_GENERAL, CcpColor::SteelBlue }; - s_captureMasks["cpp"] = CaptureMaskEntry{ TMCM_CPP, CcpColor::Yellow }; - s_allocatedCaptureMaskBits = TMCM_GENERAL | TMCM_CPP; + // Make sure the default registered CaptureMasks exist. + // Used by components that haven't added proper CaptureMask support yet. + s_registeredCaptureMasks["general"] = CaptureMaskEntry{ TMCM_GENERAL, CcpColor::SteelBlue }; + s_registeredCaptureMasks["cpp"] = CaptureMaskEntry{ TMCM_CPP, CcpColor::Yellow }; + s_registeredCaptureMaskBits = TMCM_GENERAL | TMCM_CPP; } + return s_registeredCaptureMasks; + } + + uint64_t RegisterCaptureMask( const std::string& name, std::optional color ) + { + // Guard access to registered CaptureMasks while we add/update a new entry + CcpAutoMutex lock( s_captureMaskRegisterMutex ); + + RegisteredCaptureMaskMap_t& s_registeredCaptureMasks = GetRegisteredCaptureMasks(); + if( name.empty() ) { - CCP_LOGERR_CH( s_ch, "Cannot register a capture mask without a name" ); + CCP_LOGERR_CH( s_ch, "Cannot register a CaptureMask without a name" ); return 0; } std::string lowerName( name ); std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); - // Explicitly allow change of color on an existing captureMask entry - auto existing = s_captureMasks.find( lowerName ); - if( existing != s_captureMasks.end() ) + // Explicitly allow change of color on an existing registered CaptureMask entry + auto existing = s_registeredCaptureMasks.find( lowerName ); + if( existing != s_registeredCaptureMasks.end() ) { if( color ) { @@ -140,28 +148,28 @@ namespace return existing->second.maskBit; } - if( ~s_allocatedCaptureMaskBits == 0 ) + if( ~s_registeredCaptureMaskBits == 0 ) { - CCP_LOGERR_CH( s_ch, "Cannot register capture mask '%s' - all 64 capture mask bits are already in use", lowerName.c_str() ); + CCP_LOGERR_CH( s_ch, "Cannot register CaptureMask '%s' - all 64 bits are already in use", lowerName.c_str() ); return 0; } // Allocate the lowest free bit - const uint64_t maskBit = ~s_allocatedCaptureMaskBits & ( s_allocatedCaptureMaskBits + 1 ); + const uint64_t maskBit = ~s_registeredCaptureMaskBits & ( s_registeredCaptureMaskBits + 1 ); if( !color ) { std::vector existingColors; - existingColors.reserve( s_captureMasks.size() ); - for( const auto& entry : s_captureMasks ) + existingColors.reserve( s_registeredCaptureMasks.size() ); + for( const auto& entry : s_registeredCaptureMasks ) { existingColors.push_back( entry.second.color ); } color = ColorUtil::PickDistinctColor( existingColors ); } - s_captureMasks[lowerName] = CaptureMaskEntry{ maskBit, *color }; - s_allocatedCaptureMaskBits |= maskBit; + s_registeredCaptureMasks[lowerName] = CaptureMaskEntry{ maskBit, *color }; + s_registeredCaptureMaskBits |= maskBit; CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", lowerName.c_str(), static_cast( maskBit ), CcpColorToString( *color ).c_str() ); return maskBit; } @@ -177,6 +185,21 @@ uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color ) return RegisterCaptureMask( name, color ); } +std::vector CcpGetRegisteredCaptureMasks() +{ + // Guard access to registered CaptureMasks while return list is populated + CcpAutoMutex lock( s_captureMaskRegisterMutex ); + + const RegisteredCaptureMaskMap_t& masks = GetRegisteredCaptureMasks(); + std::vector result; + result.reserve( masks.size() ); + for( const auto& entry : masks ) + { + result.push_back( CcpCaptureMaskInfo{ entry.first, entry.second.maskBit, entry.second.color } ); + } + return result; +} + bool CcpTelemetryIsConnected() { return TracyIsStarted && TracyIsConnected && s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Started; @@ -578,11 +601,16 @@ uint64_t CcpRegisterCaptureMask( const std::string& ) return 0; } -uint64_t CcpRegisterCaptureMask( const std::string&, Color ) +uint64_t CcpRegisterCaptureMask( const std::string&, CcpColor ) { return 0; } +std::vector CcpGetCaptureMasks() +{ + return {}; +} + bool CcpStartTelemetry( const char* server, int connectionType, uint32_t maxThreadCount ) { return false; diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index c02fb5f..22bfb5e 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -43,9 +43,19 @@ CARBON_CORE_API void CcpRegisterThread( CcpThreadId_t threadId, const char* name // - A Telemetry display color is also associated with the chosen CaptureMask bit, // either manually or automatically allocated based on "best available fit". // --------------------------------------------------------------------------- +struct CcpCaptureMaskInfo +{ + std::string name; // The lower-case display name of the CaptureMask (carbon-component) + uint64_t maskBit{0}; // The single bit assigned to this CaptureMask during registration + CcpColor color{CcpColor::White}; // The chosen/allocated color for the CaptureMask +}; + CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name ); CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color ); +CARBON_CORE_API std::vector CcpGetRegisteredCaptureMasks(); + + struct CcpTelemetryConfig { std::string applicationName; diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 7b76f76..7373954 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -163,6 +163,30 @@ class CcpTelemetryTest : public ::testing::Test return m_tracyClient.TryGetLock( lockId, outLock ); } + // Helper for CaptureMasks to make sure it only has a single bit set to 1 + bool IsSCaptureMaskSingleBit( uint64_t mask ) + { + return mask != 0 && ( mask & ( mask - 1 ) ) == 0; + } + + // Helper for find a CaptureMask by name from the list of already registered CaptureMasks + bool TryGetCaptureMaskNamed( const std::string& name, CcpCaptureMaskInfo& info ) + { + // CaptureMask is stored lower-case, make sure we compare it as such. + std::string lowerName( name ); + std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); + + for( const CcpCaptureMaskInfo& mask : CcpGetRegisteredCaptureMasks() ) + { + if( mask.name == lowerName ) + { + info = mask; + return true; + } + } + return false; + } + const std::string expectedNoFiber; const std::string expectedFiberName1{ "TestFiber1" }; const std::string expectedFiberName2{ "TestFiber2" }; @@ -543,35 +567,35 @@ TEST_F( CcpTelemetryTest, CcpSemaphoreTimedWaitTimesOut ) // --------------------------------------------------------------------------- // CaptureMask tests: // --------------------------------------------------------------------------- -bool IsSCaptureMaskSingleBit( uint64_t mask ) -{ - return mask != 0 && ( mask & ( mask - 1 ) ) == 0; -} -TEST_F( CcpTelemetryTest, CaptureMaskRegisterAllocatesSingleFreeBit ) +TEST_F( CcpTelemetryTest, CaptureMaskRegisterReturnsNonDefaultBit ) { - const uint64_t maskBit = CcpRegisterCaptureMask( "capturemasktest_component_a" ); + const uint64_t maskBit = CcpRegisterCaptureMask( "CaptureMaskRegisterReturnsNonDefaultBit" ); EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBit ) ); - // TMCM_GENERAL and TMCM_CPP are registered as the default CaptureMasks, so their - // bits must never be handed out to other components. + // TMCM_GENERAL and TMCM_CPP are registered as the default CaptureMasks, + // so their maskBits must never be handed out to other components. EXPECT_EQ( 0, maskBit & ( TMCM_GENERAL | TMCM_CPP ) ); } TEST_F( CcpTelemetryTest, CaptureMaskRegisterIsCaseInsensitive ) { - const uint64_t firstBit = CcpRegisterCaptureMask( "CaptureMaskTest_Component_B" ); - const uint64_t secondBit = CcpRegisterCaptureMask( "capturemasktest_component_b" ); + const uint64_t firstBit = CcpRegisterCaptureMask( "CaptureMaskRegisterIsCaseInsensitive" ); + const uint64_t secondBit = CcpRegisterCaptureMask( "capturemaskregisteriscaseinsensitive" ); EXPECT_TRUE( IsSCaptureMaskSingleBit( firstBit ) ); EXPECT_EQ( firstBit, secondBit ); + + CcpCaptureMaskInfo info; + TryGetCaptureMaskNamed( "CAPTUREmaskREGISTERisCASEinsensitive", info ); + EXPECT_EQ( firstBit, info.maskBit ); } TEST_F( CcpTelemetryTest, CaptureMaskRegisterDistinctBitPerName ) { - const uint64_t maskBitC = CcpRegisterCaptureMask( "capturemasktest_component_c" ); - const uint64_t maskBitD = CcpRegisterCaptureMask( "capturemasktest_component_d", CcpColor::Tomato ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBitC ) ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBitD ) ); - EXPECT_NE( maskBitC, maskBitD ); + const uint64_t maskBitA = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_A" ); + const uint64_t maskBitB = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_B", CcpColor::Tomato ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBitA ) ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBitB ) ); + EXPECT_NE( maskBitA, maskBitB ); } TEST_F( CcpTelemetryTest, CaptureMaskRegisterRejectEmpty ) @@ -581,9 +605,31 @@ TEST_F( CcpTelemetryTest, CaptureMaskRegisterRejectEmpty ) TEST_F( CcpTelemetryTest, CaptureMaskDefaultsAreRegistered ) { - // TODO: Change this test to check values from the GetCaptureMasks() function once it's ready - EXPECT_EQ( static_cast( TMCM_GENERAL ), CcpRegisterCaptureMask( "general", CcpColor::Black ) ); - EXPECT_EQ( static_cast( TMCM_CPP ), CcpRegisterCaptureMask( "cpp" ) ); - EXPECT_EQ( static_cast( TMCM_GENERAL ), CcpRegisterCaptureMask( "GENERAL", CcpColor::White ) ); + // The default CaptureMasks must be available from the start. + CcpCaptureMaskInfo info; + ASSERT_TRUE( TryGetCaptureMaskNamed( "general", info ) ); + EXPECT_EQ( static_cast( TMCM_GENERAL ), info.maskBit ); + EXPECT_EQ( CcpColor::SteelBlue, info.color ); // CcpColor::SteelBlue is the default assigned color for TMCM_GENERAL + ASSERT_TRUE( TryGetCaptureMaskNamed( "cpp", info ) ); + EXPECT_EQ( static_cast( TMCM_CPP ), info.maskBit ); + + // Registering a CaptureMask for the same name should result in the same maskBit returned, + // but a color change should be allowed. + const uint64_t reRegisterMaskBit = CcpRegisterCaptureMask( "GENERAL", CcpColor::OrangeRed ); + TryGetCaptureMaskNamed( "general", info ); + EXPECT_EQ( static_cast( TMCM_GENERAL ), reRegisterMaskBit ); + EXPECT_EQ( static_cast( TMCM_GENERAL ), info.maskBit ); + EXPECT_EQ( CcpColor::OrangeRed, info.color ); +} + +TEST_F( CcpTelemetryTest, CaptureMaskAutoColorIsPickedFromCandidateList ) +{ + CcpRegisterCaptureMask( "CaptureMaskAutoColorIsPickedFromCandidateList" ); + + CcpCaptureMaskInfo info; + ASSERT_TRUE( TryGetCaptureMaskNamed( "CaptureMaskAutoColorIsPickedFromCandidateList", info ) ); + const auto begin = std::begin( ColorUtil::s_awailableNamedColors ); + const auto end = std::end( ColorUtil::s_awailableNamedColors ); + EXPECT_NE( end, std::find( begin, end, info.color ) ); } From 3a7688bd2fd8eb978b3ae83d462a075f54e857e9 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Wed, 15 Jul 2026 13:02:45 +0000 Subject: [PATCH 05/40] Store registered CaptureMasks in a fixed size array Will give us the option of a O(1) lookup for display color once we change telemetry to mark zones based on CaptureMask bit. --- CcpTelemetry.cpp | 113 +++++++++++++++++++++++++++++------------------ 1 file changed, 70 insertions(+), 43 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 27d0167..cf6bd2e 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -1,6 +1,9 @@ // Copyright © 2013 CCP ehf. +#include +#include #include +#include #include #include @@ -62,17 +65,6 @@ typedef TrackableStdMap> MutexName typedef TrackableStdMap ThreadNameMap_t; typedef TrackableStdVector> EventHandlerVector_t; -// ------------------------------- -// CaptureMask specifics: -// ------------------------------- -struct CaptureMaskEntry -{ - uint64_t maskBit{0}; - CcpColor color{CcpColor::White}; -}; - -typedef TrackableStdMap RegisteredCaptureMaskMap_t; - namespace { uint32_t s_telemetryTick = 0; @@ -100,34 +92,60 @@ namespace // ------------------------------- // CaptureMask specifics: // ------------------------------- + #define CAPTURE_MASKS_MAX 64 + + struct CaptureMaskEntry + { + std::atomic slotBit{ 0 }; // used to indicate if the slot entry has been populated or not + CcpCaptureMaskInfo maskInfo{}; + }; + + // Fixed size array for registered CaptureMasks, + // allows for O(1) lookup based on single-bit CaptureMask value. + std::array s_registeredCaptureMasks{}; + CcpMutex s_captureMaskRegisterMutex( "CcpTelemetry", "CaptureMaskRegisterMutex" ); - uint64_t s_registeredCaptureMaskBits = 0; +#if defined( _MSC_VER ) + #include // _BitScanForward64 TODO: Can be removed once upgraded to C++20 - using std::countr_zero() instead. +#endif - // Callers must hold the s_captureMaskRegisterMutex before calling this function - RegisteredCaptureMaskMap_t& GetRegisteredCaptureMasks() + // Returns the index [0..63] of the single bit set bit in x + // TODO: Replace/remove this function with std::countr_zero() once upgraded to C++20 or newer. + int CountTrailingZeros64( uint64_t x ) { - static RegisteredCaptureMaskMap_t s_registeredCaptureMasks( "CcpTelemetry/s_registeredCaptureMasks" ); - - if( s_registeredCaptureMaskBits == 0 ) - { - // Make sure the default registered CaptureMasks exist. - // Used by components that haven't added proper CaptureMask support yet. - s_registeredCaptureMasks["general"] = CaptureMaskEntry{ TMCM_GENERAL, CcpColor::SteelBlue }; - s_registeredCaptureMasks["cpp"] = CaptureMaskEntry{ TMCM_CPP, CcpColor::Yellow }; - s_registeredCaptureMaskBits = TMCM_GENERAL | TMCM_CPP; - } +#if defined( _MSC_VER ) + unsigned long idx; + _BitScanForward64( &idx, x ); + return static_cast( idx ); +#else + return __builtin_ctzll( x ); +#endif + } - return s_registeredCaptureMasks; + // Store a registered CaptureMask into its slot entry + void StoreRegisteredCaptureMask( uint64_t bit, const std::string& name, CcpColor color ) + { + auto& entry = s_registeredCaptureMasks[CountTrailingZeros64( bit )]; + entry.maskInfo.name = name; + entry.maskInfo.maskBit = bit; + entry.maskInfo.color = color; + entry.slotBit.store( bit, std::memory_order_release ); } + // Initialize the default CaptureMasks + uint64_t s_registeredCaptureMaskBits = [] + { + StoreRegisteredCaptureMask( TMCM_GENERAL, "general", CcpColor::SteelBlue ); + StoreRegisteredCaptureMask( TMCM_CPP, "cpp", CcpColor::Yellow ); + return static_cast( TMCM_GENERAL | TMCM_CPP ); + }(); + uint64_t RegisterCaptureMask( const std::string& name, std::optional color ) { // Guard access to registered CaptureMasks while we add/update a new entry CcpAutoMutex lock( s_captureMaskRegisterMutex ); - RegisteredCaptureMaskMap_t& s_registeredCaptureMasks = GetRegisteredCaptureMasks(); - if( name.empty() ) { CCP_LOGERR_CH( s_ch, "Cannot register a CaptureMask without a name" ); @@ -137,15 +155,19 @@ namespace std::string lowerName( name ); std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); - // Explicitly allow change of color on an existing registered CaptureMask entry - auto existing = s_registeredCaptureMasks.find( lowerName ); - if( existing != s_registeredCaptureMasks.end() ) + // Explicitly allow change of color on an existing registered + // CaptureMask entry in case of re-register on same name. + for( auto& entry : s_registeredCaptureMasks ) { - if( color ) + const uint64_t bit = entry.slotBit.load( std::memory_order_relaxed ); + if( bit != 0 && entry.maskInfo.name == lowerName ) { - existing->second.color = *color; + if( color ) + { + entry.maskInfo.color = *color; + } + return bit; } - return existing->second.maskBit; } if( ~s_registeredCaptureMaskBits == 0 ) @@ -154,21 +176,24 @@ namespace return 0; } - // Allocate the lowest free bit + // Allocate the lowest available free bit for the new CaptureMask const uint64_t maskBit = ~s_registeredCaptureMaskBits & ( s_registeredCaptureMaskBits + 1 ); if( !color ) { std::vector existingColors; - existingColors.reserve( s_registeredCaptureMasks.size() ); - for( const auto& entry : s_registeredCaptureMasks ) + existingColors.reserve( CAPTURE_MASKS_MAX ); + for( const auto& existingEntry : s_registeredCaptureMasks ) { - existingColors.push_back( entry.second.color ); + if( existingEntry.slotBit.load( std::memory_order_relaxed ) != 0 ) + { + existingColors.push_back( existingEntry.maskInfo.color ); + } } color = ColorUtil::PickDistinctColor( existingColors ); } - s_registeredCaptureMasks[lowerName] = CaptureMaskEntry{ maskBit, *color }; + StoreRegisteredCaptureMask( maskBit, lowerName, *color ); s_registeredCaptureMaskBits |= maskBit; CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", lowerName.c_str(), static_cast( maskBit ), CcpColorToString( *color ).c_str() ); return maskBit; @@ -187,15 +212,17 @@ uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color ) std::vector CcpGetRegisteredCaptureMasks() { - // Guard access to registered CaptureMasks while return list is populated + // Guard access to registered CaptureMasks while return list is populated. CcpAutoMutex lock( s_captureMaskRegisterMutex ); - const RegisteredCaptureMaskMap_t& masks = GetRegisteredCaptureMasks(); std::vector result; - result.reserve( masks.size() ); - for( const auto& entry : masks ) + result.reserve( CAPTURE_MASKS_MAX ); + for( const auto& slot : s_registeredCaptureMasks ) { - result.push_back( CcpCaptureMaskInfo{ entry.first, entry.second.maskBit, entry.second.color } ); + if( slot.slotBit.load( std::memory_order_relaxed ) != 0 ) + { + result.push_back( slot.maskInfo ); + } } return result; } From 332a6050eb1098dc27e659329e77de1c48972856 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:21:45 +0000 Subject: [PATCH 06/40] Add CcpSetActiveCaptureMask() support Change contains: - Two overloads of CcpSetActiveCaptureMask() function - An exported CcpGetActiveCaptureMask() function - Support for an "all" active CaptureMask - Support for lazy-register of active CaptureMasks by name via a list of pending ones. Part of: https://fenriscreations.atlassian.net/browse/PLAT-11481 --- CcpTelemetry.cpp | 133 ++++++++++++++++++++++++++++++++++++----- include/CcpTelemetry.h | 12 +++- tests/CcpTelemetry.cpp | 61 +++++++++++++++++++ 3 files changed, 189 insertions(+), 17 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index cf6bd2e..ad0e928 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -67,6 +67,13 @@ typedef TrackableStdVector> EventHa namespace { + std::string ToLower( const std::string& s ) + { + std::string out( s ); + std::transform( out.begin(), out.end(), out.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); + return out; + } + uint32_t s_telemetryTick = 0; CcpTelemetryConfig s_config; @@ -94,17 +101,25 @@ namespace // ------------------------------- #define CAPTURE_MASKS_MAX 64 + CcpMutex s_captureMaskMutex( "CcpTelemetry", "CaptureMaskMutex" ); + struct CaptureMaskEntry { std::atomic slotBit{ 0 }; // used to indicate if the slot entry has been populated or not CcpCaptureMaskInfo maskInfo{}; }; - // Fixed size array for registered CaptureMasks, - // allows for O(1) lookup based on single-bit CaptureMask value. + // Fixed size array for registered CaptureMasks. + // Allows for O(1) lookup based on single-bit CaptureMask value. std::array s_registeredCaptureMasks{}; - CcpMutex s_captureMaskRegisterMutex( "CcpTelemetry", "CaptureMaskRegisterMutex" ); + // Currently-active CaptureMask. Defaults to "all" until + // narrowed in a later call to CcpSetActiveCaptureMask() + std::atomic s_activeCaptureMaskBits{ UINT64_MAX }; + + // List of CaptureMask names that have yet to be registered. + // From a CcpSetActiveCaptureMask( vector ) call. + std::vector s_pendingActiveCaptureMaskNames; #if defined( _MSC_VER ) #include // _BitScanForward64 TODO: Can be removed once upgraded to C++20 - using std::countr_zero() instead. @@ -123,7 +138,7 @@ namespace #endif } - // Store a registered CaptureMask into its slot entry + // Store a registered CaptureMask into its entry slot void StoreRegisteredCaptureMask( uint64_t bit, const std::string& name, CcpColor color ) { auto& entry = s_registeredCaptureMasks[CountTrailingZeros64( bit )]; @@ -144,7 +159,7 @@ namespace uint64_t RegisterCaptureMask( const std::string& name, std::optional color ) { // Guard access to registered CaptureMasks while we add/update a new entry - CcpAutoMutex lock( s_captureMaskRegisterMutex ); + CcpAutoMutex lock( s_captureMaskMutex ); if( name.empty() ) { @@ -152,25 +167,24 @@ namespace return 0; } - std::string lowerName( name ); - std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); + std::string lowerName = ToLower( name ); // Explicitly allow change of color on an existing registered // CaptureMask entry in case of re-register on same name. - for( auto& entry : s_registeredCaptureMasks ) + for( auto& registeredEntry : s_registeredCaptureMasks ) { - const uint64_t bit = entry.slotBit.load( std::memory_order_relaxed ); - if( bit != 0 && entry.maskInfo.name == lowerName ) + const uint64_t bit = registeredEntry.slotBit.load( std::memory_order_relaxed ); + if( bit != 0 && registeredEntry.maskInfo.name == lowerName ) { if( color ) { - entry.maskInfo.color = *color; + registeredEntry.maskInfo.color = *color; } return bit; } } - if( ~s_registeredCaptureMaskBits == 0 ) + if( s_registeredCaptureMaskBits == UINT64_MAX ) { CCP_LOGERR_CH( s_ch, "Cannot register CaptureMask '%s' - all 64 bits are already in use", lowerName.c_str() ); return 0; @@ -196,6 +210,16 @@ namespace StoreRegisteredCaptureMask( maskBit, lowerName, *color ); s_registeredCaptureMaskBits |= maskBit; CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", lowerName.c_str(), static_cast( maskBit ), CcpColorToString( *color ).c_str() ); + + // Make sure previously "pending active" CaptureMask is included + auto pendingIt = std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), lowerName ); + if( pendingIt != s_pendingActiveCaptureMaskNames.end() ) + { + s_pendingActiveCaptureMaskNames.erase( pendingIt ); + s_activeCaptureMaskBits.fetch_or( maskBit, std::memory_order_acq_rel ); + CCP_LOGWARN_CH( s_ch, "Previously pending CaptureMask '%s' added to activeCaptureMask -> 0x%llx", lowerName.c_str(), static_cast( maskBit ) ); + } + return maskBit; } } @@ -213,20 +237,84 @@ uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color ) std::vector CcpGetRegisteredCaptureMasks() { // Guard access to registered CaptureMasks while return list is populated. - CcpAutoMutex lock( s_captureMaskRegisterMutex ); + CcpAutoMutex lock( s_captureMaskMutex ); std::vector result; result.reserve( CAPTURE_MASKS_MAX ); - for( const auto& slot : s_registeredCaptureMasks ) + for( const auto& registeredEntry : s_registeredCaptureMasks ) { - if( slot.slotBit.load( std::memory_order_relaxed ) != 0 ) + if( registeredEntry.slotBit.load( std::memory_order_relaxed ) != 0 ) { - result.push_back( slot.maskInfo ); + result.push_back( registeredEntry.maskInfo ); } } return result; } +void CcpSetActiveCaptureMask( const uint64_t captureMask ) +{ + // Guard access to active and pending CaptureMasks + CcpAutoMutex lock( s_captureMaskMutex ); + + s_pendingActiveCaptureMaskNames.clear(); + s_activeCaptureMaskBits.store( captureMask, std::memory_order_release ); + CCP_LOGWARN_CH( s_ch, "Active CaptureMask set to 0x%llx", static_cast( captureMask ) ); +} + +void CcpSetActiveCaptureMask( const std::vector& maskNames ) +{ + // Guard access to all CaptureMasks members + CcpAutoMutex lock( s_captureMaskMutex ); + + s_pendingActiveCaptureMaskNames.clear(); + uint64_t newActiveCaptureMask = 0; + for( const auto& rawName : maskNames ) + { + if( rawName.empty() ) + { + continue; + } + std::string lowerName = ToLower( rawName ); + + if( lowerName == "all" ) + { + // Set all bits and clear the pending list + s_pendingActiveCaptureMaskNames.clear(); + newActiveCaptureMask = UINT64_MAX; + break; + } + + bool alreadyRegistered = false; + for( const auto& registeredEntry : s_registeredCaptureMasks ) + { + const uint64_t entryBit = registeredEntry.slotBit.load( std::memory_order_relaxed ); + if( entryBit != 0 && registeredEntry.maskInfo.name == lowerName ) + { + newActiveCaptureMask |= entryBit; + alreadyRegistered = true; + break; + } + } + + // This CaptureMask hasn't been registered (yet) so add it to the pending list + if( !alreadyRegistered ) + { + if( std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), lowerName ) == s_pendingActiveCaptureMaskNames.end() ) + { + s_pendingActiveCaptureMaskNames.push_back( std::move( lowerName ) ); + } + } + } + + s_activeCaptureMaskBits.store( newActiveCaptureMask, std::memory_order_release ); + CCP_LOGWARN_CH( s_ch, "Active CaptureMask set to 0x%llx (%zu pending unresolved name(s))", static_cast( newActiveCaptureMask ), s_pendingActiveCaptureMaskNames.size() ); +} + +uint64_t CcpGetActiveCaptureMask() +{ + return s_activeCaptureMaskBits.load( std::memory_order_acquire ); +} + bool CcpTelemetryIsConnected() { return TracyIsStarted && TracyIsConnected && s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Started; @@ -638,6 +726,19 @@ std::vector CcpGetCaptureMasks() return {}; } +void CcpSetActiveCaptureMask( uint64_t ) +{ +} + +void CcpSetActiveCaptureMask( const std::vector& ) +{ +} + +uint64_t CcpGetActiveCaptureMask() +{ + return 0; +} + bool CcpStartTelemetry( const char* server, int connectionType, uint32_t maxThreadCount ) { return false; diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 22bfb5e..6e73c85 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -42,6 +42,13 @@ CARBON_CORE_API void CcpRegisterThread( CcpThreadId_t threadId, const char* name // available CaptureMask bit from a 64bit integer mask. // - A Telemetry display color is also associated with the chosen CaptureMask bit, // either manually or automatically allocated based on "best available fit". +// - An active CaptureMask defaults to "all" but can be set/narrowed before or +// during a Telemetry session is started using either: +// - a numerical bit mask value of the active CaptureMask +// - list of "component display names" ("all" is allowed) +// - Setting active CaptureMask is available for both: +// - already registered components +// - "pending" (yet to be registered) components // --------------------------------------------------------------------------- struct CcpCaptureMaskInfo { @@ -52,9 +59,12 @@ struct CcpCaptureMaskInfo CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name ); CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color ); - CARBON_CORE_API std::vector CcpGetRegisteredCaptureMasks(); +CARBON_CORE_API void CcpSetActiveCaptureMask( const uint64_t captureMask ); +CARBON_CORE_API void CcpSetActiveCaptureMask( const std::vector& maskNames ); +CARBON_CORE_API uint64_t CcpGetActiveCaptureMask(); + struct CcpTelemetryConfig { diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 7373954..4a657a0 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -633,3 +633,64 @@ TEST_F( CcpTelemetryTest, CaptureMaskAutoColorIsPickedFromCandidateList ) EXPECT_NE( end, std::find( begin, end, info.color ) ); } +TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByBits ) +{ + uint64_t newBits = 0; + newBits |= CcpRegisterCaptureMask( "SetActiveCaptureMaskByBits_A" ); + newBits |= CcpRegisterCaptureMask( "SetActiveCaptureMaskByBits_B" ); + + uint64_t registeredCaptureMaskBits = 0; + auto registeredCaptureMasks = CcpGetRegisteredCaptureMasks(); + for ( auto captureMask : registeredCaptureMasks ) + { + ASSERT_TRUE( IsSCaptureMaskSingleBit( captureMask.maskBit ) ) << "Each entry should only contain one bit set"; + registeredCaptureMaskBits |= captureMask.maskBit; + } + EXPECT_TRUE( (newBits & registeredCaptureMaskBits) == newBits ) << "All bits in combined newBits should be present in combined registered CaptureMask bits "; + EXPECT_FALSE( (newBits & registeredCaptureMaskBits) == registeredCaptureMaskBits ) << "Registered bits should contain the additional 'general' and 'cpp' bits"; + + // newBits excludes the default "general" and "ccp" that are present in registeredCaptureMaskBits + CcpSetActiveCaptureMask( newBits ); + EXPECT_EQ( newBits, CcpGetActiveCaptureMask() ); + + // Overwrite the active CaptureMask + CcpSetActiveCaptureMask( TMCM_GENERAL ); + EXPECT_EQ( static_cast( TMCM_GENERAL ), CcpGetActiveCaptureMask() ); +} + +TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByNames ) +{ + const std::string registeredName = "SetActiveCaptureMaskByNames_RegisteredName"; + const std::string newName = "SetActiveCaptureMaskByNames_NewName"; + const std::string pendingName = "SetActiveCaptureMaskByNames_PendingName"; + const std::vector activeMaskList = { registeredName, pendingName }; + + // Only register one name + const uint64_t registeredBit = CcpRegisterCaptureMask( registeredName ); + ASSERT_TRUE( IsSCaptureMaskSingleBit( registeredBit ) ); + CcpSetActiveCaptureMask( activeMaskList ); + EXPECT_EQ( registeredBit, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should only contain the registered bit, not the pending one"; + + // Register the new name + const uint64_t newBit = CcpRegisterCaptureMask( newName ); + ASSERT_TRUE( IsSCaptureMaskSingleBit( newBit ) ); + EXPECT_EQ( registeredBit, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should still only contain the registered bit, not the new or pending one"; + + // Now add the pending one + const uint64_t pendingBit = CcpRegisterCaptureMask( pendingName ); + ASSERT_TRUE( IsSCaptureMaskSingleBit( pendingBit ) ); + const uint64_t activeBits = registeredBit | pendingBit; + EXPECT_EQ( activeBits, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should now contain both the registered and pending bits"; + + // Deal with the special "all" case + CcpSetActiveCaptureMask( std::vector{ "all" } ); + EXPECT_EQ( UINT64_MAX, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should be all bits set when using the special 'all' alias"; + CcpRegisterCaptureMask( "SetActiveCaptureMaskByNames_AfterAll" ); + EXPECT_EQ( UINT64_MAX, CcpGetActiveCaptureMask() ); + + // Overwrite the active CaptureMask + CcpSetActiveCaptureMask( TMCM_CPP ); + EXPECT_EQ( static_cast( TMCM_CPP ), CcpGetActiveCaptureMask() ); + ASSERT_TRUE( IsSCaptureMaskSingleBit( CcpGetActiveCaptureMask() ) ); +} + From 867b6d76f9867ebb58d06e60d12609dc647be472 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:20:24 +0000 Subject: [PATCH 07/40] Add support for color to the TracyTestClient. Make sure we can verify the color associated with a Zone from the TracyTestClient in tests. --- tests/CcpTelemetry.cpp | 5 +++++ tests/TracyTestClient.cpp | 5 ++++- tests/TracyTestClient.h | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 4a657a0..e271e4d 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -258,6 +258,11 @@ TEST_F( CcpTelemetryTest, SimpleZoneTest ) EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); EXPECT_TRUE( ZoneExists( zoneName ) ); + // CcpTelemetryEnterZone() hardcodes CcpColor::Yellow, make sure it is present + const auto zones = m_tracyClient.GetZones(); + ASSERT_EQ( 1u, zones.size() ); + EXPECT_EQ( static_cast( CcpColor::Yellow ), zones.front().color ); + CcpTelemetryLeaveZone( &key ); TickTelemetry( [this] { return m_tracyClient.GetZoneEndCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); diff --git a/tests/TracyTestClient.cpp b/tests/TracyTestClient.cpp index a2941b6..30959bc 100644 --- a/tests/TracyTestClient.cpp +++ b/tests/TracyTestClient.cpp @@ -425,7 +425,9 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) if( strSz >= 9 ) { const char* p = ptr; - p += 4; // skip color + uint32_t color = 0; + std::memcpy( &color, p, 4 ); + p += 4; uint32_t line = 0; std::memcpy( &line, p, 4 ); p += 4; @@ -440,6 +442,7 @@ void TracyTestClient::ProcessDecompressedData( const char* data, int sz ) m_pendingZone.function = function; m_pendingZone.source = source; m_pendingZone.line = line; + m_pendingZone.color = color; if( nameLen > 0 ) m_pendingZone.name = std::string( p, nameLen ); m_hasPendingZone = true; diff --git a/tests/TracyTestClient.h b/tests/TracyTestClient.h index 99d95fb..7c9d2e6 100644 --- a/tests/TracyTestClient.h +++ b/tests/TracyTestClient.h @@ -23,6 +23,7 @@ class TracyTestClient std::string function; std::string source; uint32_t line = 0; + uint32_t color = 0; // Defaults to Black = 0x000000 }; using ZoneStack = std::vector; From 31f7c93dc57fc155247a1d9f2091bf3b4a413234 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:52:49 +0000 Subject: [PATCH 08/40] Explicitly add includes for std types used in CcpTelemetry.h Done because of build problems on v143/v145 vs v141 identified by ccptoebeans. --- include/CcpTelemetry.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 6e73c85..30289e2 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -4,6 +4,11 @@ #ifndef CcpTelemetry_h #define CcpTelemetry_h +#include +#include +#include +#include + #include "CcpColorConstants.h" #include "CcpThread.h" #include "carbon_core_export.h" From d9f7aeac73e82d2ebb87d942825a52f07e716500 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:38:54 +0000 Subject: [PATCH 09/40] Add support for CaptureMasks in CcpTelemetryEnterZone/TelemetryZone Add CaptureMaksBit overloads for: - TelemetryZone constructor - CcpTelemetryEnterZone() Make sure tests reflect the new reality. Part of: https://fenriscreations.atlassian.net/browse/PLAT-11476 --- CcpTelemetry.cpp | 67 +++++++++++++++++++++- include/CcpTelemetry.h | 16 ++++++ tests/CcpTelemetry.cpp | 126 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 198 insertions(+), 11 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index ad0e928..946bb74 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -222,6 +222,36 @@ namespace return maskBit; } + + // Get the registered CaptureMask color for a given CaptureMask + // Default to CcpColor::White if not found + CcpColor GetCaptureMaskColor( uint64_t captureMaskBit ) + { + if( captureMaskBit == 0 ) + { + return CcpColor::White; + } + + // Find the registered CaptureMask index + const int idx = CountTrailingZeros64( captureMaskBit ); + if( idx < 0 || idx >= CAPTURE_MASKS_MAX ) + { + return CcpColor::White; + } + + const auto& entry = s_registeredCaptureMasks[idx]; + if( entry.slotBit.load( std::memory_order_acquire ) == captureMaskBit ) + { + return entry.maskInfo.color; + } + + return CcpColor::White; + } + + bool IsCaptureMaskActive( uint64_t captureMaskBit ) + { + return ( s_activeCaptureMaskBits.load( std::memory_order_acquire ) & captureMaskBit ) != 0; + } } uint64_t CcpRegisterCaptureMask( const std::string& name ) @@ -595,6 +625,7 @@ const std::string& CcpTelemetryGetActiveFiber() return *t_activeFiber; } +// Deprecated version TelemetryZone::TelemetryZone( uint32_t ctx, const char* name, const char* filename, uint32_t lineno, CcpColor color ) : m_impl(std::make_unique()) { if( s_profilerState.load( std::memory_order_acquire ) != ProfilerState::Started ) @@ -604,10 +635,30 @@ TelemetryZone::TelemetryZone( uint32_t ctx, const char* name, const char* filena CCP_ASSERT( filename != nullptr ); CCP_ASSERT( name != nullptr ); + + const int active = ( ( ctx & TMCM_CPP ) != 0 ) && IsCaptureMaskActive( ctx ); auto data = ___tracy_alloc_srcloc( lineno, filename, strlen( filename ), name, strlen( name ), static_cast( color ) ); // CCP_LOG_CH( s_ch, "[Fiber %p] Creating zone %s (%p)", t_activeFiber->c_str(), ret.first->c_str(), this ); m_impl->fiber = t_activeFiber; - m_impl->telemetryContext.emplace( ___tracy_emit_zone_begin_alloc( data, ctx & TMCM_CPP ) ); + m_impl->telemetryContext.emplace( ___tracy_emit_zone_begin_alloc( data, active ) ); +} + +// Preferred version +TelemetryZone::TelemetryZone( CaptureMaskBitTag, uint64_t captureMaskBit, const char* name, const char* filename, uint32_t lineno ) : m_impl( std::make_unique() ) +{ + if( s_profilerState.load( std::memory_order_acquire ) != ProfilerState::Started ) + { + return; + } + + CCP_ASSERT( filename != nullptr ); + CCP_ASSERT( name != nullptr ); + + const CcpColor color = GetCaptureMaskColor( captureMaskBit ); + const int active = IsCaptureMaskActive( captureMaskBit ); + auto data = ___tracy_alloc_srcloc( lineno, filename, strlen( filename ), name, strlen( name ), static_cast( color ) ); + m_impl->fiber = t_activeFiber; + m_impl->telemetryContext.emplace( ___tracy_emit_zone_begin_alloc( data, active ) ); } TelemetryZone::TelemetryZone( TelemetryZone&& other ) noexcept : m_impl( std::make_unique() ) @@ -644,13 +695,19 @@ void TelemetryZone::text( const char* text ) const } } +// Deprecated version void CcpTelemetryEnterZone( void* key, const char* name, const char* filename, uint32_t lineno ) +{ + CcpTelemetryEnterZone( key, TMCM_CPP, name, filename, lineno ); // The default assigned color for TMCM_CPP is CcpColor::Yellow +} + +// Preferred version +void CcpTelemetryEnterZone( void* key, uint64_t captureMaskBit, const char* name, const char* filename, uint32_t lineno ) { if( s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Started ) { t_manuallyTrackedZones.emplace( key ); - t_activeTaskletZoneStore->second.emplace( TMCM_CPP, name, filename, lineno, CcpColor::Yellow ); -// CCP_LOG_CH( s_ch, "[Fiber %p] [Store %p] [Zone %p] Enter", t_activeFiber, t_activeTaskletZoneStore, &t_activeTaskletZoneStore->second.top() ); + t_activeTaskletZoneStore->second.emplace( CaptureMaskBit, captureMaskBit, name, filename, lineno ); } } @@ -787,6 +844,10 @@ void CcpTelemetryEnterZone( void* key, const char* name, const char* filename, u { } +void CcpTelemetryEnterZone( void* key, uint64_t captureMaskBit, const char* name, const char* filename, uint32_t lineno ) +{ +} + void CcpTelemetryLeaveZone( void* key ) { } diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 30289e2..cac4e65 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -108,12 +108,25 @@ CARBON_CORE_API void CcpTelemetrySetActiveFiber( const std::string& name ); CARBON_CORE_API const std::string& CcpTelemetryGetActiveFiber(); CARBON_CORE_API void CcpTelemetryRemoveFiber( const std::string& name ); +// The CaptureMaskBitTag is a work-around to solve disambiguity between the two +// TelemetryZone constructors (uint32_t ctx vs uint64_t captureMaskBit) and +// still preserve ABI compatibility. +// The ambiguity would otherwise be triggered if the default CcpColor parameter +// is omitted in the original (now deprecated) uint32_t ctx constructor. +struct CaptureMaskBitTag { explicit CaptureMaskBitTag() = default; }; +inline constexpr CaptureMaskBitTag CaptureMaskBit{}; + class TelemetryZone { public: TelemetryZone() = delete; + + [[deprecated( "Use `TelemetryZone( CaptureMaskBitTag, uint64_t captureMaskBit, ... )` instead" )]] CARBON_CORE_API TelemetryZone( uint32_t ctx, const char* name, const char* filename, uint32_t lineno, CcpColor color = CcpColor::SteelBlue ); + + CARBON_CORE_API TelemetryZone( CaptureMaskBitTag, uint64_t captureMaskBit, const char* name, const char* filename, uint32_t lineno ); CARBON_CORE_API ~TelemetryZone(); + TelemetryZone( TelemetryZone&& other ) noexcept; TelemetryZone( const TelemetryZone& ) = delete; TelemetryZone& operator=( TelemetryZone&& ) = delete; @@ -126,7 +139,10 @@ class TelemetryZone std::unique_ptr m_impl; }; +[[deprecated( "Use `CcpTelemetryEnterZone( void* key, uint64_t captureMaskBit, ... )` instead" )]] CARBON_CORE_API void CcpTelemetryEnterZone( void* key, const char* name, const char* filename, uint32_t lineno ); + +CARBON_CORE_API void CcpTelemetryEnterZone( void* key, uint64_t captureMaskBit, const char* name, const char* filename, uint32_t lineno ); CARBON_CORE_API void CcpTelemetryLeaveZone( void* key ); CARBON_CORE_API void CcpTelemetryZoneAddText( void* key, const char* text ); diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index e271e4d..421a439 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -251,16 +251,16 @@ TEST_F( CcpTelemetryTest, SimpleZoneTest ) static int key = 4711; const std::string zoneName{ "TestZone" }; - CcpTelemetryEnterZone( &key, zoneName.c_str(), __FILE__, __LINE__ ); + CcpTelemetryEnterZone( &key, zoneName.c_str(), __FILE__, __LINE__ ); // Original deprecated version // 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( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); EXPECT_TRUE( ZoneExists( zoneName ) ); - // CcpTelemetryEnterZone() hardcodes CcpColor::Yellow, make sure it is present + // The default assigned color in CcpTelemetryEnterZone() for TMCM_CPP is CcpColor::Yellow, make sure it is present const auto zones = m_tracyClient.GetZones(); - ASSERT_EQ( 1u, zones.size() ); + EXPECT_EQ( 1, zones.size() ); EXPECT_EQ( static_cast( CcpColor::Yellow ), zones.front().color ); CcpTelemetryLeaveZone( &key ); @@ -273,8 +273,8 @@ 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__ ); + CcpTelemetryEnterZone( &key, "TestZone", __FILE__, __LINE__ ); // Original deprecated version + CcpTelemetryEnterZone( &key, TMCM_CPP, "TestZone2", __FILE__, __LINE__ ); // New CaptureMaskBit version TickTelemetry( [this] { return m_tracyClient.GetZones().size() == 2; } ); EXPECT_EQ( 2, m_tracyClient.GetZones().size() ); EXPECT_TRUE( ZoneExists( "TestZone" ) ); @@ -295,7 +295,7 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) { static int key1 = 1001; const std::string zoneName1{ "FirstZone" }; - CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); + CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); // Original deprecated version TickTelemetry( [this, zoneName1] { return ZoneExists( zoneName1 ); } ); EXPECT_TRUE( ZoneExists( zoneName1 ) ); EXPECT_EQ( 1, m_tracyClient.GetZones().size() ); @@ -319,11 +319,14 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) // 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__ ); + CcpTelemetryEnterZone( &key2, TMCM_GENERAL, zoneName2.c_str(), __FILE__, __LINE__ ); // New CaptureMaskBit version (CcpColor::SteelBlue) TickTelemetry( [this, zoneName2] { return ZoneExists( zoneName2 ); } ); EXPECT_TRUE( ZoneExists( zoneName2 ) ); EXPECT_FALSE( ZoneExists( zoneName1 ) ) << "FirstZone should not exist"; - EXPECT_EQ( 1, m_tracyClient.GetZones().size() ); + + const auto zones = m_tracyClient.GetZones(); + EXPECT_EQ( 1, zones.size() ); + EXPECT_EQ( static_cast( CcpColor::SteelBlue ), zones.front().color ) << "Default color for CaptureMask TMCM_GENERAL should be CcpColor::SteelBlue"; EXPECT_EQ( 2, m_tracyClient.GetZoneBeginCount() ); EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); @@ -333,6 +336,113 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) EXPECT_EQ( 2, m_tracyClient.GetZoneEndCount() ); } +TEST_F( CcpTelemetryTest, TelemetryZoneLegacyConstructor ) +{ + // Test where captureMask is in the Active list + CcpSetActiveCaptureMask( TMCM_GENERAL | TMCM_CPP ); + { + // Make sure the "legacy deprecated" TelemetryZone constructor respects overwrites of color + TelemetryZone activeZone( TMCM_CPP, "LegacyZoneIsInActiveList", __FILE__, __LINE__, CcpColor::Red ); // Overwrite the default CcpColor::Yellow of TMCM_CPP + TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); + const auto zones = m_tracyClient.GetZones(); + EXPECT_EQ( 1, zones.size() ); + EXPECT_EQ( "LegacyZoneIsInActiveList", zones.front().function ); + EXPECT_EQ( static_cast( CcpColor::Red ), zones.front().color ); + EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); + } + // Now the activeZone has gone out of scope, so the zone should have ended + TickTelemetry( [this] { return m_tracyClient.GetZoneEndCount() == 1; } ); + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); + EXPECT_TRUE( m_tracyClient.GetZones().empty() ); + + // Test where captureMask is NOT in the Active list + CcpSetActiveCaptureMask( {"NotRegisteredCaptureMask", "ClearingPreviousGeneralAndCpp", "FromTheActiveCaptureMaskList"} ); + { + TelemetryZone inactiveZone( TMCM_CPP, "LegacyZoneIsNotInActiveList", __FILE__, __LINE__, CcpColor::Blue ); + TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Because TMCM_CPP is now no longer in the ACTIVE CaptureMask list, we should not have emitted an event for it"; + EXPECT_EQ( 0, m_tracyClient.GetZones().size() ) << "Zone list should therefore be empty"; + } + TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Inactive legacy zone must not emit ZoneBegin, count should stay at 1"; + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "Inactive legacy zone must not emit ZoneEnd, count should stay at 1"; + EXPECT_TRUE( m_tracyClient.GetZones().empty() ); +} + +TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) +{ + // Register a new component CaptureMask + const uint64_t componentMaskBit = CcpRegisterCaptureMask( "TelemetryZoneCaptureMaskBitConstructor", CcpColor::Orange ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( componentMaskBit ) ); + + // Test where componentMaskBit is in the Active list + CcpSetActiveCaptureMask( componentMaskBit ); + { + TelemetryZone activeZone( CaptureMaskBit, componentMaskBit, "Active_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); + TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); + const auto zones = m_tracyClient.GetZones(); + EXPECT_EQ( 1, zones.size() ); + EXPECT_EQ( "Active_TelemetryZoneCaptureMaskBitConstructor", zones.front().function ); + EXPECT_EQ( static_cast( CcpColor::Orange ), zones.front().color ) << "Color should come from the registered CaptureMask entry"; + } + TickTelemetry( [this] { return m_tracyClient.GetZoneEndCount() == 1; } ); + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); + EXPECT_TRUE( m_tracyClient.GetZones().empty() ); + + // Test where componentMaskBit is NOT in the Active list - overwrite the Active list with only "general" + CcpSetActiveCaptureMask( TMCM_GENERAL ); + EXPECT_EQ( 0, CcpGetActiveCaptureMask() & componentMaskBit ); + { + TelemetryZone inactiveZone( CaptureMaskBit, componentMaskBit, "Inactive_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); + TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Because TMCM_GENERAL is now no longer in the ACTIVE CaptureMask list, we should not have emitted an event for it"; + EXPECT_EQ( 0, m_tracyClient.GetZones().size() ) << "Zone list should therefore be empty"; + } + TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Inactive CaptureMaskBit zone must not emit ZoneBegin"; + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "Inactive CaptureMaskBit zone must not emit ZoneEnd"; + EXPECT_TRUE( m_tracyClient.GetZones().empty() ); +} + +TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneCaptureMaskBit ) +{ + // Register a new component CaptureMask + const uint64_t componentMaskBit = CcpRegisterCaptureMask( "CcpTelemetryEnterZoneCaptureMaskBit", CcpColor::LimeGreen ); + ASSERT_TRUE( IsSCaptureMaskSingleBit( componentMaskBit ) ); + + // Test where componentMaskBit is in the Active list + CcpSetActiveCaptureMask( componentMaskBit ); + static int activeKey = 8001; + CcpTelemetryEnterZone( &activeKey, componentMaskBit, "Active_CcpTelemetryEnterZoneCaptureMaskBit", __FILE__, __LINE__ ); + TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); + const auto zones = m_tracyClient.GetZones(); + EXPECT_EQ( 1, zones.size() ); + EXPECT_EQ( "Active_CcpTelemetryEnterZoneCaptureMaskBit", zones.front().function ); + EXPECT_EQ( static_cast( CcpColor::LimeGreen ), zones.front().color ); + EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); + + CcpTelemetryLeaveZone( &activeKey ); + TickTelemetry( [this] { return m_tracyClient.GetZoneEndCount() == 1; } ); + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); + + // Test where componentMaskBit is NOT in the Active list - overwrite the Active list with only "general" + CcpSetActiveCaptureMask( TMCM_GENERAL ); + EXPECT_EQ( 0, CcpGetActiveCaptureMask() & componentMaskBit ); + static int inactiveKey = 8002; + CcpTelemetryEnterZone( &inactiveKey, componentMaskBit, "Inactive_CcpTelemetryEnterZoneCaptureMaskBit", __FILE__, __LINE__ ); + TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Inactive CaptureMaskBit enter-zone must not emit ZoneBegin"; + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "EndZone count should still be 1"; + EXPECT_TRUE( m_tracyClient.GetZones().empty() ); + + CcpTelemetryLeaveZone( &inactiveKey ); + TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "Inactive CaptureMaskBit enter-zone must not emit ZoneEnd"; +} + // --------------------------------------------------------------------------- // CcpMutex / CcpAutoMutex From fd800aaff29ed8831858f63ee8950a0f7254cd90 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:44:58 +0000 Subject: [PATCH 10/40] Use EXPECT_TRUE instead of ASSERT_TRUE in CcpTelemetry tests --- tests/CcpTelemetry.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 421a439..8a63f05 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -410,7 +410,7 @@ TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneCaptureMaskBit ) { // Register a new component CaptureMask const uint64_t componentMaskBit = CcpRegisterCaptureMask( "CcpTelemetryEnterZoneCaptureMaskBit", CcpColor::LimeGreen ); - ASSERT_TRUE( IsSCaptureMaskSingleBit( componentMaskBit ) ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( componentMaskBit ) ); // Test where componentMaskBit is in the Active list CcpSetActiveCaptureMask( componentMaskBit ); @@ -462,7 +462,7 @@ TEST_F( CcpTelemetryTest, CcpMutexAnnounceAndTerminate ) // The custom name arrives almost immediately, but the source location // resolves through extra server-query round trips; wait for both. TickTelemetry( [&] { return TryGetActiveLockNamed( lockName, lockInfo ) && !lockInfo.source.empty(); } ); - ASSERT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ); + EXPECT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ); EXPECT_FALSE( lockInfo.terminated ); // The owner and name passed to CcpMutex arrive combined as the custom lock name. EXPECT_EQ( lockName, lockInfo.name ); @@ -487,7 +487,7 @@ TEST_F( CcpTelemetryTest, CcpMutexAcquireAndRelease ) TracyTestClient::LockInfo lockInfo; mutex.Acquire(); TickTelemetry( [&] { return TryGetActiveLockNamed( lockName, lockInfo ) && lockInfo.obtainCount == 1; } ); - ASSERT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ); + EXPECT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ); EXPECT_EQ( 1, lockInfo.waitCount ); EXPECT_EQ( 1, lockInfo.obtainCount ); EXPECT_EQ( 0, lockInfo.releaseCount ); @@ -572,8 +572,8 @@ TEST_F( CcpTelemetryTest, MultipleCcpMutexesAnnounceDistinctLocks ) return TryGetActiveLockNamed( firstLockName, firstLock ) && TryGetActiveLockNamed( secondLockName, secondLock ); } ); - ASSERT_TRUE( TryGetActiveLockNamed( firstLockName, firstLock ) ); - ASSERT_TRUE( TryGetActiveLockNamed( secondLockName, secondLock ) ); + EXPECT_TRUE( TryGetActiveLockNamed( firstLockName, firstLock ) ); + EXPECT_TRUE( TryGetActiveLockNamed( secondLockName, secondLock ) ); EXPECT_NE( firstLock.id, secondLock.id ); secondMutex.Release(); @@ -594,7 +594,7 @@ TEST_F( CcpTelemetryTest, CcpSpinLockAnnounceAndTerminate ) CcpAutoSpinLock autoSpinLock( spinLock ); TickTelemetry( [&] { return TryGetActiveLockNamed( lockName, lockInfo ); } ); - ASSERT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ); + EXPECT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ); EXPECT_FALSE( lockInfo.terminated ); EXPECT_EQ( lockName, lockInfo.name ); EXPECT_TRUE( lockInfo.waitingThreads.empty() ); @@ -618,7 +618,7 @@ TEST_F( CcpTelemetryTest, CcpSpinLockAcquireAndRelease ) spinLock.Acquire(); TickTelemetry( [&] { return TryGetActiveLockNamed( lockName, lockInfo ); } ); - ASSERT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ); + EXPECT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ); const uint32_t lockId = lockInfo.id; EXPECT_EQ( 1, lockInfo.waitCount ); EXPECT_EQ( 1, lockInfo.obtainCount ); @@ -643,7 +643,7 @@ TEST_F( CcpTelemetryTest, CcpSemaphoreAnnounceAndTerminate ) std::thread waiter( [&semaphore] { semaphore.Wait(); } ); TickTelemetry( [&] { return TryGetActiveLockNamed( lockName, lockInfo ) && lockInfo.name == lockName; } ); - ASSERT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ) << "lockName: " << lockName << " lockInfo.name: " << lockInfo.name; + EXPECT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ) << "lockName: " << lockName << " lockInfo.name: " << lockInfo.name; EXPECT_FALSE( lockInfo.terminated ); EXPECT_EQ( lockName, lockInfo.name ); EXPECT_EQ( 1, lockInfo.waitCount ); @@ -673,7 +673,7 @@ TEST_F( CcpTelemetryTest, CcpSemaphoreTimedWaitTimesOut ) // No signal beforehand — TimedWait should time out and report a wait without an obtain. EXPECT_FALSE( semaphore.TimedWait( 10 ) ); TickTelemetry( [&] { return TryGetActiveLockNamed( lockName, lockInfo ) && lockInfo.waitCount == 1 && lockInfo.obtainCount == 1; } ); - ASSERT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ) << "lockName: " << lockName << " lockInfo.name: " << lockInfo.name; + EXPECT_TRUE( TryGetActiveLockNamed( lockName, lockInfo ) ) << "lockName: " << lockName << " lockInfo.name: " << lockInfo.name; EXPECT_EQ( 1, lockInfo.waitCount ); EXPECT_EQ( 1, lockInfo.obtainCount ); EXPECT_EQ( 0, lockInfo.releaseCount ); @@ -722,10 +722,10 @@ TEST_F( CcpTelemetryTest, CaptureMaskDefaultsAreRegistered ) { // The default CaptureMasks must be available from the start. CcpCaptureMaskInfo info; - ASSERT_TRUE( TryGetCaptureMaskNamed( "general", info ) ); + EXPECT_TRUE( TryGetCaptureMaskNamed( "general", info ) ); EXPECT_EQ( static_cast( TMCM_GENERAL ), info.maskBit ); EXPECT_EQ( CcpColor::SteelBlue, info.color ); // CcpColor::SteelBlue is the default assigned color for TMCM_GENERAL - ASSERT_TRUE( TryGetCaptureMaskNamed( "cpp", info ) ); + EXPECT_TRUE( TryGetCaptureMaskNamed( "cpp", info ) ); EXPECT_EQ( static_cast( TMCM_CPP ), info.maskBit ); // Registering a CaptureMask for the same name should result in the same maskBit returned, @@ -742,7 +742,7 @@ TEST_F( CcpTelemetryTest, CaptureMaskAutoColorIsPickedFromCandidateList ) CcpRegisterCaptureMask( "CaptureMaskAutoColorIsPickedFromCandidateList" ); CcpCaptureMaskInfo info; - ASSERT_TRUE( TryGetCaptureMaskNamed( "CaptureMaskAutoColorIsPickedFromCandidateList", info ) ); + EXPECT_TRUE( TryGetCaptureMaskNamed( "CaptureMaskAutoColorIsPickedFromCandidateList", info ) ); const auto begin = std::begin( ColorUtil::s_awailableNamedColors ); const auto end = std::end( ColorUtil::s_awailableNamedColors ); EXPECT_NE( end, std::find( begin, end, info.color ) ); @@ -758,7 +758,7 @@ TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByBits ) auto registeredCaptureMasks = CcpGetRegisteredCaptureMasks(); for ( auto captureMask : registeredCaptureMasks ) { - ASSERT_TRUE( IsSCaptureMaskSingleBit( captureMask.maskBit ) ) << "Each entry should only contain one bit set"; + EXPECT_TRUE( IsSCaptureMaskSingleBit( captureMask.maskBit ) ) << "Each entry should only contain one bit set"; registeredCaptureMaskBits |= captureMask.maskBit; } EXPECT_TRUE( (newBits & registeredCaptureMaskBits) == newBits ) << "All bits in combined newBits should be present in combined registered CaptureMask bits "; @@ -782,18 +782,18 @@ TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByNames ) // Only register one name const uint64_t registeredBit = CcpRegisterCaptureMask( registeredName ); - ASSERT_TRUE( IsSCaptureMaskSingleBit( registeredBit ) ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( registeredBit ) ); CcpSetActiveCaptureMask( activeMaskList ); EXPECT_EQ( registeredBit, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should only contain the registered bit, not the pending one"; // Register the new name const uint64_t newBit = CcpRegisterCaptureMask( newName ); - ASSERT_TRUE( IsSCaptureMaskSingleBit( newBit ) ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( newBit ) ); EXPECT_EQ( registeredBit, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should still only contain the registered bit, not the new or pending one"; // Now add the pending one const uint64_t pendingBit = CcpRegisterCaptureMask( pendingName ); - ASSERT_TRUE( IsSCaptureMaskSingleBit( pendingBit ) ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( pendingBit ) ); const uint64_t activeBits = registeredBit | pendingBit; EXPECT_EQ( activeBits, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should now contain both the registered and pending bits"; @@ -806,6 +806,6 @@ TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByNames ) // Overwrite the active CaptureMask CcpSetActiveCaptureMask( TMCM_CPP ); EXPECT_EQ( static_cast( TMCM_CPP ), CcpGetActiveCaptureMask() ); - ASSERT_TRUE( IsSCaptureMaskSingleBit( CcpGetActiveCaptureMask() ) ); + EXPECT_TRUE( IsSCaptureMaskSingleBit( CcpGetActiveCaptureMask() ) ); } From bbec690575e0326ed1565c3fb27efd78da63926f Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:15:09 +0000 Subject: [PATCH 11/40] Simplify auto color selection for CaptureMasks --- CcpTelemetry.cpp | 32 ++++-- include/CcpColorConstants.h | 206 +----------------------------------- tests/CMakeLists.txt | 1 - tests/CcpColorConstants.cpp | 60 ----------- tests/CcpTelemetry.cpp | 12 +-- 5 files changed, 29 insertions(+), 282 deletions(-) delete mode 100644 tests/CcpColorConstants.cpp diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 946bb74..f3ed1e0 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -117,6 +117,27 @@ namespace // narrowed in a later call to CcpSetActiveCaptureMask() std::atomic s_activeCaptureMaskBits{ UINT64_MAX }; + // Color palette for auto-assigned CaptureMasks + constexpr CcpColor s_captureMaskColorPalette[] = { + CcpColor::OrangeRed, + CcpColor::LimeGreen, + CcpColor::DodgerBlue, + CcpColor::Gold, + CcpColor::MediumPurple, + CcpColor::Turquoise, + CcpColor::DeepPink, + CcpColor::Chartreuse, + CcpColor::Tomato, + CcpColor::CornflowerBlue, + CcpColor::Khaki, + CcpColor::SeaGreen, + CcpColor::Orchid, + CcpColor::Salmon, + CcpColor::Aquamarine, + CcpColor::Crimson, + }; + size_t s_captureMaskColorPaletteCursor = 0; + // List of CaptureMask names that have yet to be registered. // From a CcpSetActiveCaptureMask( vector ) call. std::vector s_pendingActiveCaptureMaskNames; @@ -195,16 +216,7 @@ namespace if( !color ) { - std::vector existingColors; - existingColors.reserve( CAPTURE_MASKS_MAX ); - for( const auto& existingEntry : s_registeredCaptureMasks ) - { - if( existingEntry.slotBit.load( std::memory_order_relaxed ) != 0 ) - { - existingColors.push_back( existingEntry.maskInfo.color ); - } - } - color = ColorUtil::PickDistinctColor( existingColors ); + color = s_captureMaskColorPalette[s_captureMaskColorPaletteCursor++ % std::size( s_captureMaskColorPalette )]; } StoreRegisteredCaptureMask( maskBit, lowerName, *color ); diff --git a/include/CcpColorConstants.h b/include/CcpColorConstants.h index 8d390f4..ef80bbb 100644 --- a/include/CcpColorConstants.h +++ b/include/CcpColorConstants.h @@ -6,7 +6,6 @@ #include #include -#include #include "carbon_core_export.h" @@ -164,209 +163,6 @@ enum class CcpColor : uint32_t YellowGreen = 0x9acd32, }; -// Return the name of the color. -// For colors with the same RGB value (e.g. Aqua/Cyan, Gray/Grey), -// the first name declared in CcpColor enum class is returned. -CARBON_CORE_API std::string CcpColorToString( CcpColor color ); - -namespace ColorUtil -{ -// Decompose a CcpColor into its 8-bit red, green and blue components. -inline void ToRgb( CcpColor color, int& r, int& g, int& b ) -{ - const uint32_t value = static_cast( color ); - r = static_cast( ( value >> 16 ) & 0xff ); - g = static_cast( ( value >> 8 ) & 0xff ); - b = static_cast( value & 0xff ); -} - -// Find the squared Euclidean distance between two colors in RGB space. -inline int64_t GetSquaredDistance( CcpColor lhs, CcpColor rhs ) -{ - int r1, g1, b1; - int r2, g2, b2; - ToRgb( lhs, r1, g1, b1 ); - ToRgb( rhs, r2, g2, b2 ); - const int64_t dr = r1 - r2; - const int64_t dg = g1 - g2; - const int64_t db = b1 - b2; - return dr * dr + dg * dg + db * db; -} - -// All named colors, used as candidates when picking a display color. -// NOTE: -// - Remove a color from this list if you don't want it to be selected -// when automatically picking a display color for a new CaptureMask entry. -inline constexpr CcpColor s_awailableNamedColors[] = { - //CcpColor::AliceBlue, - CcpColor::AntiqueWhite, - CcpColor::Aqua /* == Cyan */, - CcpColor::Aquamarine, - //CcpColor::Azure, - CcpColor::Beige, - CcpColor::Bisque, - //CcpColor::Black, - CcpColor::BlanchedAlmond, - CcpColor::Blue, - CcpColor::BlueViolet, - CcpColor::Brown, - CcpColor::BurlyWood, - CcpColor::CadetBlue, - CcpColor::Chartreuse, - CcpColor::Chocolate, - CcpColor::Coral, - CcpColor::CornflowerBlue, - CcpColor::Cornsilk, - CcpColor::Crimson, - CcpColor::DarkBlue, - CcpColor::DarkCyan, - CcpColor::DarkGoldenrod, - //CcpColor::DarkGray /* == DarkGrey */, - CcpColor::DarkGreen, - CcpColor::DarkKhaki, - CcpColor::DarkMagenta, - CcpColor::DarkOliveGreen, - CcpColor::DarkOrange, - CcpColor::DarkOrchid, - CcpColor::DarkRed, - CcpColor::DarkSalmon, - CcpColor::DarkSeaGreen, - CcpColor::DarkSlateBlue, - CcpColor::DarkSlateGray /* == DarkSlateGrey */, - CcpColor::DarkTurquoise, - CcpColor::DarkViolet, - CcpColor::DeepPink, - CcpColor::DeepSkyBlue, - //CcpColor::DimGray /* == DimGrey */, - CcpColor::DodgerBlue, - CcpColor::FireBrick, - //CcpColor::FloralWhite, - CcpColor::ForestGreen, - CcpColor::Fuchsia /* == Magenta */, - CcpColor::Gainsboro, - //CcpColor::GhostWhite, - CcpColor::Gold, - CcpColor::Goldenrod, - //CcpColor::Gray /* == Grey */, - CcpColor::Green, - CcpColor::GreenYellow, - CcpColor::Honeydew, - CcpColor::HotPink, - CcpColor::IndianRed, - CcpColor::Indigo, - //CcpColor::Ivory, - CcpColor::Khaki, - CcpColor::Lavender, - CcpColor::LavenderBlush, - CcpColor::LawnGreen, - CcpColor::LemonChiffon, - CcpColor::LightBlue, - CcpColor::LightCoral, - CcpColor::LightCyan, - CcpColor::LightGoldenrodYellow, - CcpColor::LightGray /* == LightGrey */, - CcpColor::LightGreen, - CcpColor::LightPink, - CcpColor::LightSalmon, - CcpColor::LightSeaGreen, - CcpColor::LightSkyBlue, - CcpColor::LightSlateGray /* == LightSlateGrey */, - CcpColor::LightSteelBlue, - CcpColor::LightYellow, - CcpColor::Lime, - CcpColor::LimeGreen, - CcpColor::Linen, - CcpColor::Maroon, - CcpColor::MediumAquamarine, - CcpColor::MediumBlue, - CcpColor::MediumOrchid, - CcpColor::MediumPurple, - CcpColor::MediumSeaGreen, - CcpColor::MediumSlateBlue, - CcpColor::MediumSpringGreen, - CcpColor::MediumTurquoise, - CcpColor::MediumVioletRed, - CcpColor::MidnightBlue, - //CcpColor::MintCream, - CcpColor::MistyRose, - CcpColor::Moccasin, - CcpColor::NavajoWhite, - CcpColor::Navy, - CcpColor::OldLace, - CcpColor::Olive, - CcpColor::OliveDrab, - CcpColor::Orange, - CcpColor::OrangeRed, - CcpColor::Orchid, - CcpColor::PaleGoldenrod, - CcpColor::PaleGreen, - CcpColor::PaleTurquoise, - CcpColor::PaleVioletRed, - CcpColor::PapayaWhip, - CcpColor::PeachPuff, - CcpColor::Peru, - CcpColor::Pink, - CcpColor::Plum, - CcpColor::PowderBlue, - CcpColor::Purple, - CcpColor::RebeccaPurple, - CcpColor::Red, - CcpColor::RosyBrown, - CcpColor::RoyalBlue, - CcpColor::SaddleBrown, - CcpColor::Salmon, - CcpColor::SandyBrown, - CcpColor::SeaGreen, - //CcpColor::SeaShell, - CcpColor::Sienna, - CcpColor::Silver, - CcpColor::SkyBlue, - CcpColor::SlateBlue, - CcpColor::SlateGray /* == SlateGrey */, - //CcpColor::Snow, - CcpColor::SpringGreen, - CcpColor::SteelBlue, - CcpColor::Tan, - CcpColor::Teal, - CcpColor::Thistle, - CcpColor::Tomato, - CcpColor::Turquoise, - CcpColor::Violet, - CcpColor::Wheat, - //CcpColor::White, - //CcpColor::WhiteSmoke, - CcpColor::Yellow, - CcpColor::YellowGreen, -}; - -// Pick a display color from the s_awailableNamedColors list above -// that are reasonably distinct from incoming existingColors. -// Candidates are scored by their distance to existingColors and -// the color furthest away wins. -inline CcpColor PickDistinctColor( const std::vector& existingColors ) -{ - CcpColor bestColor = CcpColor::SteelBlue; - int64_t bestScore = -1; - for( const CcpColor candidate : s_awailableNamedColors ) - { - int64_t minDistance = INT64_MAX; - for( const CcpColor existing : existingColors ) - { - const int64_t dist = GetSquaredDistance( candidate, existing ); - if( dist < minDistance ) - { - minDistance = dist; - } - } - - if( minDistance > bestScore ) - { - bestScore = minDistance; - bestColor = candidate; - } - } - return bestColor; -} -} +std::string CcpColorToString( CcpColor color ); #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9b47dc0..0c8077f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,7 +6,6 @@ find_package(GTest CONFIG REQUIRED) find_package(lz4 CONFIG REQUIRED) add_executable(CcpCoreTest CCPCallstack.cpp - CcpColorConstants.cpp CcpCoreTest.cpp CcpCrash.cpp CCPHash.cpp diff --git a/tests/CcpColorConstants.cpp b/tests/CcpColorConstants.cpp deleted file mode 100644 index 3bd3516..0000000 --- a/tests/CcpColorConstants.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2026 Fenris Creations. - - -#include - -#include -#include -#include - -#include - - -TEST( CcpColorConstantsTest, PickedColorIsUnused ) -{ - std::vector existing{ CcpColor::SteelBlue, CcpColor::Yellow }; - for( int i = 0; i < 62; ++i ) - { - const CcpColor picked = ColorUtil::PickDistinctColor( existing ); - EXPECT_EQ( existing.end(), std::find( existing.begin(), existing.end(), picked ) ); - existing.push_back( picked ); - - // TODO: Remove this once visually validated. - // Print out selected colors for manual checking - std::fprintf( stdout, - "[%2d]: %s - (0x%06x)\n", - i, - CcpColorToString( picked ).c_str(), - static_cast( picked ) ); - } - std::fflush( stdout ); -} - -TEST( CcpColorConstantsTest, PickedColorIsDistinctFromExisting ) -{ - // With a single existing color, the pick should be far away from it in RGB space, - // not merely a slightly different shade. - const std::vector existingRed{ CcpColor::Red }; - const CcpColor pickedVsRed = ColorUtil::PickDistinctColor( existingRed ); - auto distPickedVsRed = ColorUtil::GetSquaredDistance( pickedVsRed, CcpColor::Red ); - - std::fprintf( stdout, - "[0x%06x] '%s' vs Picked color: [0x%06x] '%s' - distance = %lld \n", - CcpColor::Red, CcpColorToString(CcpColor::Red).c_str(), - pickedVsRed, CcpColorToString( pickedVsRed ).c_str(), - distPickedVsRed ); - std::fflush( stdout ); - EXPECT_GT( distPickedVsRed, 128 * 128 ); - - const std::vector existingBlue{ CcpColor::Blue }; - const CcpColor pickedVsBlue = ColorUtil::PickDistinctColor( existingBlue ); - auto distPickedVsBlue = ColorUtil::GetSquaredDistance( pickedVsBlue, CcpColor::Blue ); - - std::fprintf( stdout, - "[0x%06x] '%s' vs Picked color: [0x%06x] '%s' - distance = %lld \n", - CcpColor::Blue, CcpColorToString(CcpColor::Blue).c_str(), - pickedVsBlue, CcpColorToString( pickedVsBlue ).c_str(), - distPickedVsBlue ); - std::fflush( stdout ); - EXPECT_GT( distPickedVsBlue, 128 * 128 ); -} diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 8a63f05..5623183 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -737,15 +737,15 @@ TEST_F( CcpTelemetryTest, CaptureMaskDefaultsAreRegistered ) EXPECT_EQ( CcpColor::OrangeRed, info.color ); } -TEST_F( CcpTelemetryTest, CaptureMaskAutoColorIsPickedFromCandidateList ) +TEST_F( CcpTelemetryTest, CaptureMaskAutoAssignColor ) { - CcpRegisterCaptureMask( "CaptureMaskAutoColorIsPickedFromCandidateList" ); + CcpRegisterCaptureMask( "CaptureMaskAutoAssignColor" ); CcpCaptureMaskInfo info; - EXPECT_TRUE( TryGetCaptureMaskNamed( "CaptureMaskAutoColorIsPickedFromCandidateList", info ) ); - const auto begin = std::begin( ColorUtil::s_awailableNamedColors ); - const auto end = std::end( ColorUtil::s_awailableNamedColors ); - EXPECT_NE( end, std::find( begin, end, info.color ) ); + EXPECT_TRUE( TryGetCaptureMaskNamed( "CaptureMaskAutoAssignColor", info ) ); + EXPECT_NE( CcpColor::White, info.color ) << "CcpColor::White is the default initialized color"; + EXPECT_NE( CcpColor::SteelBlue, info.color ) << "CcpColor::SteelBlue is reserved for TMCM_GENERAL"; + EXPECT_NE( CcpColor::Yellow, info.color ) << "CcpColor::Yellow is reserved for TMCM_CPP"; } TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByBits ) From 8283b2e3a21435c9ec1a533c52747a1869723572 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:44:44 +0000 Subject: [PATCH 12/40] Use deBruijn to find the index of a single bit number --- CcpTelemetry.cpp | 32 +++++++++++++++++--------------- tests/CcpTelemetry.cpp | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 15 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index f3ed1e0..5f57931 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -142,21 +142,23 @@ namespace // From a CcpSetActiveCaptureMask( vector ) call. std::vector s_pendingActiveCaptureMaskNames; -#if defined( _MSC_VER ) - #include // _BitScanForward64 TODO: Can be removed once upgraded to C++20 - using std::countr_zero() instead. -#endif - - // Returns the index [0..63] of the single bit set bit in x - // TODO: Replace/remove this function with std::countr_zero() once upgraded to C++20 or newer. - int CountTrailingZeros64( uint64_t x ) - { -#if defined( _MSC_VER ) - unsigned long idx; - _BitScanForward64( &idx, x ); - return static_cast( idx ); -#else - return __builtin_ctzll( x ); -#endif + // Returns the index [0..63] of the lowest set bit. + // Uses a de Bruijn sequence, thanks Claude Code. + // TODO: Can be replaced with std::countr_zero() once we upgrade to C++20. + constexpr int CountTrailingZeros64( uint64_t x ) noexcept + { + constexpr uint64_t deBruijn = 0x03f79d71b4cb0a89ULL; + constexpr uint8_t lookup[64] = { + 0, 1, 48, 2, 57, 49, 28, 3, + 61, 58, 50, 42, 38, 29, 17, 4, + 62, 55, 59, 36, 53, 51, 43, 22, + 45, 39, 33, 30, 24, 18, 12, 5, + 63, 47, 56, 27, 60, 41, 37, 16, + 54, 35, 52, 21, 44, 32, 23, 11, + 46, 26, 40, 15, 34, 20, 31, 10, + 25, 14, 19, 9, 13, 8, 7, 6, + }; + return lookup[( ( x & ( 0ULL - x ) ) * deBruijn ) >> 58]; } // Store a registered CaptureMask into its entry slot diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 5623183..fc7d573 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -683,6 +684,39 @@ TEST_F( CcpTelemetryTest, CcpSemaphoreTimedWaitTimesOut ) // CaptureMask tests: // --------------------------------------------------------------------------- +TEST_F( CcpTelemetryTest, CcpRegisterCaptureMaskAllocatesBitsInOrder ) +{ + // A "hack" to force GTest to run this test in its own process. + // This is needed because GTest (unlike CTest) runs all the tests in the same + // process, meaning already registered CaptureMasks are visible. + // We need this test to check that each of the allocated CaptureMask bits: + // - Contain only a single bit in it's return value. + // - Each allocated CaptureMask bit is higher than the one before it. + ::testing::GTEST_FLAG( death_test_style ) = "threadsafe"; + EXPECT_EXIT( + { + uint64_t previousBit = TMCM_CPP; // last default-registered bit (slot 2) + for( int i = 3; i <= 64; ++i ) + { + const std::string name = "CaptureMaskBit_" + std::to_string( i ); + const uint64_t maskBit = CcpRegisterCaptureMask( name ); + if( !IsSCaptureMaskSingleBit( maskBit ) || maskBit <= previousBit ) + { + std::fprintf( stderr, + "FAIL: %s returned 0x%llx, previous was 0x%llx\n", + name.c_str(), + static_cast( maskBit ), + static_cast( previousBit ) ); + std::fflush( stderr ); + std::exit( 1 ); + } + previousBit = maskBit; + } + std::exit( 0 ); + }, + ::testing::ExitedWithCode( 0 ), ".*" ); +} + TEST_F( CcpTelemetryTest, CaptureMaskRegisterReturnsNonDefaultBit ) { const uint64_t maskBit = CcpRegisterCaptureMask( "CaptureMaskRegisterReturnsNonDefaultBit" ); From 78306a404bd1d9a25963eee21fee7a542325ad50 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Sat, 18 Jul 2026 12:20:38 +0000 Subject: [PATCH 13/40] Remove un-necessary std::atomic use on CaptureMasks --- CcpTelemetry.cpp | 62 ++++++++++++++++-------------------------------- 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 5f57931..7e36b4d 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -103,19 +103,13 @@ namespace CcpMutex s_captureMaskMutex( "CcpTelemetry", "CaptureMaskMutex" ); - struct CaptureMaskEntry - { - std::atomic slotBit{ 0 }; // used to indicate if the slot entry has been populated or not - CcpCaptureMaskInfo maskInfo{}; - }; - // Fixed size array for registered CaptureMasks. // Allows for O(1) lookup based on single-bit CaptureMask value. - std::array s_registeredCaptureMasks{}; + std::array s_registeredCaptureMasks{}; // Currently-active CaptureMask. Defaults to "all" until // narrowed in a later call to CcpSetActiveCaptureMask() - std::atomic s_activeCaptureMaskBits{ UINT64_MAX }; + uint64_t s_activeCaptureMaskBits = UINT64_MAX; // Color palette for auto-assigned CaptureMasks constexpr CcpColor s_captureMaskColorPalette[] = { @@ -161,14 +155,13 @@ namespace return lookup[( ( x & ( 0ULL - x ) ) * deBruijn ) >> 58]; } - // Store a registered CaptureMask into its entry slot + // Store a registered CaptureMask into its array slot void StoreRegisteredCaptureMask( uint64_t bit, const std::string& name, CcpColor color ) { auto& entry = s_registeredCaptureMasks[CountTrailingZeros64( bit )]; - entry.maskInfo.name = name; - entry.maskInfo.maskBit = bit; - entry.maskInfo.color = color; - entry.slotBit.store( bit, std::memory_order_release ); + entry.name = name; + entry.maskBit = bit; + entry.color = color; } // Initialize the default CaptureMasks @@ -196,14 +189,13 @@ namespace // CaptureMask entry in case of re-register on same name. for( auto& registeredEntry : s_registeredCaptureMasks ) { - const uint64_t bit = registeredEntry.slotBit.load( std::memory_order_relaxed ); - if( bit != 0 && registeredEntry.maskInfo.name == lowerName ) + if( registeredEntry.maskBit != 0 && registeredEntry.name == lowerName ) { if( color ) { - registeredEntry.maskInfo.color = *color; + registeredEntry.color = *color; } - return bit; + return registeredEntry.maskBit; } } @@ -230,7 +222,7 @@ namespace if( pendingIt != s_pendingActiveCaptureMaskNames.end() ) { s_pendingActiveCaptureMaskNames.erase( pendingIt ); - s_activeCaptureMaskBits.fetch_or( maskBit, std::memory_order_acq_rel ); + s_activeCaptureMaskBits |= maskBit; CCP_LOGWARN_CH( s_ch, "Previously pending CaptureMask '%s' added to activeCaptureMask -> 0x%llx", lowerName.c_str(), static_cast( maskBit ) ); } @@ -245,26 +237,13 @@ namespace { return CcpColor::White; } - - // Find the registered CaptureMask index - const int idx = CountTrailingZeros64( captureMaskBit ); - if( idx < 0 || idx >= CAPTURE_MASKS_MAX ) - { - return CcpColor::White; - } - - const auto& entry = s_registeredCaptureMasks[idx]; - if( entry.slotBit.load( std::memory_order_acquire ) == captureMaskBit ) - { - return entry.maskInfo.color; - } - - return CcpColor::White; + const auto& entry = s_registeredCaptureMasks[CountTrailingZeros64( captureMaskBit )]; + return entry.maskBit == captureMaskBit ? entry.color : CcpColor::White; } bool IsCaptureMaskActive( uint64_t captureMaskBit ) { - return ( s_activeCaptureMaskBits.load( std::memory_order_acquire ) & captureMaskBit ) != 0; + return ( s_activeCaptureMaskBits & captureMaskBit ) != 0; } } @@ -287,9 +266,9 @@ std::vector CcpGetRegisteredCaptureMasks() result.reserve( CAPTURE_MASKS_MAX ); for( const auto& registeredEntry : s_registeredCaptureMasks ) { - if( registeredEntry.slotBit.load( std::memory_order_relaxed ) != 0 ) + if( registeredEntry.maskBit != 0 ) { - result.push_back( registeredEntry.maskInfo ); + result.push_back( registeredEntry ); } } return result; @@ -301,7 +280,7 @@ void CcpSetActiveCaptureMask( const uint64_t captureMask ) CcpAutoMutex lock( s_captureMaskMutex ); s_pendingActiveCaptureMaskNames.clear(); - s_activeCaptureMaskBits.store( captureMask, std::memory_order_release ); + s_activeCaptureMaskBits = captureMask; CCP_LOGWARN_CH( s_ch, "Active CaptureMask set to 0x%llx", static_cast( captureMask ) ); } @@ -331,10 +310,9 @@ void CcpSetActiveCaptureMask( const std::vector& maskNames ) bool alreadyRegistered = false; for( const auto& registeredEntry : s_registeredCaptureMasks ) { - const uint64_t entryBit = registeredEntry.slotBit.load( std::memory_order_relaxed ); - if( entryBit != 0 && registeredEntry.maskInfo.name == lowerName ) + if( registeredEntry.maskBit != 0 && registeredEntry.name == lowerName ) { - newActiveCaptureMask |= entryBit; + newActiveCaptureMask |= registeredEntry.maskBit; alreadyRegistered = true; break; } @@ -350,13 +328,13 @@ void CcpSetActiveCaptureMask( const std::vector& maskNames ) } } - s_activeCaptureMaskBits.store( newActiveCaptureMask, std::memory_order_release ); + s_activeCaptureMaskBits = newActiveCaptureMask; CCP_LOGWARN_CH( s_ch, "Active CaptureMask set to 0x%llx (%zu pending unresolved name(s))", static_cast( newActiveCaptureMask ), s_pendingActiveCaptureMaskNames.size() ); } uint64_t CcpGetActiveCaptureMask() { - return s_activeCaptureMaskBits.load( std::memory_order_acquire ); + return s_activeCaptureMaskBits; } bool CcpTelemetryIsConnected() From e5ed1dce7b609620a43fc62f56c23f685b5535be Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Sat, 18 Jul 2026 12:31:36 +0000 Subject: [PATCH 14/40] Fix wrong function name in non-Telemetry code path Rename: CcpGetCaptureMasks() To: CcpGetRegisteredCaptureMasks() --- CcpTelemetry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 7e36b4d..42d9317 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -770,7 +770,7 @@ uint64_t CcpRegisterCaptureMask( const std::string&, CcpColor ) return 0; } -std::vector CcpGetCaptureMasks() +std::vector CcpGetRegisteredCaptureMasks() { return {}; } From 4a58c705b6e9233411aebe5e3c9030d91213cef3 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:17:41 +0000 Subject: [PATCH 15/40] Remove double bookkeeping on registeredCaptureMasks vs their bits --- CcpTelemetry.cpp | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 42d9317..114d391 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -164,12 +164,24 @@ namespace entry.color = color; } - // Initialize the default CaptureMasks - uint64_t s_registeredCaptureMaskBits = [] + uint64_t GetRegisteredCaptureMaskBits() { + uint64_t registeredBits = 0; + for( const auto& entry : s_registeredCaptureMasks ) + { + registeredBits |= entry.maskBit; + } + return registeredBits; + } + + // Make sure the default CaptureMasks are registered and available from the start + const bool s_defaultCaptureMasksInitialized = [] + { + CcpAutoMutex lock( s_captureMaskMutex ); + StoreRegisteredCaptureMask( TMCM_GENERAL, "general", CcpColor::SteelBlue ); StoreRegisteredCaptureMask( TMCM_CPP, "cpp", CcpColor::Yellow ); - return static_cast( TMCM_GENERAL | TMCM_CPP ); + return true; }(); uint64_t RegisterCaptureMask( const std::string& name, std::optional color ) @@ -199,34 +211,34 @@ namespace } } - if( s_registeredCaptureMaskBits == UINT64_MAX ) + const uint64_t alreadyRegisteredBits = GetRegisteredCaptureMaskBits(); + if( alreadyRegisteredBits == UINT64_MAX ) { CCP_LOGERR_CH( s_ch, "Cannot register CaptureMask '%s' - all 64 bits are already in use", lowerName.c_str() ); return 0; } // Allocate the lowest available free bit for the new CaptureMask - const uint64_t maskBit = ~s_registeredCaptureMaskBits & ( s_registeredCaptureMaskBits + 1 ); + const uint64_t newMaskBit = ~alreadyRegisteredBits & ( alreadyRegisteredBits + 1 ); if( !color ) { color = s_captureMaskColorPalette[s_captureMaskColorPaletteCursor++ % std::size( s_captureMaskColorPalette )]; } - StoreRegisteredCaptureMask( maskBit, lowerName, *color ); - s_registeredCaptureMaskBits |= maskBit; - CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", lowerName.c_str(), static_cast( maskBit ), CcpColorToString( *color ).c_str() ); + StoreRegisteredCaptureMask( newMaskBit, lowerName, *color ); + CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", lowerName.c_str(), static_cast( newMaskBit ), CcpColorToString( *color ).c_str() ); // Make sure previously "pending active" CaptureMask is included auto pendingIt = std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), lowerName ); if( pendingIt != s_pendingActiveCaptureMaskNames.end() ) { s_pendingActiveCaptureMaskNames.erase( pendingIt ); - s_activeCaptureMaskBits |= maskBit; - CCP_LOGWARN_CH( s_ch, "Previously pending CaptureMask '%s' added to activeCaptureMask -> 0x%llx", lowerName.c_str(), static_cast( maskBit ) ); + s_activeCaptureMaskBits |= newMaskBit; + CCP_LOGWARN_CH( s_ch, "Previously pending CaptureMask '%s' added to activeCaptureMask -> 0x%llx", lowerName.c_str(), static_cast( newMaskBit ) ); } - return maskBit; + return newMaskBit; } // Get the registered CaptureMask color for a given CaptureMask From 942e00920ea714fc73bf61dad393e18b99b45d44 Mon Sep 17 00:00:00 2001 From: CCP ChargeBack <35330827+ccp-chargeback@users.noreply.github.com> Date: Mon, 20 Jul 2026 09:05:16 +0000 Subject: [PATCH 16/40] Fix typo in test helper IsSCaptureMaskSingleBit() --- tests/CcpTelemetry.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index fc7d573..c54e362 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -165,7 +165,7 @@ class CcpTelemetryTest : public ::testing::Test } // Helper for CaptureMasks to make sure it only has a single bit set to 1 - bool IsSCaptureMaskSingleBit( uint64_t mask ) + bool IsCaptureMaskSingleBit( uint64_t mask ) { return mask != 0 && ( mask & ( mask - 1 ) ) == 0; } @@ -375,7 +375,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) { // Register a new component CaptureMask const uint64_t componentMaskBit = CcpRegisterCaptureMask( "TelemetryZoneCaptureMaskBitConstructor", CcpColor::Orange ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( componentMaskBit ) ); + EXPECT_TRUE( IsCaptureMaskSingleBit( componentMaskBit ) ); // Test where componentMaskBit is in the Active list CcpSetActiveCaptureMask( componentMaskBit ); @@ -411,7 +411,7 @@ TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneCaptureMaskBit ) { // Register a new component CaptureMask const uint64_t componentMaskBit = CcpRegisterCaptureMask( "CcpTelemetryEnterZoneCaptureMaskBit", CcpColor::LimeGreen ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( componentMaskBit ) ); + EXPECT_TRUE( IsCaptureMaskSingleBit( componentMaskBit ) ); // Test where componentMaskBit is in the Active list CcpSetActiveCaptureMask( componentMaskBit ); @@ -700,7 +700,7 @@ TEST_F( CcpTelemetryTest, CcpRegisterCaptureMaskAllocatesBitsInOrder ) { const std::string name = "CaptureMaskBit_" + std::to_string( i ); const uint64_t maskBit = CcpRegisterCaptureMask( name ); - if( !IsSCaptureMaskSingleBit( maskBit ) || maskBit <= previousBit ) + if( !IsCaptureMaskSingleBit( maskBit ) || maskBit <= previousBit ) { std::fprintf( stderr, "FAIL: %s returned 0x%llx, previous was 0x%llx\n", @@ -720,7 +720,7 @@ TEST_F( CcpTelemetryTest, CcpRegisterCaptureMaskAllocatesBitsInOrder ) TEST_F( CcpTelemetryTest, CaptureMaskRegisterReturnsNonDefaultBit ) { const uint64_t maskBit = CcpRegisterCaptureMask( "CaptureMaskRegisterReturnsNonDefaultBit" ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBit ) ); + EXPECT_TRUE( IsCaptureMaskSingleBit( maskBit ) ); // TMCM_GENERAL and TMCM_CPP are registered as the default CaptureMasks, // so their maskBits must never be handed out to other components. EXPECT_EQ( 0, maskBit & ( TMCM_GENERAL | TMCM_CPP ) ); @@ -730,7 +730,7 @@ TEST_F( CcpTelemetryTest, CaptureMaskRegisterIsCaseInsensitive ) { const uint64_t firstBit = CcpRegisterCaptureMask( "CaptureMaskRegisterIsCaseInsensitive" ); const uint64_t secondBit = CcpRegisterCaptureMask( "capturemaskregisteriscaseinsensitive" ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( firstBit ) ); + EXPECT_TRUE( IsCaptureMaskSingleBit( firstBit ) ); EXPECT_EQ( firstBit, secondBit ); CcpCaptureMaskInfo info; @@ -742,8 +742,8 @@ TEST_F( CcpTelemetryTest, CaptureMaskRegisterDistinctBitPerName ) { const uint64_t maskBitA = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_A" ); const uint64_t maskBitB = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_B", CcpColor::Tomato ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBitA ) ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( maskBitB ) ); + EXPECT_TRUE( IsCaptureMaskSingleBit( maskBitA ) ); + EXPECT_TRUE( IsCaptureMaskSingleBit( maskBitB ) ); EXPECT_NE( maskBitA, maskBitB ); } @@ -792,7 +792,7 @@ TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByBits ) auto registeredCaptureMasks = CcpGetRegisteredCaptureMasks(); for ( auto captureMask : registeredCaptureMasks ) { - EXPECT_TRUE( IsSCaptureMaskSingleBit( captureMask.maskBit ) ) << "Each entry should only contain one bit set"; + EXPECT_TRUE( IsCaptureMaskSingleBit( captureMask.maskBit ) ) << "Each entry should only contain one bit set"; registeredCaptureMaskBits |= captureMask.maskBit; } EXPECT_TRUE( (newBits & registeredCaptureMaskBits) == newBits ) << "All bits in combined newBits should be present in combined registered CaptureMask bits "; @@ -816,18 +816,18 @@ TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByNames ) // Only register one name const uint64_t registeredBit = CcpRegisterCaptureMask( registeredName ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( registeredBit ) ); + EXPECT_TRUE( IsCaptureMaskSingleBit( registeredBit ) ); CcpSetActiveCaptureMask( activeMaskList ); EXPECT_EQ( registeredBit, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should only contain the registered bit, not the pending one"; // Register the new name const uint64_t newBit = CcpRegisterCaptureMask( newName ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( newBit ) ); + EXPECT_TRUE( IsCaptureMaskSingleBit( newBit ) ); EXPECT_EQ( registeredBit, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should still only contain the registered bit, not the new or pending one"; // Now add the pending one const uint64_t pendingBit = CcpRegisterCaptureMask( pendingName ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( pendingBit ) ); + EXPECT_TRUE( IsCaptureMaskSingleBit( pendingBit ) ); const uint64_t activeBits = registeredBit | pendingBit; EXPECT_EQ( activeBits, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should now contain both the registered and pending bits"; @@ -840,6 +840,6 @@ TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByNames ) // Overwrite the active CaptureMask CcpSetActiveCaptureMask( TMCM_CPP ); EXPECT_EQ( static_cast( TMCM_CPP ), CcpGetActiveCaptureMask() ); - EXPECT_TRUE( IsSCaptureMaskSingleBit( CcpGetActiveCaptureMask() ) ); + EXPECT_TRUE( IsCaptureMaskSingleBit( CcpGetActiveCaptureMask() ) ); } From 7142ed653314eb3600f43f8d23ee813b4bce8fe3 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:16:51 +0000 Subject: [PATCH 17/40] Remove unused headers from `CcpColorConstants.h` --- include/CcpColorConstants.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/CcpColorConstants.h b/include/CcpColorConstants.h index ef80bbb..38731f4 100644 --- a/include/CcpColorConstants.h +++ b/include/CcpColorConstants.h @@ -7,8 +7,6 @@ #include #include -#include "carbon_core_export.h" - // Useful RGB color constants // Initially based on the type of CSS standard, see https://www.w3.org/TR/css-color-4/#named-colors. enum class CcpColor : uint32_t From 82609e4b466721753151e632cdd2665f4f7b9879 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:18:00 +0000 Subject: [PATCH 18/40] Use currently advised copyright statement Legal hasn't advised on an updated statement yet, so we stick with `CCP ehf.`. --- CcpColorConstants.cpp | 2 +- include/CcpColorConstants.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CcpColorConstants.cpp b/CcpColorConstants.cpp index eacbd4b..4520c9e 100644 --- a/CcpColorConstants.cpp +++ b/CcpColorConstants.cpp @@ -1,4 +1,4 @@ -// Copyright © 2026 Fenris Creations. +// Copyright © 2026 CCP ehf. #include "include/CcpColorConstants.h" diff --git a/include/CcpColorConstants.h b/include/CcpColorConstants.h index 38731f4..ec83fe7 100644 --- a/include/CcpColorConstants.h +++ b/include/CcpColorConstants.h @@ -1,4 +1,4 @@ -// Copyright © 2026 Fenris Creations. +// Copyright © 2026 CCP ehf. #pragma once #ifndef CCP_COLOR_CONSTANTS_H From 59ac96cc286e6dcb1a45895f6e699a58e9deac4c Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:19:51 +0000 Subject: [PATCH 19/40] Update `CcpColorString` to return a `string_view` of all possible names for a given color value To avoid unnecessary memory copies of the underlying string data, and to avoid surprises in seeing `CcpColorToString( CcpColor::Cyan )` return `Aqua`. --- CcpColorConstants.cpp | 22 +++++++++++----------- CcpTelemetry.cpp | 2 +- include/CcpColorConstants.h | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/CcpColorConstants.cpp b/CcpColorConstants.cpp index 4520c9e..56a41dc 100644 --- a/CcpColorConstants.cpp +++ b/CcpColorConstants.cpp @@ -2,15 +2,15 @@ #include "include/CcpColorConstants.h" -std::string CcpColorToString( CcpColor color ) +std::string_view CcpColorToString( CcpColor color ) { // For colors sharing the same RGB value (Aqua/Cyan, Fuchsia/Magenta, and the - // various *Gray/*Grey pairs), the first name declared in the enum wins. + // various *Gray/*Grey pairs), all names sharing that value are returned. switch( color ) { case CcpColor::AliceBlue: return "AliceBlue"; case CcpColor::AntiqueWhite: return "AntiqueWhite"; - case CcpColor::Aqua: return "Aqua"; // == Cyan + case CcpColor::Aqua: return "Aqua / Cyan"; case CcpColor::Aquamarine: return "Aquamarine"; case CcpColor::Azure: return "Azure"; case CcpColor::Beige: return "Beige"; @@ -31,7 +31,7 @@ std::string CcpColorToString( CcpColor color ) case CcpColor::DarkBlue: return "DarkBlue"; case CcpColor::DarkCyan: return "DarkCyan"; case CcpColor::DarkGoldenrod: return "DarkGoldenrod"; - case CcpColor::DarkGray: return "DarkGray"; // == DarkGrey + case CcpColor::DarkGray: return "DarkGray / DarkGrey"; case CcpColor::DarkGreen: return "DarkGreen"; case CcpColor::DarkKhaki: return "DarkKhaki"; case CcpColor::DarkMagenta: return "DarkMagenta"; @@ -42,22 +42,22 @@ std::string CcpColorToString( CcpColor color ) case CcpColor::DarkSalmon: return "DarkSalmon"; case CcpColor::DarkSeaGreen: return "DarkSeaGreen"; case CcpColor::DarkSlateBlue: return "DarkSlateBlue"; - case CcpColor::DarkSlateGray: return "DarkSlateGray"; // == DarkSlateGrey + case CcpColor::DarkSlateGray: return "DarkSlateGray / DarkSlateGrey"; case CcpColor::DarkTurquoise: return "DarkTurquoise"; case CcpColor::DarkViolet: return "DarkViolet"; case CcpColor::DeepPink: return "DeepPink"; case CcpColor::DeepSkyBlue: return "DeepSkyBlue"; - case CcpColor::DimGray: return "DimGray"; // == DimGrey + case CcpColor::DimGray: return "DimGray / DimGrey"; case CcpColor::DodgerBlue: return "DodgerBlue"; case CcpColor::FireBrick: return "FireBrick"; case CcpColor::FloralWhite: return "FloralWhite"; case CcpColor::ForestGreen: return "ForestGreen"; - case CcpColor::Fuchsia: return "Fuchsia"; // == Magenta + case CcpColor::Fuchsia: return "Fuchsia / Magenta"; case CcpColor::Gainsboro: return "Gainsboro"; case CcpColor::GhostWhite: return "GhostWhite"; case CcpColor::Gold: return "Gold"; case CcpColor::Goldenrod: return "Goldenrod"; - case CcpColor::Gray: return "Gray"; // == Grey + case CcpColor::Gray: return "Gray / Grey"; case CcpColor::Green: return "Green"; case CcpColor::GreenYellow: return "GreenYellow"; case CcpColor::Honeydew: return "Honeydew"; @@ -74,13 +74,13 @@ std::string CcpColorToString( CcpColor color ) case CcpColor::LightCoral: return "LightCoral"; case CcpColor::LightCyan: return "LightCyan"; case CcpColor::LightGoldenrodYellow: return "LightGoldenrodYellow"; - case CcpColor::LightGray: return "LightGray"; // == LightGrey + case CcpColor::LightGray: return "LightGray / LightGrey"; case CcpColor::LightGreen: return "LightGreen"; case CcpColor::LightPink: return "LightPink"; case CcpColor::LightSalmon: return "LightSalmon"; case CcpColor::LightSeaGreen: return "LightSeaGreen"; case CcpColor::LightSkyBlue: return "LightSkyBlue"; - case CcpColor::LightSlateGray: return "LightSlateGray"; // == LightSlateGrey + case CcpColor::LightSlateGray: return "LightSlateGray / LightSlateGrey"; case CcpColor::LightSteelBlue: return "LightSteelBlue"; case CcpColor::LightYellow: return "LightYellow"; case CcpColor::Lime: return "Lime"; @@ -132,7 +132,7 @@ std::string CcpColorToString( CcpColor color ) case CcpColor::Silver: return "Silver"; case CcpColor::SkyBlue: return "SkyBlue"; case CcpColor::SlateBlue: return "SlateBlue"; - case CcpColor::SlateGray: return "SlateGray"; // == SlateGrey + case CcpColor::SlateGray: return "SlateGray / SlateGrey"; case CcpColor::Snow: return "Snow"; case CcpColor::SpringGreen: return "SpringGreen"; case CcpColor::SteelBlue: return "SteelBlue"; diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 114d391..1e3f785 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -227,7 +227,7 @@ namespace } StoreRegisteredCaptureMask( newMaskBit, lowerName, *color ); - CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", lowerName.c_str(), static_cast( newMaskBit ), CcpColorToString( *color ).c_str() ); + CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", lowerName.c_str(), static_cast( newMaskBit ), CcpColorToString( *color ).data() ); // Make sure previously "pending active" CaptureMask is included auto pendingIt = std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), lowerName ); diff --git a/include/CcpColorConstants.h b/include/CcpColorConstants.h index ec83fe7..470bf5f 100644 --- a/include/CcpColorConstants.h +++ b/include/CcpColorConstants.h @@ -5,7 +5,7 @@ #define CCP_COLOR_CONSTANTS_H #include -#include +#include // Useful RGB color constants // Initially based on the type of CSS standard, see https://www.w3.org/TR/css-color-4/#named-colors. @@ -161,6 +161,6 @@ enum class CcpColor : uint32_t YellowGreen = 0x9acd32, }; -std::string CcpColorToString( CcpColor color ); +std::string_view CcpColorToString( CcpColor color ); #endif From f6094aacf7dcde2d80abe0f560573ff03016c28a Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 20 Jul 2026 16:54:52 +0000 Subject: [PATCH 20/40] Remove unnecessary `CcpRegisterCaptureMask` overload Instead, have `Color` as a default-parameter that is piped through. This alone shouldn't have required a separately overloaded implementation. Also, stop lower-casing the input name, because it causes a mismatch between what is passed into the system as "display name" versus what comes back out of the system. Additionally, remove the `all` special case handling. Enabling all capture masks can be done by passing in all capture masks. Having a sentinel value that represents a possible legal value is bound to cause surprising behaviour down the line. Sentinel values should be flagged explicitly, e.g. an alternative would have been to have an explicit function like `CcpCaptureEverything()` function. Furthermore, this simplifies initialization of pre-registered captureMasks: why run a lambda function during static initialization stage when we have initializer lists? --- CcpTelemetry.cpp | 118 +++++++++++------------------------------ include/CcpTelemetry.h | 3 +- tests/CcpTelemetry.cpp | 34 +----------- 3 files changed, 34 insertions(+), 121 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 1e3f785..ef30c16 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -105,32 +105,15 @@ namespace // Fixed size array for registered CaptureMasks. // Allows for O(1) lookup based on single-bit CaptureMask value. - std::array s_registeredCaptureMasks{}; + std::array s_registeredCaptureMasks{ + CcpCaptureMaskInfo{ "general", TMCM_GENERAL, CcpColor::SteelBlue }, + CcpCaptureMaskInfo{ "cpp", TMCM_CPP, CcpColor::Yellow }, + }; // Currently-active CaptureMask. Defaults to "all" until // narrowed in a later call to CcpSetActiveCaptureMask() uint64_t s_activeCaptureMaskBits = UINT64_MAX; - // Color palette for auto-assigned CaptureMasks - constexpr CcpColor s_captureMaskColorPalette[] = { - CcpColor::OrangeRed, - CcpColor::LimeGreen, - CcpColor::DodgerBlue, - CcpColor::Gold, - CcpColor::MediumPurple, - CcpColor::Turquoise, - CcpColor::DeepPink, - CcpColor::Chartreuse, - CcpColor::Tomato, - CcpColor::CornflowerBlue, - CcpColor::Khaki, - CcpColor::SeaGreen, - CcpColor::Orchid, - CcpColor::Salmon, - CcpColor::Aquamarine, - CcpColor::Crimson, - }; - size_t s_captureMaskColorPaletteCursor = 0; // List of CaptureMask names that have yet to be registered. // From a CcpSetActiveCaptureMask( vector ) call. @@ -174,18 +157,26 @@ namespace return registeredBits; } - // Make sure the default CaptureMasks are registered and available from the start - const bool s_defaultCaptureMasksInitialized = [] + // Get the registered CaptureMask color for a given CaptureMask + // Default to CcpColor::White if not found + CcpColor GetCaptureMaskColor( uint64_t captureMaskBit ) { - CcpAutoMutex lock( s_captureMaskMutex ); - - StoreRegisteredCaptureMask( TMCM_GENERAL, "general", CcpColor::SteelBlue ); - StoreRegisteredCaptureMask( TMCM_CPP, "cpp", CcpColor::Yellow ); - return true; - }(); + if( captureMaskBit == 0 ) + { + return CcpColor::White; + } + const auto& entry = s_registeredCaptureMasks[CountTrailingZeros64( captureMaskBit )]; + return entry.maskBit == captureMaskBit ? entry.color : CcpColor::White; + } - uint64_t RegisterCaptureMask( const std::string& name, std::optional color ) + bool IsCaptureMaskActive( uint64_t captureMaskBit ) { + return ( s_activeCaptureMaskBits & captureMaskBit ) != 0; + } +} + +uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color ) +{ // Guard access to registered CaptureMasks while we add/update a new entry CcpAutoMutex lock( s_captureMaskMutex ); @@ -195,18 +186,13 @@ namespace return 0; } - std::string lowerName = ToLower( name ); - // Explicitly allow change of color on an existing registered // CaptureMask entry in case of re-register on same name. for( auto& registeredEntry : s_registeredCaptureMasks ) { - if( registeredEntry.maskBit != 0 && registeredEntry.name == lowerName ) + if( registeredEntry.maskBit != 0 && registeredEntry.name == name ) { - if( color ) - { - registeredEntry.color = *color; - } + registeredEntry.color = color; return registeredEntry.maskBit; } } @@ -214,59 +200,26 @@ namespace const uint64_t alreadyRegisteredBits = GetRegisteredCaptureMaskBits(); if( alreadyRegisteredBits == UINT64_MAX ) { - CCP_LOGERR_CH( s_ch, "Cannot register CaptureMask '%s' - all 64 bits are already in use", lowerName.c_str() ); + CCP_LOGERR_CH( s_ch, "Cannot register CaptureMask '%s' - all 64 bits are already in use", name.c_str() ); return 0; } // Allocate the lowest available free bit for the new CaptureMask const uint64_t newMaskBit = ~alreadyRegisteredBits & ( alreadyRegisteredBits + 1 ); - if( !color ) - { - color = s_captureMaskColorPalette[s_captureMaskColorPaletteCursor++ % std::size( s_captureMaskColorPalette )]; - } - - StoreRegisteredCaptureMask( newMaskBit, lowerName, *color ); - CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", lowerName.c_str(), static_cast( newMaskBit ), CcpColorToString( *color ).data() ); + StoreRegisteredCaptureMask( newMaskBit, name, color ); + CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", name.c_str(), static_cast( newMaskBit ), CcpColorToString( color ).data() ); // Make sure previously "pending active" CaptureMask is included - auto pendingIt = std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), lowerName ); + auto pendingIt = std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), name ); if( pendingIt != s_pendingActiveCaptureMaskNames.end() ) { s_pendingActiveCaptureMaskNames.erase( pendingIt ); s_activeCaptureMaskBits |= newMaskBit; - CCP_LOGWARN_CH( s_ch, "Previously pending CaptureMask '%s' added to activeCaptureMask -> 0x%llx", lowerName.c_str(), static_cast( newMaskBit ) ); + CCP_LOGWARN_CH( s_ch, "Previously pending CaptureMask '%s' added to activeCaptureMask -> 0x%llx", name.c_str(), static_cast( newMaskBit ) ); } return newMaskBit; - } - - // Get the registered CaptureMask color for a given CaptureMask - // Default to CcpColor::White if not found - CcpColor GetCaptureMaskColor( uint64_t captureMaskBit ) - { - if( captureMaskBit == 0 ) - { - return CcpColor::White; - } - const auto& entry = s_registeredCaptureMasks[CountTrailingZeros64( captureMaskBit )]; - return entry.maskBit == captureMaskBit ? entry.color : CcpColor::White; - } - - bool IsCaptureMaskActive( uint64_t captureMaskBit ) - { - return ( s_activeCaptureMaskBits & captureMaskBit ) != 0; - } -} - -uint64_t CcpRegisterCaptureMask( const std::string& name ) -{ - return RegisterCaptureMask( name, std::nullopt ); -} - -uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color ) -{ - return RegisterCaptureMask( name, color ); } std::vector CcpGetRegisteredCaptureMasks() @@ -309,20 +262,11 @@ void CcpSetActiveCaptureMask( const std::vector& maskNames ) { continue; } - std::string lowerName = ToLower( rawName ); - - if( lowerName == "all" ) - { - // Set all bits and clear the pending list - s_pendingActiveCaptureMaskNames.clear(); - newActiveCaptureMask = UINT64_MAX; - break; - } bool alreadyRegistered = false; for( const auto& registeredEntry : s_registeredCaptureMasks ) { - if( registeredEntry.maskBit != 0 && registeredEntry.name == lowerName ) + if( registeredEntry.maskBit != 0 && registeredEntry.name == rawName ) { newActiveCaptureMask |= registeredEntry.maskBit; alreadyRegistered = true; @@ -333,9 +277,9 @@ void CcpSetActiveCaptureMask( const std::vector& maskNames ) // This CaptureMask hasn't been registered (yet) so add it to the pending list if( !alreadyRegistered ) { - if( std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), lowerName ) == s_pendingActiveCaptureMaskNames.end() ) + if( std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), rawName ) == s_pendingActiveCaptureMaskNames.end() ) { - s_pendingActiveCaptureMaskNames.push_back( std::move( lowerName ) ); + s_pendingActiveCaptureMaskNames.push_back( std::move( rawName ) ); } } } diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index cac4e65..330b925 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -62,8 +62,7 @@ struct CcpCaptureMaskInfo CcpColor color{CcpColor::White}; // The chosen/allocated color for the CaptureMask }; -CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name ); -CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color ); +CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color = CcpColor::Fuchsia ); CARBON_CORE_API std::vector CcpGetRegisteredCaptureMasks(); CARBON_CORE_API void CcpSetActiveCaptureMask( const uint64_t captureMask ); diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index c54e362..81d159c 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -696,7 +696,7 @@ TEST_F( CcpTelemetryTest, CcpRegisterCaptureMaskAllocatesBitsInOrder ) EXPECT_EXIT( { uint64_t previousBit = TMCM_CPP; // last default-registered bit (slot 2) - for( int i = 3; i <= 64; ++i ) + for( int i = 2; i < 64; ++i ) { const std::string name = "CaptureMaskBit_" + std::to_string( i ); const uint64_t maskBit = CcpRegisterCaptureMask( name ); @@ -726,18 +726,6 @@ TEST_F( CcpTelemetryTest, CaptureMaskRegisterReturnsNonDefaultBit ) EXPECT_EQ( 0, maskBit & ( TMCM_GENERAL | TMCM_CPP ) ); } -TEST_F( CcpTelemetryTest, CaptureMaskRegisterIsCaseInsensitive ) -{ - const uint64_t firstBit = CcpRegisterCaptureMask( "CaptureMaskRegisterIsCaseInsensitive" ); - const uint64_t secondBit = CcpRegisterCaptureMask( "capturemaskregisteriscaseinsensitive" ); - EXPECT_TRUE( IsCaptureMaskSingleBit( firstBit ) ); - EXPECT_EQ( firstBit, secondBit ); - - CcpCaptureMaskInfo info; - TryGetCaptureMaskNamed( "CAPTUREmaskREGISTERisCASEinsensitive", info ); - EXPECT_EQ( firstBit, info.maskBit ); -} - TEST_F( CcpTelemetryTest, CaptureMaskRegisterDistinctBitPerName ) { const uint64_t maskBitA = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_A" ); @@ -764,24 +752,13 @@ TEST_F( CcpTelemetryTest, CaptureMaskDefaultsAreRegistered ) // Registering a CaptureMask for the same name should result in the same maskBit returned, // but a color change should be allowed. - const uint64_t reRegisterMaskBit = CcpRegisterCaptureMask( "GENERAL", CcpColor::OrangeRed ); + const uint64_t reRegisterMaskBit = CcpRegisterCaptureMask( "general", CcpColor::OrangeRed ); TryGetCaptureMaskNamed( "general", info ); EXPECT_EQ( static_cast( TMCM_GENERAL ), reRegisterMaskBit ); EXPECT_EQ( static_cast( TMCM_GENERAL ), info.maskBit ); EXPECT_EQ( CcpColor::OrangeRed, info.color ); } -TEST_F( CcpTelemetryTest, CaptureMaskAutoAssignColor ) -{ - CcpRegisterCaptureMask( "CaptureMaskAutoAssignColor" ); - - CcpCaptureMaskInfo info; - EXPECT_TRUE( TryGetCaptureMaskNamed( "CaptureMaskAutoAssignColor", info ) ); - EXPECT_NE( CcpColor::White, info.color ) << "CcpColor::White is the default initialized color"; - EXPECT_NE( CcpColor::SteelBlue, info.color ) << "CcpColor::SteelBlue is reserved for TMCM_GENERAL"; - EXPECT_NE( CcpColor::Yellow, info.color ) << "CcpColor::Yellow is reserved for TMCM_CPP"; -} - TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByBits ) { uint64_t newBits = 0; @@ -831,15 +808,8 @@ TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByNames ) const uint64_t activeBits = registeredBit | pendingBit; EXPECT_EQ( activeBits, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should now contain both the registered and pending bits"; - // Deal with the special "all" case - CcpSetActiveCaptureMask( std::vector{ "all" } ); - EXPECT_EQ( UINT64_MAX, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should be all bits set when using the special 'all' alias"; - CcpRegisterCaptureMask( "SetActiveCaptureMaskByNames_AfterAll" ); - EXPECT_EQ( UINT64_MAX, CcpGetActiveCaptureMask() ); - // Overwrite the active CaptureMask CcpSetActiveCaptureMask( TMCM_CPP ); EXPECT_EQ( static_cast( TMCM_CPP ), CcpGetActiveCaptureMask() ); EXPECT_TRUE( IsCaptureMaskSingleBit( CcpGetActiveCaptureMask() ) ); } - From 9eee0e4d8dbc21732c66260bf8426be6d9ea16b1 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 12:55:23 +0000 Subject: [PATCH 21/40] Use `constexpr size_t` instead of `#define` For type-safe constants instead of preprocessor text replacement. --- CcpTelemetry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index ef30c16..477f83f 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -99,7 +99,7 @@ namespace // ------------------------------- // CaptureMask specifics: // ------------------------------- - #define CAPTURE_MASKS_MAX 64 + constexpr size_t CAPTURE_MASKS_MAX{64}; CcpMutex s_captureMaskMutex( "CcpTelemetry", "CaptureMaskMutex" ); From 253ae9f308eb8ead011af68bb9e29dc80f59de5e Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:01:56 +0000 Subject: [PATCH 22/40] Remove slow test that covers an unimportant implementation details The order of registration doesn't matter to any users of `CcpRegisterCaptureMask`, primarily because that order depends on the order of calls to that function, not the implementation itself. --- tests/CcpTelemetry.cpp | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 81d159c..653fdbe 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -684,39 +684,6 @@ TEST_F( CcpTelemetryTest, CcpSemaphoreTimedWaitTimesOut ) // CaptureMask tests: // --------------------------------------------------------------------------- -TEST_F( CcpTelemetryTest, CcpRegisterCaptureMaskAllocatesBitsInOrder ) -{ - // A "hack" to force GTest to run this test in its own process. - // This is needed because GTest (unlike CTest) runs all the tests in the same - // process, meaning already registered CaptureMasks are visible. - // We need this test to check that each of the allocated CaptureMask bits: - // - Contain only a single bit in it's return value. - // - Each allocated CaptureMask bit is higher than the one before it. - ::testing::GTEST_FLAG( death_test_style ) = "threadsafe"; - EXPECT_EXIT( - { - uint64_t previousBit = TMCM_CPP; // last default-registered bit (slot 2) - for( int i = 2; i < 64; ++i ) - { - const std::string name = "CaptureMaskBit_" + std::to_string( i ); - const uint64_t maskBit = CcpRegisterCaptureMask( name ); - if( !IsCaptureMaskSingleBit( maskBit ) || maskBit <= previousBit ) - { - std::fprintf( stderr, - "FAIL: %s returned 0x%llx, previous was 0x%llx\n", - name.c_str(), - static_cast( maskBit ), - static_cast( previousBit ) ); - std::fflush( stderr ); - std::exit( 1 ); - } - previousBit = maskBit; - } - std::exit( 0 ); - }, - ::testing::ExitedWithCode( 0 ), ".*" ); -} - TEST_F( CcpTelemetryTest, CaptureMaskRegisterReturnsNonDefaultBit ) { const uint64_t maskBit = CcpRegisterCaptureMask( "CaptureMaskRegisterReturnsNonDefaultBit" ); From ae7e72bf52e88d1d6fa8a0b989790c68b3416f9a Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:22:10 +0000 Subject: [PATCH 23/40] Remove `uint64_t` overload for `CcpSetActiveCaptureMask` This is for an upcoming QoL improvement for capture mask users, where they should no longer have to rely on the implementation of using a bitmask for the capture mask. --- CcpTelemetry.cpp | 24 +++++++++---------- include/CcpTelemetry.h | 3 +-- tests/CcpTelemetry.cpp | 54 ++++++++++++++++++------------------------ 3 files changed, 35 insertions(+), 46 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 477f83f..3043dae 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -239,21 +239,17 @@ std::vector CcpGetRegisteredCaptureMasks() return result; } -void CcpSetActiveCaptureMask( const uint64_t captureMask ) -{ - // Guard access to active and pending CaptureMasks - CcpAutoMutex lock( s_captureMaskMutex ); - - s_pendingActiveCaptureMaskNames.clear(); - s_activeCaptureMaskBits = captureMask; - CCP_LOGWARN_CH( s_ch, "Active CaptureMask set to 0x%llx", static_cast( captureMask ) ); -} - -void CcpSetActiveCaptureMask( const std::vector& maskNames ) +bool CcpSetActiveCaptureMask( const std::vector& maskNames ) { // Guard access to all CaptureMasks members CcpAutoMutex lock( s_captureMaskMutex ); + if ( maskNames.size() > CAPTURE_MASKS_MAX ) + { + CCP_LOGERR_CH( s_ch, "Failed setting active capture mask because more %lu maskNames were passed in, but only %lu are allowed", maskNames.size(), CAPTURE_MASKS_MAX ); + return false; + } + s_pendingActiveCaptureMaskNames.clear(); uint64_t newActiveCaptureMask = 0; for( const auto& rawName : maskNames ) @@ -279,13 +275,15 @@ void CcpSetActiveCaptureMask( const std::vector& maskNames ) { if( std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), rawName ) == s_pendingActiveCaptureMaskNames.end() ) { - s_pendingActiveCaptureMaskNames.push_back( std::move( rawName ) ); + s_pendingActiveCaptureMaskNames.emplace_back( rawName ); } } } s_activeCaptureMaskBits = newActiveCaptureMask; - CCP_LOGWARN_CH( s_ch, "Active CaptureMask set to 0x%llx (%zu pending unresolved name(s))", static_cast( newActiveCaptureMask ), s_pendingActiveCaptureMaskNames.size() ); + CCP_LOG_CH( s_ch, "Active CaptureMask set to 0x%llx (%zu pending unresolved name(s))", static_cast( newActiveCaptureMask ), s_pendingActiveCaptureMaskNames.size() ); + + return true; } uint64_t CcpGetActiveCaptureMask() diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 330b925..ff06960 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -65,8 +65,7 @@ struct CcpCaptureMaskInfo CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color = CcpColor::Fuchsia ); CARBON_CORE_API std::vector CcpGetRegisteredCaptureMasks(); -CARBON_CORE_API void CcpSetActiveCaptureMask( const uint64_t captureMask ); -CARBON_CORE_API void CcpSetActiveCaptureMask( const std::vector& maskNames ); +CARBON_CORE_API bool CcpSetActiveCaptureMask( const std::vector& maskNames ); CARBON_CORE_API uint64_t CcpGetActiveCaptureMask(); diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 653fdbe..e19f0df 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -340,7 +340,7 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) TEST_F( CcpTelemetryTest, TelemetryZoneLegacyConstructor ) { // Test where captureMask is in the Active list - CcpSetActiveCaptureMask( TMCM_GENERAL | TMCM_CPP ); + CcpSetActiveCaptureMask( {"cpp", "general" } ); { // Make sure the "legacy deprecated" TelemetryZone constructor respects overwrites of color TelemetryZone activeZone( TMCM_CPP, "LegacyZoneIsInActiveList", __FILE__, __LINE__, CcpColor::Red ); // Overwrite the default CcpColor::Yellow of TMCM_CPP @@ -378,7 +378,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) EXPECT_TRUE( IsCaptureMaskSingleBit( componentMaskBit ) ); // Test where componentMaskBit is in the Active list - CcpSetActiveCaptureMask( componentMaskBit ); + CcpSetActiveCaptureMask( {"TelemetryZoneCaptureMaskBitConstructor"} ); { TelemetryZone activeZone( CaptureMaskBit, componentMaskBit, "Active_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); @@ -393,7 +393,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) EXPECT_TRUE( m_tracyClient.GetZones().empty() ); // Test where componentMaskBit is NOT in the Active list - overwrite the Active list with only "general" - CcpSetActiveCaptureMask( TMCM_GENERAL ); + CcpSetActiveCaptureMask( {"general"} ); EXPECT_EQ( 0, CcpGetActiveCaptureMask() & componentMaskBit ); { TelemetryZone inactiveZone( CaptureMaskBit, componentMaskBit, "Inactive_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); @@ -414,7 +414,7 @@ TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneCaptureMaskBit ) EXPECT_TRUE( IsCaptureMaskSingleBit( componentMaskBit ) ); // Test where componentMaskBit is in the Active list - CcpSetActiveCaptureMask( componentMaskBit ); + CcpSetActiveCaptureMask( {"CcpTelemetryEnterZoneCaptureMaskBit"} ); static int activeKey = 8001; CcpTelemetryEnterZone( &activeKey, componentMaskBit, "Active_CcpTelemetryEnterZoneCaptureMaskBit", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); @@ -430,7 +430,7 @@ TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneCaptureMaskBit ) EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); // Test where componentMaskBit is NOT in the Active list - overwrite the Active list with only "general" - CcpSetActiveCaptureMask( TMCM_GENERAL ); + CcpSetActiveCaptureMask( {"general"} ); EXPECT_EQ( 0, CcpGetActiveCaptureMask() & componentMaskBit ); static int inactiveKey = 8002; CcpTelemetryEnterZone( &inactiveKey, componentMaskBit, "Inactive_CcpTelemetryEnterZoneCaptureMaskBit", __FILE__, __LINE__ ); @@ -707,6 +707,23 @@ TEST_F( CcpTelemetryTest, CaptureMaskRegisterRejectEmpty ) EXPECT_EQ( 0, CcpRegisterCaptureMask( "" ) ); } +TEST_F( CcpTelemetryTest, SetCaptureMaskRejectsTooManyMasks ) +{ + const std::vector numbers{ + "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", + "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", + "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", + "31", "32", "33", "34", "35", "36", "37", "38", "39", "40", + "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", + "51", "52", "53", "54", "55", "56", "57", "58", "59", "60", + "61", "62", "63", "64", "65", "66", "67", "68", "69", "70", + "71", "72", "73", "74", "75", "76", "77", "78", "79", "80", + "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", + "91", "92", "93", "94", "95", "96", "97", "98", "99" + }; + EXPECT_FALSE( CcpSetActiveCaptureMask( numbers ) ) << "Should have failed because more than the allowed number of masks was requested"; +} + TEST_F( CcpTelemetryTest, CaptureMaskDefaultsAreRegistered ) { // The default CaptureMasks must be available from the start. @@ -726,31 +743,6 @@ TEST_F( CcpTelemetryTest, CaptureMaskDefaultsAreRegistered ) EXPECT_EQ( CcpColor::OrangeRed, info.color ); } -TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByBits ) -{ - uint64_t newBits = 0; - newBits |= CcpRegisterCaptureMask( "SetActiveCaptureMaskByBits_A" ); - newBits |= CcpRegisterCaptureMask( "SetActiveCaptureMaskByBits_B" ); - - uint64_t registeredCaptureMaskBits = 0; - auto registeredCaptureMasks = CcpGetRegisteredCaptureMasks(); - for ( auto captureMask : registeredCaptureMasks ) - { - EXPECT_TRUE( IsCaptureMaskSingleBit( captureMask.maskBit ) ) << "Each entry should only contain one bit set"; - registeredCaptureMaskBits |= captureMask.maskBit; - } - EXPECT_TRUE( (newBits & registeredCaptureMaskBits) == newBits ) << "All bits in combined newBits should be present in combined registered CaptureMask bits "; - EXPECT_FALSE( (newBits & registeredCaptureMaskBits) == registeredCaptureMaskBits ) << "Registered bits should contain the additional 'general' and 'cpp' bits"; - - // newBits excludes the default "general" and "ccp" that are present in registeredCaptureMaskBits - CcpSetActiveCaptureMask( newBits ); - EXPECT_EQ( newBits, CcpGetActiveCaptureMask() ); - - // Overwrite the active CaptureMask - CcpSetActiveCaptureMask( TMCM_GENERAL ); - EXPECT_EQ( static_cast( TMCM_GENERAL ), CcpGetActiveCaptureMask() ); -} - TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByNames ) { const std::string registeredName = "SetActiveCaptureMaskByNames_RegisteredName"; @@ -776,7 +768,7 @@ TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByNames ) EXPECT_EQ( activeBits, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should now contain both the registered and pending bits"; // Overwrite the active CaptureMask - CcpSetActiveCaptureMask( TMCM_CPP ); + CcpSetActiveCaptureMask( {"cpp"} ); EXPECT_EQ( static_cast( TMCM_CPP ), CcpGetActiveCaptureMask() ); EXPECT_TRUE( IsCaptureMaskSingleBit( CcpGetActiveCaptureMask() ) ); } From 8a812a94e91a77d3a402720e321b3d6722be2bd6 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:03:12 +0000 Subject: [PATCH 24/40] Return list of active capture mask names instead of bitmask Since the bitmask is only an implementation details, and because `CcpSetActiveCaptureMask` takes in the names, too. --- CcpTelemetry.cpp | 17 +++++++- include/CcpTelemetry.h | 2 +- tests/CcpTelemetry.cpp | 95 +++++++++++++++++++++++++----------------- 3 files changed, 73 insertions(+), 41 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 3043dae..1211103 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -286,9 +286,22 @@ bool CcpSetActiveCaptureMask( const std::vector& maskNames ) return true; } -uint64_t CcpGetActiveCaptureMask() +std::vector CcpGetActiveCaptureMask() { - return s_activeCaptureMaskBits; + std::vector result; + size_t index{0}; + for ( const auto& registeredEntry : s_registeredCaptureMasks ) + { + uint64_t currentMaskBit = 1ULL << index; + + if ( ( s_activeCaptureMaskBits & currentMaskBit ) != 0 ) + { + result.push_back( registeredEntry.name ); + } + + ++index; + } + return result; } bool CcpTelemetryIsConnected() diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index ff06960..34ed4a4 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -66,7 +66,7 @@ CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, CcpCol CARBON_CORE_API std::vector CcpGetRegisteredCaptureMasks(); CARBON_CORE_API bool CcpSetActiveCaptureMask( const std::vector& maskNames ); -CARBON_CORE_API uint64_t CcpGetActiveCaptureMask(); +CARBON_CORE_API std::vector CcpGetActiveCaptureMask(); struct CcpTelemetryConfig diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index e19f0df..99738d1 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -38,6 +38,30 @@ // includes the AI-written, but human-reviewed test client. #include "TracyTestClient.h" +// Helper for CaptureMasks to make sure it only has a single bit set to 1 +bool IsCaptureMaskSingleBit( uint64_t mask ) +{ + return mask != 0 && ( mask & ( mask - 1 ) ) == 0; +} + +// Helper for find a CaptureMask by name from the list of already registered CaptureMasks +bool TryGetCaptureMaskNamed( const std::string& name, CcpCaptureMaskInfo& info ) +{ + // CaptureMask is stored lower-case, make sure we compare it as such. + std::string lowerName( name ); + std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); + + for( const CcpCaptureMaskInfo& mask : CcpGetRegisteredCaptureMasks() ) + { + if( mask.name == lowerName ) + { + info = mask; + return true; + } + } + return false; +} + class CcpTelemetryTest : public ::testing::Test { protected: @@ -164,29 +188,6 @@ class CcpTelemetryTest : public ::testing::Test return m_tracyClient.TryGetLock( lockId, outLock ); } - // Helper for CaptureMasks to make sure it only has a single bit set to 1 - bool IsCaptureMaskSingleBit( uint64_t mask ) - { - return mask != 0 && ( mask & ( mask - 1 ) ) == 0; - } - - // Helper for find a CaptureMask by name from the list of already registered CaptureMasks - bool TryGetCaptureMaskNamed( const std::string& name, CcpCaptureMaskInfo& info ) - { - // CaptureMask is stored lower-case, make sure we compare it as such. - std::string lowerName( name ); - std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); - - for( const CcpCaptureMaskInfo& mask : CcpGetRegisteredCaptureMasks() ) - { - if( mask.name == lowerName ) - { - info = mask; - return true; - } - } - return false; - } const std::string expectedNoFiber; const std::string expectedFiberName1{ "TestFiber1" }; @@ -394,7 +395,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) // Test where componentMaskBit is NOT in the Active list - overwrite the Active list with only "general" CcpSetActiveCaptureMask( {"general"} ); - EXPECT_EQ( 0, CcpGetActiveCaptureMask() & componentMaskBit ); + EXPECT_EQ( std::vector{"general"}, CcpGetActiveCaptureMask() ); { TelemetryZone inactiveZone( CaptureMaskBit, componentMaskBit, "Inactive_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); @@ -431,7 +432,7 @@ TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneCaptureMaskBit ) // Test where componentMaskBit is NOT in the Active list - overwrite the Active list with only "general" CcpSetActiveCaptureMask( {"general"} ); - EXPECT_EQ( 0, CcpGetActiveCaptureMask() & componentMaskBit ); + EXPECT_EQ( std::vector{"general"}, CcpGetActiveCaptureMask() ); static int inactiveKey = 8002; CcpTelemetryEnterZone( &inactiveKey, componentMaskBit, "Inactive_CcpTelemetryEnterZoneCaptureMaskBit", __FILE__, __LINE__ ); TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); @@ -683,8 +684,18 @@ TEST_F( CcpTelemetryTest, CcpSemaphoreTimedWaitTimesOut ) // --------------------------------------------------------------------------- // CaptureMask tests: // --------------------------------------------------------------------------- +class CcpTelemetryCaptureMaskTest : public ::testing::Test +{ + public: + void TearDown() + { + // Clear any active capture mask to let subsequent tests just work + CcpSetActiveCaptureMask({}); + ::testing::Test::TearDown(); + } +}; -TEST_F( CcpTelemetryTest, CaptureMaskRegisterReturnsNonDefaultBit ) +TEST_F( CcpTelemetryCaptureMaskTest, CaptureMaskRegisterReturnsNonDefaultBit ) { const uint64_t maskBit = CcpRegisterCaptureMask( "CaptureMaskRegisterReturnsNonDefaultBit" ); EXPECT_TRUE( IsCaptureMaskSingleBit( maskBit ) ); @@ -693,7 +704,7 @@ TEST_F( CcpTelemetryTest, CaptureMaskRegisterReturnsNonDefaultBit ) EXPECT_EQ( 0, maskBit & ( TMCM_GENERAL | TMCM_CPP ) ); } -TEST_F( CcpTelemetryTest, CaptureMaskRegisterDistinctBitPerName ) +TEST_F( CcpTelemetryCaptureMaskTest, CaptureMaskRegisterDistinctBitPerName ) { const uint64_t maskBitA = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_A" ); const uint64_t maskBitB = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_B", CcpColor::Tomato ); @@ -702,12 +713,25 @@ TEST_F( CcpTelemetryTest, CaptureMaskRegisterDistinctBitPerName ) EXPECT_NE( maskBitA, maskBitB ); } +TEST_F( CcpTelemetryCaptureMaskTest, EmptyActiveCaptureMask ) +{ + EXPECT_EQ( std::vector{}, CcpGetActiveCaptureMask() ); +} + TEST_F( CcpTelemetryTest, CaptureMaskRegisterRejectEmpty ) { EXPECT_EQ( 0, CcpRegisterCaptureMask( "" ) ); } -TEST_F( CcpTelemetryTest, SetCaptureMaskRejectsTooManyMasks ) +TEST_F( CcpTelemetryCaptureMaskTest, SetEmptyCaptureMasksClearsMask ) +{ + EXPECT_TRUE( CcpSetActiveCaptureMask( {"cpp"} ) ); + EXPECT_NE( std::vector{}, CcpGetActiveCaptureMask() ); + EXPECT_TRUE( CcpSetActiveCaptureMask( {} ) ); + EXPECT_EQ( std::vector{}, CcpGetActiveCaptureMask() ); +} + +TEST_F( CcpTelemetryCaptureMaskTest, SetCaptureMaskRejectsTooManyMasks ) { const std::vector numbers{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", @@ -724,7 +748,7 @@ TEST_F( CcpTelemetryTest, SetCaptureMaskRejectsTooManyMasks ) EXPECT_FALSE( CcpSetActiveCaptureMask( numbers ) ) << "Should have failed because more than the allowed number of masks was requested"; } -TEST_F( CcpTelemetryTest, CaptureMaskDefaultsAreRegistered ) +TEST_F( CcpTelemetryCaptureMaskTest, CaptureMaskDefaultsAreRegistered ) { // The default CaptureMasks must be available from the start. CcpCaptureMaskInfo info; @@ -743,7 +767,7 @@ TEST_F( CcpTelemetryTest, CaptureMaskDefaultsAreRegistered ) EXPECT_EQ( CcpColor::OrangeRed, info.color ); } -TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByNames ) +TEST_F( CcpTelemetryCaptureMaskTest, SetActiveCaptureMaskByNames ) { const std::string registeredName = "SetActiveCaptureMaskByNames_RegisteredName"; const std::string newName = "SetActiveCaptureMaskByNames_NewName"; @@ -754,21 +778,16 @@ TEST_F( CcpTelemetryTest, SetActiveCaptureMaskByNames ) const uint64_t registeredBit = CcpRegisterCaptureMask( registeredName ); EXPECT_TRUE( IsCaptureMaskSingleBit( registeredBit ) ); CcpSetActiveCaptureMask( activeMaskList ); - EXPECT_EQ( registeredBit, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should only contain the registered bit, not the pending one"; + EXPECT_EQ( std::vector{registeredName}, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should only contain the registered mask, not the pending one"; // Register the new name const uint64_t newBit = CcpRegisterCaptureMask( newName ); EXPECT_TRUE( IsCaptureMaskSingleBit( newBit ) ); - EXPECT_EQ( registeredBit, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should still only contain the registered bit, not the new or pending one"; + EXPECT_EQ( std::vector{registeredName}, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should still only contain the registered mask, not the new or pending one"; // Now add the pending one const uint64_t pendingBit = CcpRegisterCaptureMask( pendingName ); EXPECT_TRUE( IsCaptureMaskSingleBit( pendingBit ) ); const uint64_t activeBits = registeredBit | pendingBit; - EXPECT_EQ( activeBits, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should now contain both the registered and pending bits"; - - // Overwrite the active CaptureMask - CcpSetActiveCaptureMask( {"cpp"} ); - EXPECT_EQ( static_cast( TMCM_CPP ), CcpGetActiveCaptureMask() ); - EXPECT_TRUE( IsCaptureMaskSingleBit( CcpGetActiveCaptureMask() ) ); + EXPECT_EQ( activeMaskList, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should now contain both the registered and pending masks"; } From a9ab8f31b9379c5d897696e5718ae16c709de629 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:50:11 +0000 Subject: [PATCH 25/40] Use `ASSERT_*` instead of `EXPECT_*` in a few places This avoids double-checking whether we can access memory safely further down the test. If we only use EXPECT_* macros some tests may crash if the observed container is empty. --- tests/CcpTelemetry.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 99738d1..c4925ea 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -262,8 +262,8 @@ TEST_F( CcpTelemetryTest, SimpleZoneTest ) // The default assigned color in CcpTelemetryEnterZone() for TMCM_CPP is CcpColor::Yellow, make sure it is present const auto zones = m_tracyClient.GetZones(); - EXPECT_EQ( 1, zones.size() ); - EXPECT_EQ( static_cast( CcpColor::Yellow ), zones.front().color ); + ASSERT_EQ( 1, zones.size() ); + EXPECT_EQ( static_cast( CcpColor::SteelBlue ), zones.front().color ); CcpTelemetryLeaveZone( &key ); TickTelemetry( [this] { return m_tracyClient.GetZoneEndCount() == 1; } ); @@ -327,7 +327,7 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) EXPECT_FALSE( ZoneExists( zoneName1 ) ) << "FirstZone should not exist"; const auto zones = m_tracyClient.GetZones(); - EXPECT_EQ( 1, zones.size() ); + ASSERT_EQ( 1, zones.size() ); EXPECT_EQ( static_cast( CcpColor::SteelBlue ), zones.front().color ) << "Default color for CaptureMask TMCM_GENERAL should be CcpColor::SteelBlue"; EXPECT_EQ( 2, m_tracyClient.GetZoneBeginCount() ); EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); @@ -348,7 +348,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneLegacyConstructor ) TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); const auto zones = m_tracyClient.GetZones(); - EXPECT_EQ( 1, zones.size() ); + ASSERT_EQ( 1, zones.size() ); EXPECT_EQ( "LegacyZoneIsInActiveList", zones.front().function ); EXPECT_EQ( static_cast( CcpColor::Red ), zones.front().color ); EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); @@ -385,7 +385,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); const auto zones = m_tracyClient.GetZones(); - EXPECT_EQ( 1, zones.size() ); + ASSERT_EQ( 1, zones.size() ); EXPECT_EQ( "Active_TelemetryZoneCaptureMaskBitConstructor", zones.front().function ); EXPECT_EQ( static_cast( CcpColor::Orange ), zones.front().color ) << "Color should come from the registered CaptureMask entry"; } @@ -421,7 +421,7 @@ TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneCaptureMaskBit ) TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); const auto zones = m_tracyClient.GetZones(); - EXPECT_EQ( 1, zones.size() ); + ASSERT_EQ( 1, zones.size() ); EXPECT_EQ( "Active_CcpTelemetryEnterZoneCaptureMaskBit", zones.front().function ); EXPECT_EQ( static_cast( CcpColor::LimeGreen ), zones.front().color ); EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); From 6b6fdf81a6f8473c49ae0574afad526d137e167f Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 15:13:23 +0000 Subject: [PATCH 26/40] Remove `CaptureMaskBitTag` hack that was introduced for preserving ABI compatibility ABI is preserved with the overload as well, since both the `uint32_t` and `uint64_t` version have their distinct mangled names exported. So just dropping in the new library should be just fine. Of course, what it does mean is that downstream consumers of core will need to make some fixes to their code when they get _recompiled_ but that is not breaking _ABI_. That said, semantic versioning is also about the public API. So we are facing a decision here: 1. Accept that we need yet another major version tag for Core 2. Introduce terrible developer ergonomics where anyone who wants to construct the right version of `TelemetryZone` needs to pass in a really awkward sentinel value as first parameter. 3. Alternately, define a `TelemetryZoneWithCaptureMask` subclass that takes the `uint64_t` constructor without requiring the sentinel constant. 4. Last, but not least, change the way that capture mask registration works. Instead of "leaking" the bit mask implementation details through to the caller, we can return the index into the array of registered masks. That's still leaking an implementation details, but we can make that intent clear by calling it a "CaptureMaskHandle" or something similar. I'd argue that we should settle for option 4. Option 1 is annoying because just tagged a new major version for a subset of the telemetry improvements. Option 2 is terrible ergonomics on the call side. Option 3 complicates the API surface: When to use which one? OK, one is deprecated, that gives a hint, but the deprecation also implies that we _are_ going to break ABI and API again in the future. So we may as well just take the pain now. Option 4, on the other hand, does not require changes to function signatures, and therefore retains both ABI and API compatibility. It also means we no longer have to do a reverse-lookup of bitmask to color for a registered capture mask. So this commit is the first step in that direction of option 4. --- CcpTelemetry.cpp | 32 +++----------------- include/CcpTelemetry.h | 17 +---------- tests/CcpTelemetry.cpp | 66 ++++++++++++++---------------------------- 3 files changed, 26 insertions(+), 89 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 1211103..154fa59 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -584,8 +584,7 @@ const std::string& CcpTelemetryGetActiveFiber() return *t_activeFiber; } -// Deprecated version -TelemetryZone::TelemetryZone( uint32_t ctx, const char* name, const char* filename, uint32_t lineno, CcpColor color ) : m_impl(std::make_unique()) +TelemetryZone::TelemetryZone( uint32_t ctx, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete ) : m_impl(std::make_unique()) { if( s_profilerState.load( std::memory_order_acquire ) != ProfilerState::Started ) { @@ -595,31 +594,14 @@ TelemetryZone::TelemetryZone( uint32_t ctx, const char* name, const char* filena CCP_ASSERT( filename != nullptr ); CCP_ASSERT( name != nullptr ); - const int active = ( ( ctx & TMCM_CPP ) != 0 ) && IsCaptureMaskActive( ctx ); + auto color = GetCaptureMaskColor( ctx ); + const int active = IsCaptureMaskActive( ctx ); auto data = ___tracy_alloc_srcloc( lineno, filename, strlen( filename ), name, strlen( name ), static_cast( color ) ); // CCP_LOG_CH( s_ch, "[Fiber %p] Creating zone %s (%p)", t_activeFiber->c_str(), ret.first->c_str(), this ); m_impl->fiber = t_activeFiber; m_impl->telemetryContext.emplace( ___tracy_emit_zone_begin_alloc( data, active ) ); } -// Preferred version -TelemetryZone::TelemetryZone( CaptureMaskBitTag, uint64_t captureMaskBit, const char* name, const char* filename, uint32_t lineno ) : m_impl( std::make_unique() ) -{ - if( s_profilerState.load( std::memory_order_acquire ) != ProfilerState::Started ) - { - return; - } - - CCP_ASSERT( filename != nullptr ); - CCP_ASSERT( name != nullptr ); - - const CcpColor color = GetCaptureMaskColor( captureMaskBit ); - const int active = IsCaptureMaskActive( captureMaskBit ); - auto data = ___tracy_alloc_srcloc( lineno, filename, strlen( filename ), name, strlen( name ), static_cast( color ) ); - m_impl->fiber = t_activeFiber; - m_impl->telemetryContext.emplace( ___tracy_emit_zone_begin_alloc( data, active ) ); -} - TelemetryZone::TelemetryZone( TelemetryZone&& other ) noexcept : m_impl( std::make_unique() ) { m_impl->fiber = other.m_impl->fiber; @@ -656,17 +638,11 @@ void TelemetryZone::text( const char* text ) const // Deprecated version void CcpTelemetryEnterZone( void* key, const char* name, const char* filename, uint32_t lineno ) -{ - CcpTelemetryEnterZone( key, TMCM_CPP, name, filename, lineno ); // The default assigned color for TMCM_CPP is CcpColor::Yellow -} - -// Preferred version -void CcpTelemetryEnterZone( void* key, uint64_t captureMaskBit, const char* name, const char* filename, uint32_t lineno ) { if( s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Started ) { t_manuallyTrackedZones.emplace( key ); - t_activeTaskletZoneStore->second.emplace( CaptureMaskBit, captureMaskBit, name, filename, lineno ); + t_activeTaskletZoneStore->second.emplace( TMCM_CPP, name, filename, lineno ); } } diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 34ed4a4..8ab04cd 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -106,23 +106,11 @@ CARBON_CORE_API void CcpTelemetrySetActiveFiber( const std::string& name ); CARBON_CORE_API const std::string& CcpTelemetryGetActiveFiber(); CARBON_CORE_API void CcpTelemetryRemoveFiber( const std::string& name ); -// The CaptureMaskBitTag is a work-around to solve disambiguity between the two -// TelemetryZone constructors (uint32_t ctx vs uint64_t captureMaskBit) and -// still preserve ABI compatibility. -// The ambiguity would otherwise be triggered if the default CcpColor parameter -// is omitted in the original (now deprecated) uint32_t ctx constructor. -struct CaptureMaskBitTag { explicit CaptureMaskBitTag() = default; }; -inline constexpr CaptureMaskBitTag CaptureMaskBit{}; - class TelemetryZone { public: TelemetryZone() = delete; - - [[deprecated( "Use `TelemetryZone( CaptureMaskBitTag, uint64_t captureMaskBit, ... )` instead" )]] - CARBON_CORE_API TelemetryZone( uint32_t ctx, const char* name, const char* filename, uint32_t lineno, CcpColor color = CcpColor::SteelBlue ); - - CARBON_CORE_API TelemetryZone( CaptureMaskBitTag, uint64_t captureMaskBit, const char* name, const char* filename, uint32_t lineno ); + CARBON_CORE_API TelemetryZone( uint32_t ctx, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete = CcpColor::SteelBlue ); CARBON_CORE_API ~TelemetryZone(); TelemetryZone( TelemetryZone&& other ) noexcept; @@ -137,10 +125,7 @@ class TelemetryZone std::unique_ptr m_impl; }; -[[deprecated( "Use `CcpTelemetryEnterZone( void* key, uint64_t captureMaskBit, ... )` instead" )]] CARBON_CORE_API void CcpTelemetryEnterZone( void* key, const char* name, const char* filename, uint32_t lineno ); - -CARBON_CORE_API void CcpTelemetryEnterZone( void* key, uint64_t captureMaskBit, const char* name, const char* filename, uint32_t lineno ); CARBON_CORE_API void CcpTelemetryLeaveZone( void* key ); CARBON_CORE_API void CcpTelemetryZoneAddText( void* key, const char* text ); diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index c4925ea..5b38a1a 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -260,10 +260,9 @@ TEST_F( CcpTelemetryTest, SimpleZoneTest ) EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); EXPECT_TRUE( ZoneExists( zoneName ) ); - // The default assigned color in CcpTelemetryEnterZone() for TMCM_CPP is CcpColor::Yellow, make sure it is present const auto zones = m_tracyClient.GetZones(); ASSERT_EQ( 1, zones.size() ); - EXPECT_EQ( static_cast( CcpColor::SteelBlue ), zones.front().color ); + EXPECT_EQ( static_cast( CcpColor::Yellow ), zones.front().color ); CcpTelemetryLeaveZone( &key ); TickTelemetry( [this] { return m_tracyClient.GetZoneEndCount() == 1; } ); @@ -275,8 +274,8 @@ 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__ ); // Original deprecated version - CcpTelemetryEnterZone( &key, TMCM_CPP, "TestZone2", __FILE__, __LINE__ ); // New CaptureMaskBit version + CcpTelemetryEnterZone( &key, "TestZone", __FILE__, __LINE__ ); + CcpTelemetryEnterZone( &key, "TestZone2", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZones().size() == 2; } ); EXPECT_EQ( 2, m_tracyClient.GetZones().size() ); EXPECT_TRUE( ZoneExists( "TestZone" ) ); @@ -297,7 +296,7 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) { static int key1 = 1001; const std::string zoneName1{ "FirstZone" }; - CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); // Original deprecated version + CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); TickTelemetry( [this, zoneName1] { return ZoneExists( zoneName1 ); } ); EXPECT_TRUE( ZoneExists( zoneName1 ) ); EXPECT_EQ( 1, m_tracyClient.GetZones().size() ); @@ -321,14 +320,14 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) // Emit a new Zone, on the 2nd Start and validate static int key2 = 1002; const std::string zoneName2{ "SecondZone" }; - CcpTelemetryEnterZone( &key2, TMCM_GENERAL, zoneName2.c_str(), __FILE__, __LINE__ ); // New CaptureMaskBit version (CcpColor::SteelBlue) + CcpTelemetryEnterZone( &key2, zoneName2.c_str(), __FILE__, __LINE__ ); TickTelemetry( [this, zoneName2] { return ZoneExists( zoneName2 ); } ); EXPECT_TRUE( ZoneExists( zoneName2 ) ); EXPECT_FALSE( ZoneExists( zoneName1 ) ) << "FirstZone should not exist"; const auto zones = m_tracyClient.GetZones(); ASSERT_EQ( 1, zones.size() ); - EXPECT_EQ( static_cast( CcpColor::SteelBlue ), zones.front().color ) << "Default color for CaptureMask TMCM_GENERAL should be CcpColor::SteelBlue"; + EXPECT_EQ( static_cast( CcpColor::Yellow ), zones.front().color ) << "Default color for CaptureMask TMCM_GENERAL should be CcpColor::SteelBlue"; EXPECT_EQ( 2, m_tracyClient.GetZoneBeginCount() ); EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); @@ -338,19 +337,18 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) EXPECT_EQ( 2, m_tracyClient.GetZoneEndCount() ); } -TEST_F( CcpTelemetryTest, TelemetryZoneLegacyConstructor ) +TEST_F( CcpTelemetryTest, TelemetryZoneConstructor ) { // Test where captureMask is in the Active list CcpSetActiveCaptureMask( {"cpp", "general" } ); { - // Make sure the "legacy deprecated" TelemetryZone constructor respects overwrites of color - TelemetryZone activeZone( TMCM_CPP, "LegacyZoneIsInActiveList", __FILE__, __LINE__, CcpColor::Red ); // Overwrite the default CcpColor::Yellow of TMCM_CPP + TelemetryZone activeZone( TMCM_CPP, "ZoneIsInActiveList", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); const auto zones = m_tracyClient.GetZones(); ASSERT_EQ( 1, zones.size() ); - EXPECT_EQ( "LegacyZoneIsInActiveList", zones.front().function ); - EXPECT_EQ( static_cast( CcpColor::Red ), zones.front().color ); + EXPECT_EQ( "ZoneIsInActiveList", zones.front().function ); + EXPECT_EQ( static_cast( CcpColor::Yellow ), zones.front().color ); EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); } // Now the activeZone has gone out of scope, so the zone should have ended @@ -361,14 +359,14 @@ TEST_F( CcpTelemetryTest, TelemetryZoneLegacyConstructor ) // Test where captureMask is NOT in the Active list CcpSetActiveCaptureMask( {"NotRegisteredCaptureMask", "ClearingPreviousGeneralAndCpp", "FromTheActiveCaptureMaskList"} ); { - TelemetryZone inactiveZone( TMCM_CPP, "LegacyZoneIsNotInActiveList", __FILE__, __LINE__, CcpColor::Blue ); + TelemetryZone inactiveZone( TMCM_CPP, "ZoneIsNotInActiveList", __FILE__, __LINE__, CcpColor::Blue ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Because TMCM_CPP is now no longer in the ACTIVE CaptureMask list, we should not have emitted an event for it"; EXPECT_EQ( 0, m_tracyClient.GetZones().size() ) << "Zone list should therefore be empty"; } TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); - EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Inactive legacy zone must not emit ZoneBegin, count should stay at 1"; - EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "Inactive legacy zone must not emit ZoneEnd, count should stay at 1"; + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Inactive zone must not emit ZoneBegin, count should stay at 1"; + EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "Inactive zone must not emit ZoneEnd, count should stay at 1"; EXPECT_TRUE( m_tracyClient.GetZones().empty() ); } @@ -381,7 +379,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) // Test where componentMaskBit is in the Active list CcpSetActiveCaptureMask( {"TelemetryZoneCaptureMaskBitConstructor"} ); { - TelemetryZone activeZone( CaptureMaskBit, componentMaskBit, "Active_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); + TelemetryZone activeZone( componentMaskBit, "Active_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); const auto zones = m_tracyClient.GetZones(); @@ -397,7 +395,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) CcpSetActiveCaptureMask( {"general"} ); EXPECT_EQ( std::vector{"general"}, CcpGetActiveCaptureMask() ); { - TelemetryZone inactiveZone( CaptureMaskBit, componentMaskBit, "Inactive_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); + TelemetryZone inactiveZone( componentMaskBit, "Inactive_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Because TMCM_GENERAL is now no longer in the ACTIVE CaptureMask list, we should not have emitted an event for it"; EXPECT_EQ( 0, m_tracyClient.GetZones().size() ) << "Zone list should therefore be empty"; @@ -408,38 +406,16 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) EXPECT_TRUE( m_tracyClient.GetZones().empty() ); } -TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneCaptureMaskBit ) +TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneIsAlwaysCaptureMaskCpp ) { - // Register a new component CaptureMask - const uint64_t componentMaskBit = CcpRegisterCaptureMask( "CcpTelemetryEnterZoneCaptureMaskBit", CcpColor::LimeGreen ); - EXPECT_TRUE( IsCaptureMaskSingleBit( componentMaskBit ) ); - - // Test where componentMaskBit is in the Active list - CcpSetActiveCaptureMask( {"CcpTelemetryEnterZoneCaptureMaskBit"} ); - static int activeKey = 8001; - CcpTelemetryEnterZone( &activeKey, componentMaskBit, "Active_CcpTelemetryEnterZoneCaptureMaskBit", __FILE__, __LINE__ ); - TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); - EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); - const auto zones = m_tracyClient.GetZones(); - ASSERT_EQ( 1, zones.size() ); - EXPECT_EQ( "Active_CcpTelemetryEnterZoneCaptureMaskBit", zones.front().function ); - EXPECT_EQ( static_cast( CcpColor::LimeGreen ), zones.front().color ); - EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); - - CcpTelemetryLeaveZone( &activeKey ); - TickTelemetry( [this] { return m_tracyClient.GetZoneEndCount() == 1; } ); - EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); - - // Test where componentMaskBit is NOT in the Active list - overwrite the Active list with only "general" - CcpSetActiveCaptureMask( {"general"} ); - EXPECT_EQ( std::vector{"general"}, CcpGetActiveCaptureMask() ); + CcpSetActiveCaptureMask( {"cpp"} ); + EXPECT_EQ( std::vector{"cpp"}, CcpGetActiveCaptureMask() ); static int inactiveKey = 8002; - CcpTelemetryEnterZone( &inactiveKey, componentMaskBit, "Inactive_CcpTelemetryEnterZoneCaptureMaskBit", __FILE__, __LINE__ ); + CcpTelemetryEnterZone( &inactiveKey, "Active_CcpTelemetryEnterZoneCaptureMaskBit", __FILE__, __LINE__ ); TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Inactive CaptureMaskBit enter-zone must not emit ZoneBegin"; - EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "EndZone count should still be 1"; - EXPECT_TRUE( m_tracyClient.GetZones().empty() ); - + EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); + EXPECT_FALSE( m_tracyClient.GetZones().empty() ); CcpTelemetryLeaveZone( &inactiveKey ); TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "Inactive CaptureMaskBit enter-zone must not emit ZoneEnd"; From 15283312b580af0c9dffa0dcec9799de5790b138 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 15:57:45 +0000 Subject: [PATCH 27/40] Add missing export annotation to `CcpColorToString` --- include/CcpColorConstants.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/CcpColorConstants.h b/include/CcpColorConstants.h index 470bf5f..15a190c 100644 --- a/include/CcpColorConstants.h +++ b/include/CcpColorConstants.h @@ -161,6 +161,6 @@ enum class CcpColor : uint32_t YellowGreen = 0x9acd32, }; -std::string_view CcpColorToString( CcpColor color ); +CARBON_CORE_API std::string_view CcpColorToString( CcpColor color ); #endif From 1e43e260c9264a668995dc7c245508a2514aa15d Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:15:39 +0000 Subject: [PATCH 28/40] Introduce `CcpCaptureMaskHandle` as handle to a registered telemetry capture mask Users of the telemetry instrumentation should pass this handle on when creating a `TelemetryZone` if they want it to be captured as part of their registered mask. Implementation details: This currently represents the index into the array of registered capture masks, as that keeps the look up of name and color cheap. --- CcpTelemetry.cpp | 136 +++++++++++------------------------------ include/CcpTelemetry.h | 8 ++- tests/CcpTelemetry.cpp | 65 ++++++-------------- 3 files changed, 58 insertions(+), 151 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 154fa59..df4d5e0 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -67,13 +67,6 @@ typedef TrackableStdVector> EventHa namespace { - std::string ToLower( const std::string& s ) - { - std::string out( s ); - std::transform( out.begin(), out.end(), out.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); - return out; - } - uint32_t s_telemetryTick = 0; CcpTelemetryConfig s_config; @@ -103,79 +96,33 @@ namespace CcpMutex s_captureMaskMutex( "CcpTelemetry", "CaptureMaskMutex" ); - // Fixed size array for registered CaptureMasks. - // Allows for O(1) lookup based on single-bit CaptureMask value. + // Fixed size array of registered CaptureMasks. std::array s_registeredCaptureMasks{ - CcpCaptureMaskInfo{ "general", TMCM_GENERAL, CcpColor::SteelBlue }, - CcpCaptureMaskInfo{ "cpp", TMCM_CPP, CcpColor::Yellow }, + CcpCaptureMaskInfo{ "core", CcpColor::LightGreen }, // Pre-allocated capture mask for core, should core ever need it. This also solves the problem that legacy `TMCM_GENERAL` and `TMCM_CPP` otherwise cause an off-by-one error. + CcpCaptureMaskInfo{ "general", CcpColor::SteelBlue }, // legacy definition from TMCM_GENERAL, used to be a bitmask, but can now be treated as index into this array + CcpCaptureMaskInfo{ "cpp", CcpColor::Yellow }, // legacy value from TMCM_CPP, used to be a bitmask, but can now be treated as index into this array }; - // Currently-active CaptureMask. Defaults to "all" until - // narrowed in a later call to CcpSetActiveCaptureMask() - uint64_t s_activeCaptureMaskBits = UINT64_MAX; + uint64_t s_activeCaptureMaskBits{0}; // List of CaptureMask names that have yet to be registered. // From a CcpSetActiveCaptureMask( vector ) call. std::vector s_pendingActiveCaptureMaskNames; - // Returns the index [0..63] of the lowest set bit. - // Uses a de Bruijn sequence, thanks Claude Code. - // TODO: Can be replaced with std::countr_zero() once we upgrade to C++20. - constexpr int CountTrailingZeros64( uint64_t x ) noexcept - { - constexpr uint64_t deBruijn = 0x03f79d71b4cb0a89ULL; - constexpr uint8_t lookup[64] = { - 0, 1, 48, 2, 57, 49, 28, 3, - 61, 58, 50, 42, 38, 29, 17, 4, - 62, 55, 59, 36, 53, 51, 43, 22, - 45, 39, 33, 30, 24, 18, 12, 5, - 63, 47, 56, 27, 60, 41, 37, 16, - 54, 35, 52, 21, 44, 32, 23, 11, - 46, 26, 40, 15, 34, 20, 31, 10, - 25, 14, 19, 9, 13, 8, 7, 6, - }; - return lookup[( ( x & ( 0ULL - x ) ) * deBruijn ) >> 58]; - } - - // Store a registered CaptureMask into its array slot - void StoreRegisteredCaptureMask( uint64_t bit, const std::string& name, CcpColor color ) - { - auto& entry = s_registeredCaptureMasks[CountTrailingZeros64( bit )]; - entry.name = name; - entry.maskBit = bit; - entry.color = color; - } - - uint64_t GetRegisteredCaptureMaskBits() - { - uint64_t registeredBits = 0; - for( const auto& entry : s_registeredCaptureMasks ) - { - registeredBits |= entry.maskBit; - } - return registeredBits; - } - // Get the registered CaptureMask color for a given CaptureMask - // Default to CcpColor::White if not found - CcpColor GetCaptureMaskColor( uint64_t captureMaskBit ) + CcpColor GetCaptureMaskColor( CcpCaptureMaskHandle handle ) { - if( captureMaskBit == 0 ) - { - return CcpColor::White; - } - const auto& entry = s_registeredCaptureMasks[CountTrailingZeros64( captureMaskBit )]; - return entry.maskBit == captureMaskBit ? entry.color : CcpColor::White; + return s_registeredCaptureMasks[handle].color; } - bool IsCaptureMaskActive( uint64_t captureMaskBit ) + bool IsCaptureMaskActive( CcpCaptureMaskHandle handle ) { - return ( s_activeCaptureMaskBits & captureMaskBit ) != 0; + return ( s_activeCaptureMaskBits & ( 1ULL< %u with color %s", name.c_str(), handle, CcpColorToString( color ).data() ); + break; } + ++handle; } - const uint64_t alreadyRegisteredBits = GetRegisteredCaptureMaskBits(); - if( alreadyRegisteredBits == UINT64_MAX ) + if ( handle > CAPTURE_MASKS_MAX ) { - CCP_LOGERR_CH( s_ch, "Cannot register CaptureMask '%s' - all 64 bits are already in use", name.c_str() ); - return 0; + return CCP_CAPTURE_MASK_INVALID_HANDLE; } - // Allocate the lowest available free bit for the new CaptureMask - const uint64_t newMaskBit = ~alreadyRegisteredBits & ( alreadyRegisteredBits + 1 ); - - StoreRegisteredCaptureMask( newMaskBit, name, color ); - CCP_LOGWARN_CH( s_ch, "Registered a new CaptureMask for '%s' -> 0x%llx with color %s", name.c_str(), static_cast( newMaskBit ), CcpColorToString( color ).data() ); - // Make sure previously "pending active" CaptureMask is included auto pendingIt = std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), name ); if( pendingIt != s_pendingActiveCaptureMaskNames.end() ) { s_pendingActiveCaptureMaskNames.erase( pendingIt ); - s_activeCaptureMaskBits |= newMaskBit; - CCP_LOGWARN_CH( s_ch, "Previously pending CaptureMask '%s' added to activeCaptureMask -> 0x%llx", name.c_str(), static_cast( newMaskBit ) ); + s_activeCaptureMaskBits |= ( 1ULL << handle ); + CCP_LOG_CH( s_ch, "Previously pending CaptureMask '%s' added to activeCaptureMask -> %u", name.c_str(), handle ); } - return newMaskBit; + return handle; } std::vector CcpGetRegisteredCaptureMasks() @@ -231,7 +172,7 @@ std::vector CcpGetRegisteredCaptureMasks() result.reserve( CAPTURE_MASKS_MAX ); for( const auto& registeredEntry : s_registeredCaptureMasks ) { - if( registeredEntry.maskBit != 0 ) + if( ! registeredEntry.name.empty() ) { result.push_back( registeredEntry ); } @@ -260,14 +201,16 @@ bool CcpSetActiveCaptureMask( const std::vector& maskNames ) } bool alreadyRegistered = false; + size_t index{0}; for( const auto& registeredEntry : s_registeredCaptureMasks ) { - if( registeredEntry.maskBit != 0 && registeredEntry.name == rawName ) + if( registeredEntry.name == rawName ) { - newActiveCaptureMask |= registeredEntry.maskBit; + newActiveCaptureMask |= (1ULL << index); alreadyRegistered = true; break; } + ++index; } // This CaptureMask hasn't been registered (yet) so add it to the pending list @@ -584,7 +527,7 @@ const std::string& CcpTelemetryGetActiveFiber() return *t_activeFiber; } -TelemetryZone::TelemetryZone( uint32_t ctx, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete ) : m_impl(std::make_unique()) +TelemetryZone::TelemetryZone( uint32_t handle, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete ) : m_impl(std::make_unique()) { if( s_profilerState.load( std::memory_order_acquire ) != ProfilerState::Started ) { @@ -594,8 +537,8 @@ TelemetryZone::TelemetryZone( uint32_t ctx, const char* name, const char* filena CCP_ASSERT( filename != nullptr ); CCP_ASSERT( name != nullptr ); - auto color = GetCaptureMaskColor( ctx ); - const int active = IsCaptureMaskActive( ctx ); + auto color = GetCaptureMaskColor( handle ); + const int active = IsCaptureMaskActive( handle ); auto data = ___tracy_alloc_srcloc( lineno, filename, strlen( filename ), name, strlen( name ), static_cast( color ) ); // CCP_LOG_CH( s_ch, "[Fiber %p] Creating zone %s (%p)", t_activeFiber->c_str(), ret.first->c_str(), this ); m_impl->fiber = t_activeFiber; @@ -703,12 +646,7 @@ void CcpRegisterThread( CcpThreadId_t threadId, const char* name ) { } -uint64_t CcpRegisterCaptureMask( const std::string& ) -{ - return 0; -} - -uint64_t CcpRegisterCaptureMask( const std::string&, CcpColor ) +CcpCaptureMaskHandle CcpRegisterCaptureMask( const std::string&, CcpColor ) { return 0; } @@ -718,17 +656,13 @@ std::vector CcpGetRegisteredCaptureMasks() return {}; } -void CcpSetActiveCaptureMask( uint64_t ) -{ -} - -void CcpSetActiveCaptureMask( const std::vector& ) +bool CcpSetActiveCaptureMask( const std::vector& ) { } -uint64_t CcpGetActiveCaptureMask() +std::vector CcpGetActiveCaptureMask() { - return 0; + return {}; } bool CcpStartTelemetry( const char* server, int connectionType, uint32_t maxThreadCount ) diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 8ab04cd..44a4a4d 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -58,11 +58,13 @@ CARBON_CORE_API void CcpRegisterThread( CcpThreadId_t threadId, const char* name struct CcpCaptureMaskInfo { std::string name; // The lower-case display name of the CaptureMask (carbon-component) - uint64_t maskBit{0}; // The single bit assigned to this CaptureMask during registration CcpColor color{CcpColor::White}; // The chosen/allocated color for the CaptureMask }; -CARBON_CORE_API uint64_t CcpRegisterCaptureMask( const std::string& name, CcpColor color = CcpColor::Fuchsia ); +typedef uint32_t CcpCaptureMaskHandle; +constexpr uint32_t CCP_CAPTURE_MASK_INVALID_HANDLE{ UINT32_MAX }; + +CARBON_CORE_API CcpCaptureMaskHandle CcpRegisterCaptureMask( const std::string& name, CcpColor color = CcpColor::Fuchsia ); CARBON_CORE_API std::vector CcpGetRegisteredCaptureMasks(); CARBON_CORE_API bool CcpSetActiveCaptureMask( const std::vector& maskNames ); @@ -110,7 +112,7 @@ class TelemetryZone { public: TelemetryZone() = delete; - CARBON_CORE_API TelemetryZone( uint32_t ctx, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete = CcpColor::SteelBlue ); + CARBON_CORE_API TelemetryZone( CcpCaptureMaskHandle handle, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete = CcpColor::SteelBlue ); CARBON_CORE_API ~TelemetryZone(); TelemetryZone( TelemetryZone&& other ) noexcept; diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 5b38a1a..cfe34cf 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -1,5 +1,6 @@ // Copyright © 2025 CCP ehf. +#if CCP_TELEMETRY_ENABLED #include #include @@ -38,22 +39,12 @@ // includes the AI-written, but human-reviewed test client. #include "TracyTestClient.h" -// Helper for CaptureMasks to make sure it only has a single bit set to 1 -bool IsCaptureMaskSingleBit( uint64_t mask ) -{ - return mask != 0 && ( mask & ( mask - 1 ) ) == 0; -} - // Helper for find a CaptureMask by name from the list of already registered CaptureMasks bool TryGetCaptureMaskNamed( const std::string& name, CcpCaptureMaskInfo& info ) { - // CaptureMask is stored lower-case, make sure we compare it as such. - std::string lowerName( name ); - std::transform( lowerName.begin(), lowerName.end(), lowerName.begin(), []( unsigned char c ) { return static_cast( std::tolower( c ) ); } ); - for( const CcpCaptureMaskInfo& mask : CcpGetRegisteredCaptureMasks() ) { - if( mask.name == lowerName ) + if( mask.name == name ) { info = mask; return true; @@ -253,6 +244,7 @@ TEST_F( CcpTelemetryTest, SimpleZoneTest ) static int key = 4711; const std::string zoneName{ "TestZone" }; + CcpSetActiveCaptureMask( {"cpp"} ); CcpTelemetryEnterZone( &key, zoneName.c_str(), __FILE__, __LINE__ ); // Original deprecated version // Tracy's worker sleeps up to 10 ms between queue flushes, so give it // time to process and send the zone event before asserting. @@ -262,7 +254,7 @@ TEST_F( CcpTelemetryTest, SimpleZoneTest ) const auto zones = m_tracyClient.GetZones(); ASSERT_EQ( 1, zones.size() ); - EXPECT_EQ( static_cast( CcpColor::Yellow ), zones.front().color ); + EXPECT_EQ( static_cast( CcpColor::Yellow ), zones.front().color ) << CcpColorToString( CcpColor(zones.front().color) ).data(); CcpTelemetryLeaveZone( &key ); TickTelemetry( [this] { return m_tracyClient.GetZoneEndCount() == 1; } ); @@ -274,6 +266,7 @@ TEST_F( CcpTelemetryTest, StackedZones ) { // A stacked zone is a zone that has the same key as a previously created zone. static int key = 4711; + CcpSetActiveCaptureMask( {"cpp"} ); CcpTelemetryEnterZone( &key, "TestZone", __FILE__, __LINE__ ); CcpTelemetryEnterZone( &key, "TestZone2", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZones().size() == 2; } ); @@ -290,12 +283,14 @@ TEST_F( CcpTelemetryTest, StackedZones ) CcpTelemetryLeaveZone( &key ); TickTelemetry( [this] { return m_tracyClient.GetZones().empty(); } ); EXPECT_TRUE( m_tracyClient.GetZones().empty() ); + CcpSetActiveCaptureMask( {} ); } TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) { static int key1 = 1001; const std::string zoneName1{ "FirstZone" }; + CcpSetActiveCaptureMask( {"cpp"} ); CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); TickTelemetry( [this, zoneName1] { return ZoneExists( zoneName1 ); } ); EXPECT_TRUE( ZoneExists( zoneName1 ) ); @@ -335,6 +330,7 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) TickTelemetry(); EXPECT_TRUE( m_tracyClient.GetZones().empty() ); EXPECT_EQ( 2, m_tracyClient.GetZoneEndCount() ); + CcpSetActiveCaptureMask( {} ); } TEST_F( CcpTelemetryTest, TelemetryZoneConstructor ) @@ -373,13 +369,12 @@ TEST_F( CcpTelemetryTest, TelemetryZoneConstructor ) TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) { // Register a new component CaptureMask - const uint64_t componentMaskBit = CcpRegisterCaptureMask( "TelemetryZoneCaptureMaskBitConstructor", CcpColor::Orange ); - EXPECT_TRUE( IsCaptureMaskSingleBit( componentMaskBit ) ); + const CcpCaptureMaskHandle handle = CcpRegisterCaptureMask( "TelemetryZoneCaptureMaskBitConstructor", CcpColor::Orange ); // Test where componentMaskBit is in the Active list CcpSetActiveCaptureMask( {"TelemetryZoneCaptureMaskBitConstructor"} ); { - TelemetryZone activeZone( componentMaskBit, "Active_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); + TelemetryZone activeZone( handle, "Active_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); const auto zones = m_tracyClient.GetZones(); @@ -395,7 +390,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) CcpSetActiveCaptureMask( {"general"} ); EXPECT_EQ( std::vector{"general"}, CcpGetActiveCaptureMask() ); { - TelemetryZone inactiveZone( componentMaskBit, "Inactive_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); + TelemetryZone inactiveZone( handle, "Inactive_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Because TMCM_GENERAL is now no longer in the ACTIVE CaptureMask list, we should not have emitted an event for it"; EXPECT_EQ( 0, m_tracyClient.GetZones().size() ) << "Zone list should therefore be empty"; @@ -671,21 +666,10 @@ class CcpTelemetryCaptureMaskTest : public ::testing::Test } }; -TEST_F( CcpTelemetryCaptureMaskTest, CaptureMaskRegisterReturnsNonDefaultBit ) -{ - const uint64_t maskBit = CcpRegisterCaptureMask( "CaptureMaskRegisterReturnsNonDefaultBit" ); - EXPECT_TRUE( IsCaptureMaskSingleBit( maskBit ) ); - // TMCM_GENERAL and TMCM_CPP are registered as the default CaptureMasks, - // so their maskBits must never be handed out to other components. - EXPECT_EQ( 0, maskBit & ( TMCM_GENERAL | TMCM_CPP ) ); -} - TEST_F( CcpTelemetryCaptureMaskTest, CaptureMaskRegisterDistinctBitPerName ) { - const uint64_t maskBitA = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_A" ); - const uint64_t maskBitB = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_B", CcpColor::Tomato ); - EXPECT_TRUE( IsCaptureMaskSingleBit( maskBitA ) ); - EXPECT_TRUE( IsCaptureMaskSingleBit( maskBitB ) ); + const CcpCaptureMaskHandle maskBitA = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_A" ); + const CcpCaptureMaskHandle maskBitB = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_B", CcpColor::Tomato ); EXPECT_NE( maskBitA, maskBitB ); } @@ -728,19 +712,9 @@ TEST_F( CcpTelemetryCaptureMaskTest, CaptureMaskDefaultsAreRegistered ) { // The default CaptureMasks must be available from the start. CcpCaptureMaskInfo info; + EXPECT_TRUE( TryGetCaptureMaskNamed( "core", info ) ); EXPECT_TRUE( TryGetCaptureMaskNamed( "general", info ) ); - EXPECT_EQ( static_cast( TMCM_GENERAL ), info.maskBit ); - EXPECT_EQ( CcpColor::SteelBlue, info.color ); // CcpColor::SteelBlue is the default assigned color for TMCM_GENERAL EXPECT_TRUE( TryGetCaptureMaskNamed( "cpp", info ) ); - EXPECT_EQ( static_cast( TMCM_CPP ), info.maskBit ); - - // Registering a CaptureMask for the same name should result in the same maskBit returned, - // but a color change should be allowed. - const uint64_t reRegisterMaskBit = CcpRegisterCaptureMask( "general", CcpColor::OrangeRed ); - TryGetCaptureMaskNamed( "general", info ); - EXPECT_EQ( static_cast( TMCM_GENERAL ), reRegisterMaskBit ); - EXPECT_EQ( static_cast( TMCM_GENERAL ), info.maskBit ); - EXPECT_EQ( CcpColor::OrangeRed, info.color ); } TEST_F( CcpTelemetryCaptureMaskTest, SetActiveCaptureMaskByNames ) @@ -751,19 +725,16 @@ TEST_F( CcpTelemetryCaptureMaskTest, SetActiveCaptureMaskByNames ) const std::vector activeMaskList = { registeredName, pendingName }; // Only register one name - const uint64_t registeredBit = CcpRegisterCaptureMask( registeredName ); - EXPECT_TRUE( IsCaptureMaskSingleBit( registeredBit ) ); + const CcpCaptureMaskHandle handle = CcpRegisterCaptureMask( registeredName ); CcpSetActiveCaptureMask( activeMaskList ); EXPECT_EQ( std::vector{registeredName}, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should only contain the registered mask, not the pending one"; // Register the new name - const uint64_t newBit = CcpRegisterCaptureMask( newName ); - EXPECT_TRUE( IsCaptureMaskSingleBit( newBit ) ); + const CcpCaptureMaskHandle newHandle = CcpRegisterCaptureMask( newName ); EXPECT_EQ( std::vector{registeredName}, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should still only contain the registered mask, not the new or pending one"; // Now add the pending one - const uint64_t pendingBit = CcpRegisterCaptureMask( pendingName ); - EXPECT_TRUE( IsCaptureMaskSingleBit( pendingBit ) ); - const uint64_t activeBits = registeredBit | pendingBit; + const CcpCaptureMaskHandle pendingHandle = CcpRegisterCaptureMask( pendingName ); EXPECT_EQ( activeMaskList, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should now contain both the registered and pending masks"; } +#endif From 601d77f4cf2a4cc218d3682d082712e5a841da42 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:24:40 +0000 Subject: [PATCH 29/40] Add missing `uint32_t` -> `CcpCaptureMaskHandle` type change --- CcpTelemetry.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index df4d5e0..7475b09 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -527,7 +527,7 @@ const std::string& CcpTelemetryGetActiveFiber() return *t_activeFiber; } -TelemetryZone::TelemetryZone( uint32_t handle, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete ) : m_impl(std::make_unique()) +TelemetryZone::TelemetryZone( CcpCaptureMaskHandle handle, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete ) : m_impl(std::make_unique()) { if( s_profilerState.load( std::memory_order_acquire ) != ProfilerState::Started ) { From a57664a3b4a27dba145e0ac4582771c644aa5362 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:25:01 +0000 Subject: [PATCH 30/40] Remove unnecessary comment --- CcpTelemetry.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 7475b09..1409716 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -579,7 +579,6 @@ void TelemetryZone::text( const char* text ) const } } -// Deprecated version void CcpTelemetryEnterZone( void* key, const char* name, const char* filename, uint32_t lineno ) { if( s_profilerState.load( std::memory_order_acquire ) == ProfilerState::Started ) From 5d3dab5c871c84ff05716260d21d3d495d3d32e8 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:25:24 +0000 Subject: [PATCH 31/40] Remove no longer existing function overload from non-telemetry path --- CcpTelemetry.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 1409716..eeffd09 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -712,10 +712,6 @@ void CcpTelemetryEnterZone( void* key, const char* name, const char* filename, u { } -void CcpTelemetryEnterZone( void* key, uint64_t captureMaskBit, const char* name, const char* filename, uint32_t lineno ) -{ -} - void CcpTelemetryLeaveZone( void* key ) { } From f6a294a4a466c9ebaefa1f54f36ff7105f560f30 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Tue, 21 Jul 2026 16:34:42 +0000 Subject: [PATCH 32/40] Rename `CcpSetActiveCaptureMask` to `CcpSetCaptureMask` There is only one capture mask from the user's perspective, so let's keep implementation details out of the function name. --- CcpTelemetry.cpp | 2 +- include/CcpTelemetry.h | 2 +- tests/CcpTelemetry.cpp | 30 +++++++++++++++--------------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index eeffd09..4ace92b 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -180,7 +180,7 @@ std::vector CcpGetRegisteredCaptureMasks() return result; } -bool CcpSetActiveCaptureMask( const std::vector& maskNames ) +bool CcpSetCaptureMask( const std::vector& maskNames ) { // Guard access to all CaptureMasks members CcpAutoMutex lock( s_captureMaskMutex ); diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 44a4a4d..c8f165e 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -67,7 +67,7 @@ constexpr uint32_t CCP_CAPTURE_MASK_INVALID_HANDLE{ UINT32_MAX }; CARBON_CORE_API CcpCaptureMaskHandle CcpRegisterCaptureMask( const std::string& name, CcpColor color = CcpColor::Fuchsia ); CARBON_CORE_API std::vector CcpGetRegisteredCaptureMasks(); -CARBON_CORE_API bool CcpSetActiveCaptureMask( const std::vector& maskNames ); +CARBON_CORE_API bool CcpSetCaptureMask( const std::vector& maskNames ); CARBON_CORE_API std::vector CcpGetActiveCaptureMask(); diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index cfe34cf..57c2630 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -244,7 +244,7 @@ TEST_F( CcpTelemetryTest, SimpleZoneTest ) static int key = 4711; const std::string zoneName{ "TestZone" }; - CcpSetActiveCaptureMask( {"cpp"} ); + CcpSetCaptureMask( {"cpp"} ); CcpTelemetryEnterZone( &key, zoneName.c_str(), __FILE__, __LINE__ ); // Original deprecated version // Tracy's worker sleeps up to 10 ms between queue flushes, so give it // time to process and send the zone event before asserting. @@ -266,7 +266,7 @@ TEST_F( CcpTelemetryTest, StackedZones ) { // A stacked zone is a zone that has the same key as a previously created zone. static int key = 4711; - CcpSetActiveCaptureMask( {"cpp"} ); + CcpSetCaptureMask( {"cpp"} ); CcpTelemetryEnterZone( &key, "TestZone", __FILE__, __LINE__ ); CcpTelemetryEnterZone( &key, "TestZone2", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZones().size() == 2; } ); @@ -283,14 +283,14 @@ TEST_F( CcpTelemetryTest, StackedZones ) CcpTelemetryLeaveZone( &key ); TickTelemetry( [this] { return m_tracyClient.GetZones().empty(); } ); EXPECT_TRUE( m_tracyClient.GetZones().empty() ); - CcpSetActiveCaptureMask( {} ); + CcpSetCaptureMask( {} ); } TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) { static int key1 = 1001; const std::string zoneName1{ "FirstZone" }; - CcpSetActiveCaptureMask( {"cpp"} ); + CcpSetCaptureMask( {"cpp"} ); CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); TickTelemetry( [this, zoneName1] { return ZoneExists( zoneName1 ); } ); EXPECT_TRUE( ZoneExists( zoneName1 ) ); @@ -330,13 +330,13 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) TickTelemetry(); EXPECT_TRUE( m_tracyClient.GetZones().empty() ); EXPECT_EQ( 2, m_tracyClient.GetZoneEndCount() ); - CcpSetActiveCaptureMask( {} ); + CcpSetCaptureMask( {} ); } TEST_F( CcpTelemetryTest, TelemetryZoneConstructor ) { // Test where captureMask is in the Active list - CcpSetActiveCaptureMask( {"cpp", "general" } ); + CcpSetCaptureMask( {"cpp", "general" } ); { TelemetryZone activeZone( TMCM_CPP, "ZoneIsInActiveList", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); @@ -353,7 +353,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneConstructor ) EXPECT_TRUE( m_tracyClient.GetZones().empty() ); // Test where captureMask is NOT in the Active list - CcpSetActiveCaptureMask( {"NotRegisteredCaptureMask", "ClearingPreviousGeneralAndCpp", "FromTheActiveCaptureMaskList"} ); + CcpSetCaptureMask( {"NotRegisteredCaptureMask", "ClearingPreviousGeneralAndCpp", "FromTheActiveCaptureMaskList"} ); { TelemetryZone inactiveZone( TMCM_CPP, "ZoneIsNotInActiveList", __FILE__, __LINE__, CcpColor::Blue ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); @@ -372,7 +372,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) const CcpCaptureMaskHandle handle = CcpRegisterCaptureMask( "TelemetryZoneCaptureMaskBitConstructor", CcpColor::Orange ); // Test where componentMaskBit is in the Active list - CcpSetActiveCaptureMask( {"TelemetryZoneCaptureMaskBitConstructor"} ); + CcpSetCaptureMask( {"TelemetryZoneCaptureMaskBitConstructor"} ); { TelemetryZone activeZone( handle, "Active_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); @@ -387,7 +387,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) EXPECT_TRUE( m_tracyClient.GetZones().empty() ); // Test where componentMaskBit is NOT in the Active list - overwrite the Active list with only "general" - CcpSetActiveCaptureMask( {"general"} ); + CcpSetCaptureMask( {"general"} ); EXPECT_EQ( std::vector{"general"}, CcpGetActiveCaptureMask() ); { TelemetryZone inactiveZone( handle, "Inactive_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); @@ -403,7 +403,7 @@ TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneIsAlwaysCaptureMaskCpp ) { - CcpSetActiveCaptureMask( {"cpp"} ); + CcpSetCaptureMask( {"cpp"} ); EXPECT_EQ( std::vector{"cpp"}, CcpGetActiveCaptureMask() ); static int inactiveKey = 8002; CcpTelemetryEnterZone( &inactiveKey, "Active_CcpTelemetryEnterZoneCaptureMaskBit", __FILE__, __LINE__ ); @@ -661,7 +661,7 @@ class CcpTelemetryCaptureMaskTest : public ::testing::Test void TearDown() { // Clear any active capture mask to let subsequent tests just work - CcpSetActiveCaptureMask({}); + CcpSetCaptureMask({}); ::testing::Test::TearDown(); } }; @@ -685,9 +685,9 @@ TEST_F( CcpTelemetryTest, CaptureMaskRegisterRejectEmpty ) TEST_F( CcpTelemetryCaptureMaskTest, SetEmptyCaptureMasksClearsMask ) { - EXPECT_TRUE( CcpSetActiveCaptureMask( {"cpp"} ) ); + EXPECT_TRUE( CcpSetCaptureMask( {"cpp"} ) ); EXPECT_NE( std::vector{}, CcpGetActiveCaptureMask() ); - EXPECT_TRUE( CcpSetActiveCaptureMask( {} ) ); + EXPECT_TRUE( CcpSetCaptureMask( {} ) ); EXPECT_EQ( std::vector{}, CcpGetActiveCaptureMask() ); } @@ -705,7 +705,7 @@ TEST_F( CcpTelemetryCaptureMaskTest, SetCaptureMaskRejectsTooManyMasks ) "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99" }; - EXPECT_FALSE( CcpSetActiveCaptureMask( numbers ) ) << "Should have failed because more than the allowed number of masks was requested"; + EXPECT_FALSE( CcpSetCaptureMask( numbers ) ) << "Should have failed because more than the allowed number of masks was requested"; } TEST_F( CcpTelemetryCaptureMaskTest, CaptureMaskDefaultsAreRegistered ) @@ -726,7 +726,7 @@ TEST_F( CcpTelemetryCaptureMaskTest, SetActiveCaptureMaskByNames ) // Only register one name const CcpCaptureMaskHandle handle = CcpRegisterCaptureMask( registeredName ); - CcpSetActiveCaptureMask( activeMaskList ); + CcpSetCaptureMask( activeMaskList ); EXPECT_EQ( std::vector{registeredName}, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should only contain the registered mask, not the pending one"; // Register the new name From 48031231ee8cebdf6912334869679639d576014f Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Fri, 24 Jul 2026 15:58:45 +0000 Subject: [PATCH 33/40] Fix crash related to erasing fiber names There were two issues at play here: 1. The same iterator into the `FiberNameStore` may have been queued for erasure multiple times 2. Inserting a name into the `FiberNameStore` that has a pending erasure would Surprisingly, those issues primarily surfaced on macOS, and only rarely on Windows. The solution to the problem is to maintain a map instead of a queue. This way, inserting into `FiberNameStore` can cancel any pending erasure. Additionally, the map ensures that there is only ever one entry for `FiberNameStore` iterator that should be deleted. --- CcpTelemetry.cpp | 24 +++++++++++++----------- tests/CcpTelemetry.cpp | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 4ace92b..e0ebd51 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -59,7 +59,7 @@ thread_local TaskletZoneStore::iterator t_activeTaskletZoneStore{ t_taskletZoneS 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}; -std::queue> s_fiberEraseMap; // Map of fibers scheduled for erasure +std::map s_fiberEraseMap; // Map of fibers scheduled for erasure typedef TrackableStdMap> MutexNameMap_t; typedef TrackableStdMap ThreadNameMap_t; @@ -360,23 +360,24 @@ void CcpTelemetryTick() FrameMark; ++s_telemetryTick; - // Give the profiler a few seconds to receive information from the fiber name store before deallocating - // the underlying string - if ( !s_fiberEraseMap.empty() ) + auto now = std::chrono::steady_clock::now(); + + // Check if there are any pending fiber name erases + for (auto it = s_fiberEraseMap.begin(); it != s_fiberEraseMap.end(); ) { - auto now = std::chrono::steady_clock::now(); - auto elem = s_fiberEraseMap.front(); - while ( !s_fiberEraseMap.empty() && elem.second >= now ) + if ( now >= it->second ) + { + s_fiberNameStore.erase( it->first ); + it = s_fiberEraseMap.erase( it ); + } else { - s_fiberNameStore.erase( elem.first ); - s_fiberEraseMap.pop(); - elem = s_fiberEraseMap.front(); + ++it; } } 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; + auto timeSinceStart = now - s_profilerStartTime; if( timeSinceStart >= s_config.captureDuration ) { CCP_LOG_CH( s_ch, "Finalizing timed profiler run" ); @@ -494,6 +495,7 @@ void CcpTelemetrySetActiveFiber( FiberNameStore::const_iterator elem ) void CcpTelemetrySetActiveFiber( const std::string& name ) { auto elem = s_fiberNameStore.insert( name ); + s_fiberEraseMap.erase( elem.first ); // cancel any pending deletion of this name // if ( elem.second ) // { // CCP_LOG_CH( s_ch, "Registered new [Fiber %p]", elem.first->c_str() ); diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 57c2630..3924a45 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -213,6 +213,24 @@ TEST_F( CcpTelemetryTest, RemovingActiveFiberClearsIt ) EXPECT_EQ( CcpTelemetryGetActiveFiber(), expectedNoFiber ); } +TEST_F( CcpTelemetryTest, RemovingElementsFromFiberNameStoreDoesNotCrash ) +{ + // There was a crash when the same fiber was queued multiple times for + // deletion. Additionally, some silent data corruption could occur when + // inserting a fiber name again that was about to be deleted. + // This isn't the best test to properly validate both scenarios, but it + // is at least good enough to reproduce the observed crash. + CcpTelemetrySetActiveFiber( expectedFiberName1 ); + CcpTelemetryRemoveFiber( expectedFiberName1 ); + CcpTelemetrySetActiveFiber( expectedFiberName1 ); + CcpTelemetryRemoveFiber( expectedFiberName1 ); + TickTelemetry( nullptr ); + CcpTelemetrySetActiveFiber( expectedFiberName1 ); + CcpTelemetryRemoveFiber( expectedFiberName1 ); + CcpTelemetrySetActiveFiber( expectedFiberName1 ); + TickTelemetry( nullptr ); +} + TEST_F( CcpTelemetryTest, RemainingCaptureDuration ) { // The test fixture starts telemetry without a specific capture duration. From 1bb58df828a82b4b027e209c613766511f93583b Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:20:23 +0000 Subject: [PATCH 34/40] Remove obsolete tests Those two tests covered functionality that has been removed / collapsed into the same `TelemetryZone` constructor since then. --- tests/CcpTelemetry.cpp | 50 ------------------------------------------ 1 file changed, 50 deletions(-) diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 3924a45..66dd80b 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -384,56 +384,6 @@ TEST_F( CcpTelemetryTest, TelemetryZoneConstructor ) EXPECT_TRUE( m_tracyClient.GetZones().empty() ); } -TEST_F( CcpTelemetryTest, TelemetryZoneCaptureMaskBitConstructor ) -{ - // Register a new component CaptureMask - const CcpCaptureMaskHandle handle = CcpRegisterCaptureMask( "TelemetryZoneCaptureMaskBitConstructor", CcpColor::Orange ); - - // Test where componentMaskBit is in the Active list - CcpSetCaptureMask( {"TelemetryZoneCaptureMaskBitConstructor"} ); - { - TelemetryZone activeZone( handle, "Active_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); - TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); - EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ); - const auto zones = m_tracyClient.GetZones(); - ASSERT_EQ( 1, zones.size() ); - EXPECT_EQ( "Active_TelemetryZoneCaptureMaskBitConstructor", zones.front().function ); - EXPECT_EQ( static_cast( CcpColor::Orange ), zones.front().color ) << "Color should come from the registered CaptureMask entry"; - } - TickTelemetry( [this] { return m_tracyClient.GetZoneEndCount() == 1; } ); - EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); - EXPECT_TRUE( m_tracyClient.GetZones().empty() ); - - // Test where componentMaskBit is NOT in the Active list - overwrite the Active list with only "general" - CcpSetCaptureMask( {"general"} ); - EXPECT_EQ( std::vector{"general"}, CcpGetActiveCaptureMask() ); - { - TelemetryZone inactiveZone( handle, "Inactive_TelemetryZoneCaptureMaskBitConstructor", __FILE__, __LINE__ ); - TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); - EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Because TMCM_GENERAL is now no longer in the ACTIVE CaptureMask list, we should not have emitted an event for it"; - EXPECT_EQ( 0, m_tracyClient.GetZones().size() ) << "Zone list should therefore be empty"; - } - TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); - EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Inactive CaptureMaskBit zone must not emit ZoneBegin"; - EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "Inactive CaptureMaskBit zone must not emit ZoneEnd"; - EXPECT_TRUE( m_tracyClient.GetZones().empty() ); -} - -TEST_F( CcpTelemetryTest, CcpTelemetryEnterZoneIsAlwaysCaptureMaskCpp ) -{ - CcpSetCaptureMask( {"cpp"} ); - EXPECT_EQ( std::vector{"cpp"}, CcpGetActiveCaptureMask() ); - static int inactiveKey = 8002; - CcpTelemetryEnterZone( &inactiveKey, "Active_CcpTelemetryEnterZoneCaptureMaskBit", __FILE__, __LINE__ ); - TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); - EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Inactive CaptureMaskBit enter-zone must not emit ZoneBegin"; - EXPECT_EQ( 0, m_tracyClient.GetZoneEndCount() ); - EXPECT_FALSE( m_tracyClient.GetZones().empty() ); - CcpTelemetryLeaveZone( &inactiveKey ); - TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); - EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ) << "Inactive CaptureMaskBit enter-zone must not emit ZoneEnd"; -} - // --------------------------------------------------------------------------- // CcpMutex / CcpAutoMutex From 6eb92bfa1aa3e2f84d7cc8a6f2b9b0bac26ee906 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:21:48 +0000 Subject: [PATCH 35/40] Reword the "capture mask" concept as "profiler zone" As per code-review feedback, the "mask" implementation details does no longer hold up across the API surface. --- CcpTelemetry.cpp | 123 ++++++++++++++++++++--------------------- include/CcpTelemetry.h | 36 ++++++------ tests/CcpTelemetry.cpp | 98 ++++++++++++++++---------------- 3 files changed, 128 insertions(+), 129 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index e0ebd51..de319fb 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -90,87 +90,86 @@ namespace } // ------------------------------- - // CaptureMask specifics: + // ProfilerZone specifics: // ------------------------------- - constexpr size_t CAPTURE_MASKS_MAX{64}; + constexpr size_t PROFILER_ZONES_MAX{64}; - CcpMutex s_captureMaskMutex( "CcpTelemetry", "CaptureMaskMutex" ); + CcpMutex s_captureMaskMutex( "CcpTelemetry", "ProfilerZoneMutex" ); - // Fixed size array of registered CaptureMasks. - std::array s_registeredCaptureMasks{ - CcpCaptureMaskInfo{ "core", CcpColor::LightGreen }, // Pre-allocated capture mask for core, should core ever need it. This also solves the problem that legacy `TMCM_GENERAL` and `TMCM_CPP` otherwise cause an off-by-one error. - CcpCaptureMaskInfo{ "general", CcpColor::SteelBlue }, // legacy definition from TMCM_GENERAL, used to be a bitmask, but can now be treated as index into this array - CcpCaptureMaskInfo{ "cpp", CcpColor::Yellow }, // legacy value from TMCM_CPP, used to be a bitmask, but can now be treated as index into this array + // Fixed size array of registered ProfilerZones. + std::array s_registeredProfilerZones{ + CcpProfilerZoneInfo{ "core", CcpColor::LightGreen }, // Pre-allocated profiler zone for core, should core ever need it. This also solves the problem that legacy `TMCM_GENERAL` and `TMCM_CPP` otherwise cause an off-by-one error. + CcpProfilerZoneInfo{ "general", CcpColor::SteelBlue }, // legacy definition from TMCM_GENERAL, used to be a bitmask, but can now be treated as index into this array + CcpProfilerZoneInfo{ "cpp", CcpColor::Yellow }, // legacy value from TMCM_CPP, used to be a bitmask, but can now be treated as index into this array }; - uint64_t s_activeCaptureMaskBits{0}; + uint64_t s_activeProfilerZoneBits{0}; - // List of CaptureMask names that have yet to be registered. - // From a CcpSetActiveCaptureMask( vector ) call. - std::vector s_pendingActiveCaptureMaskNames; + // List of ProfilerZone names that have yet to be registered. + // From a CcpSetActiveProfilerZones( vector ) call. + std::vector s_pendingProfilerZoneNames; - // Get the registered CaptureMask color for a given CaptureMask - CcpColor GetCaptureMaskColor( CcpCaptureMaskHandle handle ) + // Get the registered ProfilerZone color for a given ProfilerZone + CcpColor GetProfilerZoneColor( CcpProfilerZoneHandle handle ) { - return s_registeredCaptureMasks[handle].color; + return s_registeredProfilerZones[handle].color; } - bool IsCaptureMaskActive( CcpCaptureMaskHandle handle ) + bool IsProfilerZoneActive( CcpProfilerZoneHandle handle ) { - return ( s_activeCaptureMaskBits & ( 1ULL< %u with color %s", name.c_str(), handle, CcpColorToString( color ).data() ); + entry = zone; + CCP_LOG_CH( s_ch, "Registered a new profiler zone for '%s' -> %u with color %s", entry.name.c_str(), handle, CcpColorToString( entry.color ).data() ); break; } ++handle; } - if ( handle > CAPTURE_MASKS_MAX ) + if ( handle > PROFILER_ZONES_MAX ) { - return CCP_CAPTURE_MASK_INVALID_HANDLE; + return CCP_PROFILER_ZONE_HANDLE_INVALID; } - // Make sure previously "pending active" CaptureMask is included - auto pendingIt = std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), name ); - if( pendingIt != s_pendingActiveCaptureMaskNames.end() ) + // Make sure previously "pending active" ProfilerZone is included + auto pendingIt = std::find( s_pendingProfilerZoneNames.begin(), s_pendingProfilerZoneNames.end(), zone.name ); + if( pendingIt != s_pendingProfilerZoneNames.end() ) { - s_pendingActiveCaptureMaskNames.erase( pendingIt ); - s_activeCaptureMaskBits |= ( 1ULL << handle ); - CCP_LOG_CH( s_ch, "Previously pending CaptureMask '%s' added to activeCaptureMask -> %u", name.c_str(), handle ); + s_pendingProfilerZoneNames.erase( pendingIt ); + s_activeProfilerZoneBits |= ( 1ULL << handle ); + CCP_LOG_CH( s_ch, "Previously pending ProfilerZone '%s' added to activeProfilerZone -> %u", zone.name.c_str(), handle ); } return handle; } -std::vector CcpGetRegisteredCaptureMasks() +std::vector CcpGetRegisteredProfilerZones() { - // Guard access to registered CaptureMasks while return list is populated. + // Guard access to registered ProfilerZones while return list is populated. CcpAutoMutex lock( s_captureMaskMutex ); - std::vector result; - result.reserve( CAPTURE_MASKS_MAX ); - for( const auto& registeredEntry : s_registeredCaptureMasks ) + std::vector result; + result.reserve( PROFILER_ZONES_MAX ); + for( const auto& registeredEntry : s_registeredProfilerZones ) { if( ! registeredEntry.name.empty() ) { @@ -180,19 +179,19 @@ std::vector CcpGetRegisteredCaptureMasks() return result; } -bool CcpSetCaptureMask( const std::vector& maskNames ) +bool CcpSetActiveProfilerZones( const std::vector& maskNames ) { - // Guard access to all CaptureMasks members + // Guard access to all ProfilerZones members CcpAutoMutex lock( s_captureMaskMutex ); - if ( maskNames.size() > CAPTURE_MASKS_MAX ) + if ( maskNames.size() > PROFILER_ZONES_MAX ) { - CCP_LOGERR_CH( s_ch, "Failed setting active capture mask because more %lu maskNames were passed in, but only %lu are allowed", maskNames.size(), CAPTURE_MASKS_MAX ); + CCP_LOGERR_CH( s_ch, "Failed setting active profiler zone because more %lu maskNames were passed in, but only %lu are allowed", maskNames.size(), PROFILER_ZONES_MAX ); return false; } - s_pendingActiveCaptureMaskNames.clear(); - uint64_t newActiveCaptureMask = 0; + s_pendingProfilerZoneNames.clear(); + uint64_t newActiveProfilerZone = 0; for( const auto& rawName : maskNames ) { if( rawName.empty() ) @@ -202,42 +201,42 @@ bool CcpSetCaptureMask( const std::vector& maskNames ) bool alreadyRegistered = false; size_t index{0}; - for( const auto& registeredEntry : s_registeredCaptureMasks ) + for( const auto& registeredEntry : s_registeredProfilerZones ) { if( registeredEntry.name == rawName ) { - newActiveCaptureMask |= (1ULL << index); + newActiveProfilerZone |= (1ULL << index); alreadyRegistered = true; break; } ++index; } - // This CaptureMask hasn't been registered (yet) so add it to the pending list + // This ProfilerZone hasn't been registered (yet) so add it to the pending list if( !alreadyRegistered ) { - if( std::find( s_pendingActiveCaptureMaskNames.begin(), s_pendingActiveCaptureMaskNames.end(), rawName ) == s_pendingActiveCaptureMaskNames.end() ) + if( std::find( s_pendingProfilerZoneNames.begin(), s_pendingProfilerZoneNames.end(), rawName ) == s_pendingProfilerZoneNames.end() ) { - s_pendingActiveCaptureMaskNames.emplace_back( rawName ); + s_pendingProfilerZoneNames.emplace_back( rawName ); } } } - s_activeCaptureMaskBits = newActiveCaptureMask; - CCP_LOG_CH( s_ch, "Active CaptureMask set to 0x%llx (%zu pending unresolved name(s))", static_cast( newActiveCaptureMask ), s_pendingActiveCaptureMaskNames.size() ); + s_activeProfilerZoneBits = newActiveProfilerZone; + CCP_LOG_CH( s_ch, "Active ProfilerZone set to 0x%llx (%zu pending unresolved name(s))", static_cast( newActiveProfilerZone ), s_pendingProfilerZoneNames.size() ); return true; } -std::vector CcpGetActiveCaptureMask() +std::vector CcpGetActiveProfilerZone() { std::vector result; size_t index{0}; - for ( const auto& registeredEntry : s_registeredCaptureMasks ) + for ( const auto& registeredEntry : s_registeredProfilerZones ) { uint64_t currentMaskBit = 1ULL << index; - if ( ( s_activeCaptureMaskBits & currentMaskBit ) != 0 ) + if ( ( s_activeProfilerZoneBits & currentMaskBit ) != 0 ) { result.push_back( registeredEntry.name ); } @@ -529,7 +528,7 @@ const std::string& CcpTelemetryGetActiveFiber() return *t_activeFiber; } -TelemetryZone::TelemetryZone( CcpCaptureMaskHandle handle, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete ) : m_impl(std::make_unique()) +TelemetryZone::TelemetryZone( CcpProfilerZoneHandle handle, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete ) : m_impl(std::make_unique()) { if( s_profilerState.load( std::memory_order_acquire ) != ProfilerState::Started ) { @@ -539,8 +538,8 @@ TelemetryZone::TelemetryZone( CcpCaptureMaskHandle handle, const char* name, con CCP_ASSERT( filename != nullptr ); CCP_ASSERT( name != nullptr ); - auto color = GetCaptureMaskColor( handle ); - const int active = IsCaptureMaskActive( handle ); + auto color = GetProfilerZoneColor( handle ); + const int active = IsProfilerZoneActive( handle ); auto data = ___tracy_alloc_srcloc( lineno, filename, strlen( filename ), name, strlen( name ), static_cast( color ) ); // CCP_LOG_CH( s_ch, "[Fiber %p] Creating zone %s (%p)", t_activeFiber->c_str(), ret.first->c_str(), this ); m_impl->fiber = t_activeFiber; @@ -647,21 +646,21 @@ void CcpRegisterThread( CcpThreadId_t threadId, const char* name ) { } -CcpCaptureMaskHandle CcpRegisterCaptureMask( const std::string&, CcpColor ) +CcpProfilerZoneHandle CcpRegisterProfilerZone( const std::string&, CcpColor ) { return 0; } -std::vector CcpGetRegisteredCaptureMasks() +std::vector CcpGetRegisteredProfilerZones() { return {}; } -bool CcpSetActiveCaptureMask( const std::vector& ) +bool CcpSetActiveProfilerZone( const std::vector& ) { } -std::vector CcpGetActiveCaptureMask() +std::vector CcpGetActiveProfilerZone() { return {}; } diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index c8f165e..845c52b 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -39,36 +39,36 @@ CARBON_CORE_API void CcpRegisterThread( CcpThreadId_t threadId, const char* name ); // --------------------------------------------------------------------------- -// CaptureMasks: -// - CaptureMask(s) are used to determine if a given Zone should be emitted to +// ProfilerZones: +// - ProfilerZone(s) are used to determine if a given Zone should be emitted to // Telemetry tracking or not based on its origin, i.e. carbon component. // - Each carbon component, core/blue/scheduler/etc..., will register for a -// CaptureMask with their chosen "component display name" and be assigned an -// available CaptureMask bit from a 64bit integer mask. -// - A Telemetry display color is also associated with the chosen CaptureMask bit, +// ProfilerZone with their chosen "component display name" and be assigned an +// available ProfilerZone bit from a 64bit integer mask. +// - A Telemetry display color is also associated with the chosen ProfilerZone bit, // either manually or automatically allocated based on "best available fit". -// - An active CaptureMask defaults to "all" but can be set/narrowed before or +// - An active ProfilerZone defaults to "all" but can be set/narrowed before or // during a Telemetry session is started using either: -// - a numerical bit mask value of the active CaptureMask +// - a numerical bit mask value of the active ProfilerZone // - list of "component display names" ("all" is allowed) -// - Setting active CaptureMask is available for both: +// - Setting active ProfilerZone is available for both: // - already registered components // - "pending" (yet to be registered) components // --------------------------------------------------------------------------- -struct CcpCaptureMaskInfo +struct CcpProfilerZoneInfo { - std::string name; // The lower-case display name of the CaptureMask (carbon-component) - CcpColor color{CcpColor::White}; // The chosen/allocated color for the CaptureMask + std::string name; // The lower-case display name of the ProfilerZone (carbon-component) + CcpColor color{CcpColor::White}; // The chosen/allocated color for the ProfilerZone }; -typedef uint32_t CcpCaptureMaskHandle; -constexpr uint32_t CCP_CAPTURE_MASK_INVALID_HANDLE{ UINT32_MAX }; +typedef uint32_t CcpProfilerZoneHandle; +constexpr uint32_t CCP_PROFILER_ZONE_HANDLE_INVALID{ UINT32_MAX }; -CARBON_CORE_API CcpCaptureMaskHandle CcpRegisterCaptureMask( const std::string& name, CcpColor color = CcpColor::Fuchsia ); -CARBON_CORE_API std::vector CcpGetRegisteredCaptureMasks(); +CARBON_CORE_API CcpProfilerZoneHandle CcpRegisterProfilerZone( const CcpProfilerZoneInfo& zone ); +CARBON_CORE_API std::vector CcpGetRegisteredProfilerZones(); -CARBON_CORE_API bool CcpSetCaptureMask( const std::vector& maskNames ); -CARBON_CORE_API std::vector CcpGetActiveCaptureMask(); +CARBON_CORE_API bool CcpSetActiveProfilerZones( const std::vector& maskNames ); +CARBON_CORE_API std::vector CcpGetActiveProfilerZone(); struct CcpTelemetryConfig @@ -112,7 +112,7 @@ class TelemetryZone { public: TelemetryZone() = delete; - CARBON_CORE_API TelemetryZone( CcpCaptureMaskHandle handle, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete = CcpColor::SteelBlue ); + CARBON_CORE_API TelemetryZone( CcpProfilerZoneHandle handle, const char* name, const char* filename, uint32_t lineno, CcpColor obsolete = CcpColor::SteelBlue ); CARBON_CORE_API ~TelemetryZone(); TelemetryZone( TelemetryZone&& other ) noexcept; diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 66dd80b..6320bb6 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -39,10 +39,10 @@ // includes the AI-written, but human-reviewed test client. #include "TracyTestClient.h" -// Helper for find a CaptureMask by name from the list of already registered CaptureMasks -bool TryGetCaptureMaskNamed( const std::string& name, CcpCaptureMaskInfo& info ) +// Helper for find a ProfilerZone by name from the list of already registered ProfilerZones +bool TryGetProfilerZoneNamed( const std::string& name, CcpProfilerZoneInfo& info ) { - for( const CcpCaptureMaskInfo& mask : CcpGetRegisteredCaptureMasks() ) + for( const CcpProfilerZoneInfo& mask : CcpGetRegisteredProfilerZones() ) { if( mask.name == name ) { @@ -262,7 +262,7 @@ TEST_F( CcpTelemetryTest, SimpleZoneTest ) static int key = 4711; const std::string zoneName{ "TestZone" }; - CcpSetCaptureMask( {"cpp"} ); + CcpSetActiveProfilerZones( {"cpp"} ); CcpTelemetryEnterZone( &key, zoneName.c_str(), __FILE__, __LINE__ ); // Original deprecated version // Tracy's worker sleeps up to 10 ms between queue flushes, so give it // time to process and send the zone event before asserting. @@ -284,7 +284,7 @@ TEST_F( CcpTelemetryTest, StackedZones ) { // A stacked zone is a zone that has the same key as a previously created zone. static int key = 4711; - CcpSetCaptureMask( {"cpp"} ); + CcpSetActiveProfilerZones( {"cpp"} ); CcpTelemetryEnterZone( &key, "TestZone", __FILE__, __LINE__ ); CcpTelemetryEnterZone( &key, "TestZone2", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZones().size() == 2; } ); @@ -301,14 +301,14 @@ TEST_F( CcpTelemetryTest, StackedZones ) CcpTelemetryLeaveZone( &key ); TickTelemetry( [this] { return m_tracyClient.GetZones().empty(); } ); EXPECT_TRUE( m_tracyClient.GetZones().empty() ); - CcpSetCaptureMask( {} ); + CcpSetActiveProfilerZones( {} ); } TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) { static int key1 = 1001; const std::string zoneName1{ "FirstZone" }; - CcpSetCaptureMask( {"cpp"} ); + CcpSetActiveProfilerZones( {"cpp"} ); CcpTelemetryEnterZone( &key1, zoneName1.c_str(), __FILE__, __LINE__ ); TickTelemetry( [this, zoneName1] { return ZoneExists( zoneName1 ); } ); EXPECT_TRUE( ZoneExists( zoneName1 ) ); @@ -340,7 +340,7 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) const auto zones = m_tracyClient.GetZones(); ASSERT_EQ( 1, zones.size() ); - EXPECT_EQ( static_cast( CcpColor::Yellow ), zones.front().color ) << "Default color for CaptureMask TMCM_GENERAL should be CcpColor::SteelBlue"; + EXPECT_EQ( static_cast( CcpColor::Yellow ), zones.front().color ) << "Default color for ProfilerZone TMCM_GENERAL should be CcpColor::SteelBlue"; EXPECT_EQ( 2, m_tracyClient.GetZoneBeginCount() ); EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); @@ -348,13 +348,13 @@ TEST_F( CcpTelemetryTest, StartStopStartTelemetryWhileClientIsRunning ) TickTelemetry(); EXPECT_TRUE( m_tracyClient.GetZones().empty() ); EXPECT_EQ( 2, m_tracyClient.GetZoneEndCount() ); - CcpSetCaptureMask( {} ); + CcpSetActiveProfilerZones( {} ); } TEST_F( CcpTelemetryTest, TelemetryZoneConstructor ) { - // Test where captureMask is in the Active list - CcpSetCaptureMask( {"cpp", "general" } ); + // Test where ProfilerZone is in the Active list + CcpSetActiveProfilerZones( {"cpp", "general" } ); { TelemetryZone activeZone( TMCM_CPP, "ZoneIsInActiveList", __FILE__, __LINE__ ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); @@ -370,12 +370,12 @@ TEST_F( CcpTelemetryTest, TelemetryZoneConstructor ) EXPECT_EQ( 1, m_tracyClient.GetZoneEndCount() ); EXPECT_TRUE( m_tracyClient.GetZones().empty() ); - // Test where captureMask is NOT in the Active list - CcpSetCaptureMask( {"NotRegisteredCaptureMask", "ClearingPreviousGeneralAndCpp", "FromTheActiveCaptureMaskList"} ); + // Test where ProfilerZone is NOT in the Active list + CcpSetActiveProfilerZones( {"NotRegisteredProfilerZone", "ClearingPreviousGeneralAndCpp", "FromTheActiveProfilerZoneList"} ); { TelemetryZone inactiveZone( TMCM_CPP, "ZoneIsNotInActiveList", __FILE__, __LINE__, CcpColor::Blue ); TickTelemetry( [this] { return m_tracyClient.GetZoneBeginCount() == 1; } ); - EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Because TMCM_CPP is now no longer in the ACTIVE CaptureMask list, we should not have emitted an event for it"; + EXPECT_EQ( 1, m_tracyClient.GetZoneBeginCount() ) << "Because TMCM_CPP is now no longer in the ACTIVE ProfilerZone list, we should not have emitted an event for it"; EXPECT_EQ( 0, m_tracyClient.GetZones().size() ) << "Zone list should therefore be empty"; } TickTelemetry( nullptr, std::chrono::milliseconds( 100 ) ); @@ -621,45 +621,45 @@ TEST_F( CcpTelemetryTest, CcpSemaphoreTimedWaitTimesOut ) } // --------------------------------------------------------------------------- -// CaptureMask tests: +// ProfilerZone tests: // --------------------------------------------------------------------------- -class CcpTelemetryCaptureMaskTest : public ::testing::Test +class CcpTelemetryProfilerZoneTest : public ::testing::Test { public: void TearDown() { - // Clear any active capture mask to let subsequent tests just work - CcpSetCaptureMask({}); + // Clear any active profiler zone to let subsequent tests just work + CcpSetActiveProfilerZones({}); ::testing::Test::TearDown(); } }; -TEST_F( CcpTelemetryCaptureMaskTest, CaptureMaskRegisterDistinctBitPerName ) +TEST_F( CcpTelemetryProfilerZoneTest, ProfilerZoneRegisterDistinctBitPerName ) { - const CcpCaptureMaskHandle maskBitA = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_A" ); - const CcpCaptureMaskHandle maskBitB = CcpRegisterCaptureMask( "CaptureMaskRegisterDistinctBitPerName_B", CcpColor::Tomato ); + const CcpProfilerZoneHandle maskBitA = CcpRegisterProfilerZone( {"ProfilerZoneRegisterDistinctBitPerName_A"} ); + const CcpProfilerZoneHandle maskBitB = CcpRegisterProfilerZone( {"ProfilerZoneRegisterDistinctBitPerName_B", CcpColor::Tomato} ); EXPECT_NE( maskBitA, maskBitB ); } -TEST_F( CcpTelemetryCaptureMaskTest, EmptyActiveCaptureMask ) +TEST_F( CcpTelemetryProfilerZoneTest, EmptyActiveProfilerZone ) { - EXPECT_EQ( std::vector{}, CcpGetActiveCaptureMask() ); + EXPECT_EQ( std::vector{}, CcpGetActiveProfilerZone() ); } -TEST_F( CcpTelemetryTest, CaptureMaskRegisterRejectEmpty ) +TEST_F( CcpTelemetryTest, ProfilerZoneRegisterRejectEmpty ) { - EXPECT_EQ( 0, CcpRegisterCaptureMask( "" ) ); + EXPECT_EQ( 0, CcpRegisterProfilerZone( {""} ) ); } -TEST_F( CcpTelemetryCaptureMaskTest, SetEmptyCaptureMasksClearsMask ) +TEST_F( CcpTelemetryProfilerZoneTest, SetEmptyProfilerZonesClearsMask ) { - EXPECT_TRUE( CcpSetCaptureMask( {"cpp"} ) ); - EXPECT_NE( std::vector{}, CcpGetActiveCaptureMask() ); - EXPECT_TRUE( CcpSetCaptureMask( {} ) ); - EXPECT_EQ( std::vector{}, CcpGetActiveCaptureMask() ); + EXPECT_TRUE( CcpSetActiveProfilerZones( {"cpp"} ) ); + EXPECT_NE( std::vector{}, CcpGetActiveProfilerZone() ); + EXPECT_TRUE( CcpSetActiveProfilerZones( {} ) ); + EXPECT_EQ( std::vector{}, CcpGetActiveProfilerZone() ); } -TEST_F( CcpTelemetryCaptureMaskTest, SetCaptureMaskRejectsTooManyMasks ) +TEST_F( CcpTelemetryProfilerZoneTest, SetProfilerZoneRejectsTooManyMasks ) { const std::vector numbers{ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", @@ -673,36 +673,36 @@ TEST_F( CcpTelemetryCaptureMaskTest, SetCaptureMaskRejectsTooManyMasks ) "81", "82", "83", "84", "85", "86", "87", "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99" }; - EXPECT_FALSE( CcpSetCaptureMask( numbers ) ) << "Should have failed because more than the allowed number of masks was requested"; + EXPECT_FALSE( CcpSetActiveProfilerZones( numbers ) ) << "Should have failed because more than the allowed number of masks was requested"; } -TEST_F( CcpTelemetryCaptureMaskTest, CaptureMaskDefaultsAreRegistered ) +TEST_F( CcpTelemetryProfilerZoneTest, ProfilerZoneDefaultsAreRegistered ) { - // The default CaptureMasks must be available from the start. - CcpCaptureMaskInfo info; - EXPECT_TRUE( TryGetCaptureMaskNamed( "core", info ) ); - EXPECT_TRUE( TryGetCaptureMaskNamed( "general", info ) ); - EXPECT_TRUE( TryGetCaptureMaskNamed( "cpp", info ) ); + // The default ProfilerZones must be available from the start. + CcpProfilerZoneInfo info; + EXPECT_TRUE( TryGetProfilerZoneNamed( "core", info ) ); + EXPECT_TRUE( TryGetProfilerZoneNamed( "general", info ) ); + EXPECT_TRUE( TryGetProfilerZoneNamed( "cpp", info ) ); } -TEST_F( CcpTelemetryCaptureMaskTest, SetActiveCaptureMaskByNames ) +TEST_F( CcpTelemetryProfilerZoneTest, SetActiveProfilerZoneByNames ) { - const std::string registeredName = "SetActiveCaptureMaskByNames_RegisteredName"; - const std::string newName = "SetActiveCaptureMaskByNames_NewName"; - const std::string pendingName = "SetActiveCaptureMaskByNames_PendingName"; + const std::string registeredName = "SetActiveProfilerZoneByNames_RegisteredName"; + const std::string newName = "SetActiveProfilerZoneByNames_NewName"; + const std::string pendingName = "SetActiveProfilerZoneByNames_PendingName"; const std::vector activeMaskList = { registeredName, pendingName }; // Only register one name - const CcpCaptureMaskHandle handle = CcpRegisterCaptureMask( registeredName ); - CcpSetCaptureMask( activeMaskList ); - EXPECT_EQ( std::vector{registeredName}, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should only contain the registered mask, not the pending one"; + const CcpProfilerZoneHandle handle = CcpRegisterProfilerZone( {registeredName} ); + CcpSetActiveProfilerZones( activeMaskList ); + EXPECT_EQ( std::vector{registeredName}, CcpGetActiveProfilerZone() ) << "ActiveProfilerZone should only contain the registered mask, not the pending one"; // Register the new name - const CcpCaptureMaskHandle newHandle = CcpRegisterCaptureMask( newName ); - EXPECT_EQ( std::vector{registeredName}, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should still only contain the registered mask, not the new or pending one"; + const CcpProfilerZoneHandle newHandle = CcpRegisterProfilerZone( {newName} ); + EXPECT_EQ( std::vector{registeredName}, CcpGetActiveProfilerZone() ) << "ActiveProfilerZone should still only contain the registered mask, not the new or pending one"; // Now add the pending one - const CcpCaptureMaskHandle pendingHandle = CcpRegisterCaptureMask( pendingName ); - EXPECT_EQ( activeMaskList, CcpGetActiveCaptureMask() ) << "ActiveCaptureMask should now contain both the registered and pending masks"; + const CcpProfilerZoneHandle pendingHandle = CcpRegisterProfilerZone( {pendingName} ); + EXPECT_EQ( activeMaskList, CcpGetActiveProfilerZone() ) << "ActiveProfilerZone should now contain both the registered and pending masks"; } #endif From 5632876d363f88fe53e09ecbad41dfb6c5a44ff0 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:20:02 +0000 Subject: [PATCH 36/40] Skip telemetry tests if `WITH_TELEMETRY=OFF` Reduces compile / link time when building without telemetry, and removes the need for the `#ifdef` in the test code. Take note that currently building `WITH_TELEMETRY=OFF` does not work in general for core, unrelated to this work. --- tests/CMakeLists.txt | 14 ++++++++++---- tests/CcpTelemetry.cpp | 2 -- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0c8077f..bc506aa 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,6 @@ enable_testing() find_package(GTest CONFIG REQUIRED) -find_package(lz4 CONFIG REQUIRED) add_executable(CcpCoreTest CCPCallstack.cpp CcpCoreTest.cpp @@ -14,15 +13,22 @@ add_executable(CcpCoreTest CCPMemoryTracker.cpp CcpSecureCrt.cpp CcpStatistics.cpp - CcpTelemetry.cpp CcpThread.cpp CcpTime.cpp StringConversions.cpp TempFile.cpp CCPLog.cpp - TracyTestClient.cpp ) -target_link_libraries(CcpCoreTest PRIVATE CcpCore GTest::gtest GTest::gtest_main lz4::lz4 Tracy::TracyClient) +target_link_libraries(CcpCoreTest PRIVATE CcpCore GTest::gtest GTest::gtest_main) + +if(WITH_TELEMETRY) + find_package(lz4 CONFIG REQUIRED) + target_sources(CcpCoreTest PRIVATE + CcpTelemetry.cpp + TracyTestClient.cpp + ) + target_link_libraries(CcpCoreTest PRIVATE lz4::lz4 Tracy::TracyClient) +endif() if(WIN32) target_link_libraries(CcpCoreTest PRIVATE ws2_32) endif() diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index 6320bb6..a8a41d7 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -1,6 +1,5 @@ // Copyright © 2025 CCP ehf. -#if CCP_TELEMETRY_ENABLED #include #include @@ -705,4 +704,3 @@ TEST_F( CcpTelemetryProfilerZoneTest, SetActiveProfilerZoneByNames ) const CcpProfilerZoneHandle pendingHandle = CcpRegisterProfilerZone( {pendingName} ); EXPECT_EQ( activeMaskList, CcpGetActiveProfilerZone() ) << "ActiveProfilerZone should now contain both the registered and pending masks"; } -#endif From e9819c3800a18a8ff043bbb860af571c60c56f29 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:14:09 +0000 Subject: [PATCH 37/40] Rename `CcpGetActiveProfilerZone` to `CcpGetActiveProfilerZones` --- CcpTelemetry.cpp | 2 +- include/CcpTelemetry.h | 2 +- tests/CcpTelemetry.cpp | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index de319fb..4592572 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -228,7 +228,7 @@ bool CcpSetActiveProfilerZones( const std::vector& maskNames ) return true; } -std::vector CcpGetActiveProfilerZone() +std::vector CcpGetActiveProfilerZones() { std::vector result; size_t index{0}; diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 845c52b..05fdce9 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -68,7 +68,7 @@ CARBON_CORE_API CcpProfilerZoneHandle CcpRegisterProfilerZone( const CcpProfiler CARBON_CORE_API std::vector CcpGetRegisteredProfilerZones(); CARBON_CORE_API bool CcpSetActiveProfilerZones( const std::vector& maskNames ); -CARBON_CORE_API std::vector CcpGetActiveProfilerZone(); +CARBON_CORE_API std::vector CcpGetActiveProfilerZones(); struct CcpTelemetryConfig diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index a8a41d7..da25987 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -642,7 +642,7 @@ TEST_F( CcpTelemetryProfilerZoneTest, ProfilerZoneRegisterDistinctBitPerName ) TEST_F( CcpTelemetryProfilerZoneTest, EmptyActiveProfilerZone ) { - EXPECT_EQ( std::vector{}, CcpGetActiveProfilerZone() ); + EXPECT_EQ( std::vector{}, CcpGetActiveProfilerZones() ); } TEST_F( CcpTelemetryTest, ProfilerZoneRegisterRejectEmpty ) @@ -653,9 +653,9 @@ TEST_F( CcpTelemetryTest, ProfilerZoneRegisterRejectEmpty ) TEST_F( CcpTelemetryProfilerZoneTest, SetEmptyProfilerZonesClearsMask ) { EXPECT_TRUE( CcpSetActiveProfilerZones( {"cpp"} ) ); - EXPECT_NE( std::vector{}, CcpGetActiveProfilerZone() ); + EXPECT_NE( std::vector{}, CcpGetActiveProfilerZones() ); EXPECT_TRUE( CcpSetActiveProfilerZones( {} ) ); - EXPECT_EQ( std::vector{}, CcpGetActiveProfilerZone() ); + EXPECT_EQ( std::vector{}, CcpGetActiveProfilerZones() ); } TEST_F( CcpTelemetryProfilerZoneTest, SetProfilerZoneRejectsTooManyMasks ) @@ -694,13 +694,13 @@ TEST_F( CcpTelemetryProfilerZoneTest, SetActiveProfilerZoneByNames ) // Only register one name const CcpProfilerZoneHandle handle = CcpRegisterProfilerZone( {registeredName} ); CcpSetActiveProfilerZones( activeMaskList ); - EXPECT_EQ( std::vector{registeredName}, CcpGetActiveProfilerZone() ) << "ActiveProfilerZone should only contain the registered mask, not the pending one"; + EXPECT_EQ( std::vector{registeredName}, CcpGetActiveProfilerZones() ) << "ActiveProfilerZone should only contain the registered mask, not the pending one"; // Register the new name const CcpProfilerZoneHandle newHandle = CcpRegisterProfilerZone( {newName} ); - EXPECT_EQ( std::vector{registeredName}, CcpGetActiveProfilerZone() ) << "ActiveProfilerZone should still only contain the registered mask, not the new or pending one"; + EXPECT_EQ( std::vector{registeredName}, CcpGetActiveProfilerZones() ) << "ActiveProfilerZone should still only contain the registered mask, not the new or pending one"; // Now add the pending one const CcpProfilerZoneHandle pendingHandle = CcpRegisterProfilerZone( {pendingName} ); - EXPECT_EQ( activeMaskList, CcpGetActiveProfilerZone() ) << "ActiveProfilerZone should now contain both the registered and pending masks"; + EXPECT_EQ( activeMaskList, CcpGetActiveProfilerZones() ) << "ActiveProfilerZone should now contain both the registered and pending masks"; } From 5cd97fbe0955262da98e50cf02e2a0ee5a067dd1 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:15:16 +0000 Subject: [PATCH 38/40] Add missing bounds check Without it, there's a risk of accessing bad memory when determining the color, and odd behaviour when checking the bit mask. --- CcpTelemetry.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 4592572..5a2659e 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -535,6 +535,12 @@ TelemetryZone::TelemetryZone( CcpProfilerZoneHandle handle, const char* name, co return; } + if ( handle >= PROFILER_ZONES_MAX ) + { + CCP_LOGERR_CH( s_ch, "Invalid profiler zone handle %d - skipping creation of zone named %s", handle, name ); + return; + } + CCP_ASSERT( filename != nullptr ); CCP_ASSERT( name != nullptr ); From 2de388f5929397d57decc9c72eb6bda71d961f8c Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:16:32 +0000 Subject: [PATCH 39/40] Use `std::optional` for tracking registered profiler zones As per code review feedback. It's a better way to track existence without relying on a sentinel value that could theoretically be valid. --- CcpTelemetry.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index 5a2659e..dcd9480 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -97,7 +97,7 @@ namespace CcpMutex s_captureMaskMutex( "CcpTelemetry", "ProfilerZoneMutex" ); // Fixed size array of registered ProfilerZones. - std::array s_registeredProfilerZones{ + std::array, PROFILER_ZONES_MAX> s_registeredProfilerZones{ CcpProfilerZoneInfo{ "core", CcpColor::LightGreen }, // Pre-allocated profiler zone for core, should core ever need it. This also solves the problem that legacy `TMCM_GENERAL` and `TMCM_CPP` otherwise cause an off-by-one error. CcpProfilerZoneInfo{ "general", CcpColor::SteelBlue }, // legacy definition from TMCM_GENERAL, used to be a bitmask, but can now be treated as index into this array CcpProfilerZoneInfo{ "cpp", CcpColor::Yellow }, // legacy value from TMCM_CPP, used to be a bitmask, but can now be treated as index into this array @@ -113,7 +113,7 @@ namespace // Get the registered ProfilerZone color for a given ProfilerZone CcpColor GetProfilerZoneColor( CcpProfilerZoneHandle handle ) { - return s_registeredProfilerZones[handle].color; + return s_registeredProfilerZones[handle]->color; } bool IsProfilerZoneActive( CcpProfilerZoneHandle handle ) @@ -136,10 +136,10 @@ CcpProfilerZoneHandle CcpRegisterProfilerZone( const CcpProfilerZoneInfo& zone ) CcpProfilerZoneHandle handle{0}; for( auto& entry : s_registeredProfilerZones ) { - if (entry.name.empty()) + if (!entry) { entry = zone; - CCP_LOG_CH( s_ch, "Registered a new profiler zone for '%s' -> %u with color %s", entry.name.c_str(), handle, CcpColorToString( entry.color ).data() ); + CCP_LOG_CH( s_ch, "Registered a new profiler zone for '%s' -> %u with color %s", entry->name.c_str(), handle, CcpColorToString( entry->color ).data() ); break; } ++handle; @@ -156,7 +156,7 @@ CcpProfilerZoneHandle CcpRegisterProfilerZone( const CcpProfilerZoneInfo& zone ) { s_pendingProfilerZoneNames.erase( pendingIt ); s_activeProfilerZoneBits |= ( 1ULL << handle ); - CCP_LOG_CH( s_ch, "Previously pending ProfilerZone '%s' added to activeProfilerZone -> %u", zone.name.c_str(), handle ); + CCP_LOG_CH( s_ch, "Previously pending ProfilerZone '%s' added to active ProfilerZones -> %u", zone.name.c_str(), handle ); } return handle; @@ -171,9 +171,9 @@ std::vector CcpGetRegisteredProfilerZones() result.reserve( PROFILER_ZONES_MAX ); for( const auto& registeredEntry : s_registeredProfilerZones ) { - if( ! registeredEntry.name.empty() ) + if( ! registeredEntry->name.empty() ) { - result.push_back( registeredEntry ); + result.push_back( *registeredEntry ); } } return result; @@ -203,7 +203,7 @@ bool CcpSetActiveProfilerZones( const std::vector& maskNames ) size_t index{0}; for( const auto& registeredEntry : s_registeredProfilerZones ) { - if( registeredEntry.name == rawName ) + if( registeredEntry->name == rawName ) { newActiveProfilerZone |= (1ULL << index); alreadyRegistered = true; @@ -238,7 +238,7 @@ std::vector CcpGetActiveProfilerZones() if ( ( s_activeProfilerZoneBits & currentMaskBit ) != 0 ) { - result.push_back( registeredEntry.name ); + result.push_back( registeredEntry->name ); } ++index; From 188e049a969899c95c2124973619e9f039dacdd4 Mon Sep 17 00:00:00 2001 From: CCP Aporia <28982391+CCP-Aporia@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:49:43 +0000 Subject: [PATCH 40/40] Return `CcpProfilerZone` where applicable As opposed to the generic `std::string`. --- CcpTelemetry.cpp | 37 +++++++++++++++++++++++++------------ include/CcpTelemetry.h | 13 +++++++------ tests/CcpTelemetry.cpp | 22 +++++++++++----------- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/CcpTelemetry.cpp b/CcpTelemetry.cpp index dcd9480..f423769 100644 --- a/CcpTelemetry.cpp +++ b/CcpTelemetry.cpp @@ -97,10 +97,10 @@ namespace CcpMutex s_captureMaskMutex( "CcpTelemetry", "ProfilerZoneMutex" ); // Fixed size array of registered ProfilerZones. - std::array, PROFILER_ZONES_MAX> s_registeredProfilerZones{ - CcpProfilerZoneInfo{ "core", CcpColor::LightGreen }, // Pre-allocated profiler zone for core, should core ever need it. This also solves the problem that legacy `TMCM_GENERAL` and `TMCM_CPP` otherwise cause an off-by-one error. - CcpProfilerZoneInfo{ "general", CcpColor::SteelBlue }, // legacy definition from TMCM_GENERAL, used to be a bitmask, but can now be treated as index into this array - CcpProfilerZoneInfo{ "cpp", CcpColor::Yellow }, // legacy value from TMCM_CPP, used to be a bitmask, but can now be treated as index into this array + std::array, PROFILER_ZONES_MAX> s_registeredProfilerZones{ + CcpProfilerZone{ "core", CcpColor::LightGreen }, // Pre-allocated profiler zone for core, should core ever need it. This also solves the problem that legacy `TMCM_GENERAL` and `TMCM_CPP` otherwise cause an off-by-one error. + CcpProfilerZone{ "general", CcpColor::SteelBlue }, // legacy definition from TMCM_GENERAL, used to be a bitmask, but can now be treated as index into this array + CcpProfilerZone{ "cpp", CcpColor::Yellow }, // legacy value from TMCM_CPP, used to be a bitmask, but can now be treated as index into this array }; uint64_t s_activeProfilerZoneBits{0}; @@ -122,7 +122,13 @@ namespace } } -CcpProfilerZoneHandle CcpRegisterProfilerZone( const CcpProfilerZoneInfo& zone ) +bool operator==( const CcpProfilerZone& lhs, const CcpProfilerZone& rhs ) +{ + // Profiler Zones need to be unique by name + return lhs.name == rhs.name; +} + +CcpProfilerZoneHandle CcpRegisterProfilerZone( const CcpProfilerZone& zone ) { // Guard access to registered ProfilerZones while we add/update a new entry CcpAutoMutex lock( s_captureMaskMutex ); @@ -142,6 +148,13 @@ CcpProfilerZoneHandle CcpRegisterProfilerZone( const CcpProfilerZoneInfo& zone ) CCP_LOG_CH( s_ch, "Registered a new profiler zone for '%s' -> %u with color %s", entry->name.c_str(), handle, CcpColorToString( entry->color ).data() ); break; } + + if ( entry->name == zone.name ) + { + CCP_LOGERR_CH( s_ch, "A profiler zone with the name %s already exists.", entry->name.c_str() ); + return CCP_PROFILER_ZONE_HANDLE_INVALID; + } + ++handle; } @@ -162,12 +175,12 @@ CcpProfilerZoneHandle CcpRegisterProfilerZone( const CcpProfilerZoneInfo& zone ) return handle; } -std::vector CcpGetRegisteredProfilerZones() +std::vector CcpGetRegisteredProfilerZones() { // Guard access to registered ProfilerZones while return list is populated. CcpAutoMutex lock( s_captureMaskMutex ); - std::vector result; + std::vector result; result.reserve( PROFILER_ZONES_MAX ); for( const auto& registeredEntry : s_registeredProfilerZones ) { @@ -228,17 +241,17 @@ bool CcpSetActiveProfilerZones( const std::vector& maskNames ) return true; } -std::vector CcpGetActiveProfilerZones() +std::vector CcpGetActiveProfilerZones() { - std::vector result; + std::vector result; size_t index{0}; for ( const auto& registeredEntry : s_registeredProfilerZones ) { uint64_t currentMaskBit = 1ULL << index; - if ( ( s_activeProfilerZoneBits & currentMaskBit ) != 0 ) + if ( registeredEntry && ( s_activeProfilerZoneBits & currentMaskBit ) != 0 ) { - result.push_back( registeredEntry->name ); + result.push_back( *registeredEntry ); } ++index; @@ -657,7 +670,7 @@ CcpProfilerZoneHandle CcpRegisterProfilerZone( const std::string&, CcpColor ) return 0; } -std::vector CcpGetRegisteredProfilerZones() +std::vector CcpGetRegisteredProfilerZones() { return {}; } diff --git a/include/CcpTelemetry.h b/include/CcpTelemetry.h index 05fdce9..44e23db 100644 --- a/include/CcpTelemetry.h +++ b/include/CcpTelemetry.h @@ -55,20 +55,21 @@ CARBON_CORE_API void CcpRegisterThread( CcpThreadId_t threadId, const char* name // - already registered components // - "pending" (yet to be registered) components // --------------------------------------------------------------------------- -struct CcpProfilerZoneInfo +struct CcpProfilerZone { - std::string name; // The lower-case display name of the ProfilerZone (carbon-component) - CcpColor color{CcpColor::White}; // The chosen/allocated color for the ProfilerZone + std::string name; + CcpColor color{CcpColor::White}; }; +CARBON_CORE_API bool operator==( const CcpProfilerZone& lhs, const CcpProfilerZone& rhs ); typedef uint32_t CcpProfilerZoneHandle; constexpr uint32_t CCP_PROFILER_ZONE_HANDLE_INVALID{ UINT32_MAX }; -CARBON_CORE_API CcpProfilerZoneHandle CcpRegisterProfilerZone( const CcpProfilerZoneInfo& zone ); -CARBON_CORE_API std::vector CcpGetRegisteredProfilerZones(); +CARBON_CORE_API CcpProfilerZoneHandle CcpRegisterProfilerZone( const CcpProfilerZone& zone ); +CARBON_CORE_API std::vector CcpGetRegisteredProfilerZones(); CARBON_CORE_API bool CcpSetActiveProfilerZones( const std::vector& maskNames ); -CARBON_CORE_API std::vector CcpGetActiveProfilerZones(); +CARBON_CORE_API std::vector CcpGetActiveProfilerZones(); struct CcpTelemetryConfig diff --git a/tests/CcpTelemetry.cpp b/tests/CcpTelemetry.cpp index da25987..0509106 100644 --- a/tests/CcpTelemetry.cpp +++ b/tests/CcpTelemetry.cpp @@ -39,9 +39,9 @@ #include "TracyTestClient.h" // Helper for find a ProfilerZone by name from the list of already registered ProfilerZones -bool TryGetProfilerZoneNamed( const std::string& name, CcpProfilerZoneInfo& info ) +bool TryGetProfilerZoneNamed( const std::string& name, CcpProfilerZone& info ) { - for( const CcpProfilerZoneInfo& mask : CcpGetRegisteredProfilerZones() ) + for( const CcpProfilerZone& mask : CcpGetRegisteredProfilerZones() ) { if( mask.name == name ) { @@ -642,7 +642,7 @@ TEST_F( CcpTelemetryProfilerZoneTest, ProfilerZoneRegisterDistinctBitPerName ) TEST_F( CcpTelemetryProfilerZoneTest, EmptyActiveProfilerZone ) { - EXPECT_EQ( std::vector{}, CcpGetActiveProfilerZones() ); + EXPECT_EQ( std::vector{}, CcpGetActiveProfilerZones() ); } TEST_F( CcpTelemetryTest, ProfilerZoneRegisterRejectEmpty ) @@ -653,9 +653,9 @@ TEST_F( CcpTelemetryTest, ProfilerZoneRegisterRejectEmpty ) TEST_F( CcpTelemetryProfilerZoneTest, SetEmptyProfilerZonesClearsMask ) { EXPECT_TRUE( CcpSetActiveProfilerZones( {"cpp"} ) ); - EXPECT_NE( std::vector{}, CcpGetActiveProfilerZones() ); + EXPECT_NE( std::vector{}, CcpGetActiveProfilerZones() ); EXPECT_TRUE( CcpSetActiveProfilerZones( {} ) ); - EXPECT_EQ( std::vector{}, CcpGetActiveProfilerZones() ); + EXPECT_EQ( std::vector{}, CcpGetActiveProfilerZones() ); } TEST_F( CcpTelemetryProfilerZoneTest, SetProfilerZoneRejectsTooManyMasks ) @@ -678,7 +678,7 @@ TEST_F( CcpTelemetryProfilerZoneTest, SetProfilerZoneRejectsTooManyMasks ) TEST_F( CcpTelemetryProfilerZoneTest, ProfilerZoneDefaultsAreRegistered ) { // The default ProfilerZones must be available from the start. - CcpProfilerZoneInfo info; + CcpProfilerZone info; EXPECT_TRUE( TryGetProfilerZoneNamed( "core", info ) ); EXPECT_TRUE( TryGetProfilerZoneNamed( "general", info ) ); EXPECT_TRUE( TryGetProfilerZoneNamed( "cpp", info ) ); @@ -689,18 +689,18 @@ TEST_F( CcpTelemetryProfilerZoneTest, SetActiveProfilerZoneByNames ) const std::string registeredName = "SetActiveProfilerZoneByNames_RegisteredName"; const std::string newName = "SetActiveProfilerZoneByNames_NewName"; const std::string pendingName = "SetActiveProfilerZoneByNames_PendingName"; - const std::vector activeMaskList = { registeredName, pendingName }; + const std::vector activeProfilerZones = { { registeredName }, { pendingName } }; // Only register one name const CcpProfilerZoneHandle handle = CcpRegisterProfilerZone( {registeredName} ); - CcpSetActiveProfilerZones( activeMaskList ); - EXPECT_EQ( std::vector{registeredName}, CcpGetActiveProfilerZones() ) << "ActiveProfilerZone should only contain the registered mask, not the pending one"; + CcpSetActiveProfilerZones( { registeredName, pendingName } ); + EXPECT_EQ( std::vector{ { registeredName } }, CcpGetActiveProfilerZones() ) << "ActiveProfilerZone should only contain the registered mask, not the pending one"; // Register the new name const CcpProfilerZoneHandle newHandle = CcpRegisterProfilerZone( {newName} ); - EXPECT_EQ( std::vector{registeredName}, CcpGetActiveProfilerZones() ) << "ActiveProfilerZone should still only contain the registered mask, not the new or pending one"; + EXPECT_EQ( std::vector{ { registeredName } }, CcpGetActiveProfilerZones() ) << "ActiveProfilerZone should still only contain the registered mask, not the new or pending one"; // Now add the pending one const CcpProfilerZoneHandle pendingHandle = CcpRegisterProfilerZone( {pendingName} ); - EXPECT_EQ( activeMaskList, CcpGetActiveProfilerZones() ) << "ActiveProfilerZone should now contain both the registered and pending masks"; + EXPECT_EQ( activeProfilerZones, CcpGetActiveProfilerZones() ) << "ActiveProfilerZone should now contain both the registered and pending masks"; }