Project
Loading...
Searching...
No Matches
compareNativeToRoot.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12// o2-test-framework-aod-native-vs-root [--threads N] [--pread] [--prefetch N] [--max-dfs N] [--bench root|native] <file>...
13//
14// Reads every tree of every DF with the TTree plugin and with the native
15// reader, serialises both as DPL does (deferred output stream into an Arrow
16// IPC stream) and requires the bytes to be identical. Exits non-zero on any
17// difference, naming the first differing column. With --bench, reads with
18// that backend only and reports wall time, CPU time and peak RSS instead.
19// A file given as <native url>=<ROOT url> is read from different places by
20// the two readers, e.g. over HTTP by one and locally by the other.
21
22#include "../src/AODInputFile.h"
23#include "../src/NativeAOD.h"
24#include "AODNative/Errors.h"
26#include "Framework/Plugins.h"
28#include <arrow/buffer.h>
29#include <arrow/dataset/file_base.h>
30#include <arrow/io/memory.h>
31#include <arrow/ipc/reader.h>
32#include <arrow/ipc/writer.h>
33#include <arrow/record_batch.h>
34#include <TGrid.h>
35#include <sys/resource.h>
36#include <chrono>
37#include <cstdio>
38#include <cstring>
39#include <regex>
40#include <string>
41
42using namespace o2::framework;
43
44namespace
45{
46
47template <typename T>
48T orThrow(arrow::Result<T> result, std::string const& what)
49{
50 if (!result.ok()) {
51 throw std::runtime_error(what + ": " + result.status().ToString());
52 }
53 return std::move(result).ValueUnsafe();
54}
55
56struct Serialised {
57 std::shared_ptr<arrow::Buffer> bytes;
58 std::string capability;
59};
60
61// Same steps as DataInputDescriptor::readTree, FragmentToBatch and
62// DataAllocator::adopt, with a heap buffer in place of the shm message.
63Serialised serialise(RootObjectReadingFactory& factory, std::shared_ptr<VirtualRootFileSystemBase> fs, std::string const& path)
64{
65 arrow::dataset::FileSource source{path, fs};
66 for (auto& capability : factory.capabilities) {
67 if (!capability.getHandle(fs, capability.lfn2objectPath(path))) {
68 continue;
69 }
70 auto format = capability.factory().format();
71 auto physicalSchema = orThrow(format->Inspect(source), "inspect");
72 std::vector<std::shared_ptr<arrow::Field>> fields;
73 for (auto& field : physicalSchema->fields()) {
74 if (!field->name().ends_with("_size")) {
75 fields.push_back(field);
76 }
77 }
78 auto datasetSchema = std::make_shared<arrow::Schema>(fields);
79 auto fragment = orThrow(format->MakeFragment(source, {}, physicalSchema), "fragment");
80 auto options = std::make_shared<arrow::dataset::ScanOptions>();
81 options->dataset_schema = datasetSchema;
82 auto batch = orThrow(orThrow(format->ScanBatchesAsync(options, fragment), "scan")().result(), "scan");
83
84 auto mock = std::make_shared<arrow::io::MockOutputStream>();
85 (void)arrow::ipc::MakeStreamWriter(mock.get(), batch->schema()).ValueOrDie()->WriteRecordBatch(*batch);
86 std::shared_ptr<arrow::ResizableBuffer> buffer = arrow::AllocateResizableBuffer(0, 64).ValueOrDie();
87 if (!buffer->Reserve(mock->Tell().ValueOrDie()).ok()) {
88 throw std::runtime_error("cannot reserve " + path);
89 }
90 auto stream = capability.factory().deferredOutputStreamer(fragment, buffer);
91 auto writer = arrow::ipc::MakeStreamWriter(stream, batch->schema()).ValueOrDie();
92 auto status = writer->WriteRecordBatch(*batch);
93 if (status.ok()) {
94 status = writer->Close();
95 }
96 if (status.ok()) {
97 status = stream->Close();
98 }
99 if (!status.ok()) {
100 throw std::runtime_error(path + ": " + status.ToString());
101 }
102 return {buffer, capability.name};
103 }
104 throw std::runtime_error("no capability reads " + path);
105}
106
107std::shared_ptr<arrow::RecordBatch> decode(std::shared_ptr<arrow::Buffer> const& bytes)
108{
109 auto reader = arrow::ipc::RecordBatchStreamReader::Open(std::make_shared<arrow::io::BufferReader>(bytes)).ValueOrDie();
110 std::shared_ptr<arrow::RecordBatch> batch;
111 (void)reader->ReadNext(&batch);
112 return batch;
113}
114
115std::string describeDifference(Serialised const& root, Serialised const& native)
116{
117 auto r = decode(root.bytes);
118 auto n = decode(native.bytes);
119 if (!r || !n) {
120 return "one of the two streams has no batch";
121 }
122 if (!r->schema()->Equals(*n->schema())) {
123 return "schemas differ:\n root: " + r->schema()->ToString() + "\n native: " + n->schema()->ToString();
124 }
125 if (r->num_rows() != n->num_rows()) {
126 return "rows differ: " + std::to_string(r->num_rows()) + " vs " + std::to_string(n->num_rows());
127 }
128 for (int i = 0; i < r->num_columns(); ++i) {
129 if (!r->column(i)->Equals(n->column(i))) {
130 return "column " + r->schema()->field(i)->name() + " differs";
131 }
132 }
133 return "same values, different bytes (" + std::to_string(root.bytes->size()) + " vs " + std::to_string(native.bytes->size()) + ")";
134}
135
136double cpuSeconds(timeval const& tv) { return tv.tv_sec + tv.tv_usec * 1e-6; }
137
138// The DFs of the file in on-disk order, at most @a maxDFs of them if not negative.
139std::vector<std::string> dataFramesOf(AODInputFile& file, int maxDFs)
140{
141 static const std::regex dfRegex{"DF_[0-9]+"};
142 std::vector<std::string> dfs;
143 for (auto& key : file.topLevelKeys()) {
144 if (std::regex_match(key.name, dfRegex) && (maxDFs < 0 || (int)dfs.size() < maxDFs) && std::ranges::find(dfs, key.name) == dfs.end()) {
145 dfs.push_back(key.name);
146 }
147 }
148 return dfs;
149}
150
151} // namespace
152
153int main(int argc, char** argv)
154{
155 o2::aod::native::Options nativeOptions;
156 int maxDFs = -1;
157 std::string bench;
158 std::vector<std::string> files;
159 for (int i = 1; i < argc; ++i) {
160 if (std::strcmp(argv[i], "--threads") == 0 && i + 1 < argc) {
161 nativeOptions.threads = std::stoi(argv[++i]);
162 } else if (std::strcmp(argv[i], "--pread") == 0) {
163 nativeOptions.localIO = o2::aod::native::Options::LocalIO::PRead;
164 } else if (std::strcmp(argv[i], "--prefetch") == 0 && i + 1 < argc) {
165 nativeOptions.prefetchDepth = std::stoi(argv[++i]);
166 } else if (std::strcmp(argv[i], "--max-dfs") == 0 && i + 1 < argc) {
167 maxDFs = std::stoi(argv[++i]);
168 } else if (std::strcmp(argv[i], "--bench") == 0 && i + 1 < argc) {
169 bench = argv[++i];
170 } else {
171 files.push_back(argv[i]);
172 }
173 }
174 if (files.empty() || (!bench.empty() && bench != "root" && bench != "native")) {
175 std::fprintf(stderr, "usage: %s [--threads N] [--pread] [--prefetch N] [--max-dfs N] [--bench root|native] <file>...\n", argv[0]);
176 return 2;
177 }
178
180 std::vector<LoadablePlugin> plugins;
181 for (auto spec : {"O2FrameworkAnalysisNativeAODSupport:NativeAODObjectReadingCapability", "O2Framework:TTreeObjectReadingCapability"}) {
182 for (auto& plugin : PluginManager::parsePluginSpecString(spec)) {
183 plugins.push_back(plugin);
184 }
185 }
186 PluginManager::loadFromPlugin<RootObjectReadingCapability, RootObjectReadingCapabilityPlugin>(plugins, factory.capabilities);
187 for (auto& name : files) {
188 if (name.starts_with("alien://") && !gGrid) {
189 TGrid::Connect("alien://");
190 }
191 }
192
193 int failures = 0;
194 if (!bench.empty()) {
195 for (auto& name : files) {
196 auto start = std::chrono::steady_clock::now();
197 rusage before;
198 getrusage(RUSAGE_SELF, &before);
199 std::shared_ptr<AODInputFile> file;
200 if (bench == "root") {
201 file = RootAODInputFile::open(name, factory);
202 } else {
203 file = openNativeAOD(name, factory, nativeOptions);
204 }
205 size_t trees = 0, bytes = 0;
206 auto order = dataFramesOf(*file, maxDFs);
207 file->setReadOrder(order);
208 int dfs = order.size();
209 for (auto& df : order) {
210 for (auto& tree : file->keysIn(df)) {
211 bytes += serialise(factory, file->filesystem(), df + "/" + tree).bytes->size();
212 ++trees;
213 }
214 }
215 auto bytesRead = file->bytesRead();
216 auto readCalls = file->readCalls();
217 file.reset();
218 double wall = std::chrono::duration<double>(std::chrono::steady_clock::now() - start).count();
219 rusage after;
220 getrusage(RUSAGE_SELF, &after);
221 double user = cpuSeconds(after.ru_utime) - cpuSeconds(before.ru_utime);
222 double sys = cpuSeconds(after.ru_stime) - cpuSeconds(before.ru_stime);
223#ifdef __APPLE__
224 double maxRssMiB = after.ru_maxrss / 1048576.;
225#else
226 double maxRssMiB = after.ru_maxrss / 1024.;
227#endif
228 std::printf("%s %s: %d DFs, %zu trees, %.1f MB out, read %.1f MB in %llu calls; wall %.2f s, user %.2f s, sys %.2f s, %.0f MB/s out, max RSS %.0f MiB\n",
229 bench.c_str(), name.c_str(), dfs, trees, bytes / 1e6, bytesRead / 1e6, (unsigned long long)readCalls,
230 wall, user, sys, bytes / 1e6 / wall, maxRssMiB);
231 }
232 return 0;
233 }
234 for (auto& file : files) {
235 auto split = file.find('=');
236 auto name = file.substr(0, split);
237 auto rootName = split == std::string::npos ? name : file.substr(split + 1);
238 auto root = RootAODInputFile::open(rootName, factory);
239 if (!root) {
240 std::printf("FAIL %s: ROOT cannot open it\n", name.c_str());
241 ++failures;
242 continue;
243 }
244 std::shared_ptr<AODInputFile> native;
245 try {
246 native = openNativeAOD(name, factory, nativeOptions);
247 } catch (o2::aod::native::FormatError const& e) {
248 std::printf("FAIL %s: native reader cannot open it: %s\n", name.c_str(), e.what());
249 ++failures;
250 continue;
251 }
252 if (root->parentFiles() != native->parentFiles() || root->metaData() != native->metaData()) {
253 std::printf("FAIL %s: parentFiles or metaData differ\n", name.c_str());
254 ++failures;
255 }
256 size_t trees = 0, rootBytes = 0, nativeBytes = 0;
257 auto order = dataFramesOf(*root, maxDFs);
258 native->setReadOrder(order);
259 int dfs = order.size();
260 for (auto& df : order) {
261 auto nativeKeys = native->keysIn(df);
262 if (nativeKeys != root->keysIn(df)) {
263 std::printf("FAIL %s: keys of %s differ\n", name.c_str(), df.c_str());
264 ++failures;
265 continue;
266 }
267 for (auto& tree : nativeKeys) {
268 auto path = df + "/" + tree;
269 try {
270 auto r = serialise(factory, root->filesystem(), path);
271 auto n = serialise(factory, native->filesystem(), path);
272 ++trees;
273 rootBytes += r.bytes->size();
274 nativeBytes += n.bytes->size();
275 if (r.capability != "ttree" || n.capability != "native-ttree") {
276 std::printf("FAIL %s %s: read by %s and %s\n", name.c_str(), path.c_str(), r.capability.c_str(), n.capability.c_str());
277 ++failures;
278 } else if (!r.bytes->Equals(*n.bytes)) {
279 std::printf("FAIL %s %s: %s\n", name.c_str(), path.c_str(), describeDifference(r, n).c_str());
280 ++failures;
281 }
282 } catch (std::exception const& e) {
283 std::printf("FAIL %s %s: %s\n", name.c_str(), path.c_str(), e.what());
284 ++failures;
285 }
286 }
287 }
288 std::printf("%s: %d DFs, %zu trees, %zu / %zu bytes serialised (root / native), native read %llu bytes in %llu calls\n",
289 name.c_str(), dfs, trees, rootBytes, nativeBytes, (unsigned long long)native->bytesRead(), (unsigned long long)native->readCalls());
290 }
291 std::printf("%s: %d failure(s)\n", failures ? "FAILED" : "OK", failures);
292 return failures ? 1 : 0;
293}
std::vector< std::shared_ptr< arrow::Field > > fields
int32_t i
configStandalone Light OpenGL display display_light Apply smoothing to points Enable Z buffer Highlight links Highlight seeds Highlight sector tracks Highlight final tracks Show individual Show related Highlight grid Show only individual collision Color clusters belonging to track objects Set line Enable drawing TPC data Enable drawing TOF data Enable drawing magnetic field Set field line step count Invert colors configStandalone Heavy OpenGL display display_heavy Show tracks with ITS contribution Show tracks with TOF contribution Enable propagation of loopers Show clusters at nominal x of pad row for early transformed data Split CE tracks when they cross the central Mark clusters Mark adjacent clusters Hide rejected tracks Apply filter on tracks to be displayed Separate increase X h configStandalone Camera window renderer OpenGL display display_camera Orient the camera such that the y axis is always upwards Full Screen MultiSample Anti Downsampling Anti Enable Vertical Sync Target frame or if the backend cannot limit to the refresh Use OpenGL indirect draws to reduce number of draw calls Display h configStandalone Vulkan display display_vulkan Have uniform buffers in host accessible device memory h configStandalone Internal display display_internal Number of updates the Qt GUI may send ahead of the last one the display has before it waits for the display Opacity of the console background Number of console lines the history is shortened to this when it reaches twice the size Time in seconds an info message fades out after setInfoTime h OpenGL display display Font size Do not use Freetype for font Renderer for event Frontend to use for event ROOT macros used as track filter Console script file
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char e
size_t bytes
StringRef key
static std::shared_ptr< RootAODInputFile > open(std::string const &name, RootObjectReadingFactory &factory, TFile *external=nullptr)
GLdouble n
Definition glcorearb.h:1982
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint buffer
Definition glcorearb.h:655
GLuint const GLchar * name
Definition glcorearb.h:781
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLuint GLuint stream
Definition glcorearb.h:1806
GLint GLint GLsizei GLint GLenum format
Definition glcorearb.h:275
std::shared_ptr< arrow::Schema > datasetSchema(std::shared_ptr< arrow::Schema > const &physical)
The physical schema without the *_size fields, as DataInputDirector derives it.
std::shared_ptr< arrow::Schema > physicalSchema(TreeInfo const &tree)
Defining ITS Vertex explicitly as messageable.
Definition Cartesian.h:288
std::shared_ptr< AODInputFile > openNativeAOD(std::string const &filename, RootObjectReadingFactory &factory, o2::aod::native::Options const &options)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
std::vector< std::string > split(const std::string &str, char delimiter=',')
static std::vector< LoadablePlugin > parsePluginSpecString(char const *str)
Parse a comma separated list of <library>:<plugin-name> plugin declarations.
std::vector< RootObjectReadingCapability > capabilities
#define main
coder decode(ctfImage, triggersD, clustersD)
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))