Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RLoopManager.cxx
Go to the documentation of this file.
1/*************************************************************************
2 * Copyright (C) 1995-2021, Rene Brun and Fons Rademakers. *
3 * All rights reserved. *
4 * *
5 * For the licensing terms see $ROOTSYS/LICENSE. *
6 * For the list of contributors see $ROOTSYS/README/CREDITS. *
7 *************************************************************************/
8
9#include "RConfigure.h" // R__USE_IMT
10#include "ROOT/RDataSource.hxx"
12#include "ROOT/InternalTreeUtils.hxx" // GetTreeFullPaths
15#include "ROOT/RDF/RDefineReader.hxx" // RDefinesWithReaders
21#include "ROOT/RDF/RVariationReader.hxx" // RVariationsWithReaders
22#include "ROOT/RLogger.hxx"
23#include "ROOT/RNTuple.hxx"
24#include "ROOT/RNTupleDS.hxx"
25#include "RtypesCore.h" // Long64_t
26#include "TStopwatch.h"
27#include "TBranchElement.h"
28#include "TBranchObject.h"
29#include "TChain.h"
30#include "TEntryList.h"
31#include "TFile.h"
32#include "TFriendElement.h"
33#include "TInterpreter.h"
34#include "TROOT.h" // IsImplicitMTEnabled, gCoreMutex, R__*_LOCKGUARD
35#include "TTreeReader.h"
36#include "TTree.h" // For MaxTreeSizeRAII. Revert when #6640 will be solved.
37
38#include "ROOT/RTTreeDS.hxx"
39
40#ifdef R__USE_IMT
43#include "ROOT/RSlotStack.hxx"
44#endif
45
46#include "ROOT/InternalIOUtils.hxx"
47#include "TSystem.h"
48
49#include <algorithm>
50#include <atomic>
51#include <cassert>
52#include <functional>
53#include <iostream>
54#include <memory>
55#include <stdexcept>
56#include <string>
57#include <sstream>
58#include <thread>
59#include <unordered_map>
60#include <vector>
61#include <set>
62#include <limits> // For MaxTreeSizeRAII. Revert when #6640 will be solved.
63
64using namespace ROOT::Detail::RDF;
65using namespace ROOT::Internal::RDF;
66
67namespace {
68/// A helper function that returns all RDF code that is currently scheduled for just-in-time compilation.
69/// This allows different RLoopManager instances to share these data.
70/// We want RLoopManagers to be able to add their code to a global "code to execute via cling",
71/// so that, lazily, we can jit everything that's needed by all RDFs in one go, which is potentially
72/// much faster than jitting each RLoopManager's code separately.
73std::string &GetCodeToJit()
74{
75 static std::string code;
76 return code;
77}
78
79std::string &GetCodeToDeclare()
80{
81 static std::string code;
82 return code;
83}
84
85// Signature of all helper functions that are created by JIT helpers, see
86// Book*Jit and JitBuildAction in RDFInterfaceUtils.cxx
87using JitHelperFunc_t = void (*)(const std::vector<std::string> &, ROOT::Internal::RDF::RColumnRegister &,
88 ROOT::Detail::RDF::RLoopManager &, void *, std::shared_ptr<void> *);
89std::unordered_map<std::size_t, JitHelperFunc_t> &GetJitHelperFuncMap()
90{
91 static std::unordered_map<std::size_t, JitHelperFunc_t> map;
92 return map;
93}
94std::unordered_map<std::size_t, std::size_t> &GetJitFuncBodyToFuncIdMap()
95{
96 static std::unordered_map<std::size_t, std::size_t> map;
97 return map;
98}
99
101{
102 // This function uses the interpreter and writes to the caches.
104
105 // Step 1: Declare the DeferredJitCall functions to the interpreter
106 // We use ProcessLine to ensure meta functionality (e.g. autoloading) is
107 // processed when needed.
108 // If instead we used Declare, builds with runtime_cxxmodules=OFF would fail
109 // in jitted actions with custom helpers with errors like:
110 // error: 'MyHelperType' is an incomplete type
111 // return std::make_unique<Action_t>(Helper_t(std::move(*h)), bl, std::move(prevNode), colRegister);
112 // ^
114 gInterpreter->ProcessLine(codeToDeclare.c_str(), &interpErrorCode);
116 throw std::runtime_error(
117 "\nAn error occurred during just-in-time compilation in RLoopManager::Run. The lines above might "
118 "indicate the cause of the error.\nAll RDF objects that have not run their event loop yet should be "
119 "considered in an invalid state.\n");
120 }
121
122 // Step 2: Retrieve the declared functions as function pointers, cache them
123 // for later use in RunDeferredCalls
126 auto clinfo = gInterpreter->ClassInfo_Factory("R_rdf");
127 assert(gInterpreter->ClassInfo_IsValid(clinfo));
128
129 for (auto &codeAndId : funcBodyToFuncIdMap) {
130 if (auto it = funcIdToFuncPointersMap.find(codeAndId.second); it == funcIdToFuncPointersMap.end()) {
131 // fast fetch of the address via gInterpreter
132 // (faster than gInterpreter->Evaluate(function name, ret), ret->GetAsPointer())
133 // Retrieve the JIT helper function we registered via RegisterJitHelperCall
134 const std::string funcName = "jitNodeRegistrator_" + std::to_string(codeAndId.second);
135 auto declid = gInterpreter->GetFunction(clinfo, funcName.c_str());
136 if (!declid) {
137 // The interpreter failed to compile the helper. Without this check
138 // we would later dereference a null function pointer and crash.
139 gInterpreter->ClassInfo_Delete(clinfo);
140 throw std::runtime_error(
141 "\nAn error occurred during just-in-time compilation in RLoopManager::Run: failed to retrieve "
142 "the JIT helper function '" +
143 funcName +
144 "'. The lines above might indicate the cause of the error.\nAll RDF objects that have not run "
145 "their event loop yet should be considered in an invalid state.\n");
146 }
147 auto minfo = gInterpreter->MethodInfo_Factory(declid);
148 assert(gInterpreter->MethodInfo_IsValid(minfo));
149 auto mname = gInterpreter->MethodInfo_GetMangledName(minfo);
150 [[maybe_unused]] auto res = funcIdToFuncPointersMap.insert(
151 {codeAndId.second, reinterpret_cast<JitHelperFunc_t>(gInterpreter->FindSym(mname))});
152 assert(res.second);
153 gInterpreter->MethodInfo_Delete(minfo);
154 }
155 }
156 gInterpreter->ClassInfo_Delete(clinfo);
157}
158
159void ThrowIfNSlotsChanged(unsigned int nSlots)
160{
162 if (currentSlots != nSlots) {
163 std::string msg = "RLoopManager::Run: when the RDataFrame was constructed the number of slots required was " +
164 std::to_string(nSlots) + ", but when starting the event loop it was " +
165 std::to_string(currentSlots) + ".";
166 if (currentSlots > nSlots)
167 msg += " Maybe EnableImplicitMT() was called after the RDataFrame was constructed?";
168 else
169 msg += " Maybe DisableImplicitMT() was called after the RDataFrame was constructed?";
170 throw std::runtime_error(msg);
171 }
172}
173
174/**
175\struct MaxTreeSizeRAII
176\brief Scope-bound change of `TTree::fgMaxTreeSize`.
177
178This RAII object stores the current value result of `TTree::GetMaxTreeSize`,
179changes it to maximum at construction time and restores it back at destruction
180time. Needed for issue #6523 and should be reverted when #6640 will be solved.
181*/
182struct MaxTreeSizeRAII {
183 Long64_t fOldMaxTreeSize;
184
185 MaxTreeSizeRAII() : fOldMaxTreeSize(TTree::GetMaxTreeSize())
186 {
187 TTree::SetMaxTreeSize(std::numeric_limits<Long64_t>::max());
188 }
189
190 ~MaxTreeSizeRAII() { TTree::SetMaxTreeSize(fOldMaxTreeSize); }
191};
192
193struct DatasetLogInfo {
194 std::string fDataSet;
195 ULong64_t fRangeStart;
196 ULong64_t fRangeEnd;
197 unsigned int fSlot;
198};
199
200std::string LogRangeProcessing(const DatasetLogInfo &info)
201{
202 std::stringstream msg;
203 msg << "Processing " << info.fDataSet << ": entry range [" << info.fRangeStart << "," << info.fRangeEnd - 1
204 << "], using slot " << info.fSlot << " in thread " << std::this_thread::get_id() << '.';
205 return msg.str();
206}
207
208auto MakeDatasetColReadersKey(std::string_view colName, const std::type_info &ti)
209{
210 // We use a combination of column name and column type name as the key because in some cases we might end up
211 // with concrete readers that use different types for the same column, e.g. std::vector and RVec here:
212 // df.Sum<vector<int>>("stdVectorBranch");
213 // df.Sum<RVecI>("stdVectorBranch");
214 return std::string(colName) + ':' + ti.name();
215}
216
217/// \brief Check if object of a certain type is in the directory
218///
219/// Attempts to read an object of the specified type via TDirectory::Get, wraps
220/// it in a std::unique_ptr to avoid leaking the object.
221template <typename T>
222bool IsObjectInDir(std::string_view objName, TDirectory &dir)
223{
224 std::unique_ptr<T> o{dir.Get<T>(objName.data())};
225 return o.get();
226}
227
228/// \brief Check if a generic object is in the directory
229///
230/// Checks if a generic object is in the directory, uses TDirectory::GetKey
231/// to avoid having to deal with memory management of the object being read
232/// without having its type.
233template <>
234bool IsObjectInDir<void>(std::string_view objName, TDirectory &dir)
235{
236 return dir.GetKey(objName.data());
237}
238} // anonymous namespace
239
240/**
241 * \brief Helper function to open a file (or the first file from a glob).
242 * This function is used at construction time of an RDataFrame, to check the
243 * concrete type of the dataset stored inside the file.
244 */
245std::unique_ptr<TFile> OpenFileWithSanityChecks(std::string_view fileNameGlob)
246{
247 // Follow same logic in TChain::Add to find the correct string to look for globbing:
248 // - If the extension ".root" is present in the file name, pass along the basename.
249 // - If not, use the "?" token to delimit the part of the string which represents the basename.
250 // - Otherwise, pass the full filename.
251 auto &&baseNameAndQuery = [&fileNameGlob]() {
252 constexpr std::string_view delim{".root"};
253 if (auto &&it = std::find_end(fileNameGlob.begin(), fileNameGlob.end(), delim.begin(), delim.end());
254 it != fileNameGlob.end()) {
255 auto &&distanceToEndOfDelim = std::distance(fileNameGlob.begin(), it + delim.length());
256 return std::make_pair(fileNameGlob.substr(0, distanceToEndOfDelim), fileNameGlob.substr(distanceToEndOfDelim));
257 } else if (auto &&lastQuestionMark = fileNameGlob.find_last_of('?'); lastQuestionMark != std::string_view::npos)
258 return std::make_pair(fileNameGlob.substr(0, lastQuestionMark), fileNameGlob.substr(lastQuestionMark));
259 else
260 return std::make_pair(fileNameGlob, std::string_view{});
261 }();
262 // Captured structured bindings variable are only valid since C++20
263 auto &&baseName = baseNameAndQuery.first;
264 auto &&query = baseNameAndQuery.second;
265
266 std::string fileToOpen{fileNameGlob};
267 if (baseName.find_first_of("[]*?") != std::string_view::npos) { // Wildcards accepted by TChain::Add
268 const auto expanded = ROOT::Internal::TreeUtils::ExpandGlob(std::string{baseName});
269 if (expanded.empty())
270 throw std::invalid_argument{"RDataFrame: The glob expression '" + std::string{baseName} +
271 "' did not match any files."};
272
273 fileToOpen = expanded.front() + std::string{query};
274 }
275
276 ::TDirectory::TContext ctxt; // Avoid changing gDirectory;
277 std::unique_ptr<TFile> inFile{TFile::Open(fileToOpen.c_str(), "READ_WITHOUT_GLOBALREGISTRATION")};
278 if (!inFile || inFile->IsZombie())
279 throw std::invalid_argument("RDataFrame: could not open file \"" + fileToOpen + "\".");
280
281 return inFile;
282}
283
284namespace ROOT {
285namespace Detail {
286namespace RDF {
287
288/// A RAII object that calls RLoopManager::CleanUpTask at destruction
300
301} // namespace RDF
302} // namespace Detail
303} // namespace ROOT
304
305ROOT::Detail::RDF::RLoopManager::RLoopManager(const ROOT::Detail::RDF::ColumnNames_t &defaultColumns)
306 : fDefaultColumns(defaultColumns),
307 fNSlots(RDFInternal::GetNSlots()),
308 fNewSampleNotifier(fNSlots),
309 fSampleInfos(fNSlots),
310 fDatasetColumnReaders(fNSlots)
311{
312}
313
315 : fDefaultColumns(defaultBranches),
316 fNSlots(RDFInternal::GetNSlots()),
317 fLoopType(ROOT::IsImplicitMTEnabled() ? ELoopType::kDataSourceMT : ELoopType::kDataSource),
318 fDataSource(std::make_unique<ROOT::Internal::RDF::RTTreeDS>(ROOT::Internal::RDF::MakeAliasedSharedPtr(tree))),
319 fNewSampleNotifier(fNSlots),
320 fSampleInfos(fNSlots),
321 fDatasetColumnReaders(fNSlots)
322{
323 fDataSource->SetNSlots(fNSlots);
324}
325
327 : fEmptyEntryRange(0, nEmptyEntries),
328 fNSlots(RDFInternal::GetNSlots()),
329 fLoopType(ROOT::IsImplicitMTEnabled() ? ELoopType::kNoFilesMT : ELoopType::kNoFiles),
330 fNewSampleNotifier(fNSlots),
331 fSampleInfos(fNSlots),
332 fDatasetColumnReaders(fNSlots)
333{
334}
335
336RLoopManager::RLoopManager(std::unique_ptr<RDataSource> ds, const ColumnNames_t &defaultBranches)
337 : fDefaultColumns(defaultBranches),
338 fNSlots(RDFInternal::GetNSlots()),
339 fLoopType(ROOT::IsImplicitMTEnabled() ? ELoopType::kDataSourceMT : ELoopType::kDataSource),
340 fDataSource(std::move(ds)),
341 fNewSampleNotifier(fNSlots),
342 fSampleInfos(fNSlots),
343 fDatasetColumnReaders(fNSlots)
344{
345 fDataSource->SetNSlots(fNSlots);
346}
347
349 : fNSlots(RDFInternal::GetNSlots()),
350 fLoopType(ROOT::IsImplicitMTEnabled() ? ELoopType::kDataSourceMT : ELoopType::kDataSource),
351 fNewSampleNotifier(fNSlots),
352 fSampleInfos(fNSlots),
353 fDatasetColumnReaders(fNSlots)
354{
355 ChangeSpec(std::move(spec));
356}
357
358namespace {
359std::optional<std::string> GetRedirectedSampleId(std::string_view path, std::string_view datasetName)
360{
361 // Mimick the redirection done in TFile::Open to see if the path points to a FUSE-mounted EOS path.
362 // If so, we create a redirected sample ID with the full xroot URL.
363 TString expandedUrl(path.data());
365 TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
366 if (strcmp(fileurl.GetProtocol(), "file") == 0) {
367 if (auto xurl = ROOT::Internal::GetEOSRedirectedXRootURL(fileurl.GetFile()))
368 return *xurl + '/' + datasetName.data();
369 }
370 return std::nullopt;
371}
372} // namespace
373
374/**
375 * @brief Changes the internal TTree held by the RLoopManager.
376 *
377 * @warning This method may lead to potentially dangerous interactions if used
378 * after the construction of the RDataFrame. Changing the specification makes
379 * sense *if and only if* the schema of the dataset is *unchanged*, i.e. the
380 * new specification refers to exactly the same number of columns, with the
381 * same names and types. The actual use case of this method is moving the
382 * processing of the same RDataFrame to a different range of entries of the
383 * same dataset (which may be stored in a different set of files).
384 *
385 * @param spec The specification of the dataset to be adopted.
386 */
388{
389 auto filesVec = spec.GetFileNameGlobs();
391 filesVec[0]); // we only need the first file, we assume all files are either TTree or RNTuple
392 auto datasetName = spec.GetTreeNames();
393
394 // Change the range of entries to be processed
395 fBeginEntry = spec.GetEntryRangeBegin();
396 fEndEntry = spec.GetEntryRangeEnd();
397
398 // Store the samples
399 fSamples = spec.MoveOutSamples();
400 fSampleMap.clear();
401
404
405 if (isTTree || isRNTuple) {
406
407 if (isTTree) {
408 // Create the internal main chain
410 for (auto &sample : fSamples) {
411 const auto &trees = sample.GetTreeNames();
412 const auto &files = sample.GetFileNameGlobs();
413 for (std::size_t i = 0ul; i < files.size(); ++i) {
414 // We need to use `<filename>?#<treename>` as an argument to TChain::Add
415 // (see https://github.com/root-project/root/pull/8820 for why)
416 const auto fullpath = files[i] + "?#" + trees[i];
417 chain->Add(fullpath.c_str());
418 // ...but instead we use `<filename>/<treename>` as a sample ID (cannot
419 // change this easily because of backward compatibility: the sample ID
420 // is exposed to users via RSampleInfo and DefinePerSample).
421 const auto sampleId = files[i] + '/' + trees[i];
422 fSampleMap.insert({sampleId, &sample});
423
424 // Also add redirected EOS xroot URL when available
426 fSampleMap.insert({redirectedSampleId.value(), &sample});
427 }
428 }
429 fDataSource = std::make_unique<ROOT::Internal::RDF::RTTreeDS>(std::move(chain), spec.GetFriendInfo());
430 } else if (isRNTuple) {
431
432 std::vector<std::string> fileNames;
433 std::set<std::string> rntupleNames;
434
435 for (auto &sample : fSamples) {
436 const auto &trees = sample.GetTreeNames();
437 const auto &files = sample.GetFileNameGlobs();
438 for (std::size_t i = 0ul; i < files.size(); ++i) {
439 const auto sampleId = files[i] + '/' + trees[i];
440 fSampleMap.insert({sampleId, &sample});
441 fileNames.push_back(files[i]);
442 rntupleNames.insert(trees[i]);
443
444 // Also add redirected EOS xroot URL when available
446 fSampleMap.insert({redirectedSampleId.value(), &sample});
447 }
448 }
449
450 if (rntupleNames.size() == 1) {
451 fDataSource = std::make_unique<ROOT::RDF::RNTupleDS>(*rntupleNames.begin(), fileNames);
452
453 } else {
454 throw std::runtime_error(
455 "More than one RNTuple name was found, please make sure to use RNTuples with the same name.");
456 }
457 }
458
459 fDataSource->SetNSlots(fNSlots);
460
461 for (unsigned int slot{}; slot < fNSlots; slot++) {
462 for (auto &v : fDatasetColumnReaders[slot])
463 v.second.reset();
464 }
465 } else {
466 std::string errMsg =
467 IsObjectInDir<void>(datasetName[0].data(), *inFile) ? "unsupported data format for" : "cannot find";
468 throw std::invalid_argument("RDataFrame: " + errMsg + " dataset \"" + std::string(datasetName[0]) + "\" in file \"" +
469 inFile->GetName() + "\".");
470 }
471}
472
473/// Run event loop with no source files, in parallel.
475{
476#ifdef R__USE_IMT
477 std::shared_ptr<ROOT::Internal::RSlotStack> slotStack = SlotStack();
478 // Working with an empty tree.
479 // Evenly partition the entries according to fNSlots. Produce around 2 tasks per slot.
480 const auto nEmptyEntries = GetNEmptyEntries();
481 const auto nEntriesPerSlot = nEmptyEntries / (fNSlots * 2);
482 auto remainder = nEmptyEntries % (fNSlots * 2);
483 std::vector<std::pair<ULong64_t, ULong64_t>> entryRanges;
484 ULong64_t begin = fEmptyEntryRange.first;
485 while (begin < fEmptyEntryRange.second) {
486 ULong64_t end = begin + nEntriesPerSlot;
487 if (remainder > 0) {
488 ++end;
489 --remainder;
490 }
491 entryRanges.emplace_back(begin, end);
492 begin = end;
493 }
494
495 // Each task will generate a subrange of entries
496 auto genFunction = [this, &slotStack](const std::pair<ULong64_t, ULong64_t> &range) {
498 auto slot = slotRAII.fSlot;
499 RCallCleanUpTask cleanup(*this, slot);
500 InitNodeSlots(nullptr, slot);
501 R__LOG_DEBUG(0, RDFLogChannel()) << LogRangeProcessing({"an empty source", range.first, range.second, slot});
502 try {
504 for (auto currEntry = range.first; currEntry < range.second; ++currEntry) {
506 }
507 } catch (...) {
508 // Error might throw in experiment frameworks like CMSSW
509 std::cerr << "RDataFrame::Run: event loop was interrupted\n";
510 throw;
511 }
512 };
513
515 pool.Foreach(genFunction, entryRanges);
516
517#endif // not implemented otherwise
518}
519
520/// Run event loop with no source files, in sequence.
522{
523 InitNodeSlots(nullptr, 0);
525 {"an empty source", fEmptyEntryRange.first, fEmptyEntryRange.second, 0u});
526 RCallCleanUpTask cleanup(*this);
527 try {
532 }
533 } catch (...) {
534 std::cerr << "RDataFrame::Run: event loop was interrupted\n";
535 throw;
536 }
537}
538
539#ifdef R__USE_IMT
540namespace {
541/// Return true on succesful entry read.
542///
543/// TTreeReader encodes successful reads in the `kEntryValid` enum value, but
544/// there can be other situations where the read is still valid. For now, these
545/// are:
546/// - If there was no match of the current entry in one or more friend trees
547/// according to their respective indexes.
548/// - If there was a missing branch at the start of a new tree in the dataset.
549///
550/// In such situations, although the entry is not complete, the processing
551/// should not be aborted and nodes of the computation graph will take action
552/// accordingly.
554{
555 treeReader.Next();
556 switch (treeReader.GetEntryStatus()) {
557 case TTreeReader::kEntryValid: return true;
558 case TTreeReader::kIndexedFriendNoMatch: return true;
560 default: return false;
561 }
562}
563} // namespace
564#endif
565
566namespace {
567struct DSRunRAII {
569 DSRunRAII(ROOT::RDF::RDataSource &ds, const std::set<std::string> &suppressErrorsForMissingColumns) : fDS(ds)
570 {
572 }
573 ~DSRunRAII() { fDS.Finalize(); }
574};
575} // namespace
576
579 unsigned int fSlot;
582 TTreeReader *treeReader = nullptr)
583 : fLM(lm), fSlot(slot), fTreeReader(treeReader)
584 {
585 fLM.InitNodeSlots(fTreeReader, fSlot);
586 fLM.GetDataSource()->InitSlot(fSlot, firstEntry);
587 }
589};
590
591/// Run event loop over data accessed through a DataSource, in sequence.
593{
594 assert(fDataSource != nullptr);
595 // Shortcut if the entry range would result in not reading anything
596 if (fBeginEntry == fEndEntry)
597 return;
598 // Apply global entry range if necessary
599 if (fBeginEntry != 0 || fEndEntry != std::numeric_limits<Long64_t>::max())
600 fDataSource->SetGlobalEntryRange(std::make_pair<std::uint64_t, std::uint64_t>(fBeginEntry, fEndEntry));
601 // Initialize data source and book finalization
603 // Ensure cleanup task is always called at the end. Notably, this also resets the column readers for those data
604 // sources that need it (currently only TTree).
605 RCallCleanUpTask cleanup(*this);
606
607 // Main event loop. We start with an empty vector of ranges because we need to initialize the nodes and the data
608 // source before the first call to GetEntryRanges, since it could trigger reading (currently only happens with
609 // TTree).
610 std::uint64_t processedEntries{};
611 std::vector<std::pair<ULong64_t, ULong64_t>> ranges{};
612 do {
613
615
616 ranges = fDataSource->GetEntryRanges();
617
619
620 try {
621 for (const auto &range : ranges) {
622 const auto start = range.first;
623 const auto end = range.second;
624 R__LOG_DEBUG(0, RDFLogChannel()) << LogRangeProcessing({fDataSource->GetLabel(), start, end, 0u});
625 for (auto entry = start; entry < end && fNStopsReceived < fNChildren; ++entry) {
626 if (fDataSource->SetEntry(0u, entry)) {
628 }
630 }
631 }
632 } catch (...) {
633 std::cerr << "RDataFrame::Run: event loop was interrupted\n";
634 throw;
635 }
636
637 } while (!ranges.empty() && fNStopsReceived < fNChildren);
638
640
641 if (fEndEntry != std::numeric_limits<Long64_t>::max() &&
642 static_cast<std::uint64_t>(fEndEntry - fBeginEntry) > processedEntries) {
643 std::ostringstream buf{};
644 buf << "RDataFrame stopped processing after ";
646 buf << " entries, whereas an entry range (begin=";
647 buf << fBeginEntry;
648 buf << ",end=";
649 buf << fEndEntry;
650 buf << ") was requested. Consider adjusting the end value of the entry range to a maximum of ";
652 buf << ".";
653 Warning("RDataFrame::Run", "%s", buf.str().c_str());
654 }
655}
656
657/// Run event loop over data accessed through a DataSource, in parallel.
659{
660#ifdef R__USE_IMT
661 assert(fDataSource != nullptr);
662 // Shortcut if the entry range would result in not reading anything
663 if (fBeginEntry == fEndEntry)
664 return;
665 // Apply global entry range if necessary
666 if (fBeginEntry != 0 || fEndEntry != std::numeric_limits<Long64_t>::max())
667 fDataSource->SetGlobalEntryRange(std::make_pair<std::uint64_t, std::uint64_t>(fBeginEntry, fEndEntry));
668
670
672
673#endif // not implemented otherwise (never called)
674}
675
676/// Execute actions and make sure named filters are called for each event.
677/// Named filters must be called even if the analysis logic would not require it, lest they report confusing results.
679{
680 // data-block callbacks run before the rest of the graph
682 for (auto &callback : fSampleCallbacks)
683 callback.second(slot, fSampleInfos[slot]);
685 }
686
687 for (auto *actionPtr : fBookedActions)
688 actionPtr->Run(slot, entry);
690 namedFilterPtr->CheckFilters(slot, entry);
691 for (auto &callback : fCallbacksEveryNEvents)
692 callback(slot);
693}
694
695/// Build TTreeReaderValues for all nodes
696/// This method loops over all filters, actions and other booked objects and
697/// calls their `InitSlot` method, to get them ready for running a task.
699{
701 for (auto *ptr : fBookedActions)
702 ptr->InitSlot(r, slot);
703 for (auto *ptr : fBookedFilters)
704 ptr->InitSlot(r, slot);
705 for (auto *ptr : fBookedDefines)
706 ptr->InitSlot(r, slot);
707 for (auto *ptr : fBookedVariations)
708 ptr->InitSlot(r, slot);
709
710 for (auto &callback : fCallbacksOnce)
711 callback(slot);
712}
713
715 if (r != nullptr) {
716 // we need to set a notifier so that we run the callbacks every time we switch to a new TTree
717 // `PrependLink` inserts this notifier into the TTree/TChain's linked list of notifiers
718 fNewSampleNotifier.GetChainNotifyLink(slot).PrependLink(*r->GetTree());
719 }
720 // Whatever the data source, initially set the "new data block" flag:
721 // - for TChains, this ensures that we don't skip the first data block because
722 // the correct tree is already loaded
723 // - for RDataSources and empty sources, which currently don't have data blocks, this
724 // ensures that we run once per task
726}
727
728void RLoopManager::UpdateSampleInfo(unsigned int slot, const std::pair<ULong64_t, ULong64_t> &range) {
730 "Empty source, range: {" + std::to_string(range.first) + ", " + std::to_string(range.second) + "}", range);
731}
732
734 // one GetTree to retrieve the TChain, another to retrieve the underlying TTree
735 auto *tree = r.GetTree()->GetTree();
736 R__ASSERT(tree != nullptr);
737 const std::string treename = ROOT::Internal::TreeUtils::GetTreeFullPaths(*tree)[0];
738 auto *file = tree->GetCurrentFile();
739 const std::string fname = file != nullptr ? file->GetName() : "#inmemorytree#";
740
741 std::pair<Long64_t, Long64_t> range = r.GetEntriesRange();
742 R__ASSERT(range.first >= 0);
743 if (range.second == -1) {
744 range.second = tree->GetEntries(); // convert '-1', i.e. 'until the end', to the actual entry number
745 }
746 // If the tree is stored in a subdirectory, treename will be the full path to it starting with the root directory '/'
747 const std::string &id = fname + (treename.rfind('/', 0) == 0 ? "" : "/") + treename;
748 if (fSampleMap.empty()) {
749 fSampleInfos[slot] = RSampleInfo(id, range, nullptr, tree->GetEntries());
750 } else {
751 if (fSampleMap.find(id) == fSampleMap.end())
752 throw std::runtime_error("Full sample identifier '" + id + "' cannot be found in the available samples.");
753 fSampleInfos[slot] = RSampleInfo(id, range, fSampleMap[id], tree->GetEntries());
754 }
755}
756
757/// Create a slot stack with the desired number of slots or reuse a shared instance.
758/// When a LoopManager runs in isolation, it will create its own slot stack from the
759/// number of slots. When it runs as part of RunGraphs(), each loop manager will be
760/// assigned a shared slot stack, so dataframe helpers can be shared in a thread-safe
761/// manner.
762std::shared_ptr<ROOT::Internal::RSlotStack> RLoopManager::SlotStack() const
763{
764#ifdef R__USE_IMT
765 if (auto shared = fSlotStack.lock(); shared) {
766 return shared;
767 } else {
768 return std::make_shared<ROOT::Internal::RSlotStack>(fNSlots);
769 }
770#else
771 return nullptr;
772#endif
773}
774
775/// Initialize all nodes of the functional graph before running the event loop.
776/// This method is called once per event-loop and performs generic initialization
777/// operations that do not depend on the specific processing slot (i.e. operations
778/// that are common for all threads).
780{
782 for (auto *filter : fBookedFilters)
783 filter->InitNode();
784 for (auto *range : fBookedRanges)
785 range->InitNode();
786 for (auto *ptr : fBookedActions)
787 ptr->Initialize();
788}
789
790/// Perform clean-up operations. To be called at the end of each event loop.
792{
793 fMustRunNamedFilters = false;
794
795 // forget RActions and detach TResultProxies
796 for (auto *ptr : fBookedActions)
797 ptr->Finalize();
798
799 fRunActions.insert(fRunActions.begin(), fBookedActions.begin(), fBookedActions.end());
800 fBookedActions.clear();
801
802 // reset children counts
803 fNChildren = 0;
804 fNStopsReceived = 0;
805 for (auto *ptr : fBookedFilters)
806 ptr->ResetChildrenCount();
807 for (auto *ptr : fBookedRanges)
808 ptr->ResetChildrenCount();
809
811 fCallbacksOnce.clear();
812}
813
814/// Perform clean-up operations. To be called at the end of each task execution.
816{
817 if (r != nullptr)
818 fNewSampleNotifier.GetChainNotifyLink(slot).RemoveLink(*r->GetTree());
819 for (auto *ptr : fBookedActions)
820 ptr->FinalizeSlot(slot);
821 for (auto *ptr : fBookedFilters)
822 ptr->FinalizeSlot(slot);
823 for (auto *ptr : fBookedDefines)
824 ptr->FinalizeSlot(slot);
825
826 if (auto ds = GetDataSource(); ds && ds->GetLabel() == "TTreeDS") {
827 // we are reading from a tree/chain and we need to re-create the RTreeColumnReaders at every task
828 // because the TTreeReader object changes at every task
829 for (auto &v : fDatasetColumnReaders[slot])
830 v.second.reset();
831 }
832}
833
834/// Add RDF nodes that require just-in-time compilation to the computation graph.
835/// This method also clears the contents of GetCodeToJit().
837{
839 if (GetCodeToJit().empty() && GetCodeToDeclare().empty()) {
841 R__LOG_INFO(RDFLogChannel()) << "Nothing to jit and execute.";
842 return;
843 }
844
846 // Check again if another thread has already cleared the global string
847 // with the code to JIT. Without this check, we could end up calling
848 // InterpreterCalc with an empty string, which would raise an exception.
849 if (GetCodeToJit().empty() && GetCodeToDeclare().empty()) {
851 R__LOG_INFO(RDFLogChannel()) << "Nothing to jit and execute.";
852 return;
853 }
854 const std::string codeToDeclare = std::move(GetCodeToDeclare());
855 const std::string code = std::move(GetCodeToJit());
856
857 TStopwatch s;
858 s.Start();
859 if (!codeToDeclare.empty()) {
861 }
862 if (!code.empty()) {
863 RDFInternal::InterpreterCalc(code, "RLoopManager::Run");
864 }
865 s.Stop();
866 R__LOG_INFO(RDFLogChannel()) << "Just-in-time compilation phase completed"
867 << (s.RealTime() > 1e-3 ? " in " + std::to_string(s.RealTime()) + " seconds."
868 : " in less than 1ms.");
869
871}
872
874{
875 if (!fJitHelperCalls.empty()) {
876 // funcMap is not thread-safe
878 TStopwatch s;
879 s.Start();
880 const auto &funcMap = GetJitHelperFuncMap();
881 for (auto &call : fJitHelperCalls) {
882 funcMap.at(call.fFunctionId)(call.fColNames, *call.fColRegister, *this, call.fJittedNode.get(),
883 &call.fExtraArgs);
884 }
885 s.Stop();
886 const auto realTime = s.RealTime();
887 R__LOG_INFO(RDFLogChannel()) << fJitHelperCalls.size() << " deferred calls completed"
888 << (realTime > 1e-3 ? " in " + std::to_string(realTime) + " seconds."
889 : " in less than 1ms.");
890 // Promoting to write lock to clear the vector
892 fJitHelperCalls.clear();
893 }
894}
895
896/// Trigger counting of number of children nodes for each node of the functional graph.
897/// This is done once before starting the event loop. Each action sends an `increase children count` signal
898/// upstream, which is propagated until RLoopManager. Each time a node receives the signal, in increments its
899/// children counter. Each node only propagates the signal once, even if it receives it multiple times.
900/// Named filters also send an `increase children count` signal, just like actions, as they always execute during
901/// the event loop so the graph branch they belong to must count as active even if it does not end in an action.
903{
904 for (auto *actionPtr : fBookedActions)
905 actionPtr->TriggerChildrenCount();
907 namedFilterPtr->TriggerChildrenCount();
908}
909
910/// Start the event loop with a different mechanism depending on IMT/no IMT, data source/no data source.
911/// Also perform a few setup and clean-up operations (jit actions if necessary, clear booked actions after the loop...).
912/// The jitting phase is skipped if the `jit` parameter is `false` (unsafe, use with care).
914{
915 // Change value of TTree::GetMaxTreeSize only for this scope. Revert when #6640 will be solved.
916 MaxTreeSizeRAII ctxtmts;
917
918 R__LOG_INFO(RDFLogChannel()) << "Starting event loop number " << fNRuns << '.';
919
921
922 if (jit)
923 Jit();
924
925 // Called here since in a RunGraphs run, multiple RLoopManager runs could be
926 // triggered from different threads.
928
929 InitNodes();
930
931 // Exceptions can occur during the event loop. In order to ensure proper cleanup of nodes
932 // we use RAII: even in case of an exception, the destructor of the object is invoked and
933 // all the cleanup takes place.
934 class NodesCleanerRAII {
936
937 public:
939 ~NodesCleanerRAII() { fRLM.CleanUpNodes(); }
940 };
941
943
944 TStopwatch s;
945 s.Start();
946
947 switch (fLoopType) {
949 throw std::runtime_error("RDataFrame: executing the computation graph without a data source, aborting.");
950 break;
953 case ELoopType::kNoFiles: RunEmptySource(); break;
955 }
956 s.Stop();
957
958 fNRuns++;
959
960 R__LOG_INFO(RDFLogChannel()) << "Finished event loop number " << fNRuns - 1 << " (" << s.CpuTime() << "s CPU, "
961 << s.RealTime() << "s elapsed).";
962}
963
964/// Return the list of default columns -- empty if none was provided when constructing the RDataFrame
969
975
982
984{
985 fBookedFilters.emplace_back(filterPtr);
986 if (filterPtr->HasName()) {
987 fBookedNamedFilters.emplace_back(filterPtr);
989 }
990}
991
997
1002
1007
1009{
1010 fBookedDefines.emplace_back(ptr);
1011}
1012
1018
1023
1028
1029// dummy call, end of recursive chain of calls
1031{
1032 return true;
1033}
1034
1035/// Call `FillReport` on all booked filters
1037{
1038 for (const auto *fPtr : fBookedNamedFilters)
1039 fPtr->FillReport(rep);
1040}
1041
1042void RLoopManager::ToJitExec(const std::string &code) const
1043{
1045 GetCodeToJit().append(code);
1046}
1047
1049 std::unique_ptr<ROOT::Internal::RDF::RColumnRegister> colRegister,
1050 const std::vector<std::string> &colNames, std::shared_ptr<void> jittedNode,
1051 std::shared_ptr<void> argument)
1052{
1054 {
1056 auto match = funcBodyToFuncIdMap.find(fStringHasher(funcBody));
1057 if (match != funcBodyToFuncIdMap.end()) {
1058 R__WRITE_LOCKGUARD(ROOT::gCoreMutex); // modifying fJitHelperCalls
1059 std::string funcName = "jitNodeRegistrator_" + std::to_string(match->second);
1060 R__LOG_DEBUG(0, RDFLogChannel()) << "JIT helper " << funcName << " was already registered.";
1061 fJitHelperCalls.emplace_back(match->second, std::move(colRegister), colNames, jittedNode, argument);
1062 return;
1063 }
1064 }
1065
1066 {
1067 // Register lazily a JIT helper
1069 auto registratorId = funcBodyToFuncIdMap.size();
1070 std::string funcName = "jitNodeRegistrator_" + std::to_string(registratorId);
1072 assert(res.second);
1073
1074 std::string toDeclare = "namespace R_rdf {\n void " + funcName + funcBody + "\n}\n";
1075 R__LOG_DEBUG(0, RDFLogChannel()) << "Registering deferred JIT helper:\n" << toDeclare;
1076
1077 GetCodeToDeclare().append(toDeclare);
1079 }
1080}
1081
1082void RLoopManager::RegisterCallback(ULong64_t everyNEvents, std::function<void(unsigned int)> &&f)
1083{
1084 if (everyNEvents == 0ull)
1085 fCallbacksOnce.emplace_back(std::move(f), fNSlots);
1086 else
1087 fCallbacksEveryNEvents.emplace_back(everyNEvents, std::move(f), fNSlots);
1088}
1089
1090std::vector<std::string> RLoopManager::GetFiltersNames()
1091{
1092 std::vector<std::string> filters;
1093 for (auto *filter : fBookedFilters) {
1094 auto name = (filter->HasName() ? filter->GetName() : "Unnamed Filter");
1095 filters.push_back(name);
1096 }
1097 return filters;
1098}
1099
1100std::vector<RNodeBase *> RLoopManager::GetGraphEdges() const
1101{
1102 std::vector<RNodeBase *> nodes(fBookedFilters.size() + fBookedRanges.size());
1103 auto it = std::copy(fBookedFilters.begin(), fBookedFilters.end(), nodes.begin());
1104 std::copy(fBookedRanges.begin(), fBookedRanges.end(), it);
1105 return nodes;
1106}
1107
1108std::vector<RDFInternal::RActionBase *> RLoopManager::GetAllActions() const
1109{
1110 std::vector<RDFInternal::RActionBase *> actions(fBookedActions.size() + fRunActions.size());
1111 auto it = std::copy(fBookedActions.begin(), fBookedActions.end(), actions.begin());
1112 std::copy(fRunActions.begin(), fRunActions.end(), it);
1113 return actions;
1114}
1115
1116std::shared_ptr<ROOT::Internal::RDF::GraphDrawing::GraphNode> RLoopManager::GetGraph(
1117 std::unordered_map<void *, std::shared_ptr<ROOT::Internal::RDF::GraphDrawing::GraphNode>> &visitedMap)
1118{
1119 // If there is already a node for the RLoopManager return it. If there is not, return a new one.
1120 auto duplicateRLoopManagerIt = visitedMap.find((void *)this);
1122 return duplicateRLoopManagerIt->second;
1123
1124 std::string name;
1125 if (fDataSource) {
1126 name = fDataSource->GetLabel();
1127 } else {
1128 name = "Empty source\\nEntries: " + std::to_string(GetNEmptyEntries());
1129 }
1130 auto thisNode = std::make_shared<ROOT::Internal::RDF::GraphDrawing::GraphNode>(
1132 visitedMap[(void *)this] = thisNode;
1133 return thisNode;
1134}
1135
1136/// Return true if AddDataSourceColumnReaders was called for column name col.
1137bool RLoopManager::HasDataSourceColumnReaders(std::string_view col, const std::type_info &ti) const
1138{
1139 const auto key = MakeDatasetColReadersKey(col, ti);
1140 assert(fDataSource != nullptr);
1141 // since data source column readers are always added for all slots at the same time,
1142 // if the reader is present for slot 0 we have it for all other slots as well.
1143 auto it = fDatasetColumnReaders[0].find(key);
1144 return (it != fDatasetColumnReaders[0].end() && it->second);
1145}
1146
1148 std::vector<std::unique_ptr<RColumnReaderBase>> &&readers,
1149 const std::type_info &ti)
1150{
1151 const auto key = MakeDatasetColReadersKey(col, ti);
1153 assert(readers.size() == fNSlots);
1154
1155 for (auto slot = 0u; slot < fNSlots; ++slot) {
1156 fDatasetColumnReaders[slot][key] = std::move(readers[slot]);
1157 }
1158}
1159
1161 const std::type_info &ti, TTreeReader *treeReader)
1162{
1164 const auto key = MakeDatasetColReadersKey(col, ti);
1165 // if a reader for this column and this slot was already there, we are doing something wrong
1166 assert(readers.find(key) == readers.end() || readers[key] == nullptr);
1167 assert(fDataSource && "Missing RDataSource to add column reader.");
1168
1170
1171 return readers[key].get();
1172}
1173
1175RLoopManager::GetDatasetColumnReader(unsigned int slot, std::string_view col, const std::type_info &ti) const
1176{
1177 const auto key = MakeDatasetColReadersKey(col, ti);
1178 if (auto it = fDatasetColumnReaders[slot].find(key); it != fDatasetColumnReaders[slot].end() && it->second)
1179 return it->second.get();
1180 else
1181 return nullptr;
1182}
1183
1185{
1186 if (callback)
1187 fSampleCallbacks.insert({nodePtr, std::move(callback)});
1188}
1189
1190void RLoopManager::SetEmptyEntryRange(std::pair<ULong64_t, ULong64_t> &&newRange)
1191{
1192 fEmptyEntryRange = std::move(newRange);
1193}
1194
1196{
1197 fBeginEntry = begin;
1198 fEndEntry = end;
1199}
1200
1202{
1203 fTTreeLifeline = std::move(lifeline);
1204}
1205
1206std::shared_ptr<ROOT::Detail::RDF::RLoopManager>
1209{
1210 // Introduce the same behaviour as in CreateLMFromFile for consistency.
1211 // Creating an RDataFrame with a non-existing file will throw early rather
1212 // than wait for the start of the graph execution.
1213 if (checkFile) {
1215 }
1216
1217 auto dataSource = std::make_unique<ROOT::Internal::RDF::RTTreeDS>(datasetName, fileNameGlob);
1218 auto lm = std::make_shared<ROOT::Detail::RDF::RLoopManager>(std::move(dataSource), defaultColumns);
1219 return lm;
1220}
1221
1222std::shared_ptr<ROOT::Detail::RDF::RLoopManager>
1223ROOT::Detail::RDF::CreateLMFromTTree(std::string_view datasetName, const std::vector<std::string> &fileNameGlobs,
1224 const std::vector<std::string> &defaultColumns, bool checkFile)
1225{
1226 if (fileNameGlobs.size() == 0)
1227 throw std::invalid_argument("RDataFrame: empty list of input files.");
1228 // Introduce the same behaviour as in CreateLMFromFile for consistency.
1229 // Creating an RDataFrame with a non-existing file will throw early rather
1230 // than wait for the start of the graph execution.
1231 if (checkFile) {
1233 }
1234 auto dataSource = std::make_unique<ROOT::Internal::RDF::RTTreeDS>(datasetName, fileNameGlobs);
1235 auto lm = std::make_shared<ROOT::Detail::RDF::RLoopManager>(std::move(dataSource), defaultColumns);
1236 return lm;
1237}
1238
1239std::shared_ptr<ROOT::Detail::RDF::RLoopManager>
1242{
1243 auto dataSource = std::make_unique<ROOT::RDF::RNTupleDS>(datasetName, fileNameGlob);
1244 auto lm = std::make_shared<ROOT::Detail::RDF::RLoopManager>(std::move(dataSource), defaultColumns);
1245 return lm;
1246}
1247
1248std::shared_ptr<ROOT::Detail::RDF::RLoopManager>
1249ROOT::Detail::RDF::CreateLMFromRNTuple(std::string_view datasetName, const std::vector<std::string> &fileNameGlobs,
1251{
1252 auto dataSource = std::make_unique<ROOT::RDF::RNTupleDS>(datasetName, fileNameGlobs);
1253 auto lm = std::make_shared<ROOT::Detail::RDF::RLoopManager>(std::move(dataSource), defaultColumns);
1254 return lm;
1255}
1256
1257std::shared_ptr<ROOT::Detail::RDF::RLoopManager>
1260{
1261
1263
1265 return CreateLMFromTTree(datasetName, fileNameGlob, defaultColumns, /*checkFile=*/false);
1268 }
1269
1270 std::string errMsg = IsObjectInDir<void>(datasetName, *inFile) ? "unsupported data format for" : "cannot find";
1271
1272 throw std::invalid_argument("RDataFrame: " + errMsg + " dataset \"" + std::string(datasetName) + "\" in file \"" +
1273 inFile->GetName() + "\".");
1274}
1275
1276std::shared_ptr<ROOT::Detail::RDF::RLoopManager>
1277ROOT::Detail::RDF::CreateLMFromFile(std::string_view datasetName, const std::vector<std::string> &fileNameGlobs,
1279{
1280
1281 if (fileNameGlobs.size() == 0)
1282 throw std::invalid_argument("RDataFrame: empty list of input files.");
1283
1285
1287 return CreateLMFromTTree(datasetName, fileNameGlobs, defaultColumns, /*checkFile=*/false);
1290 }
1291
1292 std::string errMsg = IsObjectInDir<void>(datasetName, *inFile) ? "unsupported data format for" : "cannot find";
1293
1294 throw std::invalid_argument("RDataFrame: " + errMsg + " dataset \"" + std::string(datasetName) + "\" in file \"" +
1295 inFile->GetName() + "\".");
1296}
1297
1298// outlined to pin virtual table
1300
1301void ROOT::Detail::RDF::RLoopManager::SetDataSource(std::unique_ptr<ROOT::RDF::RDataSource> dataSource)
1302{
1303 if (dataSource) {
1304 fDataSource = std::move(dataSource);
1305 fDataSource->SetNSlots(fNSlots);
1306 fLoopType = ROOT::IsImplicitMTEnabled() ? ELoopType::kDataSourceMT : ELoopType::kDataSource;
1307 }
1308}
1309
1310void ROOT::Detail::RDF::RLoopManager::DataSourceThreadTask(const std::pair<ULong64_t, ULong64_t> &entryRange,
1312 std::atomic<ULong64_t> &entryCount)
1313{
1314#ifdef R__USE_IMT
1316 const auto &slot = slotRAII.fSlot;
1317
1318 const auto &[start, end] = entryRange;
1319 const auto nEntries = end - start;
1320 entryCount.fetch_add(nEntries);
1321
1322 RDSRangeRAII _{*this, slot, start};
1323 RCallCleanUpTask cleanup(*this, slot);
1324
1325 fSampleInfos[slot] = ROOT::Internal::RDF::CreateSampleInfo(*fDataSource, slot, fSampleMap);
1326
1327 R__LOG_DEBUG(0, RDFLogChannel()) << LogRangeProcessing({fDataSource->GetLabel(), start, end, slot});
1328
1329 try {
1330 for (auto entry = start; entry < end; ++entry) {
1331 if (fDataSource->SetEntry(slot, entry)) {
1332 RunAndCheckFilters(slot, entry);
1333 }
1334 }
1335 } catch (...) {
1336 std::cerr << "RDataFrame::Run: event loop was interrupted\n";
1337 throw;
1338 }
1339#else
1340 (void)entryRange;
1341 (void)slotStack;
1342 (void)entryCount;
1343#endif
1344}
1345
1347 std::atomic<ULong64_t> &entryCount)
1348{
1349#ifdef R__USE_IMT
1351 const auto &slot = slotRAII.fSlot;
1352
1353 const auto entryRange = treeReader.GetEntriesRange(); // we trust TTreeProcessorMT to call SetEntriesRange
1354 const auto &[start, end] = entryRange;
1355 const auto nEntries = end - start;
1356 auto count = entryCount.fetch_add(nEntries);
1357
1358 RDSRangeRAII _{*this, slot, static_cast<ULong64_t>(start), &treeReader};
1359 RCallCleanUpTask cleanup(*this, slot, &treeReader);
1360
1362 {fDataSource->GetLabel(), static_cast<ULong64_t>(start), static_cast<ULong64_t>(end), slot});
1363 try {
1364 // recursive call to check filters and conditionally execute actions
1366 if (fNewSampleNotifier.CheckFlag(slot)) {
1367 UpdateSampleInfo(slot, treeReader);
1368 }
1369 RunAndCheckFilters(slot, count++);
1370 }
1371 } catch (...) {
1372 std::cerr << "RDataFrame::Run: event loop was interrupted\n";
1373 throw;
1374 }
1375 // fNStopsReceived < fNChildren is always true at the moment as we don't support event loop early quitting in
1376 // multi-thread runs, but it costs nothing to be safe and future-proof in case we add support for that later.
1377 if (treeReader.GetEntryStatus() != TTreeReader::kEntryBeyondEnd && fNStopsReceived < fNChildren) {
1378 // something went wrong in the TTreeReader event loop
1379 throw std::runtime_error("An error was encountered while processing the data. TTreeReader status code is: " +
1380 std::to_string(treeReader.GetEntryStatus()));
1381 }
1382#else
1383 (void)treeReader;
1384 (void)slotStack;
1385 (void)entryCount;
1386#endif
1387}
1388
1391
1392ROOT::Detail::RDF::RLoopManager::DeferredJitCall &ROOT::Detail::RDF::RLoopManager::DeferredJitCall::operator=(
1393 ROOT::Detail::RDF::RLoopManager::DeferredJitCall &&) noexcept = default;
1394
1395ROOT::Detail::RDF::RLoopManager::DeferredJitCall::~DeferredJitCall() = default;
1396
1398 std::size_t id, std::unique_ptr<ROOT::Internal::RDF::RColumnRegister> colRegisterPtr,
1399 const std::vector<std::string> &colNamesArg, std::shared_ptr<void> jittedNode, std::shared_ptr<void> argPtr)
1400 : fFunctionId(id),
1401 fColRegister(std::move(colRegisterPtr)),
1402 fColNames(colNamesArg),
1403 fJittedNode(jittedNode),
1404 fExtraArgs(argPtr)
1405{
1406 assert(fJittedNode != nullptr);
1407}
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:359
#define R__LOG_INFO(...)
Definition RLogger.hxx:358
std::unique_ptr< TFile > OpenFileWithSanityChecks(std::string_view fileNameGlob)
Helper function to open a file (or the first file from a glob).
#define f(i)
Definition RSha256.hxx:104
#define e(i)
Definition RSha256.hxx:103
Basic types used by ROOT and required by TInterpreter.
long long Long64_t
Portable signed long integer 8 bytes.
Definition RtypesCore.h:84
unsigned long long ULong64_t
Portable unsigned long integer 8 bytes.
Definition RtypesCore.h:85
constexpr Bool_t kTRUE
Definition RtypesCore.h:108
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:130
void Warning(const char *location, const char *msgfmt,...)
Use this function in warning situations.
Definition TError.cxx:252
const char * filters[]
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t r
char name[80]
Definition TGX11.cxx:142
#define gInterpreter
R__EXTERN TSystem * gSystem
Definition TSystem.h:582
#define R__WRITE_LOCKGUARD(mutex)
#define R__READ_LOCKGUARD(mutex)
#define _(A, B)
Definition cfortran.h:108
The head node of a RDF computation graph.
RColumnReaderBase * AddDataSourceColumnReader(unsigned int slot, std::string_view col, const std::type_info &ti, TTreeReader *treeReader)
void UpdateSampleInfo(unsigned int slot, const std::pair< ULong64_t, ULong64_t > &range)
unsigned int fNRuns
Number of event loops run.
bool CheckFilters(unsigned int, Long64_t) final
void RegisterJitHelperCall(const std::string &funcBody, std::unique_ptr< ROOT::Internal::RDF::RColumnRegister > colRegister, const std::vector< std::string > &colnames, std::shared_ptr< void > jittedNode, std::shared_ptr< void > argument=nullptr)
void EvalChildrenCounts()
Trigger counting of number of children nodes for each node of the functional graph.
void CleanUpNodes()
Perform clean-up operations. To be called at the end of each event loop.
void RunEmptySource()
Run event loop with no source files, in sequence.
void SetEmptyEntryRange(std::pair< ULong64_t, ULong64_t > &&newRange)
void Report(ROOT::RDF::RCutFlowReport &rep) const final
Call FillReport on all booked filters.
void AddSampleCallback(void *nodePtr, ROOT::RDF::SampleCallback_t &&callback)
std::vector< RFilterBase * > fBookedNamedFilters
Contains a subset of fBookedFilters, i.e. only the named filters.
void RunEmptySourceMT()
Run event loop with no source files, in parallel.
std::hash< std::string > fStringHasher
std::unordered_map< std::string, ROOT::RDF::Experimental::RSample * > fSampleMap
Keys are fname + "/" + treename as RSampleInfo::fID; Values are pointers to the corresponding sample.
void AddDataSourceColumnReaders(std::string_view col, std::vector< std::unique_ptr< RColumnReaderBase > > &&readers, const std::type_info &ti)
std::shared_ptr< ROOT::Internal::RDF::GraphDrawing::GraphNode > GetGraph(std::unordered_map< void *, std::shared_ptr< ROOT::Internal::RDF::GraphDrawing::GraphNode > > &visitedMap) final
void ToJitExec(const std::string &) const
std::vector< RDFInternal::RActionBase * > GetAllActions() const
Return all actions, either booked or already run.
std::vector< ROOT::RDF::RSampleInfo > fSampleInfos
std::set< std::string > fSuppressErrorsForMissingBranches
void ChangeSpec(ROOT::RDF::Experimental::RDatasetSpec &&spec)
Changes the internal TTree held by the RLoopManager.
std::weak_ptr< ROOT::Internal::RSlotStack > fSlotStack
Pointer to a shared slot stack in case this instance runs concurrently with others:
std::vector< RDefineBase * > fBookedDefines
void TTreeThreadTask(TTreeReader &treeReader, ROOT::Internal::RSlotStack &slotStack, std::atomic< ULong64_t > &entryCount)
The task run by every thread on an entry range (known by the input TTreeReader), for the TTree data s...
std::vector< RDFInternal::RActionBase * > fRunActions
Non-owning pointers to actions already run.
RLoopManager(const ColumnNames_t &defaultColumns={})
std::vector< RRangeBase * > fBookedRanges
std::vector< ROOT::RDF::Experimental::RSample > fSamples
Samples need to survive throughout the whole event loop, hence stored as an attribute.
std::vector< std::string > ColumnNames_t
void RunAndCheckFilters(unsigned int slot, Long64_t entry)
Execute actions and make sure named filters are called for each event.
void ChangeBeginAndEndEntries(Long64_t begin, Long64_t end)
std::vector< RFilterBase * > fBookedFilters
void Run(bool jit=true)
Start the event loop with a different mechanism depending on IMT/no IMT, data source/no data source.
std::unordered_map< void *, ROOT::RDF::SampleCallback_t > fSampleCallbacks
Registered callbacks to call at the beginning of each "data block".
std::vector< RDFInternal::RActionBase * > fBookedActions
Non-owning pointers to actions to be run.
void SetupSampleCallbacks(TTreeReader *r, unsigned int slot)
void CleanUpTask(TTreeReader *r, unsigned int slot)
Perform clean-up operations. To be called at the end of each task execution.
std::vector< RDFInternal::RCallback > fCallbacksEveryNEvents
Registered callbacks to be executed every N events.
std::vector< std::unordered_map< std::string, std::unique_ptr< RColumnReaderBase > > > fDatasetColumnReaders
Readers for TTree/RDataSource columns (one per slot), shared by all nodes in the computation graph.
void Register(RDFInternal::RActionBase *actionPtr)
std::vector< DeferredJitCall > fJitHelperCalls
const ColumnNames_t & GetDefaultColumnNames() const
Return the list of default columns – empty if none was provided when constructing the RDataFrame.
std::vector< RDFInternal::RVariationBase * > fBookedVariations
std::vector< RNodeBase * > GetGraphEdges() const
Return all graph edges known to RLoopManager This includes Filters and Ranges but not Defines.
RDataSource * GetDataSource() const
void RunDataSourceMT()
Run event loop over data accessed through a DataSource, in parallel.
std::vector< std::string > GetFiltersNames()
For each booked filter, returns either the name or "Unnamed Filter".
RDFInternal::RNewSampleNotifier fNewSampleNotifier
std::pair< ULong64_t, ULong64_t > fEmptyEntryRange
Range of entries created when no data source is specified.
std::unique_ptr< RDataSource > fDataSource
Owning pointer to a data-source object.
void DataSourceThreadTask(const std::pair< ULong64_t, ULong64_t > &entryRange, ROOT::Internal::RSlotStack &slotStack, std::atomic< ULong64_t > &entryCount)
The task run by every thread on the input entry range, for the generic RDataSource.
void InitNodeSlots(TTreeReader *r, unsigned int slot)
Build TTreeReaderValues for all nodes This method loops over all filters, actions and other booked ob...
std::vector< RDFInternal::ROneTimeCallback > fCallbacksOnce
Registered callbacks to invoke just once before running the loop.
void SetDataSource(std::unique_ptr< ROOT::RDF::RDataSource > dataSource)
void RegisterCallback(ULong64_t everyNEvents, std::function< void(unsigned int)> &&f)
void SetTTreeLifeline(std::any lifeline)
void RunDataSource()
Run event loop over data accessed through a DataSource, in sequence.
void Jit()
Add RDF nodes that require just-in-time compilation to the computation graph.
RColumnReaderBase * GetDatasetColumnReader(unsigned int slot, std::string_view col, const std::type_info &ti) const
std::shared_ptr< ROOT::Internal::RSlotStack > SlotStack() const
Create a slot stack with the desired number of slots or reuse a shared instance.
void Deregister(RDFInternal::RActionBase *actionPtr)
ELoopType fLoopType
The kind of event loop that is going to be run (e.g. on ROOT files, on no files)
void InitNodes()
Initialize all nodes of the functional graph before running the event loop.
bool HasDataSourceColumnReaders(std::string_view col, const std::type_info &ti) const
Return true if AddDataSourceColumnReaders was called for column name col.
unsigned int fNStopsReceived
Number of times that a children node signaled to stop processing entries.
Definition RNodeBase.hxx:47
unsigned int fNChildren
Number of nodes of the functional graph hanging from this object.
Definition RNodeBase.hxx:46
A binder for user-defined columns, variations and aliases.
bool CheckFlag(unsigned int slot) const
TNotifyLink< RNewSampleFlag > & GetChainNotifyLink(unsigned int slot)
This type includes all parts of RVariation that do not depend on the callable signature.
A thread-safe list of N indexes (0 to size - 1).
The dataset specification for RDataFrame.
RDataSource defines an API that RDataFrame can use to read arbitrary data formats.
virtual void Finalize()
Convenience method called after concluding an event-loop.
virtual void InitSlot(unsigned int, ULong64_t)
Convenience method called at the start of the data processing associated to a slot.
virtual void FinalizeSlot(unsigned int)
Convenience method called at the end of the data processing associated to a slot.
This type represents a sample identifier, to be used in conjunction with RDataFrame features such as ...
const_iterator begin() const
const_iterator end() const
This class provides a simple interface to execute the same task multiple times in parallel threads,...
TDirectory::TContext keeps track and restore the current directory.
Definition TDirectory.h:89
Describe directory structure in memory.
Definition TDirectory.h:45
virtual TObject * Get(const char *namecycle)
Return pointer to object identified by namecycle.
virtual TKey * GetKey(const char *, Short_t=9999) const
Definition TDirectory.h:222
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
Create / open a file.
Definition TFile.cxx:3801
Stopwatch class.
Definition TStopwatch.h:28
Double_t RealTime()
Stop the stopwatch (if it is running) and return the realtime (in seconds) passed between the start a...
void Start(Bool_t reset=kTRUE)
Start the stopwatch.
Double_t CpuTime()
Stop the stopwatch (if it is running) and return the cputime (in seconds) passed between the start an...
void Stop()
Stop the stopwatch.
Basic string class.
Definition TString.h:137
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition TSystem.cxx:1289
A simple, robust and fast interface to read values from ROOT columnar datasets such as TTree,...
Definition TTreeReader.h:46
@ kIndexedFriendNoMatch
A friend with TTreeIndex doesn't have an entry for this index.
@ kMissingBranchWhenSwitchingTree
A branch was not found when switching to the next TTree in the chain.
@ kEntryBeyondEnd
last entry loop has reached its end
@ kEntryValid
data read okay
A TTree represents a columnar dataset.
Definition TTree.h:89
static void SetMaxTreeSize(Long64_t maxsize=100000000000LL)
Set the maximum size in bytes of a Tree file (static function).
Definition TTree.cxx:9605
This class represents a WWW compatible URL.
Definition TUrl.h:33
std::shared_ptr< ROOT::Detail::RDF::RLoopManager > CreateLMFromTTree(std::string_view datasetName, std::string_view fileNameGlob, const std::vector< std::string > &defaultColumns, bool checkFile=true)
Create an RLoopManager that reads a TChain.
ROOT::RLogChannel & RDFLogChannel()
Definition RDFUtils.cxx:43
std::shared_ptr< ROOT::Detail::RDF::RLoopManager > CreateLMFromFile(std::string_view datasetName, std::string_view fileNameGlob, const std::vector< std::string > &defaultColumns)
Create an RLoopManager opening a file and checking the data format of the dataset.
std::shared_ptr< ROOT::Detail::RDF::RLoopManager > CreateLMFromRNTuple(std::string_view datasetName, std::string_view fileNameGlob, const std::vector< std::string > &defaultColumns)
Create an RLoopManager that reads an RNTuple.
void RunFinalChecks(const ROOT::RDF::RDataSource &ds, bool nodesLeftNotRun)
Definition RDFUtils.cxx:697
ROOT::RDF::RSampleInfo CreateSampleInfo(const ROOT::RDF::RDataSource &ds, unsigned int slot, const std::unordered_map< std::string, ROOT::RDF::Experimental::RSample * > &sampleMap)
Definition RDFUtils.cxx:690
unsigned int GetNSlots()
Definition RDFUtils.cxx:411
void CallInitializeWithOpts(ROOT::RDF::RDataSource &ds, const std::set< std::string > &suppressErrorsForMissingColumns)
Definition RDFUtils.cxx:679
void Erase(const T &that, std::vector< T > &v)
Erase that element from vector v
Definition Utils.hxx:204
std::unique_ptr< ROOT::Detail::RDF::RColumnReaderBase > CreateColumnReader(ROOT::RDF::RDataSource &ds, unsigned int slot, std::string_view col, const std::type_info &tid, TTreeReader *treeReader)
Definition RDFUtils.cxx:708
void InterpreterCalc(const std::string &code, const std::string &context="")
Jit code in the interpreter with TInterpreter::Calc, throw in case of errors.
Definition RDFUtils.cxx:455
void ProcessMT(ROOT::RDF::RDataSource &ds, ROOT::Detail::RDF::RLoopManager &lm)
Definition RDFUtils.cxx:702
std::vector< std::string > GetTreeFullPaths(const TTree &tree)
std::unique_ptr< TChain > MakeChainForMT(const std::string &name="", const std::string &title="")
Create a TChain object with options that avoid common causes of thread contention.
std::vector< std::string > ExpandGlob(const std::string &glob)
Expands input glob into a collection of full paths to files.
auto MakeAliasedSharedPtr(T *rawPtr)
std::function< void(unsigned int, const ROOT::RDF::RSampleInfo &)> SampleCallback_t
The type of a data-block callback, registered with an RDataFrame computation graph via e....
std::vector< std::string > ColumnNames_t
Bool_t IsImplicitMTEnabled()
Returns true if the implicit multi-threading in ROOT is enabled.
Definition TROOT.cxx:673
R__EXTERN TVirtualRWMutex * gCoreMutex
A RAII object that calls RLoopManager::CleanUpTask at destruction.
RCallCleanUpTask(RLoopManager &lm, unsigned int arg=0u, TTreeReader *reader=nullptr)
DeferredJitCall(std::size_t id, std::unique_ptr< ROOT::Internal::RDF::RColumnRegister > cols, const std::vector< std::string > &colNamesArg, std::shared_ptr< void > jittedNode, std::shared_ptr< void > arg)
ROOT::Detail::RDF::RLoopManager & fLM
RDSRangeRAII(ROOT::Detail::RDF::RLoopManager &lm, unsigned int slot, ULong64_t firstEntry, TTreeReader *treeReader=nullptr)
A RAII object to pop and push slot numbers from a RSlotStack object.