Belle II Software prerelease-11-00-00a
b2file-merge.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <framework/dataobjects/FileMetaData.h>
10#include <framework/core/FileCatalog.h>
11#include <framework/core/MetadataService.h>
12#include <framework/datastore/DataStore.h>
13#include <framework/io/RootFileInfo.h>
14#include <framework/io/RootIOUtilities.h>
15#include <framework/logging/Logger.h>
16#include <framework/pcore/Mergeable.h>
17#include <framework/utilities/KeyValuePrinter.h>
18
19#include <boost/program_options.hpp>
20#include <boost/algorithm/string.hpp>
21
22#include <TFile.h>
23#include <TTree.h>
24#include <TBranchElement.h>
25#include <TDirectory.h>
26#include <TH1.h>
27#include <TKey.h>
28#include <TClass.h>
29
30#include <filesystem>
31#include <iostream>
32#include <iomanip>
33#include <memory>
34#include <string>
35#include <set>
36#include <regex>
37
38using namespace Belle2;
39namespace po = boost::program_options;
40namespace fs = std::filesystem;
41
44using EventInfo = std::tuple<int, int, unsigned int>;
45
46namespace {
49 std::string removeLegacyGt(const std::string& globaltags)
50 {
51 std::regex legacy_gt(",?Legacy_IP_Information");
52 return std::regex_replace(globaltags, legacy_gt, "");
53 }
54
61 void collectHistograms(TDirectory& dir, const std::string& prefix,
62 std::map<std::string, std::pair<TH1*, size_t>>& histograms)
63 {
64 for (TObject* keyObj : *dir.GetListOfKeys()) {
65 auto* key = dynamic_cast<TKey*>(keyObj);
66 if (!key) continue;
67 const std::string name{key->GetName()};
68 const std::string path = prefix.empty() ? name : (prefix + "/" + name);
69 TClass* cls = TClass::GetClass(key->GetClassName());
70 if (!cls) continue;
71 if (cls->InheritsFrom(TDirectory::Class())) {
72 auto* subdir = dynamic_cast<TDirectory*>(key->ReadObj());
73 if (subdir) collectHistograms(*subdir, path, histograms);
74 } else if (cls->InheritsFrom(TH1::Class())) {
75 auto* hist = dynamic_cast<TH1*>(key->ReadObj());
76 if (!hist) continue;
77 auto it = histograms.find(path);
78 if (it == histograms.end()) {
79 hist->SetDirectory(nullptr); // detach from input file so we own it
80 histograms.emplace(path, std::make_pair(hist, size_t{1}));
81 } else {
82 it->second.first->Add(hist);
83 it->second.second++;
84 delete hist;
85 }
86 }
87 }
88 }
89
94 void writeHistograms(TFile& output,
95 const std::map<std::string, std::pair<TH1*, size_t>>& histograms)
96 {
97 for (const auto& [path, histCount] : histograms) {
98 output.cd();
99 const auto slash = path.rfind('/');
100 if (slash != std::string::npos) {
101 // Create intermediate directories level by level
102 TDirectory* dir = &output;
103 size_t start = 0;
104 const std::string dirpath = path.substr(0, slash);
105 while (start < dirpath.size()) {
106 const size_t end = dirpath.find('/', start);
107 const std::string part = (end == std::string::npos)
108 ? dirpath.substr(start) : dirpath.substr(start, end - start);
109 TDirectory* sub = dir->GetDirectory(part.c_str());
110 if (!sub) {
111 dir->mkdir(part.c_str());
112 sub = dir->GetDirectory(part.c_str());
113 }
114 if (!sub) break;
115 dir = sub;
116 if (end == std::string::npos) break;
117 start = end + 1;
118 }
119 dir->cd();
120 }
121 histCount.first->Write();
122 }
123 output.cd();
124 }
125}
126
127int main(int argc, char* argv[])
128{
129 // Parse options
130 std::string outputfilename;
131 std::vector<std::string> inputfilenames;
132 std::string jsonfilename;
133 po::options_description options("Options");
134 options.add_options()
135 ("help,h", "print all available options")
136 ("output,o", po::value<std::string>(&outputfilename), "output file name")
137 ("file", po::value<std::vector<std::string>>(&inputfilenames), "filename to merge")
138 ("force,f", "overwrite existing file")
139 ("no-catalog", "don't register output file in file catalog, This is now the default")
140 ("add-to-catalog", "register the output file in the file catalog")
141 ("job-information", po::value<std::string>(&jsonfilename), "create json file with metadata of output file and execution status")
142 ("quiet,q", "if given don't print infos, just warnings and errors")
143 ("reoptimize,O", "reoptimize basket size when merging TTrees, equivalent to `hadd -O` (much slower!)");
144 po::positional_options_description positional;
145 positional.add("output", 1);
146 positional.add("file", -1);
147 po::variables_map variables;
148 po::store(po::command_line_parser(argc, argv).options(options).positional(positional).run(), variables);
149 po::notify(variables);
150 if (variables.count("help") || variables.count("output") == 0 || inputfilenames.empty()) {
151 std::cout << "Usage: " << argv[0] << " [<options>] OUTPUTFILE INPUTFILE [INPUTFILE...]" << std::endl;
152 std::cout << " " << argv[0] << " [<options>] [--file INPUTFILE...] "
153 << "-o OUTPUTFILE [--file INPUTFILE...]" << std::endl << std::endl;
154 std::cout << options << std::endl;
155 std::cout << (R"DOC(
156This program is intended to merge files created by separate basf2 jobs. It's
157similar to hadd but does correctly update the metadata in the file and merges
158the objects in the persistent tree correctly.
159
160The following restrictions apply:
161 - The files have to be created with the same release and steering file
162 - The persistent tree is only allowed to contain FileMetaData and objects
163 inheriting from Mergeable and the same list of objects needs to be present
164 in all files.
165 - The event tree needs to contain the same DataStore entries in all files.
166)DOC");
167 return 1;
168 }
169
170 //Initialize metadata service
172 if (!jsonfilename.empty()) {
174 }
175
176 // Remove the {module:} from log messages
177 auto logConfig = LogSystem::Instance().getLogConfig();
179 logConfig->setLogInfo(l, LogConfig::c_Level | LogConfig::c_Message);
180 }
181 if(variables.count("quiet")>0){
182 logConfig->setLogLevel(LogConfig::c_Warning);
183 }
184
185 B2INFO("Merging files into " << std::quoted(outputfilename));
186 // check output file
187 if (fs::exists(outputfilename) && variables.count("force")==0) {
188 B2ERROR("Output file exists, use -f to force overwriting it");
189 return 1;
190 }
191
192 // Set the flags:
193 // Copy all entries without unpacking (fast)
194 // Layout the baskets in an optimal order for sequential reading (SortBasketByEntry)
195 // rebuild the index in case some parts of the index are missing (BuildIndexOnError)
196 const bool reoptimize = variables.count("reoptimize") > 0;
197 const std::string copyOptions = reoptimize
198 ? "SortBasketsByEntry BuildIndexOnError"
199 : "fast SortBasketsByEntry BuildIndexOnError";
200 B2INFO("Will use the merging options: " << std::quoted(copyOptions));
201
202 // First we check all input files for consistency ...
203
204 // the final metadata we will write out
205 FileMetaData* outputMetaData{nullptr};
206 // set of all parent LFNs encountered in any file
207 std::set<std::string> allParents;
208 // map of all mergeable objects found in the persistent tree. The size_t is
209 // for counting to make sure we see all objects in all files
210 std::map<std::string, std::pair<Mergeable*, size_t>> persistentMergeables;
211 // set of all random seeds to print warning on duplicates
212 std::set<std::string> allSeeds;
213 // set of all users
214 std::set<std::string> allUsers;
215 // EventInfo for the high/low event numbers of the final FileMetaData
216 std::optional<EventInfo> lowEvt, highEvt;
217 // map of sets of all branch names in the event trees to compare against to make sure
218 // that they're the same in all files
219 std::map<std::string, std::set<std::string>> allEventBranches;
220 // set of all ntuple trees names to compare against to make sure
221 // that they're the same in all files (if they exist)
222 std::set<std::string> allEventTrees;
223 // map from histogram path to (accumulated TH1*, count of files where it was found)
224 std::map<std::string, std::pair<TH1*, size_t>> mergedHistograms;
225 // Release version to compare against. Same as FileMetaData::getRelease() but with the optional -modified removed
226 std::string outputRelease;
227
228 // so let's loop over all files and create FileMetaData and merge persistent
229 // objects if they inherit from Mergeable, bail if there's something else in
230 // there. The idea is that merging the persistent stuff is fast so we catch
231 // errors more quickly when we do this as a first step and events later on.
232 for (const auto& input : inputfilenames) {
233 try {
234 RootIOUtilities::RootFileInfo fileInfo(input);
235 // Ok, load the FileMetaData from the tree
236 const auto &fileMetaData = fileInfo.getFileMetaData();
237 auto description = fileMetaData.getDataDescription();
238 auto isNtuple = description.find("isNtupleMetaData");
239 // File looks usable, start checking metadata ...
240 B2INFO("adding file " << std::quoted(input));
241 if(LogSystem::Instance().isLevelEnabled(LogConfig::c_Info)) fileMetaData.Print("all");
242 auto trees = fileInfo.getTreeNames();
243 if(allEventTrees.empty()) {
244 std::swap(allEventTrees,trees);
245 }else{
246 if(trees!=allEventTrees){
247 B2ERROR("Trees in " << std::quoted(input) << " differ from "
248 << std::quoted(inputfilenames.front()));
249 continue;
250 }
251 }
252 for(const auto& tree : allEventTrees) {
253 auto branches = ((tree=="tree") &&
254 ((isNtuple==description.end()) || (isNtuple->second != "True"))
255 ) ? fileInfo.getBranchNames() : fileInfo.getNtupleBranchNames(tree);
256 if(branches.empty()) {
257 throw std::runtime_error("Could not find any branches in " + tree);
258 }
259 if(allEventBranches[tree].empty()) {
260 std::swap(allEventBranches[tree],branches);
261 }else{
262 if(branches!=allEventBranches[tree]){
263 B2ERROR("Branches in " << std::quoted(input + ":" + tree) << " differ from "
264 << std::quoted(inputfilenames.front() + ":" + tree));
265 }
266 }
267 }
268 // Collect and accumulate any histograms stored directly in this file
269 collectHistograms(fileInfo.getFile(), "", mergedHistograms);
270
271 // File looks good so far, now fix the persistent stuff, i.e. merge all
272 // objects in persistent tree
273 for(TObject* brObj: *fileInfo.getPersistentTree().GetListOfBranches()){
274 auto* br = dynamic_cast<TBranchElement*>(brObj);
275 // FileMetaData is handled separately
276 if(br && br->GetTargetClass() == FileMetaData::Class() && std::string(br->GetName()) == "FileMetaData")
277 continue;
278 // Make sure the branch is mergeable
279 if(!br) continue;
280 if(!br->GetTargetClass()->InheritsFrom(Mergeable::Class())){
281 B2ERROR("Branch " << std::quoted(br->GetName()) << " in " << RootIOUtilities::c_treeNames[DataStore::c_Persistent] << " tree not inheriting from Mergeable");
282 continue;
283 }
284 // Ok, it's an object we now how to handle so get it from the tree
285 Mergeable* object{nullptr};
286 br->SetAddress(&object);
287 if(br->GetEntry(0)<=0) {
288 B2ERROR("Could not read branch " << std::quoted(br->GetName()) << " of entry 0 from " << RootIOUtilities::c_treeNames[DataStore::c_Persistent] << " tree in "
289 << std::quoted(input));
290 continue;
291 }
292 // and either insert it into the map of mergeables or merge with the existing one
293 auto it = persistentMergeables.insert(std::make_pair(br->GetName(), std::make_pair(object, 1)));
294 if(!it.second) {
295 try {
296 it.first->second.first->merge(object);
297 }catch(std::exception &e){
298 B2FATAL("Cannot merge " << std::quoted(br->GetName()) << " in " << std::quoted(input) << ": " << e.what());
299 }
300 it.first->second.second++;
301 // ok, merged, get rid of it.
302 delete object;
303 }else{
304 B2INFO("Found mergeable object " << std::quoted(br->GetName()) << " in " << RootIOUtilities::c_treeNames[DataStore::c_Persistent] << " tree");
305 }
306 }
307
308 std::string release = fileMetaData.getRelease();
309 if(release == "") {
310 B2ERROR("Cannot determine release used to create " << std::quoted(input));
311 continue;
312 } else if (fileMetaData.getRelease().ends_with("-modified")) {
313 B2WARNING("File " << std::quoted(input) << " created with modified software "
314 << fileMetaData.getRelease()
315 << ": cannot verify that files are compatible");
316 release = release.substr(0, release.size() - std::string("-modified").size());
317 }
318
319 // so, event tree looks good too. Now we merge the FileMetaData
320 if (!outputMetaData) {
321 // first input file, just take the event metadata
322 outputMetaData = new FileMetaData(fileMetaData);
323 outputRelease = release;
324 } else {
325 // check meta data for consistency, we could move this into FileMetaData...
326 if(release != outputRelease) {
327 B2ERROR("Release in " << std::quoted(input) << " differs from previous files: " <<
328 fileMetaData.getRelease() << " != " << outputMetaData->getRelease());
329 }
330 if(fileMetaData.getSteering() != outputMetaData->getSteering()){
331 // printing both steering files is not useful for anyone so just throw an error
332 B2ERROR("Steering file for " << std::quoted(input) << " differs from previous files.");
333 }
334 if(fileMetaData.getDatabaseGlobalTag() != outputMetaData->getDatabaseGlobalTag()){
335 // Related to BII-6093: we were adding the legacy gt only dependent on input file age, not creation release.
336 // This means there is a chance we want to merge files with and without the globaltag added if they cross the
337 // boundary. It doesn't hurt to keep the gt but we know we could process some of the files without it so as a remedy we
338 // check if the only difference is the legacy gt and if so we remove it from the output metadata ...
339 if(removeLegacyGt(fileMetaData.getDatabaseGlobalTag()) == removeLegacyGt(outputMetaData->getDatabaseGlobalTag())) {
340 outputMetaData->setDatabaseGlobalTag(removeLegacyGt(outputMetaData->getDatabaseGlobalTag()));
341 } else {
342 B2ERROR("Database globalTag in " << std::quoted(input) << " differs from previous files: " <<
343 fileMetaData.getDatabaseGlobalTag() << " != " << outputMetaData->getDatabaseGlobalTag());
344 }
345 }
346 if(fileMetaData.getDataDescription() != outputMetaData->getDataDescription()){
347 KeyValuePrinter cur(true);
348 for (const auto& descrPair : outputMetaData->getDataDescription())
349 cur.put(descrPair.first, descrPair.second);
350 KeyValuePrinter prev(true);
351 for (const auto& descrPair : fileMetaData.getDataDescription())
352 prev.put(descrPair.first, descrPair.second);
353
354 B2ERROR("dataDescription in " << std::quoted(input) << " differs from previous files:\n" << cur.string() << " vs.\n" << prev.string());
355 }
356 if(fileMetaData.isMC() != outputMetaData->isMC()){
357 B2ERROR("Type (real/MC) for " << std::quoted(input) << " differs from previous files.");
358 }
359 // update event numbers ...
360 outputMetaData->setMcEvents(outputMetaData->getMcEvents() + fileMetaData.getMcEvents());
361 outputMetaData->setNEvents(outputMetaData->getNEvents() + fileMetaData.getNEvents());
362 outputMetaData->setNFullEvents(outputMetaData->getNFullEvents() + fileMetaData.getNFullEvents());
363 }
364 if(fileMetaData.getNEvents() < 1) {
365 B2WARNING("File " << std::quoted(input) << " is empty.");
366 } else {
367 // make sure we have the correct low/high event numbers
368 EventInfo curLowEvt = EventInfo{fileMetaData.getExperimentLow(), fileMetaData.getRunLow(), fileMetaData.getEventLow()};
369 EventInfo curHighEvt = EventInfo{fileMetaData.getExperimentHigh(), fileMetaData.getRunHigh(), fileMetaData.getEventHigh()};
370 if(!lowEvt or curLowEvt < *lowEvt) lowEvt = curLowEvt;
371 if(!highEvt or curHighEvt > *highEvt) highEvt = curHighEvt;
372 }
373 // check if we have seen this random seed already in one of the previous files
374 auto it = allSeeds.insert(fileMetaData.getRandomSeed());
375 if(!it.second) {
376 B2WARNING("Duplicate Random Seed: " << std::quoted(fileMetaData.getRandomSeed()) << " present in more then one file");
377 }
378 allUsers.insert(fileMetaData.getUser());
379 // remember all parent files we encounter
380 for (int i = 0; i < fileMetaData.getNParents(); ++i) {
381 allParents.insert(fileMetaData.getParent(i));
382 }
383 }catch(std::exception &e) {
384 B2ERROR("input file " << std::quoted(input) << ": " << e.what());
385 }
386 }
387
388 //Check if the same mergeables were found in all files
389 for(const auto &val: persistentMergeables){
390 if(val.second.second != inputfilenames.size()){
391 B2ERROR("Mergeable " << std::quoted(val.first) << " only present in " << val.second.second << " out of "
392 << inputfilenames.size() << " files");
393 }
394 }
395
396 // Check for user names
397 if(allUsers.size()>1) {
398 B2WARNING("Multiple different users created input files: " << boost::algorithm::join(allUsers, ", "));
399 }
400
401 // Stop processing in case of error
402 if (LogSystem::Instance().getMessageCounter(LogConfig::c_Error) > 0) return 1;
403
404 if(!outputMetaData){
405 // technically it's rather impossible to arrive here: if there were no
406 // input files we exit with a usage message and if any of the files could
407 // not be processed then the error count should be >0. Nevertheless
408 // let's do this check to be on the very safe side and to make clang
409 // analyzer happy.
410 B2FATAL("For some reason no files could be processed");
411 return 1;
412 }
413 if(!lowEvt) {
414 B2WARNING("All Files were empty");
415 lowEvt = EventInfo{-1, -1, 0};
416 highEvt = EventInfo{-1, -1, 0};
417 }
418
419 // Final changes to metadata
420 outputMetaData->setLfn("");
421 outputMetaData->setParents(std::vector<std::string>(allParents.begin(), allParents.end()));
422 outputMetaData->setLow(std::get<0>(*lowEvt), std::get<1>(*lowEvt), std::get<2>(*lowEvt));
423 outputMetaData->setHigh(std::get<0>(*highEvt), std::get<1>(*highEvt), std::get<2>(*highEvt));
424 // If more then one file set an empty random seed
425 if(inputfilenames.size()>1){
426 outputMetaData->setRandomSeed("");
427 }
428 RootIOUtilities::setCreationData(*outputMetaData);
429 // Set (again) the release, since it's overwritten by the previous line
430 outputMetaData->setRelease(outputRelease);
431
432 // OK we have a valid FileMetaData and merged all persistent objects, now do
433 // the conversion of the event trees and create the output file.
434 auto output = std::unique_ptr<TFile>{TFile::Open(outputfilename.c_str(), "RECREATE")};
435 if (output == nullptr or output->IsZombie()) {
436 B2ERROR("Could not create output file " << std::quoted(outputfilename));
437 return 1;
438 }
439
440 for (const auto& treeName : allEventTrees) {
441 TTree* outputEventTree{nullptr};
442 for (const auto& input : inputfilenames) {
443 B2INFO("processing events from " << std::quoted(input + ":" + treeName));
444 auto tfile = std::unique_ptr<TFile>{TFile::Open(input.c_str(), "READ")};
445 // At this point, we already checked that the input files are valid and exist
446 // so it's safe to access tfile directly
447 auto* tree = dynamic_cast<TTree*>(tfile->Get(treeName.c_str()));
448 if (!outputEventTree){
449 output->cd();
450 outputEventTree = tree->CloneTree(0);
451 } else {
452 outputEventTree->CopyAddresses(tree);
453 }
454 // Now let's copy all entries using the requested flags
455 outputEventTree->CopyEntries(tree, -1, copyOptions.c_str());
456 // and reset the branch addresses to not be connected anymore
457 outputEventTree->CopyAddresses(tree, true);
458 // finally clean up and close file.
459 delete tree;
460 tfile->Close();
461 }
462 assert(outputEventTree);
463 // make sure we have an index for the basf2 tree ...
465 if(!outputEventTree->GetTreeIndex()) {
466 B2INFO("No Index found: building new index");
467 RootIOUtilities::buildIndex(outputEventTree);
468 }
469 }
470 // and finally write the tree
471 output->cd();
472 outputEventTree->Write();
473 // check if the number of full events in the metadata is zero:
474 // if so calculate number of full events now:
475 if (outputMetaData->getNFullEvents() == 0) {
476 outputMetaData->setNFullEvents(outputEventTree->GetEntries("EventMetaData.m_errorFlag == 0"));
477 }
478 }
479
480 B2INFO("Done processing events");
481
482 // Write merged histograms to output and check consistency
483 if (!mergedHistograms.empty()) {
484 B2INFO("Writing histograms");
485 writeHistograms(*output, mergedHistograms);
486 for (const auto& [name, histCount] : mergedHistograms) {
487 if (histCount.second != inputfilenames.size()) {
488 B2ERROR("Histogram " << std::quoted(name) << " only present in "
489 << histCount.second << " out of " << inputfilenames.size() << " files");
490 }
491 }
492 for (auto& [name, histCount] : mergedHistograms) {
493 delete histCount.first;
494 }
495 mergedHistograms.clear();
496 }
497
498 // we need to set the LFN to the absolute path name
499 outputMetaData->setLfn(fs::absolute(outputfilename).string());
500 // and maybe register it in the file catalog
501 if(variables.count("add-to-catalog")>0) {
502 FileCatalog::Instance().registerFile(outputfilename, *outputMetaData);
503 }
504 B2INFO("Writing FileMetaData");
505 // Create persistent tree
506 output->cd();
508 outputMetaDataTree.Branch("FileMetaData", &outputMetaData);
509 for(auto &it: persistentMergeables){
510 outputMetaDataTree.Branch(it.first.c_str(), &it.second.first);
511 }
512 outputMetaDataTree.Fill();
513 outputMetaDataTree.Write();
514
515 // now clean up the mess ...
516 for(const auto& val: persistentMergeables){
517 delete val.second.first;
518 }
519 persistentMergeables.clear();
520 auto outputMetaDataCopy = *outputMetaData;
521 delete outputMetaData;
522 output->Close();
523
524 // and now add it to the metadata service
525 MetadataService::Instance().addRootOutputFile(outputfilename, &outputMetaDataCopy, "b2file-merge");
526
527 // report completion in job metadata
528 MetadataService::Instance().addBasf2Status("finished successfully");
530}
@ c_Persistent
Object is available during entire execution time.
Definition DataStore.h:60
@ c_Event
Different object in each event, all objects/arrays are invalidated after event() function has been ca...
Definition DataStore.h:59
static FileCatalog & Instance()
Static method to get a reference to the FileCatalog instance.
virtual bool registerFile(const std::string &fileName, FileMetaData &metaData, const std::string &oldLFN="")
Register a file in the (local) file catalog.
Metadata information about a file.
void setLfn(const std::string &lfn)
Setter for LFN.
create human-readable or JSON output for key value pairs.
@ c_Error
Error: for things that went wrong and have to be fixed.
Definition LogConfig.h:30
@ c_Info
Info: for informational messages, e.g.
Definition LogConfig.h:27
@ c_Fatal
Fatal: for situations were the program execution can not be continued.
Definition LogConfig.h:31
@ c_Warning
Warning: for potential problems that the user should pay attention to.
Definition LogConfig.h:29
@ c_Level
Log level of the message.
Definition LogConfig.h:36
@ c_Message
Log message text.
Definition LogConfig.h:37
LogConfig * getLogConfig()
Returns global log system configuration.
Definition LogSystem.h:78
static LogSystem & Instance()
Static method to get a reference to the LogSystem instance.
Definition LogSystem.cc:28
Abstract base class for objects that can be merged.
Definition Mergeable.h:31
void addRootOutputFile(const std::string &fileName, const FileMetaData *metaData=nullptr, const char *type="RootOutput")
Add the metadata of a root output file.
void addBasf2Status(const std::string &message="")
Add metadata of basf2 status.
void setJsonFileName(const std::string &fileName)
Set the name of the json metadata file.
static MetadataService & Instance()
Static method to get a reference to the MetadataService instance.
void finishBasf2(bool success=true)
Add metadata for basf2 completion.
Helper class to factorize some necessary tasks when working with Belle2 output files.
const std::string c_treeNames[]
Names of trees.
void setCreationData(FileMetaData &metadata)
Fill the creation info of a file meta data: site, user, data.
void buildIndex(TTree *tree)
Build TTreeIndex on tree (assumes either EventMetaData branch exists or is a ntuple tree).
Abstract base class for different kinds of events.