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 ref.setTrackID(o2::base::Detector::offsetTrackIndex(ref.getTrackID(), nprim, idelta0, idelta1));
631 }
632
633 void initHitTreeAndOutFile(std::string prefix, int detID)
634 {
636 if (mDetectorOutFiles.find(detID) != mDetectorOutFiles.end() && mDetectorOutFiles[detID]) {
637 LOG(warn) << "Hit outfile for detID " << DetID::getName(detID) << " already initialized --> Reopening";
638 mDetectorOutFiles[detID]->Close();
639 delete mDetectorOutFiles[detID];
640 }
641 std::string name(o2::base::DetectorNameConf::getHitsFileName(detID, prefix));
642 if (mWriteToDisc) {
643 mDetectorOutFiles[detID] = new TFile(name.c_str(), "RECREATE");
644 mDetectorToTTreeMap[detID] = new TTree("o2sim", "o2sim");
645 mDetectorToTTreeMap[detID]->SetDirectory(mDetectorOutFiles[detID]);
646 } else {
647 mDetectorOutFiles[detID] = nullptr;
648 mDetectorToTTreeMap[detID] = nullptr;
649 }
650 }
651
652 // This method goes over the buffers containing data for a given event; potentially merges
653 // them and flushes into the actual output file.
654 // The method can be called asynchronously to data collection
655 bool mergeAndFlushData()
656 {
657 auto checkIfNextFlushable = [this]() -> bool {
658 mNextFlushID++;
659 return mFlushableEvents.find(mNextFlushID) != mFlushableEvents.end() && mFlushableEvents[mNextFlushID] == true;
660 };
661
662 LOG(info) << "Launching merge kernel ";
663 bool canflush = mFlushableEvents.find(mNextFlushID) != mFlushableEvents.end() && mFlushableEvents[mNextFlushID] == true;
664 if (!canflush) {
665 return false;
666 }
667 while (canflush == true) {
668 auto flusheventID = mNextFlushID;
669 LOG(info) << "Merge and flush event " << flusheventID;
670 auto iter = mSubEventInfoBuffer.find(flusheventID);
671 if (iter == mSubEventInfoBuffer.end()) {
672 LOG(error) << "No info/data found for event " << flusheventID;
673 if (!checkIfNextFlushable()) {
674 return false;
675 }
676 }
677
678 auto& subEventInfoList = (*iter).second;
679 if (subEventInfoList.size() == 0 || mNExpectedEvents == 0) {
680 LOG(error) << "No data entries found for event " << flusheventID;
681 if (!checkIfNextFlushable()) {
682 return false;
683 }
684 }
685
686 TStopwatch timer;
687 timer.Start();
688
689 // calculate trackoffsets
690 auto& confref = o2::conf::SimConfig::Instance();
691
692 // collecting trackoffsets (per data arrival id) to be used for global track-ID correction pass
693 std::vector<int> trackoffsets;
694 // collecting primary particles in each subevent (data arrival id)
695 std::vector<int> nprimaries;
696 // mapping of id to actual sub-event id (or part)
697 std::vector<int> nsubevents;
698
699 o2::dataformats::MCEventHeader* eventheader = nullptr; // The event header
700
701 // the MC labels (trackID) for hits
702 for (auto info : subEventInfoList) {
703 assert(info->npersistenttracks >= 0);
704 trackoffsets.emplace_back(info->npersistenttracks);
705 nprimaries.emplace_back(info->nprimarytracks);
706 nsubevents.emplace_back(info->part);
707 if (eventheader == nullptr) {
708 eventheader = &info->mMCEventHeader;
709 } else {
710 eventheader->getMCEventStats().add(info->mMCEventHeader.getMCEventStats());
711 }
712 }
713
714 // now see which events can be discarded in any case due to no hits
715 if (confref.isFilterOutNoHitEvents()) {
716 if (eventheader && eventheader->getMCEventStats().getNHits() == 0) {
717 LOG(info) << " Taking out event " << flusheventID << " due to no hits ";
718 cleanEvent(flusheventID);
719 if (!checkIfNextFlushable()) {
720 return true;
721 }
722 }
723 }
724
725 // attention: We need to make sure that we write everything in the same event order
726 // but iteration over keys of a standard map in C++ is ordered
727
728 // b) merge the general data
729 //
730 // for MCTrack remap the motherIds and merge at the same go
731 const auto entries = subEventInfoList.size();
732 std::vector<int> subevOrdered((int)(nsubevents.size()));
733 for (int entry = entries - 1; entry >= 0; --entry) {
734 subevOrdered[nsubevents[entry] - 1] = entry;
735 printf("HitMerger entry: %d nprimry: %5d trackoffset: %5d \n", entry, nprimaries[entry], trackoffsets[entry]);
736 }
737
738 // This is a hook that collects some useful statistics/properties on the event
739 // for use by other components;
740 // Properties are attached making use of the extensible "Info" feature which is already
741 // part of MCEventHeader. In such a way, one can also do this pass outside and attach arbitrary
742 // metadata to MCEventHeader without needing to change the data layout or API of the class itself.
743 // NOTE: This function might also be called directly in the primary server!?
744 auto mcheaderhook = [eventheader](std::vector<MCTrack> const& tracks) {
745 int eta1Point2Counter = 0;
746 int eta1Point0Counter = 0;
747 int eta0Point8Counter = 0;
748 int eta1Point2CounterPi = 0;
749 int eta1Point0CounterPi = 0;
750 int eta0Point8CounterPi = 0;
751 int prims = 0;
752 for (auto& tr : tracks) {
753 if (tr.isPrimary()) {
754 prims++;
755 const auto eta = tr.GetEta();
756 if (eta < 1.2) {
757 eta1Point2Counter++;
758 if (std::abs(tr.GetPdgCode()) == 211) {
759 eta1Point2CounterPi++;
760 }
761 }
762 if (eta < 1.0) {
763 eta1Point0Counter++;
764 if (std::abs(tr.GetPdgCode()) == 211) {
765 eta1Point0CounterPi++;
766 }
767 }
768 if (eta < 0.8) {
769 eta0Point8Counter++;
770 if (std::abs(tr.GetPdgCode()) == 211) {
771 eta0Point8CounterPi++;
772 }
773 }
774 } else {
775 break; // track layout is such that all prims are first anyway
776 }
777 }
778 // attach these properties to eventheader
779 // we only need to make the names standard
780 eventheader->putInfo("prims_eta_1.2", eta1Point2Counter);
781 eventheader->putInfo("prims_eta_1.0", eta1Point0Counter);
782 eventheader->putInfo("prims_eta_0.8", eta0Point8Counter);
783 eventheader->putInfo("prims_eta_1.2_pi", eta1Point2CounterPi);
784 eventheader->putInfo("prims_eta_1.0_pi", eta1Point0CounterPi);
785 eventheader->putInfo("prims_eta_0.8_pi", eta0Point8CounterPi);
786 eventheader->putInfo("prims_total", prims);
787 };
788 reorderAndMergeMCTracks(flusheventID, mOutTree, nprimaries, subevOrdered, mcheaderhook, eventheader);
789
790 if (mOutTree) {
791 // adjusting and merging track references
792 remapTrackIdsAndMerge<std::vector<o2::TrackReference>>("TrackRefs", flusheventID, *mOutTree, trackoffsets, nprimaries, subevOrdered, mTrackRefBuffer);
793
794 // write MC event headers
795 {
796 auto headerbr = o2::base::getOrMakeBranch(*mOutTree, "MCEventHeader.", &eventheader);
797 headerbr->SetAddress(&eventheader);
798 headerbr->Fill();
799 headerbr->ResetAddress();
800 }
801
802 {
803 auto headerbr = o2::base::getOrMakeBranch(*mMCHeaderTree, "MCEventHeader.", &eventheader);
804 headerbr->SetAddress(&eventheader);
805 headerbr->Fill();
806 headerbr->ResetAddress();
807 }
808 }
809
810 // c) do the merge procedure for all hits ... delegate this to detector specific functions
811 // since they know about types; number of branches; etc.
812 // this will also fix the trackIDs inside the hits
813 for (int id = 0; id < mDetectorInstances.size(); ++id) {
814 auto& det = mDetectorInstances[id];
815 if (det) {
816 auto hittree = mDetectorToTTreeMap[id];
817 if (hittree) {
818 det->mergeHitEntriesAndFlush(flusheventID, *hittree, trackoffsets, nprimaries, subevOrdered);
819 hittree->SetEntries(hittree->GetEntries() + 1);
820 LOG(info) << "flushing tree to file " << hittree->GetDirectory()->GetFile()->GetName();
821 }
822 }
823 }
824
825 // increase the entry count in the tree
826 if (mOutTree) {
827 mOutTree->SetEntries(mOutTree->GetEntries() + 1);
828 LOG(info) << "outtree has file " << mOutTree->GetDirectory()->GetFile()->GetName();
829 }
830 if (mMCHeaderTree) {
831 mMCHeaderTree->SetEntries(mMCHeaderTree->GetEntries() + 1);
832 LOG(info) << "mc header outtree has file " << mMCHeaderTree->GetDirectory()->GetFile()->GetName();
833 }
834
835 cleanEvent(flusheventID);
836 LOG(info) << "Merge/flush for event " << flusheventID << " took " << timer.RealTime();
837 if (!checkIfNextFlushable()) {
838 break;
839 }
840 } // end while
841 if (mWriteToDisc && mOutFile) {
842 LOG(info) << "Writing TTrees";
843 mOutFile->Write("", TObject::kOverwrite);
844 for (int id = 0; id < mDetectorInstances.size(); ++id) {
845 auto& det = mDetectorInstances[id];
846 if (det && mDetectorOutFiles[id]) {
847 mDetectorOutFiles[id]->Write("", TObject::kOverwrite);
848 }
849 }
850 if (mMCHeaderOnlyOutFile) {
851 mMCHeaderOnlyOutFile->Write("", TObject::kOverwrite);
852 }
853 }
854 return true;
855 }
856
857 std::map<uint32_t, uint32_t> mPartsCheckSum;
858 std::string mOutFileName;
859
860 // structures for the final flush
861 TFile* mOutFile;
862 TTree* mOutTree;
863 TFile* mMCHeaderOnlyOutFile;
864 TTree* mMCHeaderTree;
865
866 template <class K, class V>
867 using Hashtable = tbb::concurrent_unordered_map<K, V>;
868 Hashtable<int, TFile*> mDetectorOutFiles;
869 Hashtable<int, TTree*> mDetectorToTTreeMap;
870
871 // intermediate structures to collect data per event
872 std::thread mMergerIOThread;
873 bool mergingInProgress = false;
874
875 Hashtable<int, std::vector<std::vector<o2::MCTrack>*>> mMCTrackBuffer;
876 Hashtable<int, std::vector<std::vector<o2::TrackReference>*>> mTrackRefBuffer;
877 Hashtable<int, std::list<o2::data::SubEventInfo*>> mSubEventInfoBuffer;
878 Hashtable<int, bool> mFlushableEvents;
879
880 int mEventChecksum = 0;
881 int mNExpectedEvents = 0;
882 int mNextFlushID = 1;
883 TStopwatch mTimer;
884
885 bool mAsService = false;
886 bool mForwardKine = true;
887 bool mWriteToDisc = true;
888
889 int mPipeToDriver = -1;
890
891 std::vector<std::unique_ptr<o2::base::Detector>> mDetectorInstances;
892 std::vector<int> mExternalDetIDs;
893
894 // output folder configuration
895 std::string mInitialOutputDir; // initial output folder of the process (initialized during construction)
896 std::string mCurrentOutputDir; // current output folder asked
897
898 // channel to PUB status messages to outside subscribers
899 fair::mq::Channel mPubChannel;
900
901 // init detector instances
902 void initDetInstances();
903 void initExternalDetInstances();
904 void initHitFiles(std::string prefix);
905};
906
907void O2HitMerger::initHitFiles(std::string prefix)
908{
910
911 // a little helper lambda
912 auto isActivated = [](std::string s) -> bool {
913 // access user configuration for list of wanted modules
915 auto active = std::find(modulelist.begin(), modulelist.end(), s) != modulelist.end();
916 return active; };
917
918 for (int i = DetID::First; i <= DetID::Last; ++i) {
919 if (!isActivated(DetID::getName(i))) {
920 continue;
921 }
922 // init the detector specific output files
923 initHitTreeAndOutFile(prefix, i);
924 }
925
926 // external (CAD) detectors are not part of the readout-detector list (their module names
927 // are not DetID names); their slots were determined in initDetInstances()
928 for (auto detID : mExternalDetIDs) {
929 initHitTreeAndOutFile(prefix, detID);
930 }
931}
932
933// init detector instances used to write hit data to a TTree
934void O2HitMerger::initDetInstances()
935{
937
938 // a little helper lambda
939 auto isActivated = [](std::string s) -> bool {
940 // access user configuration for list of wanted modules
942 auto active = std::find(modulelist.begin(), modulelist.end(), s) != modulelist.end();
943 return active; };
944
945 mDetectorInstances.resize(DetID::nDetectors);
946 // like a factory of detector objects
947
948 int counter = 0;
949 for (int i = DetID::First; i <= DetID::Last; ++i) {
950 if (!isActivated(DetID::getName(i))) {
951 continue;
952 }
953
954 if (i == DetID::TPC) {
955 mDetectorInstances[i] = std::move(std::make_unique<o2::tpc::Detector>(true));
956 counter++;
957 }
958 if (i == DetID::ITS) {
959 mDetectorInstances[i] = std::move(std::make_unique<o2::its::Detector>(true));
960 counter++;
961 }
962 if (i == DetID::MFT) {
963 mDetectorInstances[i] = std::move(std::make_unique<o2::mft::Detector>(true));
964 counter++;
965 }
966 if (i == DetID::TRD) {
967 mDetectorInstances[i] = std::move(std::make_unique<o2::trd::Detector>(true));
968 counter++;
969 }
970 if (i == DetID::PHS) {
971 mDetectorInstances[i] = std::move(std::make_unique<o2::phos::Detector>(true));
972 counter++;
973 }
974 if (i == DetID::CPV) {
975 mDetectorInstances[i] = std::move(std::make_unique<o2::cpv::Detector>(true));
976 counter++;
977 }
978 if (i == DetID::EMC) {
979 mDetectorInstances[i] = std::move(std::make_unique<o2::emcal::Detector>(true));
980 counter++;
981 }
982 if (i == DetID::HMP) {
983 mDetectorInstances[i] = std::move(std::make_unique<o2::hmpid::Detector>(true));
984 counter++;
985 }
986 if (i == DetID::TOF) {
987 mDetectorInstances[i] = std::move(std::make_unique<o2::tof::Detector>(true));
988 counter++;
989 }
990 if (i == DetID::FT0) {
991 mDetectorInstances[i] = std::move(std::make_unique<o2::ft0::Detector>(true));
992 counter++;
993 }
994 if (i == DetID::FV0) {
995 mDetectorInstances[i] = std::move(std::make_unique<o2::fv0::Detector>(true));
996 counter++;
997 }
998 if (i == DetID::FDD) {
999 mDetectorInstances[i] = std::move(std::make_unique<o2::fdd::Detector>(true));
1000 counter++;
1001 }
1002 if (i == DetID::MCH) {
1003 mDetectorInstances[i] = std::move(std::make_unique<o2::mch::Detector>(true));
1004 counter++;
1005 }
1006 if (i == DetID::MID) {
1007 mDetectorInstances[i] = std::move(std::make_unique<o2::mid::Detector>(true));
1008 counter++;
1009 }
1010 if (i == DetID::ZDC) {
1011 mDetectorInstances[i] = std::move(std::make_unique<o2::zdc::Detector>(true));
1012 counter++;
1013 }
1014 if (i == DetID::FOC) {
1015 TString sName = "$O2_ROOT/share/Detectors/Geometry/FOC/geometryFiles/geometry_Sheets.txt";
1016 gSystem->ExpandPathName(sName);
1017 mDetectorInstances[i] = std::move(std::make_unique<o2::focal::Detector>(true, sName.Data()));
1018 counter++;
1019 }
1020#ifdef ENABLE_UPGRADES
1021 if (i == DetID::IT3) {
1022 mDetectorInstances[i] = std::move(std::make_unique<o2::its::Detector>(true, "IT3"));
1023 counter++;
1024 }
1025 if (i == DetID::TRK) {
1026 mDetectorInstances[i] = std::move(std::make_unique<o2::trk::Detector>(true));
1027 counter++;
1028 }
1029 if (i == DetID::FT3) {
1030 mDetectorInstances[i] = std::move(std::make_unique<o2::ft3::Detector>(true));
1031 counter++;
1032 }
1033 if (i == DetID::FCT) {
1034 mDetectorInstances[i] = std::move(std::make_unique<o2::fct::Detector>(true));
1035 counter++;
1036 }
1037 if (i == DetID::TF3) {
1038 mDetectorInstances[i] = std::move(std::make_unique<o2::iotof::Detector>(true));
1039 counter++;
1040 }
1041 if (i == DetID::RCH) {
1042 mDetectorInstances[i] = std::move(std::make_unique<o2::rich::Detector>(true));
1043 counter++;
1044 }
1045 if (i == DetID::MI3) {
1046 mDetectorInstances[i] = std::move(std::make_unique<o2::mi3::Detector>(true));
1047 counter++;
1048 }
1049 if (i == DetID::ECL) {
1050 mDetectorInstances[i] = std::move(std::make_unique<o2::ecal::Detector>(true));
1051 counter++;
1052 }
1053 if (i == DetID::FD3) {
1054 mDetectorInstances[i] = std::move(std::make_unique<o2::fd3::Detector>(true));
1055 counter++;
1056 }
1057#endif
1058 }
1059 if (counter != DetID::nDetectors) {
1060 LOG(warning) << " O2HitMerger: Some Detectors are potentially missing in this initialization ";
1061 }
1062
1063 // also register external (CAD-derived) sensitive detectors so their hits are persisted
1064 // in parallel (multi-worker) mode
1065 initExternalDetInstances();
1066}
1067
1068// init detector instances for external (CAD-derived) sensitive detectors.
1069// These are not part of the hard-coded DetID switch above: they are described in the
1070// external geometry JSON (the same file used by build_geometry.C on the worker side) and
1071// tied to an existing (free) DetID. The merger only needs an instance able to interpret the
1072// generic o2::ext::Hit wire format and write the "<name>Hit" branch; no geometry is built here.
1073void O2HitMerger::initExternalDetInstances()
1074{
1076
1077 auto& simConfig = o2::conf::SimConfig::Instance();
1078 const auto extGeomFile = simConfig.getExtGeomFilename();
1079 if (extGeomFile.empty()) {
1080 return;
1081 }
1082
1083 // mirror the worker-side activation: an external detector participates when its module
1084 // name is part of the active module list
1085 auto const& activeModules = simConfig.getActiveModules();
1086 auto isActivated = [&activeModules](std::string const& s) -> bool {
1087 return std::find(activeModules.begin(), activeModules.end(), s) != activeModules.end();
1088 };
1089
1090 for (auto* extdet : o2::ext::ExternalDetector::createFromJSON(extGeomFile)) {
1091 const std::string name = extdet->GetName();
1092 if (!isActivated(name)) {
1093 delete extdet; // not requested in the active module list
1094 continue;
1095 }
1096 const int detID = extdet->GetDetId();
1097 if (detID < DetID::First || detID > DetID::Last) {
1098 LOG(error) << "O2HitMerger: external detector " << name << " has invalid DetID " << detID << "; skipping";
1099 delete extdet;
1100 continue;
1101 }
1102 if (mDetectorInstances[detID]) {
1103 LOG(error) << "O2HitMerger: DetID " << DetID::getName(detID) << " requested by external detector " << name
1104 << " is already occupied; its hits will not be persisted. Assign a free DetID.";
1105 delete extdet;
1106 continue;
1107 }
1108 mDetectorInstances[detID].reset(extdet);
1109 mExternalDetIDs.emplace_back(detID);
1110 LOG(info) << "O2HitMerger: registered external detector " << name << " on DetID " << DetID::getName(detID)
1111 << " (branch " << name << "Hit)";
1112 }
1113}
1114
1115} // namespace devices
1116} // namespace o2
1117
1118#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 int offsetTrackIndex(int trackID, int nprimaries, int primaryOffset, int secondaryOffset)
Definition Detector.h:177
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:300
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"