Project
Loading...
Searching...
No Matches
MergerAlgorithm.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
16
18
20#include "Mergers/ObjectStore.h"
21#include "Mergers/Mergeable.h"
22#include "Framework/Logger.h"
23
24#include <TEfficiency.h>
25#include <TGraph.h>
26#include <TH1.h>
27#include <TH2.h>
28#include <TH3.h>
29#include <THn.h>
30#include <THnSparse.h>
31#include <TObjArray.h>
32#include <TObject.h>
33#include <TTree.h>
34#include <TPad.h>
35#include <TCanvas.h>
36#include <algorithm>
37#include <stdexcept>
38
40{
41
42size_t estimateTreeSize(TTree* tree)
43{
44 size_t totalSize = 0;
45 auto branchList = tree->GetListOfBranches();
46 for (const auto* branch : *branchList) {
47 totalSize += dynamic_cast<const TBranch*>(branch)->GetTotalSize();
48 }
49 return totalSize;
50}
51
52// Mergeable objects are kept as primitives in TCanvas object in underlying TPad.
53// TPad is a linked list of primitives of any type (https://root.cern.ch/doc/master/classTPad.html)
54// including other TPads. So in order to collect all mergeable objects from TCanvas
55// we need to recursively transverse whole TPad structure.
56auto collectUnderlyingObjects(TCanvas* canvas) -> std::vector<TObject*>
57{
58 auto collectFromTPad = [](TPad* pad, std::vector<TObject*>& objects, const auto& collectFromTPad) {
59 if (!pad) {
60 return;
61 }
62 auto* primitives = pad->GetListOfPrimitives();
63 for (int i = 0; i < primitives->GetSize(); ++i) {
64 auto* primitive = primitives->At(i);
65 if (auto* primitivePad = dynamic_cast<TPad*>(primitive)) {
66 collectFromTPad(primitivePad, objects, collectFromTPad);
67 } else if (isMergeable(primitive)) {
68 objects.push_back(primitive);
69 }
70 }
71 };
72
73 std::vector<TObject*> collectedObjects;
74 collectFromTPad(canvas, collectedObjects, collectFromTPad);
75
76 return collectedObjects;
77}
78
85
86auto matchCollectedToPairs(const std::vector<TObject*>& targetObjects, const std::vector<TObject*> otherObjects) -> std::vector<MatchedCollectedObjects>
87{
88 std::vector<MatchedCollectedObjects> matchedObjects;
89 matchedObjects.reserve(std::max(targetObjects.size(), otherObjects.size()));
90 for (const auto& targetObject : targetObjects) {
91 if (const auto found_it = std::ranges::find_if(otherObjects, [&targetObject](TObject* obj) { return std::string_view(targetObject->GetName()) == std::string_view(obj->GetName()); });
92 found_it != otherObjects.end()) {
93 matchedObjects.emplace_back(targetObject, *found_it);
94 }
95 }
96 return matchedObjects;
97}
98
99// calls the default Merge methods of TObjects
100Long64_t mergeDefault(TObject* const target, TObject* const other)
101{
102 Long64_t errorCode = 0;
103
104 TObjArray otherCollection;
105 otherCollection.SetOwner(false);
106 otherCollection.Add(other);
107
108 if (target->InheritsFrom(TH1::Class())) {
109 // this includes TH1, TH2, TH3
110 auto targetTH1 = reinterpret_cast<TH1*>(target);
111 if (targetTH1->TestBit(TH1::kIsAverage)) {
112 // Merge() does not support averages, we have to use Add()
113 // this will break if collection.size != 1
114 if (auto otherTH1 = dynamic_cast<TH1*>(otherCollection.First())) {
115 errorCode = targetTH1->Add(otherTH1) == kFALSE ? -1 : 0;
116 }
117 } else {
118 // Add() does not support histograms with labels, thus we resort to Merge() by default
119 errorCode = targetTH1->Merge(&otherCollection);
120 }
121 } else if (target->InheritsFrom(THnBase::Class())) {
122 // this includes THn and THnSparse
123 errorCode = reinterpret_cast<THnBase*>(target)->Merge(&otherCollection);
124 } else if (target->InheritsFrom(TTree::Class())) {
125 auto targetTree = reinterpret_cast<TTree*>(target);
126 auto otherTree = reinterpret_cast<TTree*>(other);
127 auto targetTreeSize = estimateTreeSize(targetTree);
128 auto otherTreeSize = estimateTreeSize(otherTree);
129 if (auto totalSize = targetTreeSize + otherTreeSize; totalSize > 100000000) {
130 LOG(warn) << "The tree '" << targetTree->GetName() << "' would be larger than 100MB (" << totalSize << "B) after merging, skipping to let the system survive";
131 errorCode = 0;
132 } else {
133 errorCode = targetTree->Merge(&otherCollection);
134 }
135 } else if (target->InheritsFrom(TGraph::Class())) {
136 errorCode = reinterpret_cast<TGraph*>(target)->Merge(&otherCollection);
137 } else if (target->InheritsFrom(TEfficiency::Class())) {
138 errorCode = reinterpret_cast<TEfficiency*>(target)->Merge(&otherCollection);
139 } else {
140 LOG(warn) << "Object '" + std::string(target->GetName()) + "' with type '" + std::string(target->ClassName()) + "' is not one of the mergeable types, skipping";
141 }
142 return errorCode;
143}
144
145void merge(TObject* const target, TObject* const other)
146{
147 if (target == nullptr) {
148 throw std::runtime_error("Merging target is nullptr");
149 }
150 if (other == nullptr) {
151 throw std::runtime_error("Object to be merged in is nullptr");
152 }
153 if (other == target) {
154 throw std::runtime_error("Merging target and the other object point to the same address");
155 }
156 // fixme: should we check if names match?
157
158 // We expect that both objects follow the same structure, but we allow to add missing objects to TCollections.
159 // First we check if an object contains a MergeInterface, as it should overlap default Merge() methods of TObject.
160 if (auto custom = dynamic_cast<MergeInterface*>(target)) {
161
162 custom->merge(dynamic_cast<MergeInterface* const>(other));
163
164 } else if (auto targetCollection = dynamic_cast<TCollection*>(target)) {
165
166 auto otherCollection = dynamic_cast<TCollection*>(other);
167 if (otherCollection == nullptr) {
168 throw std::runtime_error(std::string("The target object '") + target->GetName() +
169 "' is a TCollection, while the other object '" + other->GetName() + "' is not.");
170 }
171
172 auto otherIterator = otherCollection->MakeIterator();
173 while (auto otherObject = otherIterator->Next()) {
174 TObject* targetObject = targetCollection->FindObject(otherObject->GetName());
175 if (targetObject) {
176 // That might be another collection or a concrete object to be merged, we walk on the collection recursively.
177 merge(targetObject, otherObject);
178 } else {
179 // We prefer to clone instead of passing the pointer in order to simplify deleting the `other`.
180 targetCollection->Add(otherObject->Clone());
181 }
182 }
183 delete otherIterator;
184 } else if (auto targetCanvas = dynamic_cast<TCanvas*>(target)) {
185
186 auto otherCanvas = dynamic_cast<TCanvas*>(other);
187 if (otherCanvas == nullptr) {
188 throw std::runtime_error(std::string("The target object '") + target->GetName() +
189 "' is a TCanvas, while the other object '" + other->GetName() + "' is not.");
190 }
191
192 const auto targetObjects = collectUnderlyingObjects(targetCanvas);
193 const auto otherObjects = collectUnderlyingObjects(otherCanvas);
194 if (targetObjects.size() != otherObjects.size()) {
195 throw std::runtime_error(std::string("Trying to merge canvas: ") + targetCanvas->GetName() + " and canvas " + otherObjects.size() + "but contents are not the same");
196 }
197
198 const auto matched = matchCollectedToPairs(targetObjects, otherObjects);
199 if (targetObjects.size() != matched.size()) {
200 throw std::runtime_error(std::string("Trying to merge canvas: ") + targetCanvas->GetName() + " and canvas " + otherObjects.size() + "but contents are not the same");
201 }
202
203 for (const auto& [targetObject, otherObject] : matched) {
204 merge(targetObject, otherObject);
205 }
206
207 } else {
208 Long64_t errorCode = mergeDefault(target, other);
209
210 if (errorCode == -1) {
211 LOG(error) << "Failed to merge the input object '" + std::string(other->GetName()) + "' of type '" + std::string(other->ClassName()) //
212 + " and the target object '" + std::string(target->GetName()) + "' of type '" + std::string(target->ClassName()) + "'";
213
214 // we retry with debug options enabled in ROOT in hopes to get some logs explaining the issue
215 gDebug = true;
216 errorCode = mergeDefault(target, other);
217 gDebug = false;
218 if (errorCode == -1) {
219 LOG(error) << "Merging '" + std::string(other->GetName()) + "' and '" + std::string(target->GetName()) //
220 + "' failed again after a retry for debugging purposes. See ROOT warnings for details.";
221 } else {
222 LOG(warn) << "Merging '" + std::string(other->GetName()) + "' and '" + std::string(target->GetName()) //
223 + "' succeeded after retrying for debugging purposes.";
224 }
225 }
226 }
227}
228
229void merge(VectorOfTObjectPtrs& targets, const VectorOfTObjectPtrs& others)
230{
231 for (const auto& other : others) {
232 if (const auto targetSameName = std::find_if(targets.begin(), targets.end(), [&other](const auto& target) {
233 return std::string_view{other->GetName()} == std::string_view{target->GetName()};
234 });
235 targetSameName != targets.end()) {
236 merge(targetSameName->get(), other.get());
237 } else {
238 targets.push_back(std::shared_ptr<TObject>(other->Clone(), deleteTCollections));
239 }
240 }
241}
242
243void deleteRecursive(TCollection* Coll)
244{
245 // I can iterate a collection
246 Coll->SetOwner(false);
247 auto ITelem = Coll->MakeIterator();
248 while (auto* element = ITelem->Next()) {
249 if (auto* Coll2 = dynamic_cast<TCollection*>(element)) {
250 Coll2->SetOwner(false);
251 deleteRecursive(Coll2);
252 }
253 Coll->Remove(element); // Remove from mother collection
254 delete element; // Delete payload
255 }
256 delete ITelem;
257}
258
260{
261 if (auto* L = dynamic_cast<TCollection*>(obj)) {
263 delete L;
264 } else {
265 delete obj;
266 }
267}
268
269} // namespace o2::mergers::algorithm
std::vector< std::string > objects
atype::type element
int32_t i
Definition of O2 Mergers merging interface, v0.1.
Mergeable concept.
Algorithms for merging objects.
Definition of ObjectStore for Mergers, v0.1.
An interface which allows to merge custom objects.
GLenum target
Definition glcorearb.h:1641
Long64_t mergeDefault(TObject *const target, TObject *const other)
void deleteRecursive(TCollection *Coll)
auto matchCollectedToPairs(const std::vector< TObject * > &targetObjects, const std::vector< TObject * > otherObjects) -> std::vector< MatchedCollectedObjects >
void merge(TObject *const target, TObject *const other)
A function which merges TObjects.
void deleteTCollections(TObject *obj)
auto collectUnderlyingObjects(TCanvas *canvas) -> std::vector< TObject * >
size_t estimateTreeSize(TTree *tree)
bool isMergeable(TObject *obj)
Definition Mergeable.cxx:26
std::vector< TObjectPtr > VectorOfTObjectPtrs
Definition ObjectStore.h:43
VectorOfTObjectPtrs other
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))