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