ATLAS Offline Software
Loading...
Searching...
No Matches
PhysliteTestXaod.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
6
7
8//
9// includes
10//
11
13
14#include <AsgTesting/UnitTest.h>
19
22#ifndef XAOD_STANDALONE
24#endif
25
26#include <Rtypes.h>
27
28#include <algorithm>
29#include <array>
30#include <format>
31#include <iostream>
32#include <memory>
33#include <span>
34#include <stdexcept>
35#include <utility>
36#include <vector>
37
38//
39// method implementations
40//
41
42namespace columnar
43{
44 namespace TestUtils
45 {
46 namespace
47 {
48 using namespace columnar::TestUtils;
49
51 struct IEventReader
52 {
53 virtual ~IEventReader() = default;
54 virtual StatusCode readFrom(TFile* file) = 0;
55 virtual Long64_t getEntries() = 0;
56 virtual StatusCode getEntry(std::uint64_t entry) = 0;
57 virtual void clearStore() = 0;
58 virtual bool hasClearStore() const = 0;
59 virtual void printBenchmarkTable(float emptyTime) = 0;
60 };
61
62
68 struct XAODEventReader final : IEventReader
69 {
70 xAOD::TEvent m_event;
71 xAOD::TStore m_store;
72 Benchmark m_benchmarkGetEntry;
73 Benchmark m_benchmarkClear;
74 Benchmark m_benchmarkEmptyClear;
75
76 StatusCode readFrom(TFile* file) override
77 {
78 using namespace asg::msgUserCode;
79 ANA_CHECK(m_event.readFrom(file));
80 // Print known keys diagnostic
81 std::cout << "known container keys:" << std::endl;
82 for (auto& [container, key] : knownKeys)
83 {
84 std::cout << std::format(" {} -> 0x{:x}, 0x{:x} -> {}",
85 container, m_event.getHash(container), key, m_event.getName(key)) << std::endl;
86 }
87 return StatusCode::SUCCESS;
88 }
89
90 Long64_t getEntries() override
91 {
92 return m_event.getEntries();
93 }
94
95 StatusCode getEntry(std::uint64_t entry) override
96 {
97 m_benchmarkGetEntry.startTimer();
98 auto result = m_event.getEntry(entry);
99 m_benchmarkGetEntry.stopTimer();
100 return result >= 0 ? StatusCode::SUCCESS : StatusCode::FAILURE;
101 }
102
103 void clearStore() override
104 {
105 m_benchmarkClear.startTimer();
106 m_store.clear();
107 m_benchmarkClear.stopTimer();
108 // Measure empty-clear overhead
109 m_benchmarkEmptyClear.startTimer();
110 m_store.clear();
111 m_benchmarkEmptyClear.stopTimer();
112 }
113
114 bool hasClearStore() const override { return true; }
115
116 void printBenchmarkTable(float emptyTime) override
117 {
118 std::string header = " | getEntry(ns) | clear(ns) | empty clear(ns)";
119 std::cout << "\n" << header << std::endl;
120 std::cout << std::string(header.size(), '-') << std::endl;
121 std::cout << "xAOD |";
122 auto getEntryTime = m_benchmarkGetEntry.getEntryTime(emptyTime);
123 auto clearTime = m_benchmarkClear.getEntryTime(emptyTime);
124 auto emptyClearTime = m_benchmarkEmptyClear.getEntryTime(emptyTime);
125 if (getEntryTime)
126 std::cout << std::format("{:>13.0f} |", getEntryTime.value());
127 else
128 std::cout << " |";
129 if (clearTime)
130 std::cout << std::format("{:>10.0f} |", clearTime.value());
131 else
132 std::cout << " |";
133 if (emptyClearTime)
134 std::cout << std::format("{:>16.0f}", emptyClearTime.value());
135 std::cout << std::endl;
136 m_benchmarkGetEntry.setSilence();
137 m_benchmarkClear.setSilence();
138 m_benchmarkEmptyClear.setSilence();
139 }
140 };
141
142
143#ifndef XAOD_STANDALONE
145 struct POOLEventReader final : IEventReader
146 {
147 POOL::TEvent m_event;
148 Benchmark m_benchmarkGetEntry;
149
150 StatusCode readFrom(TFile* file) override
151 {
152 return m_event.readFrom(file);
153 }
154
155 Long64_t getEntries() override
156 {
157 return m_event.getEntries();
158 }
159
160 StatusCode getEntry(std::uint64_t entry) override
161 {
162 m_benchmarkGetEntry.startTimer();
163 auto result = m_event.getEntry(entry);
164 m_benchmarkGetEntry.stopTimer();
165 return result >= 0 ? StatusCode::SUCCESS : StatusCode::FAILURE;
166 }
167
168 void clearStore() override
169 {
170 // POOL::TEvent has no separate store to clear
171 }
172
173 bool hasClearStore() const override { return false; }
174
175 void printBenchmarkTable(float emptyTime) override
176 {
177 std::string header = " | getEntry(ns)";
178 std::cout << "\n" << header << std::endl;
179 std::cout << std::string(header.size(), '-') << std::endl;
180 std::cout << "POOL |";
181 auto getEntryTime = m_benchmarkGetEntry.getEntryTime(emptyTime);
182 if (getEntryTime)
183 std::cout << std::format("{:>13.0f}", getEntryTime.value());
184 std::cout << std::endl;
185 m_benchmarkGetEntry.setSilence();
186 }
187 };
188#endif
189
190
191 std::unique_ptr<IEventReader> makeEventReader()
192 {
193 // while xAOD::TEvent/TStore exists in Athena builds, they don't
194 // allow objects to be retrieved via the event store, so we use
195 // POOLRootAccess in Athena builds.
196#ifdef XAOD_STANDALONE
197 return std::make_unique<XAODEventReader>();
198#else
199 return std::make_unique<POOLEventReader>();
200#endif
201 }
202
203
209 struct ToolData
210 {
211 std::string name;
212 IXAODToolCaller* xAODToolCaller = nullptr;
213 bool noRepeatCall = false;
214
215 // Arrays of benchmarks indexed by call index
216 // retrieve and copyRecord only used for prep and call (not repeat)
217 std::array<Benchmark, 2> benchmarkRetrieve;
218 std::array<Benchmark, 2> benchmarkCopyRecord;
219 std::array<Benchmark, 3> benchmarkCall;
220 std::array<Benchmark, 2> benchmarkClear;
221
222 StatusCode retrieve(std::size_t index, IXAODToolCaller::EventStoreType& evtStore)
223 {
224 if (index >= benchmarkRetrieve.size())
225 throw std::out_of_range("ToolData::retrieve index out of range");
226 benchmarkRetrieve[index].startTimer();
227 auto result = xAODToolCaller->retrieve(evtStore);
228 benchmarkRetrieve[index].stopTimer();
229 return result;
230 }
231
232 StatusCode copyRecord(std::size_t index, IXAODToolCaller::EventStoreType& evtStore,
233 const std::string& postfix)
234 {
235 if (index >= benchmarkCopyRecord.size())
236 throw std::out_of_range("ToolData::copyRecord index out of range");
237 benchmarkCopyRecord[index].startTimer();
238 auto result = xAODToolCaller->copyRecord(evtStore, postfix);
239 benchmarkCopyRecord[index].stopTimer();
240 return result;
241 }
242
243 StatusCode call(std::size_t index)
244 {
245 if (index >= benchmarkCall.size())
246 throw std::out_of_range("ToolData::call index out of range");
247 benchmarkCall[index].startTimer();
248 auto result = xAODToolCaller->call();
249 benchmarkCall[index].stopTimer();
250 return result;
251 }
252
253 void clear(std::size_t index)
254 {
255 if (index >= benchmarkClear.size())
256 throw std::out_of_range("ToolData::clear index out of range");
257 benchmarkClear[index].startTimer();
258 xAODToolCaller->clear();
259 benchmarkClear[index].stopTimer();
260 }
261 };
262 }
263
264
265
266 void runXaodTest (const UserConfiguration& userConfiguration, std::span<const TestDefinition> testDefinitions, TFile *file)
267 {
268 using namespace asg::msgUserCode;
269
270 auto eventReader = makeEventReader();
271 ANA_CHECK_THROW(eventReader->readFrom(file));
272
273 std::vector<ToolData> toolDataVec;
274 for (const auto& testDefinition : testDefinitions)
275 {
276 if (testDefinition.xAODToolCaller != nullptr)
277 {
278 ToolData toolData;
279 toolData.name = testDefinition.name;
280 toolData.xAODToolCaller = testDefinition.xAODToolCaller;
281 toolData.noRepeatCall = testDefinition.noRepeatCall;
282 toolDataVec.push_back(std::move(toolData));
283 }
284 }
285
286 IXAODToolCaller::EventStoreType* evtStore = nullptr;
287 if (!testDefinitions.empty() && testDefinitions[0].tool)
288 evtStore = &*testDefinitions[0].tool->evtStore();
289
290 Benchmark benchmarkEmpty;
291
292 const std::uint64_t numberOfEvents = eventReader->getEntries();
293 if (numberOfEvents == 0){
294 throw std::runtime_error ("ColumnarPhysLiteTest: numberOfEvents == 0");
295 }
296 std::uint64_t entry = 0;
297
298 // Instead of running for a fixed number of events, we run for a
299 // fixed amount of time. That is because individual tools can
300 // vary wildly in how long they take to run, and we mostly want to
301 // make sure that we ran the tool enough to get a precise
302 // performance estimate.
303 const auto startTime = std::chrono::high_resolution_clock::now();
304 for (; (std::chrono::high_resolution_clock::now() - startTime) < userConfiguration.targetTime; ++entry)
305 {
306 benchmarkEmpty.startTimer();
307 benchmarkEmpty.stopTimer();
308 ANA_CHECK_THROW(eventReader->getEntry(entry % numberOfEvents));
309 if (eventReader->hasClearStore())
310 {
311 static const std::string prepPostfix = "Prep";
312 for (auto& toolData : toolDataVec)
313 ASSERT_SUCCESS (toolData.retrieve (0, *evtStore));
314 for (auto& toolData : toolDataVec)
315 ASSERT_SUCCESS (toolData.copyRecord (0, *evtStore, prepPostfix));
316 for (auto& toolData : toolDataVec)
317 ASSERT_SUCCESS (toolData.call (0));
318 for (auto& toolData : toolDataVec)
319 toolData.clear (0);
320 eventReader->clearStore();
321 }
322 static const std::string callPostfix = "Call";
323 for (auto& toolData : toolDataVec)
324 ASSERT_SUCCESS (toolData.retrieve (1, *evtStore));
325 for (auto& toolData : toolDataVec)
326 ASSERT_SUCCESS (toolData.copyRecord (1, *evtStore, callPostfix));
327 for (auto& toolData : toolDataVec)
328 {
329 ASSERT_SUCCESS (toolData.call (1));
330 // a second call, immediately after, to have the tool in
331 // instruction cache and simulate it being run on multi-event
332 // batches. this is a bit too good, as it will run on exactly
333 // the same event, instead of different events as would happen
334 // in a real multi-event batch.
335 if (userConfiguration.runToolTwice && !toolData.noRepeatCall)
336 {
337 ASSERT_SUCCESS (toolData.call (2));
338 }
339 }
340 for (auto& toolData : toolDataVec)
341 toolData.clear (1);
342 if (eventReader->hasClearStore())
343 eventReader->clearStore();
344 }
345 std::cout << "Total entries read: " << entry << std::endl;
346 const float emptyTime = benchmarkEmpty.getEntryTime(0).value();
347 std::cout << "Empty benchmark time: " << emptyTime << "ns (tick=" << Benchmark::getTickDuration() << "ns)" << std::endl;
348 eventReader->printBenchmarkTable(emptyTime);
349
350 // Fill vector of ToolPerfData pairs from benchmarks
351 std::vector<std::pair<ToolPerfData, ToolPerfData>> toolPerfData;
352 for (auto& toolData : toolDataVec)
353 {
354 ToolPerfData prepPerfData;
355 prepPerfData.name = toolData.name;
356 if (eventReader->hasClearStore()) {
357 prepPerfData.timeRetrieve = toolData.benchmarkRetrieve[0].getEntryTime(emptyTime);
358 prepPerfData.timeCopyRecord = toolData.benchmarkCopyRecord[0].getEntryTime(emptyTime);
359 prepPerfData.timeCall = toolData.benchmarkCall[0].getEntryTime(emptyTime);
360 prepPerfData.timeClear = toolData.benchmarkClear[0].getEntryTime(emptyTime);
361 toolData.benchmarkRetrieve[0].setSilence();
362 toolData.benchmarkCopyRecord[0].setSilence();
363 toolData.benchmarkCall[0].setSilence();
364 toolData.benchmarkClear[0].setSilence();
365 }
366
367 ToolPerfData callPerfData;
368 callPerfData.name = toolData.name;
369 callPerfData.timeRetrieve = toolData.benchmarkRetrieve[1].getEntryTime(emptyTime);
370 callPerfData.timeCopyRecord = toolData.benchmarkCopyRecord[1].getEntryTime(emptyTime);
371 callPerfData.timeCall = toolData.benchmarkCall[1].getEntryTime(emptyTime);
372 callPerfData.timeClear = toolData.benchmarkClear[1].getEntryTime(emptyTime);
373 if (userConfiguration.runToolTwice) {
374 callPerfData.timeCall2 = toolData.benchmarkCall[2].getEntryTime(emptyTime);
375 toolData.benchmarkCall[2].setSilence();
376 }
377 toolData.benchmarkRetrieve[1].setSilence();
378 toolData.benchmarkCopyRecord[1].setSilence();
379 toolData.benchmarkCall[1].setSilence();
380 toolData.benchmarkClear[1].setSilence();
381
382 toolPerfData.emplace_back(std::move(prepPerfData), std::move(callPerfData));
383 }
384
385 // Helper lambda for printing optional time values
386 auto printOptionalTime = [](std::optional<float> time, int width, bool trailingBar = true) {
387 if (time)
388 std::cout << std::format("{:>{}.0f}", time.value(), width);
389 else
390 std::cout << std::format("{:>{}}", "", width);
391 if (trailingBar)
392 std::cout << " |";
393 };
394
395 // Calculate name width from all entries
396 std::size_t nameWidth = std::string_view("tool name").size();
397 for (const auto& [prep, call] : toolPerfData)
398 nameWidth = std::max(nameWidth, prep.name.size());
399
400 // Print tool performance table with grouped columns
401 std::string groupHeader = std::format("{:{}} |{:^42}|{:^52}",
402 "", nameWidth, "prep", "call");
403 std::string colHeader = std::format("{:{}} | retr(ns) | copy(ns) | call(ns) | clr(ns) | retr(ns) | copy(ns) | call(ns) | 2nd(ns) | clr(ns)",
404 "tool name", nameWidth);
405
406 std::cout << "\ntool benchmarks:" << std::endl;
407 std::cout << groupHeader << std::endl;
408 std::cout << colHeader << std::endl;
409 std::cout << std::string(colHeader.size(), '-') << std::endl;
410
411 // Print data rows
412 for (const auto& [prep, call] : toolPerfData)
413 {
414 std::cout << std::format("{:{}} |", prep.name, nameWidth);
415 printOptionalTime(prep.timeRetrieve, 9);
416 printOptionalTime(prep.timeCopyRecord, 9);
417 printOptionalTime(prep.timeCall, 9);
418 printOptionalTime(prep.timeClear, 8);
419 printOptionalTime(call.timeRetrieve, 9);
420 printOptionalTime(call.timeCopyRecord, 9);
421 printOptionalTime(call.timeCall, 9);
422 printOptionalTime(call.timeCall2, 8);
423 printOptionalTime(call.timeClear, 8, false);
424 std::cout << std::endl;
425 }
426
427 // Print total line only when there are multiple tools
428 if (toolPerfData.size() > 1)
429 {
430 std::optional<float> totalPrepRetrieve, totalPrepCopyRecord, totalPrepCall, totalPrepClear;
431 std::optional<float> totalCallRetrieve, totalCallCopyRecord, totalCallCall, totalCallClear, totalRepeat;
432 for (const auto& [prep, call] : toolPerfData)
433 {
434 if (prep.timeRetrieve)
435 totalPrepRetrieve = totalPrepRetrieve.value_or(0) + prep.timeRetrieve.value();
436 if (prep.timeCopyRecord)
437 totalPrepCopyRecord = totalPrepCopyRecord.value_or(0) + prep.timeCopyRecord.value();
438 if (prep.timeCall)
439 totalPrepCall = totalPrepCall.value_or(0) + prep.timeCall.value();
440 if (prep.timeClear)
441 totalPrepClear = totalPrepClear.value_or(0) + prep.timeClear.value();
442 if (call.timeRetrieve)
443 totalCallRetrieve = totalCallRetrieve.value_or(0) + call.timeRetrieve.value();
444 if (call.timeCopyRecord)
445 totalCallCopyRecord = totalCallCopyRecord.value_or(0) + call.timeCopyRecord.value();
446 if (call.timeCall)
447 totalCallCall = totalCallCall.value_or(0) + call.timeCall.value();
448 if (call.timeClear)
449 totalCallClear = totalCallClear.value_or(0) + call.timeClear.value();
450 if (call.timeCall2)
451 totalRepeat = totalRepeat.value_or(0) + call.timeCall2.value();
452 }
453
454 std::cout << std::string(colHeader.size(), '-') << std::endl;
455 std::cout << std::format("{:{}} |", "total", nameWidth);
456 printOptionalTime(totalPrepRetrieve, 9);
457 printOptionalTime(totalPrepCopyRecord, 9);
458 printOptionalTime(totalPrepCall, 9);
459 printOptionalTime(totalPrepClear, 8);
460 printOptionalTime(totalCallRetrieve, 9);
461 printOptionalTime(totalCallCopyRecord, 9);
462 printOptionalTime(totalCallCall, 9);
463 printOptionalTime(totalRepeat, 8);
464 printOptionalTime(totalCallClear, 8, false);
465 std::cout << std::endl;
466 }
467 }
468 }
469}
#define ANA_CHECK(EXP)
check whether the given expression was successful
#define ANA_CHECK_THROW(EXP)
check whether the given expression was successful, throwing an exception on failure
int numberOfEvents()
const double width
this is a simple benchmarking helper class wrapping timers from std::chrono
Definition Benchmark.h:51
static float getTickDuration()
Definition Benchmark.h:86
std::optional< float > getEntryTime(float emptyTime) const
Definition Benchmark.h:74
std::decay_t< decltype(*std::declval< asg::AsgTool >().evtStore())> EventStoreType
the type used for the event store
virtual void clear()
clear any cached data between events
virtual StatusCode call()=0
call the tool for a single event
virtual StatusCode copyRecord(EventStoreType &evtStore, const std::string &postfix)=0
do any copying and recording needed
virtual StatusCode retrieve(EventStoreType &evtStore)=0
retrieve everything we need from the event store
str index
Definition DeMoScan.py:362
::StatusCode StatusCode
StatusCode definition for legacy code.
void runXaodTest(const UserConfiguration &userConfiguration, std::span< const TestDefinition > testDefinitions, TFile *file)
const std::unordered_map< std::string, SG::sgkey_t > knownKeys
call(args, bufsize=0, executable=None, stdin=None, preexec_fn=None, close_fds=False, shell=False, cwd=None, env=None, universal_newlines=False, startupinfo=None, creationflags=0, message="", logger=msg, loglevel=None, timeout=None, retry=2, timefactor=1.5, sleeptime=10)
Definition trfUtils.py:157
the performance data for running a single tool
std::optional< float > timeClear
std::optional< float > timeCall2
std::optional< float > timeRetrieve
std::optional< float > timeCall
std::optional< float > timeCopyRecord
TFile * file