The Gaudi Framework  master (bdbe7744)
Loading...
Searching...
No Matches
NVTXAuditor.cpp
Go to the documentation of this file.
1/***********************************************************************************\
2* (c) Copyright 2026 CERN for the benefit of the LHCb and ATLAS collaborations *
3* *
4* This software is distributed under the terms of the Apache version 2 licence, *
5* copied verbatim in the file "LICENSE". *
6* *
7* In applying this licence, CERN does not waive the privileges and immunities *
8* granted to it by virtue of its status as an Intergovernmental Organization *
9* or submit itself to any jurisdiction. *
10\***********************************************************************************/
11#include <Gaudi/Auditor.h>
17
18#include <nvtx3/nvToolsExt.h>
19#include <tbb/concurrent_hash_map.h>
20
21#include <cstdint>
22#include <format>
23#include <string>
24
25namespace {
30 static uint32_t cycle_color( uint64_t i ) {
31 constexpr uint32_t alpha = 0xFFu << 24;
32 constexpr uint8_t cernR = 0;
33 constexpr uint8_t cernG = 51;
34 constexpr uint8_t cernB = 160;
35 auto r = static_cast<uint8_t>( ( i * 23 ) + cernR );
36 auto g = static_cast<uint8_t>( ( i * 47 ) + cernG );
37 auto b = static_cast<uint8_t>( ( i * 71 ) + cernB );
38 return alpha | ( static_cast<uint32_t>( r ) << 16 ) | ( static_cast<uint32_t>( g ) << 8 ) |
39 static_cast<uint32_t>( b );
40 }
41} // namespace
42
54class NVTXAuditor : public extends<Gaudi::Auditor, IIncidentListener> {
55public:
57
58private:
59 // Domain used for all NVTX events emitted by this auditor. Written once during initialization and then only read.
60 static inline nvtxDomainHandle_t s_domain{ nullptr };
61
62 // Number of categories created (one per event slot). Written once during initialization and then only read; also
63 // used to guard against double initialization.
64 static inline uint32_t s_numCategories{ 0 };
65
66 // The auditor uses "process ranges" and stores handles to them for in-flight events. "process ranges" are used
67 // instead "thread ranges" because they can be started and stopped on different threads which might be not always
68 // guaranteed with suspension.
69 tbb::concurrent_hash_map<std::string, nvtxRangeId_t> m_ranges;
70
78
79 return base_class::initialize()
80 .andThen( [this]() {
81 auto incidentSvc = service<IIncidentSvc>( "IncidentSvc" );
82 if ( incidentSvc.isValid() ) {
83 incidentSvc->addListener( this, "ALL" );
84 } else {
85 info() << "No IncidentSvc, NVTXAuditor will not be able to annotate incidents." << endmsg;
86 }
87 } )
88 .andThen( [this]() {
89 if ( s_numCategories > 0 ) {
90 error() << "Attempting to initialize NVTX categories more than once." << endmsg;
92 }
93
94 s_domain = nvtxDomainCreateA( "Gaudi" );
95
96 auto hiveWhiteBoard = service<IHiveWhiteBoard>( "EventDataSvc" );
97 if ( hiveWhiteBoard.isValid() ) {
99 s_numCategories = hiveWhiteBoard->getNumberOfStores();
100 for ( auto slot = 0u; slot < s_numCategories; ++slot ) {
101 // slot + 1 for 1-indexing of categories in NVTX
102 nvtxDomainNameCategoryA( s_domain, slot + 1, std::format( "Slot {}", slot ).c_str() );
103 }
104 } else {
106 s_numCategories = 1;
107 nvtxDomainNameCategoryA( s_domain, 1, "Slot 0" );
108 }
109 return StatusCode::SUCCESS;
110 } );
111 }
112
117 StatusCode finalize() override {
118 if ( s_domain ) {
119 nvtxDomainDestroy( s_domain );
120 s_domain = nullptr;
121 }
122 return base_class::finalize();
123 }
124
134 void handle( const Incident& incident ) override {
135 auto eventAttrib = nvtxEventAttributes_t{};
136 eventAttrib.version = NVTX_VERSION;
137 eventAttrib.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE;
138 eventAttrib.messageType = NVTX_MESSAGE_TYPE_ASCII;
139 eventAttrib.message.ascii = incident.type().c_str();
140
141 if ( !incident.context().valid() ) {
142 nvtxDomainMarkEx( s_domain, &eventAttrib );
143 debug() << std::format( "Marked incident {} from {}", incident.type(), incident.source() ) << endmsg;
144 return;
145 }
146
147 eventAttrib.colorType = NVTX_COLOR_ARGB;
148 eventAttrib.color = ::cycle_color( incident.context().evt() );
149 eventAttrib.category = incident.context().slot() + 1;
150 eventAttrib.payloadType = NVTX_PAYLOAD_TYPE_UNSIGNED_INT64;
151 eventAttrib.payload.ullValue = incident.context().evt();
152
153 nvtxDomainMarkEx( s_domain, &eventAttrib );
154 debug() << std::format( "Marked incident {} from {} with context {}", incident.type(), incident.source(),
155 incident.context() )
156 << endmsg;
157 }
158
166 std::string make_key( std::string const& event, std::string const& caller, EventContext const& ctx ) const {
167 if ( ctx.valid() ) { return std::format( "{}:{}:{}", caller, event, ctx.evt() ); }
168 return std::format( "{}:{}", caller, event );
169 }
170
174 std::string make_label( std::string const& event, std::string const& caller ) const {
175 return std::format( "{}:{}", caller, event );
176 }
177
187 nvtxRangeId_t start_range( std::string const& label, EventContext const& ctx ) {
188 auto eventAttrib = nvtxEventAttributes_t{};
189 eventAttrib.version = NVTX_VERSION;
190 eventAttrib.size = NVTX_EVENT_ATTRIB_STRUCT_SIZE;
191 eventAttrib.messageType = NVTX_MESSAGE_TYPE_ASCII;
192 eventAttrib.message.ascii = label.c_str();
193
194 if ( !ctx.valid() ) {
195 debug() << std::format( "Starting range for {}", label ) << endmsg;
196 return nvtxDomainRangeStartEx( s_domain, &eventAttrib );
197 }
198
199 eventAttrib.colorType = NVTX_COLOR_ARGB;
200 eventAttrib.color = ::cycle_color( ctx.evt() );
201 eventAttrib.category = ctx.slot() + 1;
202 eventAttrib.payloadType = NVTX_PAYLOAD_TYPE_UNSIGNED_INT64;
203 eventAttrib.payload.ullValue = ctx.evt();
204
205 debug() << std::format( "Starting range for {} with context {}", label, ctx ) << endmsg;
206 return nvtxDomainRangeStartEx( s_domain, &eventAttrib );
207 }
208
219 void before( std::string const& event, std::string const& caller, EventContext const& ctx ) override {
220 // Suspension pauses the execution, so close the currently active
221 // execution range instead of creating a range for the suspension.
222 if ( IAuditor::Suspension == event ) {
223 auto execute_key = make_key( IAuditor::Execute, caller, ctx );
224 auto acc = decltype( m_ranges )::accessor{};
225 if ( !m_ranges.find( acc, execute_key ) ) {
226 error() << std::format( "Execution range for {} does not exist while suspending", execute_key ) << endmsg;
227 } else {
228 nvtxDomainRangeEnd( s_domain, acc->second );
229 m_ranges.erase( acc );
230 debug() << std::format( "Suspended execution range for {}", execute_key ) << endmsg;
231 }
232 return;
233 }
234
235 auto key = make_key( event, caller, ctx );
236 auto label = make_label( event, caller );
237 auto id = start_range( label, ctx );
238 auto ret = m_ranges.emplace( key, id );
239 if ( !ret ) {
240 error() << std::format( "Range for {} already exists", key ) << endmsg;
241 nvtxDomainRangeEnd( s_domain, id ); // the newly started range would otherwise never be closed
242 } else {
243 debug() << std::format( "Started range for {}", key ) << endmsg;
244 }
245 }
246
258 void after( std::string const& event, std::string const& caller, EventContext const& ctx,
259 StatusCode const& = StatusCode::SUCCESS ) override {
260 // Suspension has ended, so resume the execution by creating a new
261 // execution range. Do not create a range for Suspend itself.
262 if ( IAuditor::Suspension == event ) {
263 auto execute_key = make_key( IAuditor::Execute, caller, ctx );
264 auto label = make_label( IAuditor::Execute, caller );
265 auto id = start_range( label, ctx );
266 auto ret = m_ranges.emplace( execute_key, id );
267 if ( !ret ) {
268 error() << std::format( "Execution range for {} already exists while resuming", execute_key ) << endmsg;
269 nvtxDomainRangeEnd( s_domain, id ); // the newly started range would otherwise never be closed
270 } else {
271 debug() << std::format( "Resumed execution range for {}", execute_key ) << endmsg;
272 }
273 return;
274 }
275
276 auto key = make_key( event, caller, ctx );
277 auto acc = decltype( m_ranges )::accessor{};
278 if ( m_ranges.find( acc, key ) ) {
279 nvtxDomainRangeEnd( s_domain, acc->second );
280 m_ranges.erase( acc );
281 } else {
282 error() << std::format( "Range for {} does not exist", key ) << endmsg;
283 }
284 debug() << std::format( "Finished range for {}", key ) << endmsg;
285 }
286};
287
MsgStream & endmsg(MsgStream &s)
MsgStream Modifier: endmsg. Calls the output method of the MsgStream.
Definition MsgStream.h:198
#define DECLARE_COMPONENT(type)
MsgStream & error() const
shortcut for the method msgStream(MSG::ERROR)
MsgStream & debug() const
shortcut for the method msgStream(MSG::DEBUG)
MsgStream & info() const
shortcut for the method msgStream(MSG::INFO)
This class represents an entry point to all the event specific data.
ContextEvt_t evt() const
ContextID_t slot() const
bool valid() const
SmartIF< T > service(std::string_view name, bool createIf=false) const
Access a service by name, creating it if it doesn't already exist.
Definition Auditor.h:63
Base class for all Incidents (computing events).
Definition Incident.h:24
const EventContext & context() const
Access to the EventContext of the source of the incident.
Definition Incident.h:55
const std::string & type() const
Access to the incident type.
Definition Incident.h:43
const std::string & source() const
Access to the source of the incident.
Definition Incident.h:49
Auditor that uses NVTX to annotate ranges and incidents for profiling with NVIDIA tools.
std::string make_label(std::string const &event, std::string const &caller) const
Helper to create a label for the audited event.
static nvtxDomainHandle_t s_domain
tbb::concurrent_hash_map< std::string, nvtxRangeId_t > m_ranges
nvtxRangeId_t start_range(std::string const &label, EventContext const &ctx)
Start an NVTX range for the audited event.
void after(std::string const &event, std::string const &caller, EventContext const &ctx, StatusCode const &=StatusCode::SUCCESS) override
Handle 'after' audit for given event by ending the associated NVTX range.
void handle(const Incident &incident) override
Handle an incident by annotating it with an NVTX marker.
static uint32_t s_numCategories
StatusCode initialize() override
Initialize the auditor and register for all available incidents if the Incident service exists.
void before(std::string const &event, std::string const &caller, EventContext const &ctx) override
Handle 'before' audit for given event by starting an associated NVTX range.
std::string make_key(std::string const &event, std::string const &caller, EventContext const &ctx) const
Helper to create an identifier key for storing an associated range in the map.
StatusCode finalize() override
Finalize the auditor, releasing the NVTX domain.
This class is used for returning status codes from appropriate routines.
Definition StatusCode.h:64
StatusCode andThen(F &&f, ARGS &&... args) const
Chain code blocks making the execution conditional a success result.
Definition StatusCode.h:151
constexpr static const auto SUCCESS
Definition StatusCode.h:99
constexpr static const auto FAILURE
Definition StatusCode.h:100
Base class used to extend a class implementing other interfaces.
Definition extends.h:19
extends base_class
Typedef to this class.
Definition extends.h:23
dict g
Definition gaudirun.py:590