Belle II Software  release-08-01-10
FullSimTimingModule.cc
1 /**************************************************************************
2  * basf2 (Belle II Analysis Software Framework) *
3  * Author: The Belle II Collaboration *
4  * *
5  * See git log for contributors and copyright holders. *
6  * This file is licensed under LGPL-3.0, see LICENSE.md. *
7  **************************************************************************/
8 
9 #include <simulation/modules/fullsim/FullSimTimingModule.h>
10 #include <simulation/kernel/RunManager.h>
11 #include <framework/gearbox/Unit.h>
12 #include <G4EventManager.hh>
13 #include <G4VSteppingVerbose.hh>
14 #include <G4RegionStore.hh>
15 #include <framework/logging/Logger.h>
16 
17 #include <TProfile.h>
18 #include <TFile.h>
19 
20 using namespace Belle2;
21 
22 //-----------------------------------------------------------------
23 // Register the Module
24 //-----------------------------------------------------------------
25 REG_MODULE(FullSimTiming);
26 
27 //-----------------------------------------------------------------
28 // Implementation
29 //-----------------------------------------------------------------
30 
32 {
33  // Set module properties
34  setDescription("Provide more detailed timing info for FullSim module");
35 
36  //So, we have to get rid of the old instance first if we want to get our
37  //timing instance in there, otherwise we get an G4Exception
38  G4VSteppingVerbose* old = G4VSteppingVerbose::GetInstance();
39  if (old) {
40  delete old;
41  G4VSteppingVerbose::SetInstance(nullptr);
42  }
43  //Now we create our timing class and set it as the class to be used. Set the callback to our processStep member
44  m_timing = new Simulation::StepTiming([&](G4Track * track, G4LogicalVolume * volume, double time) {
45  processStep(track, volume, time);
46  });
47  G4VSteppingVerbose::SetInstance(m_timing);
48 
49  // Parameter definitions
50  addParam("rootFile", m_rootFileName, "Name of the output root file. If none "
51  " is given, no file will be created", std::string(""));
52 }
53 
54 
56 {
57  m_isInitialized = false;
58 
59  //There are two (useful) scenarios: either this module is in front or after
60  //the FullSim module. In both cases we want it to work correctly. As the
61  //FullSim module overrides the verbosity of the SteppingManager we have to be
62  //careful when to initialize, it must be after the FullSim::initialize(). So
63  //either here if we are after the FullSim module or in the first event() call
64  //if we are before
65 
67  //So, we check if the Simulation has been intialized already. If so we can
68  //initialize the StepTiming now, otherwise we do it in the first call to the
69  //event function. For this we just check if the PrimaryGeneratorAction has
70  //been set
71  if (runManager.GetUserPrimaryGeneratorAction()) {
72  //initialization is done by just calling the event function once
73  event();
74  }
75 }
76 
78 {
79  if (!m_isInitialized) {
80  //Override verbosity of the Stepping manager to make sure that the
81  //TrackingVerbose instance is called
82  G4SteppingManager* stepping = G4EventManager::GetEventManager()->GetTrackingManager()->GetSteppingManager();
83  //Set the timing class
84  stepping->SetVerbose(m_timing);
85  //Make sure the SteppingManager is set to verbose otherwise the timing class will not be called
86  stepping->SetVerboseLevel(1);
87  //Make sure the timing class knows about the stepping manager
88  m_timing->SetManager(stepping);
89  //And set it to silent so the TrackingManager does not print the TrackBanner
90  m_timing->SetSilent(1);
91  m_timing->SetSilentStepInfo(1);
92  //Ok, we want to plot the regions in the order they were created so we need
93  //to map from region to index in the store
94  int index {0};
95  for (G4Region* region : * (G4RegionStore::GetInstance())) {
96  //We don't care about regions wihtout volumes
97  if (region->GetNumberOfRootVolumes() == 0) continue;
98  //Increase the index otherwise
99  m_regionIndices[region] = index++;
100  }
101  //create profile to store timings
102  m_timingProfile = new TProfile(
103  "SimulationTiming", "Simulation Timing;region;stepping time per event / ms",
104  index, 0, index, "s"
105  );
106  //Set counter variables
107  m_totalTime = 0;
108  m_eventCount = 0;
109  m_eventTime = 0;
110  //And we only do this once
111  m_isInitialized = true;
112  }
113  //If the eventTime is >0 we recorded at least one step, so let's update the
114  //profile and reset the counters
115  if (m_eventTime > 0) {
116  ++m_eventCount;
117  m_eventTime = 0;
118  for (auto& it : m_regionCache) {
119  int index = m_regionIndices[it.first];
120  m_timingProfile->Fill(index, it.second);
121  it.second = 0;
122  }
123  }
124 }
125 
126 void FullSimTimingModule::processStep(G4Track*, G4LogicalVolume* volume, double time)
127 {
128  const G4Region* const region = volume->GetRegion();
129  //Check if we are still in the same region to speed up the lookup
130  if (m_lastRegion == end(m_regionCache) || m_lastRegion->first != region) {
131  //Apparently not, so find or create the region in the Cache
132  m_lastRegion = m_regionCache.insert(std::make_pair(region, 0.0)).first;
133  }
134  //Record times
135  m_lastRegion->second += time;
136  m_totalTime += time;
137  m_eventTime += time;
138 }
139 
141 {
142  //Call event one last time to get the last timing in case we were before the FullSim module
143  event();
144  //Ok, now finish the results
145  B2RESULT("FullSimTiming: Total simulation stepping time: " << (m_totalTime / Unit::s) << " s");
146  B2RESULT("FullSimTiming: Number of simulated events: " << m_eventCount);
147  //Remove stat box
148  m_timingProfile->SetStats(0);
149  //Convert to ms
150  m_timingProfile->Scale(1. / Unit::ms);
151  //Set the labels of the profile to the region names and check the maximum length
152  size_t regionWidth {6};
153  for (auto& it : m_regionIndices) {
154  m_timingProfile->GetXaxis()->SetBinLabel(it.second + 1, it.first->GetName().c_str());
155  regionWidth = std::max(regionWidth, it.first->GetName().size());
156  }
157  //Do we want a root file?
158  if (!m_rootFileName.empty()) {
159  TFile* file = new TFile(m_rootFileName.c_str(), "RECREATE");
160  file->cd();
161  m_timingProfile->Write();
162  file->Close();
163  }
164  if (m_totalTime > 0) {
165  //Now print the result as a table. But first we need the total average
166  double totalAvg {0};
167  size_t maxContentWidth {4};
168  size_t maxErrorWidth {4};
169  for (int i = 0; i < m_timingProfile->GetNbinsX(); ++i) {
170  //We want to calculate the total avg per event and the width required to
171  //show all times and their errors. So we need times and errors in ms
172  const double content = m_timingProfile->GetBinContent(i + 1);
173  const double error = m_timingProfile->GetBinError(i + 1);
174  //add up the averages.
175  totalAvg += content;
176  //and calculate the number of digits
177  const size_t contentWidth = std::floor(std::log10(content > 0 ? content : 1) + 1);
178  const size_t errorWidth = std::floor(std::log10(error > 0 ? error : 1) + 1);
179  //and get the maximum, including dot and 2 digits
180  maxContentWidth = std::max(maxContentWidth, contentWidth + 3);
181  maxErrorWidth = std::max(maxErrorWidth, errorWidth + 3);
182  }
183  //We want percentages ...
184  totalAvg /= 100;
185  //And finally we print the table
186  B2RESULT("FullSimTiming: Simulation stepping time per event and region");
187  for (int i = 0; i < m_timingProfile->GetNbinsX(); ++i) {
188  B2RESULT(" "
189  << std::setw(regionWidth + 1) << std::left << m_timingProfile->GetXaxis()->GetBinLabel(i + 1) << " "
190  << std::right << std::fixed << std::setprecision(2)
191  << std::setw(maxContentWidth) << m_timingProfile->GetBinContent(i + 1) << " ms +- "
192  << std::setw(maxErrorWidth) << m_timingProfile->GetBinError(i + 1) << " ms ("
193  << std::setw(6) << (m_timingProfile->GetBinContent(i + 1) / totalAvg) << " %)"
194  );
195  }
196  }
197 }
std::string m_rootFileName
filename of the root file to store the timing profile
double m_totalTime
total time spent stepping through the simulation geometry
bool m_isInitialized
remember whether we aleady initialized everything
Simulation::StepTiming * m_timing
instance of the StepTiming class which records the step times in geant4
virtual void initialize() override
Setup the timing structure.
virtual void event() override
Save the simulation time for each region in a profile.
FullSimTimingModule()
Constructor: Sets the description, the properties and the parameters of the module.
std::map< const G4Region *, double >::iterator m_lastRegion
iterator pointing to the region of the last step to speed up lookup
virtual void terminate() override
Save the output.
double m_eventTime
stepping time of the current event
void processStep(G4Track *track, G4LogicalVolume *volume, double time)
store the time it took a Geant4 track for one step inside a volume
unsigned int m_eventCount
number of events with m_eventTime>0
std::map< const G4Region *, int > m_regionIndices
map between region and and bin indices in the TProfile
std::map< const G4Region *, double > m_regionCache
map containing the amount of time spent in each region
TProfile * m_timingProfile
profile of the time spent per event in each region
Base class for Modules.
Definition: Module.h:72
void setDescription(const std::string &description)
Sets the description of the module.
Definition: Module.cc:214
The run manager controls the flow of the Geant4 program and manages the event loop(s) within a run.
Definition: RunManager.h:32
static RunManager & Instance()
Static method to get a reference to the RunManager instance.
Definition: RunManager.cc:29
Class to perform more detailed timing studies of the Geant4 simulation.
Definition: StepTiming.h:34
static const double ms
[millisecond]
Definition: Unit.h:96
static const double s
[second]
Definition: Unit.h:95
REG_MODULE(arichBtest)
Register the Module.
void addParam(const std::string &name, T &paramVariable, const std::string &description, const T &defaultValue)
Adds a new parameter to the module.
Definition: Module.h:560
Abstract base class for different kinds of events.