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