ATLAS Offline Software
Loading...
Searching...
No Matches
AthenaRootSharedWriterSvc.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
9
10#include "GaudiKernel/IAlgManager.h"
12
13#include "TBranch.h"
14#include "TClass.h"
15#include "TFile.h"
16#include "TFileMerger.h"
17#include "TKey.h"
18#include "TLeaf.h"
19#include "TMemFile.h"
20#include "TMessage.h"
21#include "TMonitor.h"
22#include "TServerSocket.h"
23#include "TSocket.h"
24#include "TString.h"
25#include "TTree.h"
26
27#include <set>
28#include <map>
29
30void* getCachedDummyAddress(TClass* cl, std::unordered_map<TClass*, void*>& cache)
31{
32 if (!cl) return nullptr;
33
34 void*& map_ptr = cache[cl];
35 if (!map_ptr) {
36 map_ptr = cl->New();
37 }
38
39 return &map_ptr;
40}
41
42/* Code from ROOT tutorials/net/parallelMergeServer.C, reduced to handle TTrees only */
43
44struct ParallelFileMerger : public TObject
45{
46 TString fFilename;
47 TFileMerger fMerger;
48 std::unordered_map<TClass*, void*> *m_cache;
49
50 ParallelFileMerger(const char *filename, std::unordered_map<TClass*, void*>* cache, int compress = ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault
51 ) : fFilename(filename), fMerger(kFALSE, kTRUE), m_cache(cache)
52 {
53 fMerger.OutputFile(filename, "RECREATE", compress);
54 }
55
59
60 ULong_t Hash() const
61 {
62 return fFilename.Hash();
63 }
64
65 const char* GetName() const
66 {
67 return fFilename;
68 }
69
70// Add missing branches to client tree and BackFill before merging
71 bool syncBranches(TTree* fromTree, TTree* toTree)
72 {
73 bool updated = false;
74 const TObjArray* fromBranches = fromTree->GetListOfBranches();
75 const TObjArray* toBranches = toTree->GetListOfBranches();
76 int nBranches = fromBranches->GetEntriesFast();
77 int nEntries = toTree->GetEntries();
78 for (int k = 0; k < nBranches; ++k) {
79 TBranch* branch = static_cast<TBranch*>(fromBranches->UncheckedAt(k));
80 if (toBranches->FindObject(branch->GetName()) == nullptr) {
81 TBranch* newBranch = nullptr;
82 TClass* cl = TClass::GetClass(branch->GetClassName());
83 if (cl != nullptr) {
84 newBranch = toTree->Branch(branch->GetName(), branch->GetClassName(), nullptr, branch->GetBasketSize(), branch->GetSplitLevel());
86 newBranch->SetAddress(empty);
87 } else {
88 TObjArray* outLeaves = branch->GetListOfLeaves();
89 TLeaf* leaf = static_cast<TLeaf*>(outLeaves->UncheckedAt(0));
90 std::string_view attr = leaf->GetName();
91 std::string_view type = leaf->GetTypeName();
92 std::string branchSpec(attr);
93 branchSpec += '/';
94 if (type == "Int_t") branchSpec += 'I';
95 else if (type == "Short_t") branchSpec += 'S';
96 else if (type == "Long_t") branchSpec += 'L';
97 else if (type == "UInt_t") branchSpec +='i';
98 else if (type == "UShort_t") branchSpec+='s';
99 else if (type == "ULong_t") branchSpec +='l';
100 else if (type == "Float_t") branchSpec +='F';
101 else if (type == "Double_t") branchSpec+='D';
102 else if (type == "Char_t") branchSpec +='B';
103 else if (type == "UChar_t") branchSpec +='b';
104 else if (type == "Bool_t") branchSpec +='O';
105 else { branchSpec += type; }// fallback
106 newBranch = toTree->Branch(branch->GetName(), static_cast<void*>(nullptr), branchSpec.c_str(), 2048);
107 }
108 for (int m = 0; m < nEntries; ++m) {
109 newBranch->BackFill();
110 }
111 updated = true;
112 }
113 }
114 return updated;
115 }
116
117 Bool_t MergeTrees(TFile *input)
118 {
119 fMerger.AddFile(input);
120 TIter nextKey(input->GetListOfKeys());
121 while (TKey* key = static_cast<TKey*>(nextKey())) {
122 TClass* cl = TClass::GetClass(key->GetClassName());
123 if (cl != nullptr && cl->InheritsFrom("TTree")) {
124 TTree* outCollTree = static_cast<TTree*>(fMerger.GetOutputFile()->Get(key->GetName()));
125 TTree* inCollTree = static_cast<TTree*>(input->Get(key->GetName()));
126 if (inCollTree != nullptr && outCollTree != nullptr) {
127 if (syncBranches(outCollTree, inCollTree)) {
128 input->Write();
129 }
130 syncBranches(inCollTree, outCollTree);
131 }
132 }
133 }
134
135 Bool_t result = fMerger.PartialMerge(TFileMerger::kIncremental | TFileMerger::kResetable | TFileMerger::kKeepCompression);
136 nextKey = input->GetListOfKeys();
137 while (TKey* key = static_cast<TKey*>(nextKey())) {
138 TClass* cl = TClass::GetClass(key->GetClassName());
139 if (cl != nullptr && 0 != cl->GetResetAfterMerge()) {
140 key->Delete();
141 input->GetListOfKeys()->Remove(key);
142 delete key;
143 }
144 }
145 return result;
146 }
147};
148
149//___________________________________________________________________________
150AthenaRootSharedWriterSvc::AthenaRootSharedWriterSvc(const std::string& name, ISvcLocator* pSvcLocator)
151 : base_class(name, pSvcLocator)
153}
154//___________________________________________________________________________
156 ATH_MSG_INFO("in initialize()");
157
158 // Initialize IConversionSvc
159 ATH_CHECK(m_cnvSvc.retrieve());
160 IProperty* propertyServer = dynamic_cast<IProperty*>(m_cnvSvc.get());
161 if (propertyServer == nullptr) {
162 ATH_MSG_ERROR("Unable to cast conversion service to IProperty");
163 return StatusCode::FAILURE;
164 } else {
165 std::string propertyName = "ParallelCompression";
166 bool parallelCompression(false);
167 BooleanProperty parallelCompressionProp(propertyName, parallelCompression);
168 if (propertyServer->getProperty(&parallelCompressionProp).isFailure()) {
169 ATH_MSG_INFO("Conversion service does not have ParallelCompression property");
170 } else if (parallelCompressionProp.value()) {
171 int streamPort = 0;
172 propertyName = "StreamPortString";
173 std::string streamPortString("");
174 StringProperty streamPortStringProp(propertyName, streamPortString);
175 if (propertyServer->getProperty(&streamPortStringProp).isFailure()) {
176 ATH_MSG_INFO("Conversion service does not have StreamPortString property, using default: " << streamPort);
177 } else {
178 streamPort = atoi(streamPortStringProp.value().substr(streamPortStringProp.value().find(':') + 1).c_str());
179 }
180 m_rootServerSocket = new TServerSocket(streamPort, (streamPort == 0 ? false : true), 100, -1, ESocketBindOption::kInaddrLoopback);
181 if (m_rootServerSocket == nullptr || !m_rootServerSocket->IsValid()) {
182 ATH_MSG_FATAL("Could not create ROOT TServerSocket: " << streamPort);
183 return StatusCode::FAILURE;
184 }
185 streamPort = m_rootServerSocket->GetLocalPort();
186 const std::string newStreamPortString{streamPortStringProp.value().substr(0,streamPortStringProp.value().find(':')+1) + std::to_string(streamPort)};
187 if (propertyServer->setProperty(propertyName,newStreamPortString).isFailure()) {
188 ATH_MSG_FATAL("Could not set Conversion Service property " << propertyName << " from " << streamPortString << " to " << newStreamPortString);
189 return StatusCode::FAILURE;
190 }
191 m_rootMonitor = new TMonitor;
193 ATH_MSG_DEBUG("Successfully created ROOT TServerSocket and added it to TMonitor: ready to accept connections, " << streamPort);
194 }
195 }
196 // Count the number of output streams
197 const IAlgManager* algMgr = Gaudi::svcLocator()->as<IAlgManager>();
198 for (const auto& alg : algMgr->getAlgorithms()) {
199 if (alg->type() == "AthenaOutputStream") {
200 ATH_MSG_DEBUG("Counting " << alg->name() << " as an output stream algorithm");
202 }
203 }
204 if (m_numberOfStreams == 0) {
205 ATH_MSG_WARNING("No output stream algorithm found, setting the number of streams to 1");
207 } else {
208 ATH_MSG_INFO("Found a total of " << m_numberOfStreams << " output streams");
209 }
210
211 return StatusCode::SUCCESS;
212}
213//___________________________________________________________________________
214StatusCode AthenaRootSharedWriterSvc::share(int numClients, bool motherClient) {
215 ATH_MSG_DEBUG("Start commitOutput loop");
216 StatusCode sc = m_cnvSvc->commitOutput("", false);
217
218 // Allow ROOT clients to start up (by setting active clients)
219 // and wait to stop the ROOT server until all clients are done and metadata is written (commitOutput fail).
220 bool anyActiveClients = (m_rootServerSocket != nullptr);
221 while (sc.isSuccess() || sc.isRecoverable() || anyActiveClients) {
222 if (sc.isSuccess()) {
223 ATH_MSG_VERBOSE("Success in commitOutput loop");
224 } else if (m_rootMonitor != nullptr) {
225 TSocket* socket = m_rootMonitor->Select(1);
226 if (socket != nullptr && socket != (TSocket*)-1) {
227 ATH_MSG_DEBUG("ROOT Monitor got: " << socket);
228 if (socket->IsA() == TServerSocket::Class()) {
229 TSocket* client = (static_cast<TServerSocket*>(socket))->Accept();
230 client->Send(m_rootClientIndex, 0);
231 client->Send(1, 1);
234 if (m_rootClientCount < (numClients-1)*m_numberOfStreams + 1) {
235 m_rootMonitor->Add(client);
236 ATH_MSG_INFO("ROOT Monitor add client: " << m_rootClientIndex << ", " << client);
237 } else {
238 ATH_MSG_WARNING("ROOT Monitor do NOT add client: " << m_rootClientIndex << ", " << client);
239 client->Close("force");
241 }
242 } else {
243 TMessage* message = nullptr;
244 Int_t result = socket->Recv(message);
245 if (result < 0) {
246 ATH_MSG_ERROR("ROOT Monitor got an error while receiving the message from the socket: " << result);
247 return StatusCode::FAILURE;
248 }
249 if (message == nullptr) {
250 ATH_MSG_WARNING("ROOT Monitor got no message from socket: " << socket);
251 } else if (message->What() == kMESS_STRING) {
252 char str[64];
253 message->ReadString(str, 64);
254 ATH_MSG_INFO("ROOT Monitor client: " << socket << ", " << str);
255 m_rootMonitor->Remove(socket);
256 ATH_MSG_DEBUG("ROOT Monitor client: " << socket << ", " << socket->GetBytesRecv() << ", " << socket->GetBytesSent());
257 socket->Close();
259 if (m_rootMonitor->GetActive() == 0 || m_rootClientCount == 0) {
260 if (!motherClient) {
261 anyActiveClients = false;
262 ATH_MSG_INFO("ROOT Monitor: No more active clients...");
263 } else {
264 motherClient = false;
265 ATH_MSG_INFO("ROOT Monitor: Mother process is done...");
266 if (!m_cnvSvc->commitCatalog().isSuccess()) {
267 ATH_MSG_FATAL("Failed to commit file catalog.");
268 return StatusCode::FAILURE;
269 }
270 }
271 }
272 } else if (message->What() == kMESS_ANY) {
273 long long length;
274 TString filename;
275 int clientId;
276 message->ReadInt(clientId);
277 message->ReadTString(filename);
278 message->ReadLong64(length);
279 ATH_MSG_DEBUG("ROOT Monitor client: " << socket << ", " << clientId << ": " << filename << ", " << length);
280 std::unique_ptr<TMemFile> transient(new TMemFile(filename, message->Buffer() + message->Length(), length, "UPDATE"));
281 message->SetBufferOffset(message->Length() + length);
282 ParallelFileMerger* info = static_cast<ParallelFileMerger*>(m_rootMergers.FindObject(filename));
283 if (!info) {
284 info = new ParallelFileMerger(filename, &m_dummyCache, transient->GetCompressionSettings());
285 m_rootMergers.Add(info);
286 ATH_MSG_INFO("ROOT Monitor ParallelFileMerger: " << info << ", for: " << filename);
287 }
288 info->MergeTrees(transient.get());
289 }
290 delete message; message = nullptr;
291 }
292 }
293 } else if (m_rootMonitor == nullptr) {
294 usleep(100);
295 }
296 // Once commitOutput failed all legacy clients are finished (writing metadata), do not call again.
297 if (sc.isSuccess() || sc.isRecoverable()) {
298 sc = m_cnvSvc->commitOutput("", false);
299 if (sc.isFailure() && !sc.isRecoverable()) {
300 ATH_MSG_INFO("commitOutput failed, metadata done.");
301 if (anyActiveClients && m_rootClientCount == 0) {
302 ATH_MSG_INFO("ROOT Monitor: No clients, terminating the loop...");
303 anyActiveClients = false;
304 }
305 }
306 }
307 }
308 ATH_MSG_INFO("End commitOutput loop");
309 return StatusCode::SUCCESS;
310}
311//___________________________________________________________________________
313 m_rootMergers.Delete();
314 return StatusCode::SUCCESS;
315}
316//___________________________________________________________________________
318 ATH_MSG_INFO("in finalize()");
319 delete m_rootMonitor; m_rootMonitor = nullptr;
320 delete m_rootServerSocket; m_rootServerSocket = nullptr;
321 for (auto& [cl, ptr] : m_dummyCache) {
322 if (cl && ptr) {
323 cl->Destructor(ptr, false);
324 }
325 }
326 m_dummyCache.clear();
327 return StatusCode::SUCCESS;
328}
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_FATAL(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
void * getCachedDummyAddress(TClass *cl, std::unordered_map< TClass *, void * > &cache)
This file contains the class definition for the AthenaRootSharedWriterSvc class.
double length(const pvec &v)
static Double_t sc
static const Attributes_t empty
Templated class containing a cut, name of cut and description of cut(optional) Typically,...
Definition CutFlow.h:28
virtual StatusCode stop() override
virtual StatusCode finalize() override
virtual StatusCode share(int numClients=0, bool motherClient=false) override
virtual StatusCode initialize() override
Gaudi Service Interface method implementations:
std::unordered_map< TClass *, void * > m_dummyCache
AthenaRootSharedWriterSvc(const std::string &name, ISvcLocator *pSvcLocator)
Standard Service Constructor.
ServiceHandle< AthenaPoolSharedIOCnvSvc > m_cnvSvc
T * Get(TFile &f, const std::string &n, const std::string &dir="", const chainmap_t *chainmap=0, std::vector< std::string > *saved=0)
get a histogram given a path, and an optional initial directory if histogram is not found,...
bool syncBranches(TTree *fromTree, TTree *toTree)
ParallelFileMerger(const char *filename, std::unordered_map< TClass *, void * > *cache, int compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault)
std::unordered_map< TClass *, void * > * m_cache
Bool_t MergeTrees(TFile *input)