Warning, file /eic-opticks/tests/check_simg4ox_timing.py was not indexed
or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001
0002 """Validate simg4ox per-event CPU/GPU timing and run metadata."""
0003
0004 from __future__ import annotations
0005
0006 import argparse
0007 import csv
0008 import json
0009 import math
0010 from pathlib import Path
0011
0012
0013 ORDERED_COLUMNS = (
0014 "start_offset_s",
0015 "cpu_pre_start_time_offset_s",
0016 "cpu_pre_end_time_offset_s",
0017 "gpu_submit_time_offset_s",
0018 "gpu_wait_start_time_offset_s",
0019 "gpu_start_time_offset_s",
0020 "gpu_end_time_offset_s",
0021 "gpu_wait_end_time_offset_s",
0022 "cpu_post_start_time_offset_s",
0023 "cpu_post_end_time_offset_s",
0024 "end_offset_s",
0025 )
0026
0027 NONNEGATIVE_COLUMNS = (
0028 "runtime_s",
0029 "cpu_pre_runtime_s",
0030 "cpu_post_runtime_s",
0031 "cpu_runtime_s",
0032 "gpu_queue_delay_s",
0033 "gpu_runtime_s",
0034 "gpu_wait_runtime_s",
0035 "process_cpu_s",
0036 "thread_cpu_s",
0037 )
0038
0039
0040 def main() -> None:
0041 parser = argparse.ArgumentParser(description=__doc__)
0042 parser.add_argument("timing_csv", type=Path)
0043 parser.add_argument("--events", type=int, required=True)
0044 parser.add_argument("--particle")
0045 parser.add_argument("--require-photons", action="store_true")
0046 args = parser.parse_args()
0047
0048 with args.timing_csv.open(newline="") as stream:
0049 rows = list(csv.DictReader(stream))
0050
0051 if len(rows) != args.events:
0052 raise AssertionError(f"Expected {args.events} timing rows, found {len(rows)}")
0053 if [int(row["event_id"]) for row in rows] != list(range(args.events)):
0054 raise AssertionError("Event IDs are not consecutive and zero-based")
0055
0056 for row in rows:
0057 event_id = int(row["event_id"])
0058 ordered = [float(row[column]) for column in ORDERED_COLUMNS]
0059 if not all(math.isfinite(value) for value in ordered):
0060 raise AssertionError(f"Event {event_id} contains non-finite timestamps")
0061 if any(later + 1e-9 < earlier for earlier, later in zip(ordered, ordered[1:])):
0062 raise AssertionError(f"Event {event_id} stage timestamps are not monotonic: {ordered}")
0063 for column in NONNEGATIVE_COLUMNS:
0064 value = float(row[column])
0065 if not math.isfinite(value) or value < 0:
0066 raise AssertionError(f"Event {event_id} has invalid {column}={value}")
0067 expected_wait = float(row["gpu_queue_delay_s"]) + float(row["gpu_runtime_s"])
0068 if not math.isclose(
0069 float(row["gpu_wait_runtime_s"]),
0070 expected_wait,
0071 rel_tol=1e-6,
0072 abs_tol=1e-9,
0073 ):
0074 raise AssertionError(f"Event {event_id} blocking GPU wait does not span queue plus execution")
0075 if args.particle and row["primary_particle"] != args.particle:
0076 raise AssertionError(f"Event {event_id} particle is {row['primary_particle']}, expected {args.particle}")
0077 if args.require_photons and int(row["num_photons"]) <= 0:
0078 raise AssertionError(f"Event {event_id} did not produce GPU optical photons")
0079
0080 manifest_path = args.timing_csv.with_suffix(".manifest.json")
0081 manifest = json.loads(manifest_path.read_text())
0082 if manifest["schema_version"] != 2:
0083 raise AssertionError("Manifest does not use the shared timeline schema version")
0084 if manifest["dispatch_mode"] != "blocking":
0085 raise AssertionError("simg4ox timing manifest must identify blocking dispatch")
0086 if manifest["event_count"] != args.events:
0087 raise AssertionError("Manifest event_count does not match timing CSV")
0088 if args.particle and manifest["primary_particle"] != args.particle:
0089 raise AssertionError("Manifest particle does not match the requested source")
0090
0091 print(f"TIMING_EVENTS={len(rows)}")
0092 print(f"TIMING_CPU_BUSY_S={sum(float(row['cpu_runtime_s']) for row in rows):.9f}")
0093 print(f"TIMING_GPU_BUSY_S={sum(float(row['gpu_runtime_s']) for row in rows):.9f}")
0094
0095
0096 if __name__ == "__main__":
0097 main()