BmnRoot 26.10.0-18-g4e3414f9
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
merge.C
Go to the documentation of this file.
1//========================================================================================================
2// merge.C
3// Helper macro for NICA-Scheduler partial output processing.
4// mode: 0 - save resulting TChain pointing to partial files
5// 1 - merge partial files to result file and delete partial files
6// 2 - merge partial files to result file and keep partial files
7//========================================================================================================
8
9#if !defined(__CLING__) || defined(__MAKECLING__)
10// ROOT includes
11#include "TChain.h"
12#include "TChainElement.h"
13#include "TClass.h"
14#include "TDirectory.h"
15#include "TFile.h"
16#include "TFileMerger.h"
17#include "TKey.h"
18#include "TObjArray.h"
19#include "TSystem.h"
20#include "TTree.h"
21
22#include <cstdio>
23#include <iostream>
24#include <sstream>
25#include <string>
26#include <vector>
27
28using namespace std;
29#endif
30
31// Split a whitespace-separated file list. The first item is the final result file,
32// all following items are partial output files produced by parallel_mode.
33vector<TString> split_file_list(const char* pcFileList)
34{
35 vector<TString> files;
36 string token;
37 stringstream ss(pcFileList == nullptr ? "" : pcFileList);
38
39 while (ss >> token) {
40 TString fileName(token.c_str());
41 gSystem->ExpandPathName(fileName);
42 files.push_back(fileName);
43 }
44
45 return files;
46}
47
48// Find the first TTree/TNtuple object stored in a ROOT directory.
49// If the tree is inside a subdirectory, return the full path: "dir/tree".
51{
52 if (directory == nullptr || directory->GetListOfKeys() == nullptr)
53 return "";
54
55 TIter next(directory->GetListOfKeys());
56 TKey* key = nullptr;
57
58 while ((key = (TKey*)next()) != nullptr) {
59 TClass* cl = TClass::GetClass(key->GetClassName());
60 if (cl == nullptr)
61 continue;
62
63 TString objectName = key->GetName();
64 TString fullName = (prefix.Length() == 0) ? objectName : prefix + "/" + objectName;
65
66 if (cl->InheritsFrom(TTree::Class()))
67 return fullName;
68
70 TObject* obj = key->ReadObj();
71 TDirectory* subdir = dynamic_cast<TDirectory*>(obj);
72 TString treeName = find_tree_name_in_directory(subdir, fullName);
73 if (treeName.Length() > 0)
74 return treeName;
75 }
76 }
77
78 return "";
79}
80
81// Open one ROOT file and return the first TTree name stored in it.
83{
84 TFile* file = TFile::Open(fileName, "READ");
85 if (file == nullptr || file->IsZombie()) {
86 cout << "ERROR: cannot open ROOT file \"" << fileName << "\" to detect TTree name" << endl;
87 if (file != nullptr) {
88 file->Close();
89 delete file;
90 }
91 return "";
92 }
93
94 TString treeName = find_tree_name_in_directory(file);
95
96 file->Close();
97 delete file;
98
99 return treeName;
100}
101
102// Detect the TTree name from partial output files. It checks all partial files
103// because the first partial file may be missing or corrupted after a failed subtask.
104TString detect_tree_name_from_partial_files(const vector<TString>& files)
105{
106 // files[0] is the result file name, partial files start from files[1]
107 for (size_t i = 1; i < files.size(); ++i) {
108 TString treeName = find_tree_name_in_file(files[i]);
109 if (treeName.Length() > 0) {
110 cout << "Detected TTree name for TChain: \"" << treeName << "\" from file \"" << files[i] << "\"" << endl;
111 return treeName;
112 }
113
114 cout << "WARNING: no TTree object was found in partial file \"" << files[i] << "\"" << endl;
115 }
116
117 return "";
118}
119
120// Check that a partial file contains the detected TTree. This prevents creating
121// an apparently valid, but actually incomplete or empty, TChain.
122bool file_contains_tree(const TString& fileName, const TString& treeName)
123{
124 TFile* file = TFile::Open(fileName, "READ");
125 if (file == nullptr || file->IsZombie()) {
126 cout << "ERROR: cannot open ROOT file \"" << fileName << "\"" << endl;
127 if (file != nullptr) {
128 file->Close();
129 delete file;
130 }
131 return false;
132 }
133
134 TObject* obj = file->Get(treeName);
135 bool result = (obj != nullptr && obj->InheritsFrom(TTree::Class()));
136
137 file->Close();
138 delete file;
139
140 return result;
141}
142
143// pcFileList - file list with space delimiter. The first file is the result file;
144// all following files are partial result files.
145// mode: 0 - save only resulting TChain to the first file name with the data trees pointing to partial files
146// 1 - merge to the first file name and delete partial files
147// 2 - merge to the first file name and not delete partial files
148void merge(const char* pcFileList, int mode = 1)
149{
150 vector<TString> files = split_file_list(pcFileList);
151
152 if ((mode < 0) || (mode > 2)) {
153 cout << "Second parameter ('mode') can be 0-2 only. The mode is assigned to a default value (1)." << endl;
154 mode = 1;
155 }
156
157 if (files.empty()) {
158 cout << "ERROR: empty file list, nothing has been merged" << endl;
159 return;
160 }
161
162 if (files.size() == 1) {
163 cout << "Only one file name in the input string, nothing has been merged" << endl;
164 return;
165 }
166
167 TString sResultFile = files[0];
168
169 // MERGE FILES: mode 1 = merge and delete partials, mode 2 = merge and keep partials
170 if (mode > 0) {
171 Bool_t res;
172 TString sFirstFile = files[1];
173
174 bool isError = false;
175 TFileMerger mergerFull, // full merge with the first partial file to keep metadata objects
176 mergerPart; // partial merge for other files
177
178 // The first partial file is merged fully later, because it usually contains
179 // metadata objects such as FairBaseParSet. Other partial files are merged first.
180 mergerFull.AddFile(sFirstFile.Data());
181 for (size_t i = 2; i < files.size(); ++i)
182 mergerPart.AddFile(files[i].Data());
183
184 // Merge only the data tree from the other partial files (kOnlyListed requires the object
185 // names to be set explicitly); metadata objects are taken from the first partial file.
186 // If no TTree was found, fall back to full merging of all objects.
189 if (treeName.Length() > 0) {
190 // kOnlyListed matches plain key names at any directory level, so strip a possible
191 // subdirectory path from the detected tree name
192 Ssiz_t slash = treeName.Last('/');
193 if (slash != kNPOS)
194 treeName.Remove(0, slash + 1);
195 mergerPart.AddObjectNames(treeName.Data());
196 mergeType |= TFileMerger::kOnlyListed;
197 } else
198 cout << "WARNING: no TTree was found in partial output files, merging all their objects" << endl;
199
200 mergerPart.OutputFile(sResultFile);
201 res = mergerPart.PartialMerge(mergeType);
202
203 if (res == kFALSE) {
204 cout << "ERROR: partial merging to the result file failed" << endl;
205 mergerPart.Reset();
206 mergerFull.Reset();
207 return;
208 }
209
210 mergerPart.Reset();
211
212 mergerFull.AddFile(sResultFile.Data());
213 mergerFull.OutputFile(sResultFile);
214 res = mergerFull.Merge();
215
216 if (res == kFALSE) {
217 cout << "ERROR: full merging to the result file failed" << endl;
218 mergerFull.Reset();
219 return;
220 }
221
222 mergerFull.Reset();
223
224 // Delete partial files only after both merge steps have succeeded.
225 if (mode == 1) {
226 for (size_t i = 1; i < files.size(); ++i) {
227 if (remove(files[i].Data()) != 0) {
228 isError = true;
229 cout << "ERROR: deleting temporary file " << files[i] << " failed" << endl;
230 }
231 }
232
233 if (isError)
234 cout << "There were errors while temporary files removing" << endl;
235 else
236 cout << endl << "Temporary files were successfully removed" << endl;
237 } else
238 cout << "Partial result files were merged and preserved" << endl;
239 }
240
241 // SAVE ONLY TCHAIN: mode 0
242 else {
244 if (treeName.Length() == 0) {
245 cout << "ERROR: no TTree was found in partial output files. "
246 << "TChain cannot be created. Check that merge=\"chain\" is used only for ROOT files containing TTree "
247 "objects."
248 << endl;
249 return;
250 }
251
252 // Verify that all partial files contain the same tree before writing the TChain.
253 for (size_t i = 1; i < files.size(); ++i) {
254 if (!file_contains_tree(files[i], treeName)) {
255 cout << "ERROR: partial file \"" << files[i] << "\" does not contain TTree \"" << treeName
256 << "\". TChain was not created." << endl;
257 return;
258 }
259 }
260
261 TChain chainUnion(treeName.Data());
262
263 for (size_t i = 1; i < files.size(); ++i)
264 chainUnion.Add(files[i].Data());
265
266 TFile fChain(sResultFile, "RECREATE");
267 if (fChain.IsZombie()) {
268 cout << "ERROR: result TChain file \"" << sResultFile << "\" was not created" << endl;
269 return;
270 }
271
272 chainUnion.Write();
273 fChain.Close();
274
275 Long64_t events = chainUnion.GetEntries();
276 cout << "The Chain with " << events << " event(s) was written to file \"" << sResultFile
277 << "\" using TTree name \"" << treeName << "\" and pointing to following files:" << endl;
278
279 TObjArray* fileElements = chainUnion.GetListOfFiles();
280 TIter next(fileElements);
281 TChainElement* chEl = nullptr;
282
283 while ((chEl = (TChainElement*)next()) != nullptr) {
284 const char* pc = chEl->GetTitle();
285 cout << pc << endl;
286 }
287 }
288}
int i
Definition P4_F32vec4.h:22
bool Bool_t
int Int_t
int Ssiz_t
constexpr Bool_t kFALSE
constexpr Ssiz_t kNPOS
long long Long64_t
R__EXTERN TSystem * gSystem
TObjArray * GetListOfFiles() const
virtual Int_t Add(const char *name, Long64_t nentries=TTree::kMaxEntries)
Long64_t GetEntries() const override
static TClass * GetClass(Bool_t load=kTRUE, Bool_t silent=kFALSE)
Bool_t InheritsFrom(const char *cl) const override
T * Get(const char *namecycle)
static TClass * Class()
virtual TList * GetListOfKeys() const
virtual Bool_t OutputFile(const char *url, Bool_t force)
virtual Bool_t Merge(Bool_t=kTRUE)
void AddObjectNames(const char *name)
virtual Bool_t AddFile(const char *url, Bool_t cpProgress=kTRUE)
virtual Bool_t PartialMerge(Int_t type=kAll|kIncremental)
virtual void Reset()
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
void Close(Option_t *option="") override
const char * GetTitle() const override
R__ALWAYS_INLINE Bool_t IsZombie() const
virtual Bool_t InheritsFrom(const char *classname) const
Ssiz_t Length() const
const char * Data() const
Ssiz_t Last(char c) const
TString & Remove(EStripType s, char c)
Int_t Write(const char *name=nullptr, Int_t option=0, Int_t bufsize=0) const override
static TClass * Class()
Definition json.hpp:4446
TString detect_tree_name_from_partial_files(const vector< TString > &files)
Definition merge.C:104
vector< TString > split_file_list(const char *pcFileList)
Definition merge.C:33
bool file_contains_tree(const TString &fileName, const TString &treeName)
Definition merge.C:122
TString find_tree_name_in_file(const TString &fileName)
Definition merge.C:82
void merge(const char *pcFileList, int mode=1)
Definition merge.C:148
TString find_tree_name_in_directory(TDirectory *directory, const TString &prefix="")
Definition merge.C:50