Project
Loading...
Searching...
No Matches
MergeEventPool.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
42
45#include <fairlogger/Logger.h>
46#include <TFile.h>
47#include <TFileMerger.h>
48#include <TGrid.h>
49#include <TMap.h>
50#include <TObjString.h>
51#include <TTree.h>
52#include <boost/program_options.hpp>
53#include <algorithm>
54#include <filesystem>
55#include <fstream>
56#include <memory>
57#include <optional>
58#include <set>
59#include <string>
60#include <vector>
61
62namespace bpo = boost::program_options;
63namespace fs = std::filesystem;
64
65namespace
66{
67const char* kTrackBranch = "MCTrack";
68const char* kHeaderBranch = "MCEventHeader.";
69const char* kTrackRefBranch = "TrackRefs";
70const char* kProtocol = "alien://";
71
72bool isAlienPath(std::string const& path)
73{
74 return o2::utils::Str::beginsWith(path, kProtocol);
75}
76
77// Connects to AliEn if that has not happened yet
78bool GridOn()
79{
80 if (gGrid) {
81 return true;
82 }
83 LOG(info) << "Connecting to AliEn ...";
84 if (!TGrid::Connect("alien:") || !gGrid) {
85 LOG(error) << "Could not connect to AliEn; check your alien token";
86 return false;
87 }
88 return true;
89}
90
91// Reads the lines of a local text file. nullopt if it could not be opened.
92std::optional<std::vector<std::string>> readLocalListFileLines(std::string const& path)
93{
94 std::ifstream in(path);
95 if (!in.is_open()) {
96 return std::nullopt;
97 }
98 std::vector<std::string> lines;
99 std::string line;
100 while (std::getline(in, line)) {
101 lines.push_back(line);
102 }
103 return lines;
104}
105
106// Reads a text file listing input paths, one per line ('#' comments and blank lines
107// ignored). Each listed path is either a .root file (local or alien://) or itself
108// another list file. The lists themselves are always read locally.
109// Returns how many entries, here or in a nested list, could not be resolved.
110size_t expandInputEntry(std::string const& rawEntry, std::vector<std::string>& out, std::vector<std::string>& stack)
111{
112 // done here so that the expansion works also when the variables appear in a list file
113 const auto entry = o2::utils::expandShellVarsInFileName(rawEntry);
114 if (o2::utils::Str::endsWith(entry, ".root")) {
115 out.push_back(entry);
116 return 0;
117 }
118 if (std::find(stack.begin(), stack.end(), entry) != stack.end()) {
119 LOG(error) << "Reference to an existing list " << entry << "; ignoring";
120 return 1;
121 }
122 auto lines = readLocalListFileLines(entry);
123 if (!lines) {
124 LOG(error) << "Cannot open " << entry << " (neither a .root file nor a readable local list)";
125 return 1;
126 }
127 stack.push_back(entry);
128 size_t unresolved = 0;
129 for (auto line : *lines) {
131 if (line.empty() || line[0] == '#') {
132 continue;
133 }
134 unresolved += expandInputEntry(line, out, stack);
135 }
136 stack.pop_back();
137 return unresolved;
138}
139
140// Expands a list of raw --input entries (each either a .root file or a list) into the flat
141// list of .root files to merge, dropping repetitions. Returns how many entries did not resolve.
142size_t expandInputs(std::vector<std::string> const& rawEntries, std::vector<std::string>& infiles)
143{
144 std::vector<std::string> resolved;
145 std::vector<std::string> stack;
146 size_t unresolved = 0;
147 for (auto const& e : rawEntries) {
148 unresolved += expandInputEntry(e, resolved, stack);
149 }
150 std::set<std::string> seen;
151 for (auto const& f : resolved) {
152 if (seen.insert(f).second) {
153 infiles.push_back(f);
154 } else {
155 LOG(warning) << "Input " << f << " is listed more than once; merging it only once";
156 }
157 }
158 return unresolved;
159}
160
161// Checks that a file is readable and holds a tree with the branches expected from a
162// standard o2-sim event pool, reporting its event count and compression settings.
163// Returns an empty string when the file is usable, the reason otherwise.
164std::string inspectFile(std::string const& path, std::string const& treename,
165 Long64_t& entries, int& compression)
166{
167 std::unique_ptr<TFile> file(TFile::Open(path.c_str(), "READ"));
168 if (!file || file->IsZombie()) {
169 return "file does not exist or cannot be opened";
170 }
171 auto tree = (TTree*)file->Get(treename.c_str());
172 if (!tree) {
173 return "no tree named '" + treename + "' in the file";
174 }
175 if (tree->GetBranch(kTrackBranch) == nullptr || tree->GetBranch(kHeaderBranch) == nullptr ||
176 tree->GetBranch(kTrackRefBranch) == nullptr) {
177 return std::string("missing the required '") + kTrackBranch + "', '" + kHeaderBranch + "' and/or '" +
178 kTrackRefBranch + "' branch";
179 }
180 entries = tree->GetEntries();
181 compression = file->GetCompressionSettings();
182 return {};
183}
184
185// Checks every input before anything is written, collecting the usable ones and reporting
186// the total number of events and the compression settings of the first usable input.
187// Returns true when every input passed.
188bool checkFiles(std::vector<std::string> const& files, std::string const& treename,
189 std::vector<std::string>& usable, Long64_t& totalEvents, int& compression)
190{
191 bool ok = true;
192 totalEvents = 0;
193 compression = -1;
194 for (auto const& f : files) {
195 Long64_t entries = 0;
196 int fileCompression = -1;
197 const auto issue = inspectFile(f, treename, entries, fileCompression);
198 if (!issue.empty()) {
199 LOG(error) << "Input file " << f << ": " << issue;
200 ok = false;
201 continue;
202 }
203 if (compression < 0) {
204 compression = fileCompression;
205 }
206 totalEvents += entries;
207 usable.push_back(f);
208 LOG(info) << " OK " << f << " (" << entries << " events)";
209 }
210 return ok;
211}
212
213// Records what the merge was asked for and what actually went into it.
214void writeMergeInfo(std::string const& outfile, std::vector<std::string> const& requested,
215 std::vector<std::string> const& merged, size_t unresolved, Long64_t events)
216{
217 std::unique_ptr<TFile> file(TFile::Open(outfile.c_str(), "UPDATE"));
218 if (!file || file->IsZombie()) {
219 LOG(warning) << "Cannot add the merge information to " << outfile;
220 return;
221 }
222 // the files that were asked for but did not make it, so that the gap can be named from
223 // the file alone and not just counted
224 std::string mergedList, skippedList;
225 for (auto const& f : requested) {
226 if (std::find(merged.begin(), merged.end(), f) != merged.end()) {
227 mergedList += f + "\n";
228 } else {
229 skippedList += f + "\n";
230 }
231 }
232 TMap info;
233 info.SetOwnerKeyValue();
234 info.Add(new TObjString("inputsRequested"), new TObjString(std::to_string(requested.size()).c_str()));
235 info.Add(new TObjString("inputsMerged"), new TObjString(std::to_string(merged.size()).c_str()));
236 info.Add(new TObjString("inputsUnresolved"), new TObjString(std::to_string(unresolved).c_str()));
237 info.Add(new TObjString("events"), new TObjString(std::to_string(events).c_str()));
238 info.Add(new TObjString("mergedFiles"), new TObjString(mergedList.c_str()));
239 info.Add(new TObjString("skippedFiles"), new TObjString(skippedList.c_str()));
240 file->cd();
241 info.Write("mergeInfo", TObject::kSingleKey);
242}
243
244// Re-opens the merged output and checks that it holds the expected tree, branches and
245// number of events, so that a truncated or half-written pool does not pass unnoticed.
246bool validateOutput(std::string const& outfile, std::string const& treename, Long64_t expected)
247{
248 Long64_t entries = 0;
249 int compression = -1;
250 const auto issue = inspectFile(outfile, treename, entries, compression);
251 if (!issue.empty()) {
252 LOG(error) << "Merged file " << outfile << " is not usable: " << issue;
253 return false;
254 }
255 if (entries != expected) {
256 LOG(error) << "Merged file " << outfile << " has " << entries << " events, but " << expected
257 << " were merged into it";
258 return false;
259 }
260 return true;
261}
262} // namespace
263
264int main(int argc, char* argv[])
265{
266 bpo::options_description options("o2-generators-merge-evtpool options");
267 auto add = options.add_options();
268 add("input,i", bpo::value<std::string>()->required(),
269 "comma-separated list of inputs: event-pool ROOT files (local or alien://), and/or "
270 "local text files listing more paths (one per line, '#' comments allowed)");
271 add("output,o", bpo::value<std::string>()->default_value("evtpool.root"),
272 "output ROOT file with the merged event pool");
273 add("check-tree,t", bpo::value<std::string>()->default_value("o2sim"),
274 "name of the tree the inputs and the merged pool are checked against; everything the "
275 "input files contain is merged regardless");
276 add("skip-non-existing-files", bpo::bool_switch(),
277 "skip inputs that cannot be resolved or opened instead of aborting the merge");
278 add("help,h", "produce help message");
279 bpo::variables_map vm;
280 try {
281 bpo::store(bpo::parse_command_line(argc, argv, options), vm);
282 if (vm.count("help")) {
283 LOG(info) << options;
284 return 0;
285 }
286 bpo::notify(vm);
287 } catch (const bpo::error& e) {
288 LOG(error) << "Error parsing command-line arguments: " << e.what() << "\n\n"
289 << options;
290 return 1;
291 }
292 const auto rawEntries = o2::utils::Str::tokenize(vm["input"].as<std::string>(), ',');
293 if (rawEntries.empty()) {
294 LOG(error) << "No input files given";
295 return 1;
296 }
297 // option similar in aodMerger
298 const bool skipMissing = vm["skip-non-existing-files"].as<bool>();
299 std::vector<std::string> infiles;
300 const size_t unresolved = expandInputs(rawEntries, infiles);
301 if (unresolved > 0 && !skipMissing) {
302 LOG(error) << "Some --input entries could not be resolved; "
303 "pass --skip-non-existing-files to merge the rest anyway";
304 return 1;
305 }
306 if (infiles.empty()) {
307 LOG(error) << "No input files resolved from the given --input entries";
308 return 1;
309 }
310 // Check Grid connection if any input is on AliEn
311 if (std::any_of(infiles.begin(), infiles.end(), isAlienPath) && !GridOn()) {
312 LOG(error) << "Some inputs live on AliEn but the grid is not available";
313 return 1;
314 }
315 const std::string outfile = vm["output"].as<std::string>();
316 const std::string treename = vm["check-tree"].as<std::string>();
317 LOG(info) << "Validating " << infiles.size() << " input file(s) ...";
318 std::vector<std::string> usable;
319 Long64_t totalEvents = 0;
320 int compression = -1;
321 if (!checkFiles(infiles, treename, usable, totalEvents, compression) && !skipMissing) {
322 LOG(error) << "Validation failed; not writing any output "
323 "(pass --skip-non-existing-files to merge the rest anyway)";
324 return 1;
325 }
326 if (usable.empty()) {
327 LOG(error) << "None of the input files could be used; not writing any output";
328 return 1;
329 }
330
331 // merged into a temporary name and renamed only once the result has been checked, so that
332 // a failed job never leaves something behind that looks like a finished pool
333 const std::string partfile = outfile + ".part";
334 auto discardPart = [&partfile]() {
335 std::error_code ec;
336 fs::remove(partfile, ec);
337 return 1;
338 };
339
340 LOG(info) << "Merging " << totalEvents << " events from " << usable.size() << " file(s) into "
341 << outfile << " ...";
342 {
343 TFileMerger merger(/*isLocal*/ false, /*histoOneGo*/ false);
344 merger.SetPrintLevel(0);
345 if (!merger.OutputFile(partfile.c_str(), "RECREATE", compression)) {
346 LOG(error) << "Cannot create output file " << partfile;
347 return discardPart();
348 }
349 for (auto const& f : usable) {
350 if (!merger.AddFile(f.c_str())) {
351 LOG(error) << "Cannot add " << f << " to the merge";
352 return discardPart();
353 }
354 }
355 if (!merger.Merge()) {
356 LOG(error) << "Merging failed; no output written";
357 return discardPart();
358 }
359 }
360
361 writeMergeInfo(partfile, infiles, usable, unresolved, totalEvents);
362 if (!validateOutput(partfile, treename, totalEvents)) {
363 LOG(error) << "The merged pool did not pass the final check; no output written";
364 return discardPart();
365 }
366
367 std::error_code ec;
368 fs::rename(partfile, outfile, ec);
369 if (ec) {
370 LOG(error) << "Cannot move " << partfile << " to " << outfile << ": " << ec.message();
371 return discardPart();
372 }
373
374 LOG(info) << "Done: wrote " << totalEvents << " events from " << usable.size() << " of "
375 << infiles.size() << " input file(s) to " << outfile;
376 return 0;
377}
std::vector< long > entries
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 events
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
configStandalone Reconstruction rec_tpc multiplier for the combined cluster track error during track following area in cm for the search of for z only used if decrease removal by when above this local occupancy rowx Shift of TPC revert cluster flag edge bit distance to edge exceeds this ZOffset for secondary multiple of radius max P of electron K BB bands crossing max P of electron p BB bands crossing max P of electron d BB bands crossing max P of electron t BB bands crossing min accepted dEdxMaxTPC of the track Additive extra cluster error for Y2 if edge flag set Additive extra cluster error for Y2 if single set Additive extra cluster error for Y2 if or single set Additive extra cluster error for Z2 if or single set Extra errors to add to track when crossing depending on addErrorsCECrossing Inner portion of y range in sector that is not used in searching for extrapolated track candidates factor2YS for track merging factor2K for track merging Margin in cm of Y position when rejecting edge clusters based on uncorrected track Y Max fraction of shared clusters for tracklet Except pad from cluster finding if total number of charges in a fragment is above this Except pad from cluster finding if number of consecutive charges in a fragment is above this Threshold where a timebin is considered disabling the noisy pad check for that pad Treat signal above this minimum as saturated Smoothing factor for the exponential Highly Ionising Particle tail filter Number of timebins to use for the averaging Add additional custom track errors when crossing relative as fraction of Do N fit passes in final fit of merger(must be odd to end with inward fit)") AddOptionRTC(trackFitRejectMode
char error[CURL_ERROR_SIZE]
uint32_t stack
Definition RawData.h:1
bool checkFiles(std::vector< std::string > const &filenames, std::string const &treename)
GLdouble f
Definition glcorearb.h:310
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
int32_t const char int32_t line
std::string expandShellVarsInFileName(std::string const &input)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
static void trim(std::string &s)
Definition StringUtils.h:70
static bool beginsWith(const std::string &s, const std::string &start)
static std::vector< std::string > tokenize(const std::string &src, char delim, bool trimToken=true, bool skipEmpty=true)
static bool endsWith(const std::string &s, const std::string &ending)
std::map< std::string, ID > expected
#define main
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))