LDMX Software
EventFile.cxx
1#include <regex.h>
2
3#include <ctime>
4
5#include "TBranchElement.h"
6#include "TTreeReader.h"
7
8// LDMX
9#include "Framework/Event.h"
10#include "Framework/EventFile.h"
11#include "Framework/Exception/Exception.h"
12#include "Framework/RunHeader.h"
13
14namespace framework {
15
16namespace {
22int onDiskRunHeaderVersion(TTree* run_tree) {
23 if (not run_tree) return 0;
24 auto* branch{dynamic_cast<TBranchElement*>(run_tree->GetBranch("RunHeader"))};
25 if (not branch) return 0;
26 return branch->GetClassVersion();
27}
28} // namespace
29
31 const std::string& filename, EventFile* parent,
32 bool is_output_file, bool is_single_output,
33 bool is_loopable)
34 : file_name_(filename),
35 is_output_file_(is_output_file),
36 is_single_output_(is_single_output),
37 is_loopable_(is_loopable),
38 parent_(parent) {
39 if (is_output_file_) {
40 // we are writting out so open the file and make sure it is writable
41 file_ = new TFile(file_name_.c_str(), "RECREATE");
42 if (!file_->IsOpen() or !file_->IsWritable()) {
43 EXCEPTION_RAISE("FileError",
44 "Output file '" + file_name_ + "' is not writable.");
45 }
46
47 // set compression settings
48 // Check out the TFile constructor for explanation of how this integer is
49 // built Short Reference: setting = 100*algorithem + level algorithm = 0
50 // ==> use global default
51 file_->SetCompressionSettings(params.get<int>("compression_setting", 9));
52
53 if (parent_) {
54 // output file when there are input files
55 // might be drop/keep rules, so we should have these rules to make sure
56 // it works
57
58 // turn everything on
59 // hypothetically could turn everything off? Doesn't work for some
60 // reason?
61 pre_clone_rules_.emplace_back("*", true);
62
63 // except EventHeader (copies over to output)
64 pre_clone_rules_.emplace_back("EventHeader*", true);
65
66 // reactivate all branches so default behavior is drop
67 reactivate_rules_.push_back("*");
68 }
69 } else {
70 // open file with only reading enabled
71 file_ = new TFile(file_name_.c_str());
72 // double check that file is open
73 if (!file_->IsOpen()) {
74 EXCEPTION_RAISE("FileError", "Input file '" + file_name_ +
75 "' is not readable or does not exist.");
76 }
77
78 bool skip_corrupted = params.get<bool>("skip_corrupted_input_files", false);
79
80 // make sure file is not a zombie file
81 // (i.e. process ended without closing or the file was corrupted some other
82 // way)
83 if (file_->IsZombie()) {
84 if (not skip_corrupted) {
85 EXCEPTION_RAISE("FileError", "Input file '" + file_name_ +
86 "' is corrupted. Framework will not "
87 "attempt to recover this file.");
88 }
89 return;
90 }
91
92 // Get the tree name from the configuration
93 auto tree_name{params.get<std::string>("tree_name")};
94 tree_ = static_cast<TTree*>(file_->Get(tree_name.c_str()));
95 if (!tree_) {
96 if (not skip_corrupted) {
97 EXCEPTION_RAISE("FileError", "File '" + file_name_ +
98 "' does not have a TTree named '" +
99 tree_name + "' in it.");
100 }
101 return;
102 }
103 entries_ = tree_->GetEntriesFast();
104 }
105
107}
108
110 const std::string& filename, bool is_loopable)
111 : EventFile(params, filename, nullptr, false, false, is_loopable) {}
112
114 const std::string& filename)
115 : EventFile(params, filename, nullptr, false, false, false) {}
116
118 const std::string& filename, EventFile* parent,
119 bool is_single_output)
120 : EventFile(params, filename, parent, true, is_single_output, false) {}
121
123 // Before an output file, the Event tree needs to be written.
124 if (tree_ && is_output_file_) {
125 // make sure we are in output file before writing
126 file_->cd();
127 tree_->Write();
128 file_->Close();
129 }
130}
131
133 if (is_output_file_) return file_->IsZombie();
134 return (!tree_ or file_->IsZombie() or file_->GetNkeys() == 0);
135}
136
137void EventFile::addDrop(const std::string& rule) {
138 int offset;
139 bool is_keep = false, is_drop = false, is_ignore = false;
140 // keywords must appear at the start of the rule string
141 if (rule.find("keep") == 0) {
142 offset = 4;
143 is_keep = true;
144 } else if (rule.find("drop") == 0) {
145 offset = 4;
146 is_drop = true;
147 } else if (rule.find("ignore") == 0) {
148 offset = 6;
149 is_ignore = true;
150 }
151
152 // none of (keep,drop,ignore) was provided => not valid rule
153 if (int(is_keep) + int(is_drop) + int(is_ignore) != 1) return;
154
155 std::string srule = rule.substr(offset);
156 size_t i;
157 for (i = srule.find_first_of(" \t\n\r"); i != std::string::npos;
158 i = srule.find_first_of(" \t\n\r"))
159 srule.erase(i, 1);
160
161 // name of branch is not given
162 if (srule.length() == 0) return;
163
164 // add wild card at end for matching purposes
165 if (srule.back() != '*') srule += ".*"; // add wildcard to back
166
167 // Guard: EventHeader must never be dropped or ignored
168 if (is_drop or is_ignore) {
169 regex_t guard_reg;
170 if (regcomp(&guard_reg, srule.c_str(),
171 REG_EXTENDED | REG_ICASE | REG_NOSUB) == 0) {
172 bool matches_event_header =
173 (regexec(&guard_reg, ldmx::EventHeader::BRANCH.c_str(), 0, 0, 0) ==
174 0);
175 regfree(&guard_reg);
176 if (matches_event_header) {
177 EXCEPTION_RAISE("BadRule",
178 "Drop/ignore rule '" + rule +
179 "' would affect EventHeader which is required by "
180 "the framework and cannot be removed.");
181 }
182 }
183 }
184
185 if (is_keep) {
186 // turn both the input and output tree's on
187 // root needs . removed otherwise it gets cranky
188 srule.erase(std::remove(srule.begin(), srule.end(), '.'), srule.end());
189 pre_clone_rules_.emplace_back(srule, true);
190 // this branch will then be copied over into output tree and be active
191 } else if (is_ignore) {
192 // don't even read it from the input file
193 // pass regex (with dots) to event bus so setInputTree skips these branches
194 event_->addIgnore(srule); // requires event_ to be set
195 // root needs . removed otherwise it gets cranky
196 srule.erase(std::remove(srule.begin(), srule.end(), '.'), srule.end());
197 // warn if this rule drops all collections
198 if (srule == "*")
199 ldmx_log(fatal) << "Ignore rule '" << rule
200 << "' will hide all input collections from processors.";
201 pre_clone_rules_.emplace_back(srule, false);
202 // these branches won't be copied over into output tree
203 } else if (is_drop) {
204 // drop means allowing it on reading but not writing
205 // pass these regex to event bus so Event::add knows
206 event_->addDrop(srule); // requires event_ to be set
207
208 // root needs . removed otherwise it gets cranky
209 srule.erase(std::remove(srule.begin(), srule.end(), '.'), srule.end());
210 // warn if this rule drops all collections
211 if (srule == "*")
212 ldmx_log(fatal) << "Drop rule '" << rule
213 << "' will drop all collections from the output file.";
214 pre_clone_rules_.emplace_back(srule, false);
215 // these branches won't be copied over into output tree
216 // reactivate input branch after clone
217 reactivate_rules_.push_back(srule);
218 }
219}
220
221bool EventFile::nextEvent(bool storeCurrentEvent) {
222 if (ientry_ < 0) {
223 // first entry of this file
224 if (parent_) {
225 // we have a parent file
226 if (!parent_->tree_) {
227 // this should _never_ happen
228 EXCEPTION_RAISE("EventFile", "No event tree in the file");
229 }
230 // Only clone parent tree if either
231 // 1) There is no tree setup yet (first input file)
232 // 2) This is not single output (new input file --> new output file)
233 if (!tree_ or !is_single_output_) {
234 // clones parent_->tree_ to our tree_ keeping drop/keep rules in mind
235 // clone tree (only copies over branches that are active on input tree)
236
237 file_->cd(); // go into output file
238
239 for (auto const& rule_pair : pre_clone_rules_)
240 parent_->tree_->SetBranchStatus(rule_pair.first.c_str(),
241 rule_pair.second);
242
243 tree_ = parent_->tree_->CloneTree(0);
244
245 // reactivate any drop branches (drop) on input tree
246 for (auto const& rule : reactivate_rules_)
247 parent_->tree_->SetBranchStatus(rule.c_str(), 1);
248 }
251 } // we have a parent file
252 } else {
253 // later than first entry of file
254 if (is_output_file_) {
256 if (storeCurrentEvent) // we should store before moving on
257 tree_->Fill(); // fill the clones...
258 } // we are an output file
259
260 // the event bus may not be defined
261 // for this file if we are input file and
262 // there is an output file during this run
263 if (event_) {
264 event_->clear();
266 } // event bus defined
267 } // first or not first entry in this file
268
269 if (parent_) {
270 // we have a parent, follow their lead
271 if (!parent_->nextEvent()) {
272 return false;
273 }
275 entries_++;
276 } else if (is_output_file_) {
277 // we don't have a parent and we
278 // are an output file
279 // Just increment the number of entries
280 // and the index_ of the current entry
281 ientry_++;
282 entries_++;
283 } else {
284 // we don't have a parent and
285 // we aren't an output file
286 // try to load another entry from our tree
287 if (ientry_ + 1 >= entries_) {
288 if (is_loopable_) {
289 // reset the event counter: reuse events from start of pileup tree
290 ientry_ = -1;
291 } else
292 return false;
293 }
294 ientry_++;
295 tree_->GetEntry(ientry_);
296 }
297
298 // if we have an event_
299 // make sure it is iterated as well
300 return event_ ? event_->nextEvent() : true;
301}
302
304 event_ = evt;
305 if (is_output_file_) {
306 // we are an output file
307 if (!tree_ && !parent_) {
308 // we don't have a tree and we don't have a parent
309 // ==> *Production Mode* create a new tree
311 ientry_ = 0;
312 entries_ = 0;
313 }
314
315 if (parent_) {
316 // we have a parent file so give
317 // the parent's tree to the event bus
318 // as the input tree
320 }
321
322 // give our tree to the event as the output tree
324 } else {
325 // we are an input file
326 // so give our tree to the event as input tree
328 } // output or input file
329}
330
331int EventFile::skipToEvent(int offset) {
332 // make sure the event number exists
333 ientry_ = offset % entries_ - 1;
334 return ientry_;
335}
336
338 parent_ = parent;
339
340 // we can assume parent_->tree_ is valid
341 // because (for input files) the tree_ is imported
342 // from the file and then checked if its valid in the
343 // EventFile constructor
344
345 // Enter output file
346 file_->cd();
347
348 // need to turn on/off the same branches as in the initial setup...
349 for (auto const& rule_pair : pre_clone_rules_)
350 parent_->tree_->SetBranchStatus(rule_pair.first.c_str(), rule_pair.second);
351
352 // Copy over addresses from the new parent
353 parent_->tree_->CopyAddresses(tree_);
354
355 // and reactivate any dropping rules
356 for (auto const& rule : reactivate_rules_)
357 parent_->tree_->SetBranchStatus(rule.c_str(), 1);
358
359 // Reset the entry index_ with the new parent index_
361
362 // import run headers from new input file
364
365 return;
366}
367
368void EventFile::writeRunTree(bool completed) {
369 if (not is_output_file_) {
370 EXCEPTION_RAISE("MisCall",
371 "Cannot write the run tree on an input event file.");
372 }
373
374 // TODO: Tree name shouldn't be hardcoded.
375
376 // stamp completion onto the headers before they go out
377 for (auto& [num, run_header] : run_map_) run_header->setCompleted(completed);
378
389 file_->cd();
390
391 // rebuild so the branch address stays valid across calls
392 delete run_tree_;
393 run_tree_ = nullptr;
394 file_->Delete("LDMX_Run;*"); // drop any earlier cycle
395 run_tree_ = new TTree("LDMX_Run", "LDMX run header");
396
397 // create the branch on this tree
398 ldmx::RunHeader* the_handle = nullptr;
399 run_tree_->Branch("RunHeader", "ldmx::RunHeader", &the_handle, 32000, 3);
400 // ROOT allocates a RunHeader when given a null pointer and leaves
401 // ownership with the caller, so take it to avoid leaking it
402 std::unique_ptr<ldmx::RunHeader> root_allocated(the_handle);
403
404 // copy over the run headers into the tree
405 for (auto& [num, run_header] : run_map_) {
406 the_handle = run_header.get();
407 run_tree_->Fill();
408 }
409
410 run_tree_->Write("", TObject::kOverwrite);
411 file_->Flush(); // get the key on disk before any kill
412}
413
414void EventFile::writeRunHeader(std::shared_ptr<ldmx::RunHeader> run_header) {
415 int run_number = run_header->getRunNumber();
416
417 if (run_map_.find(run_number) != run_map_.end()) {
418 EXCEPTION_RAISE("RunMap", "Run map already contains a run with number '" +
419 std::to_string(run_number) + "'.");
420 }
421
422 run_map_[run_number] = run_header;
423}
424
426 if (run_map_.find(run_number) != run_map_.end()) {
427 return run_map_.at(run_number).get();
428 }
429 return nullptr;
430}
431
433 ldmx::RunHeader* rh{this->getRunHeaderPtr(run_number)};
434 if (rh != nullptr) {
435 return *rh;
436 }
437 EXCEPTION_RAISE("RunHeader", "Unable to find header for run " +
438 std::to_string(run_number));
439}
440
441std::vector<int> EventFile::getIncompleteRuns() const {
442 std::vector<int> incomplete;
443 // without the flag on disk we have nothing to judge by
444 if (not run_headers_have_completeness_) return incomplete;
445 for (const auto& [num, run_header] : run_map_) {
446 if (not run_header->isCompleted()) incomplete.push_back(num);
447 }
448 return incomplete;
449}
450
452 // choose which file to import from
453 auto the_import_file{file_}; // if this is an input file
455 the_import_file = parent_->file_; // output file with input parent
456 else if (is_output_file_)
457 return; // output file, no input parent to read from
458
459 if (the_import_file) {
460 // the file exist
461 TTreeReader old_run_tree("LDMX_Run", the_import_file);
462 TTreeReaderValue<ldmx::RunHeader> old_run_header(old_run_tree, "RunHeader");
463 // older headers stream into a default false, which means nothing
465 onDiskRunHeaderVersion(old_run_tree.GetTree()) >=
467 // TODO check that setup went correctly
468 while (old_run_tree.Next()) {
469 auto* old_run_header_ptr = old_run_header.Get();
470 if (old_run_header_ptr != nullptr) {
471 int run_number = old_run_header_ptr->getRunNumber();
472 run_map_[run_number] =
473 std::make_shared<ldmx::RunHeader>(*old_run_header_ptr);
474 }
475 }
476 }
477
478 return;
479}
480} // namespace framework
Class implementing an event buffer system for storing event data.
This class manages all ROOT file input/output operations.
Definition EventFile.h:28
void updateParent(EventFile *parent)
Change pointer to different parent file.
TFile * file_
The backing TFile for this EventFile.
Definition EventFile.h:315
void addDrop(const std::string &rule)
Add a rule for dropping collections from the output.
void setupEvent(Event *evt)
Set an Event object containing the event data to work with this file.
std::map< int, std::shared_ptr< ldmx::RunHeader > > run_map_
Map of run numbers to RunHeader objects (owned via shared_ptr)
Definition EventFile.h:344
Long64_t entries_
The number of entries in the tree.
Definition EventFile.h:297
void writeRunHeader(std::shared_ptr< ldmx::RunHeader > runHeader)
Write the run header into the run map.
bool nextEvent(bool storeCurrentEvent=true)
Prepare the next event.
void writeRunTree(bool completed=false)
Write the map of run headers to the file as a TTree of RunHeader.
ldmx::RunHeader * getRunHeaderPtr(int runNumber)
Update the RunHeader for a given run, if it exists in the input file.
Long64_t ientry_
The current entry in the tree.
Definition EventFile.h:300
std::vector< std::pair< std::string, bool > > pre_clone_rules_
Pre-clone rules.
Definition EventFile.h:332
bool run_headers_have_completeness_
True when the run headers we read were written with a completion flag.
Definition EventFile.h:350
TTree * run_tree_
The run tree, owned by file_ once written.
Definition EventFile.h:347
bool is_loopable_
True if this is an input file with pileup overlay events *‍/.
Definition EventFile.h:312
~EventFile()
Destructor.
void importRunHeaders()
Fill the internal map of run numbers to RunHeader objects from the input file.
std::vector< int > getIncompleteRuns() const
The runs in this file whose writer did not close cleanly.
int skipToEvent(int offset)
Skip events using an offset.
std::string file_name_
The file name.
Definition EventFile.h:303
ldmx::RunHeader & getRunHeader(int runNumber)
Get the RunHeader for a given run, if it exists in the input file.
bool is_single_output_
True if there is only one output file.
Definition EventFile.h:309
EventFile(const framework::config::Parameters &params, const std::string &filename, EventFile *parent, bool isOutputFile, bool isSingleOutput, bool isLoopable)
Constructor to make a general file.
Definition EventFile.cxx:30
EventFile * parent_
A parent file containing event data.
Definition EventFile.h:321
std::vector< std::string > reactivate_rules_
Vector of drop rules that have been parsed and need to be used to reactivate these branches on the in...
Definition EventFile.h:341
bool is_output_file_
True if file is an output file being written to disk.
Definition EventFile.h:306
TTree * tree_
The tree with event data.
Definition EventFile.h:318
Event * event_
The object containing the actual event data (trees and branches).
Definition EventFile.h:324
bool isCorrupted() const
Check if the file we have is corrupted.
Implements an event buffer system for storing event data.
Definition Event.h:42
void addIgnore(const std::string &exp)
Add an ignore rule to the list of regex expressions to ignore on input.
Definition Event.cxx:34
void clear()
Clear this object's data (including passengers).
Definition Event.cxx:179
TTree * createTree()
Create the output data tree.
Definition Event.cxx:115
void setOutputTree(TTree *tree)
Set the output data tree.
Definition Event.cxx:121
void beforeFill()
Action to be executed before the tree is filled.
Definition Event.cxx:170
void onEndOfEvent()
Perform end of event action (doesn't do anything right now).
Definition Event.cxx:184
bool nextEvent()
Go to the next event by retrieving the event header.
Definition Event.cxx:165
void setInputTree(TTree *tree)
Set the input data tree.
Definition Event.cxx:123
void addDrop(const std::string &exp)
Add a drop rule to the list of regex expressions to drop.
Definition Event.cxx:24
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:29
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78
static const std::string BRANCH
Name of EventHeader branch.
Definition EventHeader.h:49
Run-specific configuration and data stored in its own output TTree alongside the event TTree in the o...
Definition RunHeader.h:67
static constexpr int VERSION_WITH_COMPLETED
The first class version that writes completed_.
Definition RunHeader.h:75
All classes in the ldmx-sw project use this namespace.