Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-10-20 08:45:52

0001 #include <cassert>
0002 #include <cmath>
0003 #include <cstdint>
0004 #include <filesystem>
0005 #include <fstream>
0006 #include <functional>
0007 #include <stdexcept>
0008 #include <string>
0009 #include <string_view>
0010 #include <unistd.h>
0011 #include <unordered_map>
0012 #include <vector>
0013 
0014 #include "EventTiming.hh"
0015 #include "nlohmann/json.hpp"
0016 
0017 namespace
0018 {
0019 namespace fs = std::filesystem;
0020 
0021 std::vector<EventTimingSample> sequence;
0022 std::size_t                    sequence_index = 0;
0023 
0024 EventTimingSample TimedSample(
0025     std::int64_t steady_ns,
0026     std::int64_t process_cpu_ns,
0027     std::int64_t thread_cpu_ns)
0028 {
0029     EventTimingSample sample{};
0030     sample.valid_mask = EventTimingSample::bit(EventTimingCapture::Monotonic) | EventTimingSample::bit(EventTimingCapture::ProcessCpu) | EventTimingSample::bit(EventTimingCapture::ThreadCpu);
0031     sample.steady_time_ns = steady_ns;
0032     sample.process_cpu_ns = process_cpu_ns;
0033     sample.thread_cpu_ns = thread_cpu_ns;
0034     return sample;
0035 }
0036 
0037 EventTimingSample NextSample(EventTimingCapture mask)
0038 {
0039     assert((EventTimingSample::bit(mask) & EventTimingSample::bit(EventTimingCapture::Monotonic)) != 0u);
0040     assert((EventTimingSample::bit(mask) & EventTimingSample::bit(EventTimingCapture::ProcessCpu)) != 0u);
0041     assert((EventTimingSample::bit(mask) & EventTimingSample::bit(EventTimingCapture::ThreadCpu)) != 0u);
0042     assert(sequence_index < sequence.size());
0043     return sequence[sequence_index++];
0044 }
0045 
0046 void SetSequence(std::initializer_list<EventTimingSample> samples)
0047 {
0048     sequence.assign(samples.begin(), samples.end());
0049     sequence_index = 0;
0050 }
0051 
0052 fs::path TestDirectory()
0053 {
0054     return fs::temp_directory_path() /
0055            ("EventTimingRecorderTest-" + std::to_string(static_cast<long long>(::getpid())));
0056 }
0057 
0058 std::vector<std::string> Split(std::string_view text, char delimiter)
0059 {
0060     std::vector<std::string> fields;
0061     std::size_t              start = 0;
0062     for (std::size_t index = 0; index <= text.size(); ++index)
0063     {
0064         if (index == text.size() || text[index] == delimiter)
0065         {
0066             fields.emplace_back(text.substr(start, index - start));
0067             start = index + 1;
0068         }
0069     }
0070     return fields;
0071 }
0072 
0073 std::unordered_map<std::string, std::string> ReadSingleRow(const fs::path& path)
0074 {
0075     std::ifstream input(path);
0076     assert(input);
0077     std::string header;
0078     std::string values;
0079     const bool  read_header = static_cast<bool>(std::getline(input, header));
0080     const bool  read_values = static_cast<bool>(std::getline(input, values));
0081     assert(read_header);
0082     assert(read_values);
0083     std::string extra;
0084     const bool  read_extra = static_cast<bool>(std::getline(input, extra));
0085     assert(!read_extra);
0086 
0087     const auto names = Split(header, ',');
0088     const auto fields = Split(values, ',');
0089     assert(names.size() == fields.size());
0090     std::unordered_map<std::string, std::string> row;
0091     for (std::size_t index = 0; index < names.size(); ++index)
0092         row.emplace(names[index], fields[index]);
0093     return row;
0094 }
0095 
0096 void AssertNear(double actual, double expected)
0097 {
0098     assert(std::abs(actual - expected) < 1e-9);
0099 }
0100 
0101 EventTimingMetadata Metadata()
0102 {
0103     EventTimingMetadata metadata{};
0104     metadata.geometry = "geom\"etry\\name\nline";
0105     metadata.config = "config=value";
0106     metadata.macro = "/run/beamOn 1";
0107     metadata.simphony_version = "test-version";
0108     metadata.geant4_version = "11.test";
0109     metadata.primary_particle = "mu-";
0110     metadata.primary_momentum_gev_c = 5.0;
0111     metadata.primary_multiplicity = 100000;
0112     metadata.random_seed = 12345;
0113     metadata.gpu_name = "GPU \"test\"";
0114     metadata.gpu_device_id = 2;
0115     metadata.gpu_memory_bytes = 24'000'000'000ull;
0116     metadata.cuda_driver_version = 12040;
0117     metadata.cuda_runtime_version = 12030;
0118     return metadata;
0119 }
0120 
0121 void CompleteOneEvent(EventTimingRecorder& recorder)
0122 {
0123     recorder.BeginRun();
0124     recorder.BeginEvent(0);
0125     recorder.SubmitGpu(4, 500);
0126     recorder.BeginGpu();
0127     recorder.EndGpu();
0128     recorder.EndEvent(25, 7);
0129 }
0130 
0131 void TestGoldenCsvAndManifest(const fs::path& directory)
0132 {
0133     const fs::path output = directory / "events.csv";
0134     SetSequence({
0135         TimedSample(1'000'000'000, 100'000, 50'000),
0136         TimedSample(1'100'000'000, 1'000'000, 500'000),
0137         TimedSample(1'300'000'000, 1'300'000, 650'000),
0138         TimedSample(1'350'000'000, 1'350'000, 700'000),
0139         TimedSample(1'750'000'000, 1'600'000, 850'000),
0140         TimedSample(2'000'000'000, 1'900'000, 1'050'000),
0141     });
0142 
0143     EventTimingRecorder recorder(output, &NextSample);
0144     CompleteOneEvent(recorder);
0145     recorder.Write(Metadata());
0146     assert(sequence_index == sequence.size());
0147 
0148     std::ifstream csv(output);
0149     assert(csv);
0150     std::string header;
0151     const bool  read_header = static_cast<bool>(std::getline(csv, header));
0152     assert(read_header);
0153     const std::string expected_header =
0154         "scenario,dispatch_mode,event_id,start_time,end_time,cpu_start_time,cpu_end_time,"
0155         "cpu_pre_start_time,cpu_pre_end_time,gpu_submit_time,gpu_start_time,gpu_end_time,"
0156         "gpu_wait_start_time,gpu_wait_end_time,cpu_post_start_time,cpu_post_end_time,"
0157         "start_offset_s,end_offset_s,cpu_start_time_offset_s,cpu_end_time_offset_s,"
0158         "cpu_pre_start_time_offset_s,cpu_pre_end_time_offset_s,gpu_submit_time_offset_s,"
0159         "gpu_start_time_offset_s,gpu_end_time_offset_s,gpu_wait_start_time_offset_s,"
0160         "gpu_wait_end_time_offset_s,cpu_post_start_time_offset_s,cpu_post_end_time_offset_s,"
0161         "runtime_s,cpu_pre_runtime_s,cpu_post_runtime_s,cpu_runtime_s,gpu_queue_delay_s,"
0162         "gpu_runtime_s,gpu_wait_runtime_s,primary_particle,primary_momentum_gev_c,"
0163         "primary_multiplicity,num_gensteps,num_photons,num_gpu_hits,num_g4_hits,"
0164         "process_cpu_pre_s,process_cpu_post_s,process_cpu_s,thread_cpu_pre_s,"
0165         "thread_cpu_post_s,thread_cpu_s";
0166     assert(header == expected_header);
0167 
0168     const auto row = ReadSingleRow(output);
0169     assert(row.at("scenario") == "simg4ox_blocking");
0170     assert(row.at("dispatch_mode") == "blocking");
0171     assert(row.at("event_id") == "0");
0172     assert(row.at("primary_particle") == "mu-");
0173     assert(row.at("num_gensteps") == "4");
0174     assert(row.at("num_photons") == "500");
0175     assert(row.at("num_gpu_hits") == "25");
0176     assert(row.at("num_g4_hits") == "7");
0177 
0178     const auto value = [&row](const char* name) { return std::stod(row.at(name)); };
0179     AssertNear(value("cpu_pre_runtime_s"),
0180                value("cpu_pre_end_time_offset_s") - value("cpu_pre_start_time_offset_s"));
0181     AssertNear(value("gpu_queue_delay_s"),
0182                value("gpu_start_time_offset_s") - value("gpu_submit_time_offset_s"));
0183     AssertNear(value("gpu_runtime_s"),
0184                value("gpu_end_time_offset_s") - value("gpu_start_time_offset_s"));
0185     AssertNear(value("cpu_post_runtime_s"),
0186                value("cpu_post_end_time_offset_s") - value("cpu_post_start_time_offset_s"));
0187     AssertNear(value("cpu_runtime_s"),
0188                value("cpu_pre_runtime_s") + value("cpu_post_runtime_s"));
0189     AssertNear(value("gpu_wait_runtime_s"),
0190                value("gpu_queue_delay_s") + value("gpu_runtime_s"));
0191     AssertNear(value("process_cpu_pre_s"), 0.0003);
0192     AssertNear(value("process_cpu_post_s"), 0.0003);
0193     AssertNear(value("thread_cpu_pre_s"), 0.00015);
0194     AssertNear(value("thread_cpu_post_s"), 0.0002);
0195 
0196     const fs::path manifest_path = directory / "events.manifest.json";
0197     std::ifstream  manifest_input(manifest_path);
0198     assert(manifest_input);
0199     const nlohmann::json      manifest = nlohmann::json::parse(manifest_input);
0200     const EventTimingMetadata metadata = Metadata();
0201     assert(manifest.at("schema_version") == 2);
0202     assert(manifest.at("application") == "simg4ox");
0203     assert(manifest.at("dispatch_mode") == "blocking");
0204     assert(manifest.at("event_count") == 1);
0205     assert(manifest.at("geometry") == metadata.geometry);
0206     assert(manifest.at("gpu_name") == metadata.gpu_name);
0207     assert(manifest.at("gpu_device_id") == metadata.gpu_device_id);
0208     assert(manifest.at("gpu_memory_bytes") == metadata.gpu_memory_bytes);
0209     assert(manifest.at("cuda_driver_version") == metadata.cuda_driver_version);
0210     assert(manifest.at("cuda_runtime_version") == metadata.cuda_runtime_version);
0211 
0212     for (const fs::directory_entry& entry : fs::directory_iterator(directory))
0213         assert(entry.path().filename().string().find(".tmp.") == std::string::npos);
0214 }
0215 
0216 void TestAbsoluteTimestampsRoundTripAtLongUptime(const fs::path& directory)
0217 {
0218     constexpr std::int64_t origin_ns = 10'000'000'000'000'000ll;
0219     constexpr std::int64_t start_ns = origin_ns + 101;
0220     constexpr std::int64_t end_ns = origin_ns + 541;
0221     SetSequence({
0222         TimedSample(origin_ns, 0, 0),
0223         TimedSample(start_ns, 0, 0),
0224         TimedSample(origin_ns + 211, 0, 0),
0225         TimedSample(origin_ns + 307, 0, 0),
0226         TimedSample(origin_ns + 419, 0, 0),
0227         TimedSample(end_ns, 0, 0),
0228     });
0229 
0230     const fs::path      output = directory / "long-uptime.csv";
0231     EventTimingRecorder recorder(output, &NextSample);
0232     CompleteOneEvent(recorder);
0233     recorder.Write(Metadata());
0234 
0235     const auto row = ReadSingleRow(output);
0236     assert(std::stod(row.at("start_time")) == static_cast<double>(start_ns) / 1'000'000'000.0);
0237     assert(std::stod(row.at("end_time")) == static_cast<double>(end_ns) / 1'000'000'000.0);
0238 }
0239 
0240 void ExpectLogicError(
0241     const fs::path&                                  output,
0242     const std::function<void(EventTimingRecorder&)>& action)
0243 {
0244     EventTimingRecorder recorder(output);
0245     recorder.BeginRun();
0246     bool threw = false;
0247     try
0248     {
0249         action(recorder);
0250     }
0251     catch (const std::logic_error& error)
0252     {
0253         threw = std::string(error.what()).find("EventTimingRecorder") != std::string::npos;
0254     }
0255     assert(threw);
0256     assert(!fs::exists(output));
0257     fs::path manifest = output;
0258     manifest.replace_extension(".manifest.json");
0259     assert(!fs::exists(manifest));
0260 }
0261 
0262 void TestInvalidTransitions(const fs::path& directory)
0263 {
0264     int        index = 0;
0265     const auto path = [&] { return directory / ("invalid-" + std::to_string(index++) + ".csv"); };
0266 
0267     ExpectLogicError(path(), [](EventTimingRecorder& recorder) { recorder.SubmitGpu(1, 2); });
0268     ExpectLogicError(path(), [](EventTimingRecorder& recorder) { recorder.BeginGpu(); });
0269     ExpectLogicError(path(), [](EventTimingRecorder& recorder) { recorder.EndGpu(); });
0270     ExpectLogicError(path(), [](EventTimingRecorder& recorder) { recorder.EndEvent(1, 2); });
0271     ExpectLogicError(path(), [](EventTimingRecorder& recorder) {
0272         recorder.BeginEvent(0);
0273         recorder.BeginEvent(1);
0274     });
0275     ExpectLogicError(path(), [](EventTimingRecorder& recorder) {
0276         recorder.BeginEvent(0);
0277         recorder.BeginGpu();
0278     });
0279     ExpectLogicError(path(), [](EventTimingRecorder& recorder) {
0280         recorder.BeginEvent(0);
0281         recorder.SubmitGpu(1, 2);
0282         recorder.EndGpu();
0283     });
0284     ExpectLogicError(path(), [](EventTimingRecorder& recorder) {
0285         recorder.BeginEvent(0);
0286         recorder.SubmitGpu(1, 2);
0287         recorder.BeginGpu();
0288         recorder.EndEvent(1, 2);
0289     });
0290     ExpectLogicError(path(), [](EventTimingRecorder& recorder) {
0291         recorder.BeginEvent(0);
0292         recorder.Write(Metadata());
0293     });
0294     ExpectLogicError(path(), [](EventTimingRecorder& recorder) {
0295         recorder.BeginEvent(0);
0296         recorder.BeginRun();
0297     });
0298 
0299     EventTimingRecorder disabled;
0300     disabled.SubmitGpu(1, 2);
0301     disabled.BeginGpu();
0302     disabled.EndGpu();
0303     disabled.EndEvent(1, 2);
0304     disabled.Write(Metadata());
0305 }
0306 
0307 void TestClockInversion(const fs::path& directory)
0308 {
0309     SetSequence({
0310         TimedSample(100, 0, 0),
0311         TimedSample(200, 0, 0),
0312         TimedSample(150, 0, 0),
0313     });
0314     const fs::path      output = directory / "inversion.csv";
0315     EventTimingRecorder recorder(output, &NextSample);
0316     recorder.BeginRun();
0317     recorder.BeginEvent(3);
0318     bool threw = false;
0319     try
0320     {
0321         recorder.SubmitGpu(1, 2);
0322     }
0323     catch (const std::logic_error&)
0324     {
0325         threw = true;
0326     }
0327     assert(threw);
0328     assert(!fs::exists(output));
0329 }
0330 
0331 void TestIoFailure(const fs::path& directory)
0332 {
0333     const fs::path regular_file = directory / "not-a-directory";
0334     {
0335         std::ofstream output(regular_file);
0336         output << "file";
0337     }
0338     const fs::path      destination = regular_file / "events.csv";
0339     EventTimingRecorder recorder(destination);
0340     CompleteOneEvent(recorder);
0341 
0342     bool threw = false;
0343     try
0344     {
0345         recorder.Write(Metadata());
0346     }
0347     catch (const std::runtime_error& error)
0348     {
0349         threw = std::string(error.what()).find(destination.string()) != std::string::npos;
0350     }
0351     assert(threw);
0352     assert(!fs::exists(destination));
0353 }
0354 } // namespace
0355 
0356 int main()
0357 {
0358     const fs::path directory = TestDirectory();
0359     fs::remove_all(directory);
0360     fs::create_directories(directory);
0361 
0362     TestGoldenCsvAndManifest(directory);
0363     TestAbsoluteTimestampsRoundTripAtLongUptime(directory);
0364     TestInvalidTransitions(directory);
0365     TestClockInversion(directory);
0366     TestIoFailure(directory);
0367 
0368     fs::remove_all(directory);
0369     return 0;
0370 }