|
|
|||
Warning, file /eic-opticks/sysrap/EventTiming.hh was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /** 0002 * @file EventTiming.hh 0003 * Defines timestamp samples, process-wide lifecycle profiling, and per-event 0004 * CPU/GPU timeline recording. 0005 */ 0006 0007 #pragma once 0008 0009 #include <cstdint> 0010 #include <filesystem> 0011 #include <initializer_list> 0012 #include <istream> 0013 #include <string> 0014 #include <string_view> 0015 #include <utility> 0016 #include <vector> 0017 0018 #include "SYSRAP_API_EXPORT.hh" 0019 0020 /** 0021 * Selects the measurements requested from EventTimingSample::Capture(). 0022 * 0023 * Values may be combined with operator|(). A monotonic timestamp is always 0024 * captured, even when Monotonic is absent from the requested mask. Other 0025 * measurements are optional and their availability is recorded in 0026 * EventTimingSample::valid_mask. 0027 */ 0028 enum class EventTimingCapture : std::uint32_t 0029 { 0030 /** Capture the monotonic timestamp in nanoseconds. */ 0031 Monotonic = 1u << 0, 0032 /** Capture the Unix wall-clock timestamp in microseconds. */ 0033 Wall = 1u << 1, 0034 /** Capture process CPU time in nanoseconds. */ 0035 ProcessCpu = 1u << 2, 0036 /** Capture calling-thread CPU time in nanoseconds. */ 0037 ThreadCpu = 1u << 3, 0038 /** Capture virtual-memory and resident-set sizes in KiB. */ 0039 Memory = 1u << 4 0040 }; 0041 0042 /** 0043 * Combines two EventTimingCapture values into one capture mask. 0044 * 0045 * @param a first capture selection 0046 * @param b second capture selection 0047 * @return mask containing every selection present in either operand 0048 */ 0049 constexpr EventTimingCapture operator|(EventTimingCapture a, EventTimingCapture b) 0050 { 0051 return static_cast<EventTimingCapture>( 0052 static_cast<std::uint32_t>(a) | static_cast<std::uint32_t>(b)); 0053 } 0054 0055 /** 0056 * Stores one timestamp together with optional CPU and memory measurements. 0057 * 0058 * All clocks use signed integer counts to support difference calculations. 0059 * The valid_mask determines which values are usable; a stored zero does not 0060 * by itself indicate that a field was captured. Memory values use KiB, wall 0061 * time uses microseconds since the Unix epoch, and all other times use 0062 * nanoseconds. 0063 */ 0064 struct SYSRAP_API EventTimingSample 0065 { 0066 /** 0067 * Memory-query callback used to make memory capture replaceable in tests. 0068 * 0069 * The callback writes virtual-memory and resident-set sizes in KiB and 0070 * returns zero on success. 0071 */ 0072 using MemoryQuery = int (*)(std::int32_t&, std::int32_t&); 0073 0074 /** Bit set containing the EventTimingCapture values available below. */ 0075 std::uint32_t valid_mask{0}; 0076 /** Monotonic timestamp in nanoseconds. */ 0077 std::int64_t steady_time_ns{0}; 0078 /** Unix wall-clock timestamp in microseconds. */ 0079 std::int64_t wall_time_us{0}; 0080 /** CPU time consumed by the process in nanoseconds. */ 0081 std::int64_t process_cpu_ns{0}; 0082 /** CPU time consumed by the calling thread in nanoseconds. */ 0083 std::int64_t thread_cpu_ns{0}; 0084 /** Process virtual-memory size in KiB. */ 0085 std::int64_t vm_kb{0}; 0086 /** Process resident-set size in KiB. */ 0087 std::int64_t rss_kb{0}; 0088 0089 /** 0090 * Returns the valid-mask bit associated with a capture field. 0091 * 0092 * @param field capture field to convert 0093 * @return unsigned bit value for field 0094 */ 0095 static constexpr std::uint32_t bit(EventTimingCapture field) 0096 { 0097 return static_cast<std::uint32_t>(field); 0098 } 0099 0100 /** 0101 * Captures a sample using the process memory query supplied by SysRap. 0102 * 0103 * Monotonic time is unconditional. A failed optional clock or memory 0104 * query leaves the corresponding validity bit clear. 0105 * 0106 * @param mask optional measurements to request 0107 * @return captured sample and its validity mask 0108 */ 0109 static EventTimingSample Capture(EventTimingCapture mask); 0110 /** 0111 * Captures a sample using a caller-provided memory query. 0112 * 0113 * This overload is primarily useful for deterministic testing. The query 0114 * is consulted only when Memory is requested. A null or failing query 0115 * leaves both memory fields invalid. 0116 * 0117 * @param mask optional measurements to request 0118 * @param query callback that obtains VM and RSS values in KiB 0119 * @return captured sample and its validity mask 0120 */ 0121 static EventTimingSample Capture(EventTimingCapture mask, MemoryQuery query); 0122 0123 /** 0124 * Tests whether a measurement is valid in this sample. 0125 * 0126 * @param field measurement to test 0127 * @return true when field was captured successfully 0128 */ 0129 bool has(EventTimingCapture field) const; 0130 /** 0131 * Serializes the sample as six comma-separated fields. 0132 * 0133 * The field order is wall time, monotonic time, process CPU time, thread 0134 * CPU time, VM, and RSS. Invalid optional fields are emitted empty. 0135 * 0136 * @return stable metadata representation of this sample 0137 * @throws std::logic_error if the monotonic timestamp is invalid 0138 */ 0139 std::string serialize() const; 0140 /** 0141 * Parses the six-field representation produced by serialize(). 0142 * 0143 * Wall, CPU, and memory fields may be empty. VM and RSS must either both 0144 * be present or both be empty. 0145 * 0146 * @param text serialized timing sample 0147 * @return parsed sample with validity bits reconstructed 0148 * @throws std::invalid_argument if the field count, an integer, or the 0149 * paired memory fields are invalid 0150 */ 0151 static EventTimingSample parse(std::string_view text); 0152 /** 0153 * Tests whether text is a valid serialized timing sample. 0154 * 0155 * @param text candidate six-field representation 0156 * @return true when parse() accepts text 0157 */ 0158 static bool looksLikeSerialized(std::string_view text); 0159 /** @return human-readable representation including field names */ 0160 std::string desc() const; 0161 0162 /** 0163 * Computes the signed monotonic interval from begin to end. 0164 * 0165 * @return end minus begin in nanoseconds 0166 * @throws std::logic_error if either sample lacks monotonic time 0167 */ 0168 static std::int64_t elapsedNs( 0169 const EventTimingSample& begin, 0170 const EventTimingSample& end); 0171 /** 0172 * Computes the signed virtual-memory change from begin to end. 0173 * 0174 * @return end minus begin in KiB 0175 * @throws std::logic_error if either sample lacks memory measurements 0176 */ 0177 static std::int64_t deltaVmKb( 0178 const EventTimingSample& begin, 0179 const EventTimingSample& end); 0180 /** 0181 * Computes the signed resident-set change from begin to end. 0182 * 0183 * @return end minus begin in KiB 0184 * @throws std::logic_error if either sample lacks memory measurements 0185 */ 0186 static std::int64_t deltaRssKb( 0187 const EventTimingSample& begin, 0188 const EventTimingSample& end); 0189 0190 /** @return true when all stored fields and validity bits are equal */ 0191 bool operator==(const EventTimingSample&) const = default; 0192 }; 0193 0194 /** Represents one named record in the process-wide lifecycle profile. */ 0195 struct EventTimingProfileRecord 0196 { 0197 /** Record name, optionally prefixed with the thread-local tag from Add(). */ 0198 std::string name; 0199 /** Measurements associated with the named point. */ 0200 EventTimingSample sample; 0201 /** Optional caller-defined annotation stored as one CSV field. */ 0202 std::string metadata; 0203 }; 0204 0205 /** Selects whether profile output replaces or extends an existing CSV file. */ 0206 enum class EventTimingWriteMode 0207 { 0208 /** Replace the destination with the current in-memory records. */ 0209 Replace, 0210 /** Preserve existing file records, then append the in-memory records. */ 0211 Append 0212 }; 0213 0214 /** 0215 * Collects named lifecycle samples in a process-wide profile. 0216 * 0217 * Profiling is explicitly enabled through Configure(). When disabled, Mark() 0218 * returns a monotonic-only sample without recording it, allowing callers to 0219 * retain inexpensive local duration measurements. Record insertion and 0220 * snapshots are synchronized. Tags are thread-local, while configuration and 0221 * the record collection are process-wide; Configure() is therefore expected 0222 * to run before worker threads begin recording. 0223 */ 0224 class SYSRAP_API EventTimingProfile 0225 { 0226 public: 0227 /** 0228 * Configures process-wide lifecycle profiling. 0229 * 0230 * The output may contain exactly one printf-style integer conversion 0231 * (`%d`, `%i`, or `%u`) that Path() expands using path_index. Configuration 0232 * does not clear records already in memory. 0233 * 0234 * @param enabled whether subsequent marks are recorded 0235 * @param output destination path or indexed path pattern 0236 * @param path_index integer used to expand an indexed output path 0237 */ 0238 static void Configure( 0239 bool enabled, 0240 const std::filesystem::path& output = "event_timing_profile.csv", 0241 int path_index = 0); 0242 /** @return true when process-wide profile recording is enabled */ 0243 static bool Enabled(); 0244 /** 0245 * Captures and optionally records a named lifecycle point. 0246 * 0247 * Enabled marks contain monotonic time, wall time, and process memory. 0248 * Disabled marks contain only monotonic time and are not added to the 0249 * process-wide record collection. 0250 * 0251 * @param name lifecycle-point name without the current thread tag 0252 * @param metadata optional annotation stored with the record 0253 * @return sample captured for this mark 0254 */ 0255 static EventTimingSample Mark( 0256 std::string_view name, 0257 std::string_view metadata = {}); 0258 /** 0259 * Captures and records a lifecycle point without the calling thread's tag. 0260 * 0261 * Any current tag is restored before returning so surrounding event marks 0262 * retain their prefix. 0263 * 0264 * @param name lifecycle-point name 0265 * @param metadata optional annotation stored with the record 0266 * @return sample captured for this mark 0267 */ 0268 static EventTimingSample MarkUntagged( 0269 std::string_view name, 0270 std::string_view metadata = {}); 0271 /** 0272 * Adds a caller-provided sample when profiling is enabled. 0273 * 0274 * @param name record name without the current thread tag 0275 * @param sample measurements to store 0276 * @param metadata optional annotation stored with the record 0277 */ 0278 static void Add( 0279 std::string_view name, 0280 const EventTimingSample& sample, 0281 std::string_view metadata = {}); 0282 0283 /** 0284 * Sets the calling thread's prefix for subsequently added record names. 0285 * 0286 * The format must contain exactly one `%d`, `%i`, or `%u` conversion and 0287 * may specify zero padding, width, and integer precision. 0288 * 0289 * @param index value substituted into format 0290 * @param format printf-style integer format for the tag 0291 * @throws std::invalid_argument if format is unsupported or ambiguous 0292 */ 0293 static void SetTag(int index, std::string_view format = "A%0.3d_"); 0294 /** @return true when the calling thread has a nonempty tag */ 0295 static bool HasTag(); 0296 /** @return copy of the calling thread's current tag */ 0297 static std::string Tag(); 0298 /** Clears the calling thread's current tag. */ 0299 static void UnsetTag(); 0300 0301 /** 0302 * Formats unsigned metadata values as a comma-separated key-value list. 0303 * 0304 * @param values ordered name/value pairs 0305 * @return text in `name=value,name=value` form 0306 */ 0307 static std::string Annotation( 0308 std::initializer_list<std::pair<std::string_view, std::uint64_t>> values); 0309 /** 0310 * Returns the RSS change between the two most recent records. 0311 * 0312 * @return signed RSS change in KiB, or -1 when unavailable 0313 */ 0314 static std::int64_t DeltaRssKb(); 0315 /** 0316 * Returns the RSS change between the first and most recent records. 0317 * 0318 * @return signed RSS change in KiB, or -1 when unavailable 0319 */ 0320 static std::int64_t RangeRssKb(); 0321 0322 /** Removes every in-memory profile record. */ 0323 static void Clear(); 0324 /** @return number of in-memory profile records */ 0325 static std::size_t Size(); 0326 /** @return synchronized copy of the in-memory profile records */ 0327 static std::vector<EventTimingProfileRecord> Records(); 0328 /** @return newline-delimited human-readable description of all records */ 0329 static std::string Describe(); 0330 0331 /** 0332 * Serializes records as RFC 4180-compatible profile CSV. 0333 * 0334 * @param records ordered records to serialize 0335 * @return CSV text including the six-column header 0336 * @throws std::logic_error if a record lacks monotonic time 0337 */ 0338 static std::string SerializeCsv( 0339 const std::vector<EventTimingProfileRecord>& records); 0340 /** 0341 * Parses profile CSV from an input stream. 0342 * 0343 * @param input stream positioned at the CSV header 0344 * @param source path reported in parse diagnostics 0345 * @return records in file order 0346 * @throws std::runtime_error if the header, quoting, or a row is invalid 0347 */ 0348 static std::vector<EventTimingProfileRecord> ParseCsv( 0349 std::istream& input, 0350 const std::filesystem::path& source); 0351 /** 0352 * Reads and parses a profile CSV file. 0353 * 0354 * @param path input profile path 0355 * @return records in file order 0356 * @throws std::runtime_error if the file cannot be opened or parsed 0357 */ 0358 static std::vector<EventTimingProfileRecord> ReadFile( 0359 const std::filesystem::path& path); 0360 0361 /** 0362 * Resolves the configured profile output path. 0363 * 0364 * @return literal configured path or indexed path expansion 0365 * @throws std::invalid_argument if the path is empty or its format is invalid 0366 */ 0367 static std::filesystem::path Path(); 0368 /** 0369 * Writes a snapshot of the in-memory records when profiling is enabled. 0370 * 0371 * Parent directories are created as needed. Replacement uses a uniquely 0372 * named temporary file followed by rename so readers never observe a 0373 * partially written CSV. Append mode first parses the existing file and 0374 * rewrites the combined record sequence. 0375 * 0376 * @param mode whether to replace or extend an existing profile 0377 * @throws std::invalid_argument if the configured path pattern is invalid 0378 * @throws std::runtime_error if directory creation, I/O, parsing, or rename fails 0379 */ 0380 static void Write(EventTimingWriteMode mode = EventTimingWriteMode::Replace); 0381 }; 0382 0383 /** 0384 * Describes the run recorded by EventTimingRecorder. 0385 * 0386 * These values are written to the timeline manifest; primary-particle values 0387 * are also repeated in every event CSV row. Momentum is measured in GeV/c and 0388 * GPU memory in bytes. 0389 */ 0390 struct EventTimingMetadata 0391 { 0392 /** Geometry selection used by the run. */ 0393 std::string geometry; 0394 /** JSON configuration selection used by the run. */ 0395 std::string config; 0396 /** Geant4 macro path or identifier used by the run. */ 0397 std::string macro; 0398 /** Simphony version string. */ 0399 std::string simphony_version; 0400 /** Geant4 version string. */ 0401 std::string geant4_version; 0402 /** Name of the generated primary particle. */ 0403 std::string primary_particle{"opticalphoton"}; 0404 /** Primary momentum in GeV/c. */ 0405 double primary_momentum_gev_c{0.0}; 0406 /** Number of primary particles generated per event. */ 0407 int primary_multiplicity{0}; 0408 /** Random seed, or -1 when not reported. */ 0409 long random_seed{-1}; 0410 /** CUDA device name. */ 0411 std::string gpu_name; 0412 /** CUDA device ordinal, or -1 when not reported. */ 0413 int gpu_device_id{-1}; 0414 /** Total device memory in bytes. */ 0415 std::uint64_t gpu_memory_bytes{0}; 0416 /** CUDA driver version encoded by the CUDA runtime API. */ 0417 int cuda_driver_version{0}; 0418 /** CUDA runtime version encoded by the CUDA runtime API. */ 0419 int cuda_runtime_version{0}; 0420 }; 0421 0422 /** 0423 * Records the blocking CPU/GPU timeline of a serial event stream. 0424 * 0425 * An empty output path disables the recorder and makes lifecycle methods 0426 * no-ops. An enabled recorder accepts one active event at a time in this 0427 * sequence: 0428 * 0429 * @code 0430 * BeginRun() 0431 * BeginEvent() -> SubmitGpu() -> BeginGpu() -> EndGpu() -> EndEvent() 0432 * @endcode 0433 * 0434 * The event sequence may repeat before Write(). Each boundary captures 0435 * monotonic, process-CPU, and thread-CPU time. This class is intentionally 0436 * single-threaded and validates both lifecycle order and nondecreasing 0437 * monotonic timestamps. 0438 */ 0439 class SYSRAP_API EventTimingRecorder 0440 { 0441 public: 0442 /** Callback used to inject deterministic timing samples in tests. */ 0443 using SampleProvider = EventTimingSample (*)(EventTimingCapture); 0444 0445 /** 0446 * Constructs a recorder for one timeline output. 0447 * 0448 * @param output event CSV path; an empty path disables the recorder 0449 * @param provider optional replacement for EventTimingSample::Capture() 0450 */ 0451 explicit EventTimingRecorder( 0452 std::filesystem::path output = {}, 0453 SampleProvider provider = nullptr); 0454 0455 /** @return true when a nonempty output path enables recording */ 0456 bool enabled() const; 0457 /** @return configured event CSV output path */ 0458 const std::filesystem::path& output() const; 0459 /** 0460 * Starts a run, discarding any previously buffered event rows. 0461 * 0462 * @throws std::logic_error if an event is active 0463 */ 0464 void BeginRun(); 0465 /** 0466 * Starts CPU pre-processing for one event. 0467 * 0468 * @param event_id identifier written to the event CSV 0469 * @throws std::logic_error if BeginRun() was not called, another event is 0470 * active, or the monotonic sample precedes the run origin 0471 */ 0472 void BeginEvent(int event_id); 0473 /** 0474 * Marks GPU submission and records the generated workload size. 0475 * 0476 * @param num_gensteps number of gensteps submitted for transport 0477 * @param num_photons number of optical photons submitted for transport 0478 * @throws std::logic_error unless CPU pre-processing is active or when the 0479 * timestamp precedes the event start 0480 */ 0481 void SubmitGpu(std::int64_t num_gensteps, std::int64_t num_photons); 0482 /** 0483 * Marks the beginning of GPU execution after any queue delay. 0484 * 0485 * @throws std::logic_error unless GPU work has been submitted or when the 0486 * timestamp precedes submission 0487 */ 0488 void BeginGpu(); 0489 /** 0490 * Marks the end of GPU execution and beginning of CPU post-processing. 0491 * 0492 * @throws std::logic_error unless GPU execution is active or when the 0493 * timestamp precedes its start 0494 */ 0495 void EndGpu(); 0496 /** 0497 * Completes the active event and records output hit counts. 0498 * 0499 * @param num_gpu_hits number of hits returned by GPU optical transport 0500 * @param num_g4_hits number of hits recorded by Geant4 0501 * @throws std::logic_error unless CPU post-processing is active or when 0502 * the timestamp precedes GPU completion 0503 */ 0504 void EndEvent(std::size_t num_gpu_hits, std::size_t num_g4_hits); 0505 /** 0506 * Writes all completed rows and the adjacent JSON manifest. 0507 * 0508 * The manifest path is derived by replacing the CSV extension with 0509 * `.manifest.json`. Parent directories are created as needed, and each 0510 * destination is prepared in a temporary file before replacement. 0511 * 0512 * @param metadata run metadata written to the manifest and event rows 0513 * @throws std::logic_error if an event is active 0514 * @throws std::runtime_error if directory creation or output I/O fails 0515 */ 0516 void Write(const EventTimingMetadata& metadata) const; 0517 0518 private: 0519 /** Internal lifecycle states for the single active event. */ 0520 enum class State 0521 { 0522 /** No event is active. */ 0523 Idle, 0524 /** Geant4 and CPU pre-processing are active. */ 0525 CpuPre, 0526 /** GPU work is submitted but execution has not begun. */ 0527 GpuQueued, 0528 /** GPU execution is active. */ 0529 GpuRunning, 0530 /** GPU execution ended and CPU post-processing is active. */ 0531 CpuPost 0532 }; 0533 0534 /** Captured boundaries and workload counts for one completed event. */ 0535 struct Row 0536 { 0537 int event_id{-1}; 0538 std::int64_t num_gensteps{0}; 0539 std::int64_t num_photons{0}; 0540 std::size_t num_gpu_hits{0}; 0541 std::size_t num_g4_hits{0}; 0542 EventTimingSample event_start; 0543 EventTimingSample gpu_submit; 0544 EventTimingSample gpu_start; 0545 EventTimingSample gpu_end; 0546 EventTimingSample event_end; 0547 }; 0548 0549 EventTimingSample Capture() const; 0550 static const char* StateName(State state); 0551 Row& ActiveRow(); 0552 const Row& ActiveRow() const; 0553 void RequireState(State expected, std::string_view operation) const; 0554 void RequireOrdered( 0555 const EventTimingSample& begin, 0556 const EventTimingSample& end, 0557 std::string_view operation) const; 0558 std::string SerializeCsv(const EventTimingMetadata& metadata) const; 0559 std::string SerializeManifest(const EventTimingMetadata& metadata) const; 0560 std::filesystem::path ManifestPath() const; 0561 0562 std::filesystem::path output_; 0563 SampleProvider provider_{nullptr}; 0564 State state_{State::Idle}; 0565 bool run_started_{false}; 0566 int active_event_id_{-1}; 0567 EventTimingSample run_origin_; 0568 std::vector<Row> rows_; 0569 };
| [ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
|
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
|