45#include <fairlogger/Logger.h>
47#include <TFileMerger.h>
50#include <TObjString.h>
52#include <boost/program_options.hpp>
63namespace fs = std::filesystem;
67const char* kTrackBranch =
"MCTrack";
68const char* kHeaderBranch =
"MCEventHeader.";
69const char* kTrackRefBranch =
"TrackRefs";
70const char* kProtocol =
"alien://";
72bool isAlienPath(std::string
const&
path)
83 LOG(info) <<
"Connecting to AliEn ...";
84 if (!TGrid::Connect(
"alien:") || !gGrid) {
85 LOG(error) <<
"Could not connect to AliEn; check your alien token";
92std::optional<std::vector<std::string>> readLocalListFileLines(std::string
const&
path)
94 std::ifstream in(
path);
98 std::vector<std::string> lines;
100 while (std::getline(in, line)) {
101 lines.push_back(line);
110size_t expandInputEntry(std::string
const& rawEntry, std::vector<std::string>& out, std::vector<std::string>&
stack)
115 out.push_back(
entry);
119 LOG(error) <<
"Reference to an existing list " <<
entry <<
"; ignoring";
122 auto lines = readLocalListFileLines(
entry);
124 LOG(error) <<
"Cannot open " <<
entry <<
" (neither a .root file nor a readable local list)";
128 size_t unresolved = 0;
129 for (
auto line : *lines) {
131 if (
line.empty() || line[0] ==
'#') {
134 unresolved += expandInputEntry(line, out,
stack);
142size_t expandInputs(std::vector<std::string>
const& rawEntries, std::vector<std::string>& infiles)
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);
150 std::set<std::string> seen;
151 for (
auto const&
f : resolved) {
152 if (seen.insert(
f).second) {
153 infiles.push_back(
f);
155 LOG(warning) <<
"Input " <<
f <<
" is listed more than once; merging it only once";
164std::string inspectFile(std::string
const&
path, std::string
const& treename,
165 Long64_t& entries,
int& compression)
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";
171 auto tree = (TTree*)
file->Get(treename.c_str());
173 return "no tree named '" + treename +
"' in the file";
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";
180 entries =
tree->GetEntries();
181 compression =
file->GetCompressionSettings();
188bool checkFiles(std::vector<std::string>
const& files, std::string
const& treename,
189 std::vector<std::string>& usable, Long64_t& totalEvents,
int& compression)
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;
203 if (compression < 0) {
204 compression = fileCompression;
206 totalEvents += entries;
208 LOG(info) <<
" OK " <<
f <<
" (" << entries <<
" events)";
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)
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;
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";
229 skippedList +=
f +
"\n";
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()));
238 info.Add(
new TObjString(
"mergedFiles"),
new TObjString(mergedList.c_str()));
239 info.Add(
new TObjString(
"skippedFiles"),
new TObjString(skippedList.c_str()));
241 info.Write(
"mergeInfo", TObject::kSingleKey);
246bool validateOutput(std::string
const& outfile, std::string
const& treename, Long64_t
expected)
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;
256 LOG(error) <<
"Merged file " << outfile <<
" has " << entries <<
" events, but " <<
expected
257 <<
" were merged into it";
264int main(
int argc,
char* argv[])
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;
281 bpo::store(bpo::parse_command_line(argc, argv, options), vm);
282 if (vm.count(
"help")) {
283 LOG(info) << options;
287 }
catch (
const bpo::error& e) {
288 LOG(error) <<
"Error parsing command-line arguments: " << e.what() <<
"\n\n"
293 if (rawEntries.empty()) {
294 LOG(error) <<
"No input files given";
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";
306 if (infiles.empty()) {
307 LOG(error) <<
"No input files resolved from the given --input entries";
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";
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)";
326 if (usable.empty()) {
327 LOG(error) <<
"None of the input files could be used; not writing any output";
333 const std::string partfile = outfile +
".part";
334 auto discardPart = [&partfile]() {
336 fs::remove(partfile, ec);
340 LOG(info) <<
"Merging " << totalEvents <<
" events from " << usable.size() <<
" file(s) into "
341 << outfile <<
" ...";
343 TFileMerger merger(
false,
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();
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();
355 if (!merger.Merge()) {
356 LOG(error) <<
"Merging failed; no output written";
357 return discardPart();
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();
368 fs::rename(partfile, outfile, ec);
370 LOG(error) <<
"Cannot move " << partfile <<
" to " << outfile <<
": " << ec.message();
371 return discardPart();
374 LOG(info) <<
"Done: wrote " << totalEvents <<
" events from " << usable.size() <<
" of "
375 << infiles.size() <<
" input file(s) to " << outfile;
GLsizei const GLchar *const * path
int32_t const char * file
int32_t const char int32_t line
std::string expandShellVarsInFileName(std::string const &input)
std::string to_string(gsl::span< T, Size > span)
static void trim(std::string &s)
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
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))