Back to home page

EIC code displayed by LXR

 
 

    


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 };