Project
Loading...
Searching...
No Matches
O2HitMerger.h
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
13
14#ifndef ALICEO2_DEVICES_HITMERGER_H_
15#define ALICEO2_DEVICES_HITMERGER_H_
16
17#include <memory>
18#include <string>
19#include <type_traits>
20#include <fairmq/Message.h>
21#include <fairmq/Device.h>
22#include <fairlogger/Logger.h>
24#include <DetectorsBase/Stack.h>
28#include <gsl/gsl>
29#include <TFile.h>
30#include <TMemFile.h>
31#include <TTree.h>
32#include <TString.h>
33#include <TSystem.h>
34#include <TROOT.h>
35#include <memory>
36#include <TMessage.h>
37#include <fairmq/Parts.h>
38#include <ctime>
39#include <TStopwatch.h>
40#include <sstream>
41#include <cassert>
42#include "FairSystemInfo.h"
43
44#include "O2HitMerger.h"
45#include "O2SimDevice.h"
50#include <TOFSimulation/Detector.h>
63
65#include <map>
66#include <vector>
67#include <list>
68#include <csignal>
69#include <mutex>
70#include <filesystem>
71#include <functional>
72
74
75#ifdef ENABLE_UPGRADES
85#endif
86
87#include <tbb/concurrent_unordered_map.h>
88
89namespace o2
90{
91namespace devices
92{
93
94// Function communicating to primary particle server that it is now safe to shutdown.
95// From the perspective of o2-sim, this is the case when all configs have been propagated and the system
96// is running ok: For instance after the HitMerger is initialized and got it's first data from Geant workers.
97bool primaryServer_sendShutdownPermission(fair::mq::Channel& channel)
98{
99 std::unique_ptr<fair::mq::Message> request(channel.NewSimpleMessage((int)o2::O2PrimaryServerInfoRequest::AllowShutdown));
100 std::unique_ptr<fair::mq::Message> reply(channel.NewMessage());
101
102 int timeoutinMS = 100;
103 if (channel.Send(request, timeoutinMS) > 0) {
104 LOG(info) << "Sending Shutdown permission to particle server";
105 if (channel.Receive(reply, timeoutinMS) > 0) {
106 // the answer is a simple ack with a status code
107 LOG(info) << "Shutdown permission was acknowledged";
108 } else {
109 LOG(error) << "No answer received within " << timeoutinMS << "ms\n";
110 return false;
111 }
112 return true;
113 }
114 return false;
115}
116
118{
119
120 class TMessageWrapper : public TMessage
121 {
122 public:
123 TMessageWrapper(void* buf, Int_t len) : TMessage(buf, len) { ResetBit(kIsOwner); }
124 ~TMessageWrapper() override = default;
125 };
126
127 public:
130 {
131 mTimer.Start();
132 mInitialOutputDir = std::filesystem::current_path().string();
133 mCurrentOutputDir = mInitialOutputDir;
134 }
135
137 ~O2HitMerger() override
138 {
139 FairSystemInfo sysinfo;
140 LOG(info) << "TIME-STAMP " << mTimer.RealTime() << "\t";
141 mTimer.Continue();
142 LOG(info) << "MEM-STAMP " << sysinfo.GetCurrentMemory() / (1024. * 1024) << " "
143 << sysinfo.GetMaxMemory() << " MB\n";
144 }
145
146 private:
148 void InitTask() final
149 {
150 LOG(info) << "INIT HIT MERGER";
151 ROOT::EnableThreadSafety();
152
153 std::string outfilename("o2sim_merged_hits.root"); // default name
154 // query the sim config ... which is used to extract the filenames
155 if (o2::devices::O2SimDevice::querySimConfig(GetChannels().at("o2sim-primserv-info").at(0))) {
157 mNExpectedEvents = o2::conf::SimConfig::Instance().getNEvents();
158 } else {
159 // we didn't manage to get a configuration --> better to fail
160 LOG(fatal) << "No configuration received. Aborting";
161 }
165
166 mOutFileName = outfilename.c_str();
167 if (mWriteToDisc) {
168 mOutFile = new TFile(outfilename.c_str(), "RECREATE");
169 mOutTree = new TTree("o2sim", "o2sim");
170 mOutTree->SetDirectory(mOutFile);
171
172 mMCHeaderOnlyOutFile = new TFile(o2::base::NameConf::getMCHeadersFileName(o2::conf::SimConfig::Instance().getOutPrefix().c_str()).c_str(), "RECREATE");
173 mMCHeaderTree = new TTree("o2sim", "o2sim");
174 mMCHeaderTree->SetDirectory(mMCHeaderOnlyOutFile);
175 }
176 // detectors init only once
177 if (mDetectorInstances.size() == 0) {
178 initDetInstances();
179 // has to be after init of Detectors
181 initHitFiles(o2::conf::SimConfig::Instance().getOutPrefix());
182 }
183
184 // init pipe
185 auto pipeenv = getenv("ALICE_O2SIMMERGERTODRIVER_PIPE");
186 if (pipeenv) {
187 mPipeToDriver = atoi(pipeenv);
188 LOG(info) << "ASSIGNED PIPE HANDLE " << mPipeToDriver;
189 } else {
190 LOG(warning) << "DID NOT FIND ENVIRONMENT VARIABLE TO INIT PIPE";
191 }
192
193 // if no data to expect we shut down the device NOW since it would otherwise hang
194 if (mNExpectedEvents == 0) {
195 if (mAsService) {
196 waitForControlInput();
197 } else {
198 LOG(info) << "NOT EXPECTING ANY DATA; SHUTTING DOWN";
199 raise(SIGINT);
200 }
201 }
202 }
203
204 bool setWorkingDirectory(std::string const& dir)
205 {
206 namespace fs = std::filesystem;
207
208 // sets the output directory where simulation files are produced
209 // and creates it when it doesn't exist already
210
211 // 2 possibilities:
212 // a) dir is relative dir. Then we interpret it as relative to the initial
213 // base directory
214 // b) or dir is itself absolut.
215 try {
216 fs::current_path(fs::path(mInitialOutputDir)); // <--- to make sure relative start is always the same
217 if (!dir.empty()) {
218 auto absolutePath = fs::absolute(fs::path(dir));
219 if (!fs::exists(absolutePath)) {
220 if (!fs::create_directory(absolutePath)) {
221 LOG(error) << "Could not create directory " << absolutePath.string();
222 return false;
223 }
224 }
225 // set the current path
226 fs::current_path(absolutePath.string().c_str());
227 mCurrentOutputDir = fs::current_path().string();
228 }
229 LOG(info) << "FINAL PATH " << mCurrentOutputDir;
230 } catch (std::exception e) {
231 LOG(error) << " could not change path to " << dir;
232 }
233 return true;
234 }
235
236 // function for intermediate/on-the-fly reinitializations
237 bool ReInit(o2::conf::SimReconfigData const& reconfig)
238 {
239 if (reconfig.stop) {
240 return false;
241 }
242 if (!setWorkingDirectory(reconfig.outputDir)) {
243 return false;
244 }
245
246 std::string outfilename("o2sim_merged_hits.root"); // default name
248 mNExpectedEvents = reconfig.nEvents;
249 mOutFileName = outfilename.c_str();
250 if (mWriteToDisc) {
251 mOutFile = new TFile(outfilename.c_str(), "RECREATE");
252 mOutTree = new TTree("o2sim", "o2sim");
253 mOutTree->SetDirectory(mOutFile);
254
255 mMCHeaderOnlyOutFile = new TFile(o2::base::NameConf::getMCHeadersFileName(reconfig.outputPrefix).c_str(), "RECREATE");
256 mMCHeaderTree = new TTree("o2sim", "o2sim");
257 mMCHeaderTree->SetDirectory(mMCHeaderOnlyOutFile);
258 }
259 // reinit detectorInstance files (also make sure they are closed before continuing)
260 initHitFiles(reconfig.outputPrefix);
261
262 // clear "counter" datastructures
263 mPartsCheckSum.clear();
264 mEventChecksum = 0;
265
266 // clear collector datastructures
267 mMCTrackBuffer.clear();
268 mTrackRefBuffer.clear();
269 mSubEventInfoBuffer.clear();
270 mFlushableEvents.clear();
271 mNextFlushID = 1;
272
273 return true;
274 }
275
276 template <typename T, typename V>
277 V insertAdd(std::map<T, V>& m, T const& key, V value)
278 {
279 const auto iter = m.find(key);
280 V accum{0};
281 if (iter != m.end()) {
282 iter->second += value;
283 accum = iter->second;
284 } else {
285 m.insert(std::make_pair(key, value));
286 accum = value;
287 }
288 return accum;
289 }
290
291 template <typename T>
292 bool isDataComplete(T checksum, T nparts)
293 {
294 return checksum == nparts * (nparts + 1) / 2;
295 }
296
297 void consumeHits(int eventID, fair::mq::Parts& data, int& index)
298 {
299 auto detIDmessage = std::move(data.At(index++));
300 // this should be a detector ID
301 if (detIDmessage->GetSize() == 4) {
302 auto ptr = (int*)detIDmessage->GetData();
304 LOG(debug2) << "I1 " << ptr[0] << " NAME " << id.getName() << " MB "
305 << data.At(index)->GetSize() / 1024. / 1024.;
306
307 // get the detector that can interpret it
308 auto detector = mDetectorInstances[id].get();
309 if (detector) {
310 detector->collectHits(eventID, data, index);
311 }
312 }
313 }
314
315 template <typename T, typename BT>
316 void consumeData(int eventID, fair::mq::Parts& data, int& index, BT& buffer)
317 {
318 auto decodeddata = o2::base::decodeTMessage<T*>(data, index);
319 if (buffer.find(eventID) == buffer.end()) {
320 buffer[eventID] = typename BT::mapped_type();
321 }
322 buffer[eventID].push_back(decodeddata);
323 // delete decodeddata; --> we store the pointers
324 index++;
325 }
326
327 // fills a special branch of SubEventInfos in order to keep
328 // track of which entry corresponds to which event etc.
329 // also creates the MCEventHeader branch expected for physics analysis
330 void fillSubEventInfoEntry(o2::data::SubEventInfo& info)
331 {
332 if (mSubEventInfoBuffer.find(info.eventID) == mSubEventInfoBuffer.end()) {
333 mSubEventInfoBuffer[info.eventID] = std::list<o2::data::SubEventInfo*>();
334 }
335 mSubEventInfoBuffer[info.eventID].push_back(&info);
336 }
337
339 {
340 o2::simpubsub::publishMessage(GetChannels()["merger-notifications"].at(0), o2::simpubsub::simStatusString("MERGER", "STATUS", "AWAITING INPUT"));
341
342 auto factory = fair::mq::TransportFactory::CreateTransportFactory("zeromq");
343 auto channel = fair::mq::Channel{"o2sim-control", "sub", factory};
344 auto controlsocketname = getenv("ALICE_O2SIMCONTROL");
345 LOG(info) << "SOCKETNAME " << controlsocketname;
346 channel.Connect(std::string(controlsocketname));
347 channel.Validate();
348 std::unique_ptr<fair::mq::Message> reply(channel.NewMessage());
349
350 LOG(info) << "WAITING FOR INPUT";
351 if (channel.Receive(reply) > 0) {
352 auto data = reply->GetData();
353 auto size = reply->GetSize();
354
355 std::string command(reinterpret_cast<char const*>(data), size);
356 LOG(info) << "message: " << command;
357
359 o2::conf::parseSimReconfigFromString(command, reconfig);
360 return ReInit(reconfig);
361 } else {
362 LOG(info) << "NOTHING RECEIVED";
363 }
364 return true;
365 }
366
367 bool ConditionalRun() override
368 {
369 auto& channel = GetChannels().at("simdata").at(0);
370 fair::mq::Parts request;
371 auto bytes = channel.Receive(request);
372 if (bytes < 0) {
373 LOG(error) << "Some error occurred on socket during receive on sim data";
374 return true; // keep going
375 }
376 TStopwatch timer;
377 timer.Start();
378 auto more = handleSimData(request, 0);
379 LOG(info) << "HitMerger processing took " << timer.RealTime();
380 if (!more && mAsService) {
381 LOG(info) << " CONTROL ";
382 // if we are done treating data we may go back to init phase
383 // for the next batch
384 return waitForControlInput();
385 }
386
387 static bool initAcknowledged = false;
388 if (!initAcknowledged) {
389 primaryServer_sendShutdownPermission(GetChannels().at("o2sim-primserv-info").at(0));
390 initAcknowledged = true;
391 }
392
393 return more;
394 }
395
396 bool handleSimData(fair::mq::Parts& data, int /*index*/)
397 {
398 bool expectmore = true;
399 int index = 0;
400 auto infoptr = o2::base::decodeTMessage<o2::data::SubEventInfo*>(data, index++);
401 o2::data::SubEventInfo& info = *infoptr;
402 auto accum = insertAdd<uint32_t, uint32_t>(mPartsCheckSum, info.eventID, (uint32_t)info.part);
403
404 LOG(info) << "SIMDATA channel got " << data.Size() << " parts for event " << info.eventID << " part " << info.part << " out of " << info.nparts;
405
406 fillSubEventInfoEntry(info);
407 consumeData<std::vector<o2::MCTrack>>(info.eventID, data, index, mMCTrackBuffer);
408 consumeData<std::vector<o2::TrackReference>>(info.eventID, data, index, mTrackRefBuffer);
409 while (index < data.Size()) {
410 consumeHits(info.eventID, data, index);
411 }
412
413 if (isDataComplete<uint32_t>(accum, info.nparts)) {
414 LOG(info) << "Event " << info.eventID << " complete. Marking as flushable";
415 mFlushableEvents[info.eventID] = true;
416
417 // check if previous flush finished
418 // start merging only when no merging currently happening
419 // Like this we don't have to join/wait on the thread here and do not block the outer ConditionalRun handling
420 // TODO: Let this run fully asynchronously (not even triggered by ConditionalRun)
421 if (!mergingInProgress) {
422 if (mMergerIOThread.joinable()) {
423 mMergerIOThread.join();
424 }
425 // start hit merging and flushing in a separate thread in order not to block
426 mMergerIOThread = std::thread([info, this]() { mergingInProgress = true; mergeAndFlushData(); mergingInProgress = false; });
427 }
428
429 mEventChecksum += info.eventID;
430 // we also need to check if we have all events
431 if (isDataComplete<uint32_t>(mEventChecksum, info.maxEvents)) {
432 LOG(info) << "ALL EVENTS HERE; CHECKSUM " << mEventChecksum;
433
434 // flush remaining data and close file
435 if (mMergerIOThread.joinable()) {
436 mMergerIOThread.join();
437 }
438 mMergerIOThread = std::thread([info, this]() { mergingInProgress = true; mergeAndFlushData(); mergingInProgress = false; });
439 if (mMergerIOThread.joinable()) {
440 mMergerIOThread.join();
441 }
442
443 expectmore = false;
444 }
445
446 if (mPipeToDriver != -1) {
447 if (write(mPipeToDriver, &info.eventID, sizeof(info.eventID)) == -1) {
448 LOG(error) << "FAILED WRITING TO PIPE";
449 };
450 }
451 }
452 return expectmore;
453 }
454
455 void cleanEvent(int eventID)
456 {
457 // cleanup intermediate per-Event buffers
458 }
459
460 template <typename T>
461 void backInsert(T const& from, T& to)
462 {
463 std::copy(from.begin(), from.end(), std::back_inserter(to));
464 }
465
466 void reorderAndMergeMCTracks(int eventID, TTree* target, const std::vector<int>& nprimaries, const std::vector<int>& nsubevents, std::function<void(std::vector<MCTrack> const&)> tracks_analysis_hook, o2::dataformats::MCEventHeader const* mceventheader)
467 {
468 // avoid doing this for trivial cases
469 std::vector<MCTrack>* mcTracksPerSubEvent = nullptr;
470 auto targetdata = std::make_unique<std::vector<MCTrack>>();
471
472 auto& vectorOfSubEventMCTracks = mMCTrackBuffer[eventID];
473 const auto entries = vectorOfSubEventMCTracks.size();
474
475 if (entries > 1) {
476 //
477 // loop over subevents to store the primary events
478 //
479 int nprimTot = 0;
480 for (int entry = entries - 1; entry >= 0; --entry) {
481 int index = nsubevents[entry];
482 nprimTot += nprimaries[index];
483 printf("merge %d %5d %5d %5d \n", entry, index, nsubevents[entry], nsubevents[index]);
484 for (int i = 0; i < nprimaries[index]; i++) {
485 auto& track = (*vectorOfSubEventMCTracks[index])[i];
486 if (track.isTransported()) { // reset daughters only if track was transported, it will be fixed below
487 track.SetFirstDaughterTrackId(-1);
488 track.SetLastDaughterTrackId(-1);
489 }
490 targetdata->push_back(track);
491 }
492 }
493 //
494 // loop a second time to store the secondaries and fix the mother track IDs
495 //
496 Int_t idelta1 = nprimTot;
497 Int_t idelta0 = 0;
498 for (int entry = entries - 1; entry >= 0; --entry) {
499 int index = nsubevents[entry];
500
501 auto& subEventTracks = *(vectorOfSubEventMCTracks[index]);
502 // we need to fetch the right mctracks here!!
503 Int_t npart = (int)(subEventTracks.size());
504 Int_t nprim = nprimaries[index];
505 idelta1 -= nprim;
506
507 for (Int_t i = nprim; i < npart; i++) {
508 auto& track = subEventTracks[i];
509 Int_t cId = track.getMotherTrackId();
510 if (cId >= nprim) {
511 cId += idelta1;
512 } else {
513 cId += idelta0;
514 }
515 track.SetMotherTrackId(cId);
516 track.SetFirstDaughterTrackId(-1);
517
518 Int_t hwm = (int)(targetdata->size());
519 auto& mother = (*targetdata)[cId];
520 if (mother.getFirstDaughterTrackId() == -1) {
521 mother.SetFirstDaughterTrackId(hwm);
522 }
523 mother.SetLastDaughterTrackId(hwm);
524
525 targetdata->push_back(track);
526 }
527 idelta0 += nprim;
528 idelta1 += npart;
529 }
530 }
531 //
532 // write to output
533 auto filladdr = (entries > 1) ? targetdata.get() : vectorOfSubEventMCTracks[0];
534
535 // we give the possibility to produce some MC track statistics
536 // to be saved as part of the MCHeader structure
537 tracks_analysis_hook(*filladdr);
538
539 if (mWriteToDisc && target) {
540 auto targetbr = o2::base::getOrMakeBranch(*target, "MCTrack", &filladdr);
541 targetbr->SetAddress(&filladdr);
542 targetbr->Fill();
543 targetbr->ResetAddress();
544 }
545 // forwarding the track data to other consumers (pub/sub)
546 if (mForwardKine) {
547 auto free_tmessage = [](void* data, void* hint) { delete static_cast<TMessage*>(hint); };
548 auto& channel = GetChannels().at("kineforward").at(0);
549 TMessage* tmsg = new TMessage(kMESS_OBJECT);
550 tmsg->WriteObjectAny((void*)filladdr, TClass::GetClass("std::vector<o2::MCTrack>"));
551 std::unique_ptr<fair::mq::Message> trackmessage(channel.NewMessage(tmsg->Buffer(), tmsg->BufferSize(), free_tmessage, tmsg));
552 tmsg = new TMessage(kMESS_OBJECT);
553 tmsg->WriteObjectAny((void*)mceventheader, TClass::GetClass("o2::dataformats::MCEventHeader"));
554 std::unique_ptr<fair::mq::Message> headermessage(channel.NewMessage(tmsg->Buffer(), tmsg->BufferSize(), free_tmessage, tmsg));
555 fair::mq::Parts reply;
556 reply.AddPart(std::move(headermessage));
557 reply.AddPart(std::move(trackmessage));
558 channel.Send(reply);
559 LOG(info) << "Forward publish MC tracks on channel";
560 }
561
562 // cleanup buffered data
563 for (auto ptr : vectorOfSubEventMCTracks) {
564 delete ptr; // avoid this by using unique ptr
565 }
566 }
567
568 template <typename T, typename M>
569 void remapTrackIdsAndMerge(std::string brname, int eventID, TTree& target,
570 const std::vector<int>& trackoffsets, const std::vector<int>& nprimaries, const std::vector<int>& subevOrdered, M& mapOfVectorOfTs)
571 {
572 //
573 // Remap the mother track IDs by adding an offset.
574 // The offset calculated as the sum of the number of entries in the particle list of the previous subevents.
575 // This method is called by O2HitMerger::mergeAndFlushData(int)
576 //
577 T* incomingdata = nullptr;
578 std::unique_ptr<T> targetdata(nullptr);
579 auto& vectorOfT = mapOfVectorOfTs[eventID];
580 const auto entries = vectorOfT.size();
581
582 if (entries == 1) {
583 // nothing to do in case there is only one entry
584 incomingdata = vectorOfT[0];
585 } else {
586 targetdata = std::make_unique<T>();
587 // loop over subevents
588 Int_t nprimTot = 0;
589 for (int entry = 0; entry < entries; entry++) {
590 nprimTot += nprimaries[entry];
591 }
592 Int_t idelta0 = 0;
593 Int_t idelta1 = nprimTot;
594 for (int entry = entries - 1; entry >= 0; --entry) {
595 Int_t index = subevOrdered[entry];
596 Int_t nprim = nprimaries[index];
597 incomingdata = vectorOfT[index];
598 idelta1 -= nprim;
599 for (auto& data : *incomingdata) {
600 updateTrackIdWithOffset(data, nprim, idelta0, idelta1);
601 targetdata->push_back(data);
602 }
603 idelta0 += nprim;
604 idelta1 += trackoffsets[index];
605 }
606 }
607 auto dataaddr = (entries == 1) ? incomingdata : targetdata.get();
608 auto targetbr = o2::base::getOrMakeBranch(target, brname.c_str(), &dataaddr);
609 targetbr->SetAddress(&dataaddr);
610 targetbr->Fill();
611 targetbr->ResetAddress();
612
613 // cleanup mem
614 for (auto ptr : vectorOfT) {
615 delete ptr; // avoid this by using unique ptr
616 }
617 }
618
619 void updateTrackIdWithOffset(MCTrack& track, Int_t nprim, Int_t idelta0, Int_t idelta1)
620 {
621 Int_t cId = track.getMotherTrackId();
622 Int_t ioffset = (cId < nprim) ? idelta0 : idelta1;
623 if (cId != -1) {
624 track.SetMotherTrackId(cId + ioffset);
625 }
626 }
627
628 void updateTrackIdWithOffset(TrackReference& ref, Int_t nprim, Int_t idelta0, Int_t idelta1)
629 {
630 Int_t cId = ref.getTrackID();
631 Int_t ioffset = (cId < nprim) ? idelta0 : idelta1;
632 ref.setTrackID(cId + ioffset);
633 }
634
635 void initHitTreeAndOutFile(std::string prefix, int detID)
636 {
638 if (mDetectorOutFiles.find(detID) != mDetectorOutFiles.end() && mDetectorOutFiles[detID]) {
639 LOG(warn) << "Hit outfile for detID " << DetID::getName(detID) << " already initialized --> Reopening";
640 mDetectorOutFiles[detID]->Close();
641 delete mDetectorOutFiles[detID];
642 }
643 std::string name(o2::base::DetectorNameConf::getHitsFileName(detID, prefix));
644 if (mWriteToDisc) {
645 mDetectorOutFiles[detID] = new TFile(name.c_str(), "RECREATE");
646 mDetectorToTTreeMap[detID] = new TTree("o2sim", "o2sim");
647 mDetectorToTTreeMap[detID]->SetDirectory(mDetectorOutFiles[detID]);
648 } else {
649 mDetectorOutFiles[detID] = nullptr;
650 mDetectorToTTreeMap[detID] = nullptr;
651 }
652 }
653
654 // This method goes over the buffers containing data for a given event; potentially merges
655 // them and flushes into the actual output file.
656 // The method can be called asynchronously to data collection
657 bool mergeAndFlushData()
658 {
659 auto checkIfNextFlushable = [this]() -> bool {
660 mNextFlushID++;
661 return mFlushableEvents.find(mNextFlushID) != mFlushableEvents.end() && mFlushableEvents[mNextFlushID] == true;
662 };
663
664 LOG(info) << "Launching merge kernel ";
665 bool canflush = mFlushableEvents.find(mNextFlushID) != mFlushableEvents.end() && mFlushableEvents[mNextFlushID] == true;
666 if (!canflush) {
667 return false;
668 }
669 while (canflush == true) {
670 auto flusheventID = mNextFlushID;
671 LOG(info) << "Merge and flush event " << flusheventID;
672 auto iter = mSubEventInfoBuffer.find(flusheventID);
673 if (iter == mSubEventInfoBuffer.end()) {
674 LOG(error) << "No info/data found for event " << flusheventID;
675 if (!checkIfNextFlushable()) {
676 return false;
677 }
678 }
679
680 auto& subEventInfoList = (*iter).second;
681 if (subEventInfoList.size() == 0 || mNExpectedEvents == 0) {
682 LOG(error) << "No data entries found for event " << flusheventID;
683 if (!checkIfNextFlushable()) {
684 return false;
685 }
686 }
687
688 TStopwatch timer;
689 timer.Start();
690
691 // calculate trackoffsets
692 auto& confref = o2::conf::SimConfig::Instance();
693
694 // collecting trackoffsets (per data arrival id) to be used for global track-ID correction pass
695 std::vector<int> trackoffsets;
696 // collecting primary particles in each subevent (data arrival id)
697 std::vector<int> nprimaries;
698 // mapping of id to actual sub-event id (or part)
699 std::vector<int> nsubevents;
700
701 o2::dataformats::MCEventHeader* eventheader = nullptr; // The event header
702
703 // the MC labels (trackID) for hits
704 for (auto info : subEventInfoList) {
705 assert(info->npersistenttracks >= 0);
706 trackoffsets.emplace_back(info->npersistenttracks);
707 nprimaries.emplace_back(info->nprimarytracks);
708 nsubevents.emplace_back(info->part);
709 if (eventheader == nullptr) {
710 eventheader = &info->mMCEventHeader;
711 } else {
712 eventheader->getMCEventStats().add(info->mMCEventHeader.getMCEventStats());
713 }
714 }
715
716 // now see which events can be discarded in any case due to no hits
717 if (confref.isFilterOutNoHitEvents()) {
718 if (eventheader && eventheader->getMCEventStats().getNHits() == 0) {
719 LOG(info) << " Taking out event " << flusheventID << " due to no hits ";
720 cleanEvent(flusheventID);
721 if (!checkIfNextFlushable()) {
722 return true;
723 }
724 }
725 }
726
727 // attention: We need to make sure that we write everything in the same event order
728 // but iteration over keys of a standard map in C++ is ordered
729
730 // b) merge the general data
731 //
732 // for MCTrack remap the motherIds and merge at the same go
733 const auto entries = subEventInfoList.size();
734 std::vector<int> subevOrdered((int)(nsubevents.size()));
735 for (int entry = entries - 1; entry >= 0; --entry) {
736 subevOrdered[nsubevents[entry] - 1] = entry;
737 printf("HitMerger entry: %d nprimry: %5d trackoffset: %5d \n", entry, nprimaries[entry], trackoffsets[entry]);
738 }
739
740 // This is a hook that collects some useful statistics/properties on the event
741 // for use by other components;
742 // Properties are attached making use of the extensible "Info" feature which is already
743 // part of MCEventHeader. In such a way, one can also do this pass outside and attach arbitrary
744 // metadata to MCEventHeader without needing to change the data layout or API of the class itself.
745 // NOTE: This function might also be called directly in the primary server!?
746 auto mcheaderhook = [eventheader](std::vector<MCTrack> const& tracks) {
747 int eta1Point2Counter = 0;
748 int eta1Point0Counter = 0;
749 int eta0Point8Counter = 0;
750 int eta1Point2CounterPi = 0;
751 int eta1Point0CounterPi = 0;
752 int eta0Point8CounterPi = 0;
753 int prims = 0;
754 for (auto& tr : tracks) {
755 if (tr.isPrimary()) {
756 prims++;
757 const auto eta = tr.GetEta();
758 if (eta < 1.2) {
759 eta1Point2Counter++;
760 if (std::abs(tr.GetPdgCode()) == 211) {
761 eta1Point2CounterPi++;
762 }
763 }
764 if (eta < 1.0) {
765 eta1Point0Counter++;
766 if (std::abs(tr.GetPdgCode()) == 211) {
767 eta1Point0CounterPi++;
768 }
769 }
770 if (eta < 0.8) {
771 eta0Point8Counter++;
772 if (std::abs(tr.GetPdgCode()) == 211) {
773 eta0Point8CounterPi++;
774 }
775 }
776 } else {
777 break; // track layout is such that all prims are first anyway
778 }
779 }
780 // attach these properties to eventheader
781 // we only need to make the names standard
782 eventheader->putInfo("prims_eta_1.2", eta1Point2Counter);
783 eventheader->putInfo("prims_eta_1.0", eta1Point0Counter);
784 eventheader->putInfo("prims_eta_0.8", eta0Point8Counter);
785 eventheader->putInfo("prims_eta_1.2_pi", eta1Point2CounterPi);
786 eventheader->putInfo("prims_eta_1.0_pi", eta1Point0CounterPi);
787 eventheader->putInfo("prims_eta_0.8_pi", eta0Point8CounterPi);
788 eventheader->putInfo("prims_total", prims);
789 };
790 reorderAndMergeMCTracks(flusheventID, mOutTree, nprimaries, subevOrdered, mcheaderhook, eventheader);
791
792 if (mOutTree) {
793 // adjusting and merging track references
794 remapTrackIdsAndMerge<std::vector<o2::TrackReference>>("TrackRefs", flusheventID, *mOutTree, trackoffsets, nprimaries, subevOrdered, mTrackRefBuffer);
795
796 // write MC event headers
797 {
798 auto headerbr = o2::base::getOrMakeBranch(*mOutTree, "MCEventHeader.", &eventheader);
799 headerbr->SetAddress(&eventheader);
800 headerbr->Fill();
801 headerbr->ResetAddress();
802 }
803
804 {
805 auto headerbr = o2::base::getOrMakeBranch(*mMCHeaderTree, "MCEventHeader.", &eventheader);
806 headerbr->SetAddress(&eventheader);
807 headerbr->Fill();
808 headerbr->ResetAddress();
809 }
810 }
811
812 // c) do the merge procedure for all hits ... delegate this to detector specific functions
813 // since they know about types; number of branches; etc.
814 // this will also fix the trackIDs inside the hits
815 for (int id = 0; id < mDetectorInstances.size(); ++id) {
816 auto& det = mDetectorInstances[id];
817 if (det) {
818 auto hittree = mDetectorToTTreeMap[id];
819 if (hittree) {
820 det->mergeHitEntriesAndFlush(flusheventID, *hittree, trackoffsets, nprimaries, subevOrdered);
821 hittree->SetEntries(hittree->GetEntries() + 1);
822 LOG(info) << "flushing tree to file " << hittree->GetDirectory()->GetFile()->GetName();
823 }
824 }
825 }
826
827 // increase the entry count in the tree
828 if (mOutTree) {
829 mOutTree->SetEntries(mOutTree->GetEntries() + 1);
830 LOG(info) << "outtree has file " << mOutTree->GetDirectory()->GetFile()->GetName();
831 }
832 if (mMCHeaderTree) {
833 mMCHeaderTree->SetEntries(mMCHeaderTree->GetEntries() + 1);
834 LOG(info) << "mc header outtree has file " << mMCHeaderTree->GetDirectory()->GetFile()->GetName();
835 }
836
837 cleanEvent(flusheventID);
838 LOG(info) << "Merge/flush for event " << flusheventID << " took " << timer.RealTime();
839 if (!checkIfNextFlushable()) {
840 break;
841 }
842 } // end while
843 if (mWriteToDisc && mOutFile) {
844 LOG(info) << "Writing TTrees";
845 mOutFile->Write("", TObject::kOverwrite);
846 for (int id = 0; id < mDetectorInstances.size(); ++id) {
847 auto& det = mDetectorInstances[id];
848 if (det && mDetectorOutFiles[id]) {
849 mDetectorOutFiles[id]->Write("", TObject::kOverwrite);
850 }
851 }
852 if (mMCHeaderOnlyOutFile) {
853 mMCHeaderOnlyOutFile->Write("", TObject::kOverwrite);
854 }
855 }
856 return true;
857 }
858
859 std::map<uint32_t, uint32_t> mPartsCheckSum;
860 std::string mOutFileName;
861
862 // structures for the final flush
863 TFile* mOutFile;
864 TTree* mOutTree;
865 TFile* mMCHeaderOnlyOutFile;
866 TTree* mMCHeaderTree;
867
868 template <class K, class V>
869 using Hashtable = tbb::concurrent_unordered_map<K, V>;
870 Hashtable<int, TFile*> mDetectorOutFiles;
871 Hashtable<int, TTree*> mDetectorToTTreeMap;
872
873 // intermediate structures to collect data per event
874 std::thread mMergerIOThread;
875 bool mergingInProgress = false;
876
877 Hashtable<int, std::vector<std::vector<o2::MCTrack>*>> mMCTrackBuffer;
878 Hashtable<int, std::vector<std::vector<o2::TrackReference>*>> mTrackRefBuffer;
879 Hashtable<int, std::list<o2::data::SubEventInfo*>> mSubEventInfoBuffer;
880 Hashtable<int, bool> mFlushableEvents;
881
882 int mEventChecksum = 0;
883 int mNExpectedEvents = 0;
884 int mNextFlushID = 1;
885 TStopwatch mTimer;
886
887 bool mAsService = false;
888 bool mForwardKine = true;
889 bool mWriteToDisc = true;
890
891 int mPipeToDriver = -1;
892
893 std::vector<std::unique_ptr<o2::base::Detector>> mDetectorInstances;
894 std::vector<int> mExternalDetIDs;
895
896 // output folder configuration
897 std::string mInitialOutputDir; // initial output folder of the process (initialized during construction)
898 std::string mCurrentOutputDir; // current output folder asked
899
900 // channel to PUB status messages to outside subscribers
901 fair::mq::Channel mPubChannel;
902
903 // init detector instances
904 void initDetInstances();
905 void initExternalDetInstances();
906 void initHitFiles(std::string prefix);
907};
908
909void O2HitMerger::initHitFiles(std::string prefix)
910{
912
913 // a little helper lambda
914 auto isActivated = [](std::string s) -> bool {
915 // access user configuration for list of wanted modules
917 auto active = std::find(modulelist.begin(), modulelist.end(), s) != modulelist.end();
918 return active; };
919
920 for (int i = DetID::First; i <= DetID::Last; ++i) {
921 if (!isActivated(DetID::getName(i))) {
922 continue;
923 }
924 // init the detector specific output files
925 initHitTreeAndOutFile(prefix, i);
926 }
927
928 // external (CAD) detectors are not part of the readout-detector list (their module names
929 // are not DetID names); their slots were determined in initDetInstances()
930 for (auto detID : mExternalDetIDs) {
931 initHitTreeAndOutFile(prefix, detID);
932 }
933}
934
935// init detector instances used to write hit data to a TTree
936void O2HitMerger::initDetInstances()
937{
939
940 // a little helper lambda
941 auto isActivated = [](std::string s) -> bool {
942 // access user configuration for list of wanted modules
944 auto active = std::find(modulelist.begin(), modulelist.end(), s) != modulelist.end();
945 return active; };
946
947 mDetectorInstances.resize(DetID::nDetectors);
948 // like a factory of detector objects
949
950 int counter = 0;
951 for (int i = DetID::First; i <= DetID::Last; ++i) {
952 if (!isActivated(DetID::getName(i))) {
953 continue;
954 }
955
956 if (i == DetID::TPC) {
957 mDetectorInstances[i] = std::move(std::make_unique<o2::tpc::Detector>(true));
958 counter++;
959 }
960 if (i == DetID::ITS) {
961 mDetectorInstances[i] = std::move(std::make_unique<o2::its::Detector>(true));
962 counter++;
963 }
964 if (i == DetID::MFT) {
965 mDetectorInstances[i] = std::move(std::make_unique<o2::mft::Detector>(true));
966 counter++;
967 }
968 if (i == DetID::TRD) {
969 mDetectorInstances[i] = std::move(std::make_unique<o2::trd::Detector>(true));
970 counter++;
971 }
972 if (i == DetID::PHS) {
973 mDetectorInstances[i] = std::move(std::make_unique<o2::phos::Detector>(true));
974 counter++;
975 }
976 if (i == DetID::CPV) {
977 mDetectorInstances[i] = std::move(std::make_unique<o2::cpv::Detector>(true));
978 counter++;
979 }
980 if (i == DetID::EMC) {
981 mDetectorInstances[i] = std::move(std::make_unique<o2::emcal::Detector>(true));
982 counter++;
983 }
984 if (i == DetID::HMP) {
985 mDetectorInstances[i] = std::move(std::make_unique<o2::hmpid::Detector>(true));
986 counter++;
987 }
988 if (i == DetID::TOF) {
989 mDetectorInstances[i] = std::move(std::make_unique<o2::tof::Detector>(true));
990 counter++;
991 }
992 if (i == DetID::FT0) {
993 mDetectorInstances[i] = std::move(std::make_unique<o2::ft0::Detector>(true));
994 counter++;
995 }
996 if (i == DetID::FV0) {
997 mDetectorInstances[i] = std::move(std::make_unique<o2::fv0::Detector>(true));
998 counter++;
999 }
1000 if (i == DetID::FDD) {
1001 mDetectorInstances[i] = std::move(std::make_unique<o2::fdd::Detector>(true));
1002 counter++;
1003 }
1004 if (i == DetID::MCH) {
1005 mDetectorInstances[i] = std::move(std::make_unique<o2::mch::Detector>(true));
1006 counter++;
1007 }
1008 if (i == DetID::MID) {
1009 mDetectorInstances[i] = std::move(std::make_unique<o2::mid::Detector>(true));
1010 counter++;
1011 }
1012 if (i == DetID::ZDC) {
1013 mDetectorInstances[i] = std::move(std::make_unique<o2::zdc::Detector>(true));
1014 counter++;
1015 }
1016 if (i == DetID::FOC) {
1017 TString sName = "$O2_ROOT/share/Detectors/Geometry/FOC/geometryFiles/geometry_Sheets.txt";
1018 gSystem->ExpandPathName(sName);
1019 mDetectorInstances[i] = std::move(std::make_unique<o2::focal::Detector>(true, sName.Data()));
1020 counter++;
1021 }
1022#ifdef ENABLE_UPGRADES
1023 if (i == DetID::IT3) {
1024 mDetectorInstances[i] = std::move(std::make_unique<o2::its::Detector>(true, "IT3"));
1025 counter++;
1026 }
1027 if (i == DetID::TRK) {
1028 mDetectorInstances[i] = std::move(std::make_unique<o2::trk::Detector>(true));
1029 counter++;
1030 }
1031 if (i == DetID::FT3) {
1032 mDetectorInstances[i] = std::move(std::make_unique<o2::ft3::Detector>(true));
1033 counter++;
1034 }
1035 if (i == DetID::FCT) {
1036 mDetectorInstances[i] = std::move(std::make_unique<o2::fct::Detector>(true));
1037 counter++;
1038 }
1039 if (i == DetID::TF3) {
1040 mDetectorInstances[i] = std::move(std::make_unique<o2::iotof::Detector>(true));
1041 counter++;
1042 }
1043 if (i == DetID::RCH) {
1044 mDetectorInstances[i] = std::move(std::make_unique<o2::rich::Detector>(true));
1045 counter++;
1046 }
1047 if (i == DetID::MI3) {
1048 mDetectorInstances[i] = std::move(std::make_unique<o2::mi3::Detector>(true));
1049 counter++;
1050 }
1051 if (i == DetID::ECL) {
1052 mDetectorInstances[i] = std::move(std::make_unique<o2::ecal::Detector>(true));
1053 counter++;
1054 }
1055 if (i == DetID::FD3) {
1056 mDetectorInstances[i] = std::move(std::make_unique<o2::fd3::Detector>(true));
1057 counter++;
1058 }
1059#endif
1060 }
1061 if (counter != DetID::nDetectors) {
1062 LOG(warning) << " O2HitMerger: Some Detectors are potentially missing in this initialization ";
1063 }
1064
1065 // also register external (CAD-derived) sensitive detectors so their hits are persisted
1066 // in parallel (multi-worker) mode
1067 initExternalDetInstances();
1068}
1069
1070// init detector instances for external (CAD-derived) sensitive detectors.
1071// These are not part of the hard-coded DetID switch above: they are described in the
1072// external geometry JSON (the same file used by build_geometry.C on the worker side) and
1073// tied to an existing (free) DetID. The merger only needs an instance able to interpret the
1074// generic o2::ext::Hit wire format and write the "<name>Hit" branch; no geometry is built here.
1075void O2HitMerger::initExternalDetInstances()
1076{
1078
1079 auto& simConfig = o2::conf::SimConfig::Instance();
1080 const auto extGeomFile = simConfig.getExtGeomFilename();
1081 if (extGeomFile.empty()) {
1082 return;
1083 }
1084
1085 // mirror the worker-side activation: an external detector participates when its module
1086 // name is part of the active module list
1087 auto const& activeModules = simConfig.getActiveModules();
1088 auto isActivated = [&activeModules](std::string const& s) -> bool {
1089 return std::find(activeModules.begin(), activeModules.end(), s) != activeModules.end();
1090 };
1091
1092 for (auto* extdet : o2::ext::ExternalDetector::createFromJSON(extGeomFile)) {
1093 const std::string name = extdet->GetName();
1094 if (!isActivated(name)) {
1095 delete extdet; // not requested in the active module list
1096 continue;
1097 }
1098 const int detID = extdet->GetDetId();
1099 if (detID < DetID::First || detID > DetID::Last) {
1100 LOG(error) << "O2HitMerger: external detector " << name << " has invalid DetID " << detID << "; skipping";
1101 delete extdet;
1102 continue;
1103 }
1104 if (mDetectorInstances[detID]) {
1105 LOG(error) << "O2HitMerger: DetID " << DetID::getName(detID) << " requested by external detector " << name
1106 << " is already occupied; its hits will not be persisted. Assign a free DetID.";
1107 delete extdet;
1108 continue;
1109 }
1110 mDetectorInstances[detID].reset(extdet);
1111 mExternalDetIDs.emplace_back(detID);
1112 LOG(info) << "O2HitMerger: registered external detector " << name << " on DetID " << DetID::getName(detID)
1113 << " (branch " << name << "Hit)";
1114 }
1115}
1116
1117} // namespace devices
1118} // namespace o2
1119
1120#endif
Definition of the DescriptorInnerBarrelITS3 class.
Definition of the Names Generator class.
Definition of the Stack class.
Sensitive detector built from an externally provided (CAD-derived) geometry.
Definition of the Detector class.
Definition of the Detector class.
Definition of the FV0 detector class.
int32_t i
Definition of the Detector class.
Definition of the Detector class.
bool waitForControlInput(int workerID)
TBranch * ptr
ECal geometry creation and hit processing.
Definition of the Detector class.
Definition of the Detector class.
Definition of the Detector class.
StringRef key
static std::string getHitsFileName(DId d, const std::string_view prefix=STANDARDSIMPREFIX)
static std::string getMCKinematicsFileName(const std::string_view prefix=STANDARDSIMPREFIX)
Definition NameConf.h:46
static std::string getMCHeadersFileName(const std::string_view prefix=STANDARDSIMPREFIX)
Definition NameConf.h:52
bool writeToDisc() const
Definition SimConfig.h:180
bool asService() const
Definition SimConfig.h:174
unsigned int getNEvents() const
Definition SimConfig.h:157
bool forwardKine() const
Definition SimConfig.h:179
static SimConfig & Instance()
Definition SimConfig.h:112
std::vector< std::string > const & getReadoutDetectors() const
Definition SimConfig.h:141
void putInfo(std::string const &key, T const &value)
void add(MCEventStats const &other)
merge from another object
Static class with identifiers, bitmasks and names for ALICE detectors.
Definition DetID.h:58
static constexpr const char * getName(ID id)
names of defined detectors
Definition DetID.h:146
static constexpr ID FV0
Definition DetID.h:76
static constexpr ID PHS
Definition DetID.h:67
static constexpr ID MID
Definition DetID.h:73
static constexpr ID ITS
Definition DetID.h:63
static constexpr ID First
Definition DetID.h:95
static constexpr ID MFT
Definition DetID.h:71
static constexpr int nDetectors
number of defined detectors
Definition DetID.h:97
static constexpr ID ZDC
Definition DetID.h:74
static constexpr ID FT0
Definition DetID.h:75
static constexpr ID CPV
Definition DetID.h:68
static constexpr ID TRD
Definition DetID.h:65
static constexpr ID Last
if extra detectors added, update this !!!
Definition DetID.h:93
static constexpr ID FOC
Definition DetID.h:80
static constexpr ID TPC
Definition DetID.h:64
static constexpr ID EMC
Definition DetID.h:69
static constexpr ID FDD
Definition DetID.h:77
static constexpr ID MCH
Definition DetID.h:72
static constexpr ID HMP
Definition DetID.h:70
static constexpr ID TOF
Definition DetID.h:66
O2HitMerger()
Default constructor.
~O2HitMerger() override
Default destructor.
static bool querySimConfig(fair::mq::Channel &channel)
Definition O2SimDevice.h:96
static ShmManager & Instance()
Definition ShmManager.h:61
const GLfloat * m
Definition glcorearb.h:4066
GLuint buffer
Definition glcorearb.h:655
GLuint entry
Definition glcorearb.h:5735
GLsizeiptr size
Definition glcorearb.h:659
GLuint index
Definition glcorearb.h:781
GLuint const GLchar * name
Definition glcorearb.h:781
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum target
Definition glcorearb.h:1641
GLboolean * data
Definition glcorearb.h:298
GLenum GLenum GLsizei len
Definition glcorearb.h:4232
GLenum GLuint GLenum GLsizei const GLchar * buf
Definition glcorearb.h:2514
GLuint id
Definition glcorearb.h:650
GLuint counter
Definition glcorearb.h:3987
TBranch * getOrMakeBranch(TTree &tree, const char *brname, T *ptr)
Definition Detector.h:281
bool parseSimReconfigFromString(std::string const &argumentstring, SimReconfigData &config)
bool primaryServer_sendShutdownPermission(fair::mq::Channel &channel)
Definition O2HitMerger.h:97
auto get(const std::byte *buffer, size_t=0)
Definition DataHeader.h:454
const bool const int TrackITSInternal< NLayers > & track
std::string simStatusString(std::string const &origin, std::string const &topic, std::string const &message)
bool publishMessage(fair::mq::Channel &channel, std::string const &message)
struct o2::upgrades_utils::@469 tracks
structure to keep trigger-related info
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
TODO: Make this a base class of SimConfigData?
Definition SimConfig.h:205
LOG(info)<< "Compressed in "<< sw.CpuTime()<< " s"