|
| 1 | +<!-- |
| 2 | +Copyright (c) 2014-2024 Key4hep-Project. |
| 3 | +
|
| 4 | +This file is part of Key4hep. |
| 5 | +See https://key4hep.github.io/key4hep-doc/ for further info. |
| 6 | +
|
| 7 | +Licensed under the Apache License, Version 2.0 (the "License"); |
| 8 | +you may not use this file except in compliance with the License. |
| 9 | +You may obtain a copy of the License at |
| 10 | +
|
| 11 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 12 | +
|
| 13 | +Unless required by applicable law or agreed to in writing, software |
| 14 | +distributed under the License is distributed on an "AS IS" BASIS, |
| 15 | +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 16 | +See the License for the specific language governing permissions and |
| 17 | +limitations under the License. |
| 18 | +--> |
| 19 | + |
| 20 | +# Monitoring Functional algorithms |
| 21 | + |
| 22 | +It might be necessary to monitor an algorithm, e.g. by keeping track of how many |
| 23 | +times different branches in the algorithm logic are taken. Since Functional |
| 24 | +algorithms run potentially multithreaded, the usual approach of simply using |
| 25 | +mutable counters or histograms for this is not easily possible. Gaudi, provides |
| 26 | +some threadsafe tools to make monitoring Functional algorithms possible more |
| 27 | +easily. These tools generally take care of updating counters and histograms in a |
| 28 | +threadsafe way, as well as collecting all the data from potentially multiple |
| 29 | +instances running on different threads at the end of the execution. We describe |
| 30 | +how to use these tools here. |
| 31 | + |
| 32 | +In Gaudi all of these tools live in the `Gaudi::Accumulators` namespace and we |
| 33 | +refer to the [documentation of |
| 34 | +that](ttps://gaudi.web.cern.ch/doxygen/v40r0/da/dd5/namespace_gaudi_1_1_accumulators.html) |
| 35 | +for more technical details. We will focus on the use of some of these tools. |
| 36 | + |
| 37 | +## Using histograms |
| 38 | +Histograms come in two flavors: |
| 39 | +- **Static histograms** are fully defined at compile time, i.e. axis ranges, |
| 40 | + binning as well as axis titles cannot be altered at runtime. |
| 41 | +- **Non-static (or configurable) histograms** are somewhat dynamically |
| 42 | + configurable at runtime. Effectively only their dimension is fixed at compile |
| 43 | + time. |
| 44 | + |
| 45 | +The main difference between the two flavors is how they are declared in a |
| 46 | +Functional algorithm and obviously the configurability at runtime. Both flavors |
| 47 | +are filled the same way. |
| 48 | + |
| 49 | +Histograms also exist as generic versions and as ROOT backed versions. The main |
| 50 | +difference is that the ROOT backed version will obviously produce ROOTs |
| 51 | +`TH[1-3][I,D,F]`s (and are hence also limited to at most 3 dimensions), where as |
| 52 | +the generic versions can have arbitrary dimensions and will be serialized to |
| 53 | +JSON. |
| 54 | + |
| 55 | +### Declaring a histogram |
| 56 | + |
| 57 | +In the following we declare a static (`StaticRootHistogram`) and a dynamic |
| 58 | +(`RootHistogram`) ROOT histogram with different dimensions. The unspecified template |
| 59 | +parameters in this case default to full atomicity and double as arithmetic type. |
| 60 | + |
| 61 | +::::{tab-set} |
| 62 | +:::{tab-item} Static histogram |
| 63 | +```cpp |
| 64 | +#include <Gaudi/Accumulators/RootHistogram.h> |
| 65 | + |
| 66 | +struct MyAlgorithm : public /* whatever Functional you want */ { |
| 67 | + |
| 68 | + // Define as a member of your (Functional) algorithm |
| 69 | + mutable Gaudi::Accumulators::StaticRootHistogram<1> m_hist1d{ |
| 70 | + this, "MyHistogram1D", "A 1D histogram", {100, 0., 100.f}}; |
| 71 | + |
| 72 | + // For two dimensions simply specify two axis in the constructor |
| 73 | + mutable Gaudi::Accumulators::StaticRootHistogram<2> m_hist2d{ |
| 74 | + this, "MyHistogram2D", "A 2D histogram", {100, 0.0, 100.0}, {25, 0.0, 1.0, "axis title"}}; |
| 75 | +}; |
| 76 | +``` |
| 77 | +::: |
| 78 | +:::{tab-item} Configurable histogram |
| 79 | +```cpp |
| 80 | +#include <Gaudi/Accumulators/RootHistogram.h> |
| 81 | +
|
| 82 | +#include <functional> |
| 83 | +
|
| 84 | +struct MyAlgorithm : public /* whatever Functional you want */ { |
| 85 | +
|
| 86 | + // Define as a a member of your (Functional) algorithm |
| 87 | + // NOTE: it is possible to define a default for the axis, but not strictly necessary |
| 88 | + // NOTE: The name we give this histogram is also how we are going to customize from python later |
| 89 | + mutable Gaudi::Accumulators::RootHistogram<1> m_hist1d{ |
| 90 | + this, "Hist1D", "A 1D histogram with configurable axis", {100, 0., 100.f}}; |
| 91 | +
|
| 92 | + // We can also just define the dimensions and the title |
| 93 | + mutable Gaudi::Accumulators::RootHistogram<2> m_hist2d{this, "Hist2D", "A 2D histogram"}; |
| 94 | +
|
| 95 | + // NOTE: For configurable histograms you ahve to register this empty callback |
| 96 | + void registerCallBack(Gaudi::StateMachine::Transition, std::function<void()>){}; |
| 97 | +}; |
| 98 | +``` |
| 99 | +::: |
| 100 | + |
| 101 | +### Filling a histogram |
| 102 | + |
| 103 | +Filling a histogram is done by using the `operator[]` of the histogram classes |
| 104 | +and is the same, regardless of whether the histograms are configurable or |
| 105 | +static, e.g. |
| 106 | + |
| 107 | +```cpp |
| 108 | +auto operator()(/* However your signature looks like */) { |
| 109 | + // assume that we have computed value somehow and now want to fill the histogram with it |
| 110 | + ++m_hist1d[value]; |
| 111 | + |
| 112 | + // For multi-dimensional histograms we have to construct a proxy index on the fly |
| 113 | + ++m_hist2d[{value, value}]; // We didn't claim we use meaningful values for filling |
| 114 | +} |
| 115 | +``` |
| 116 | +
|
| 117 | +By default the histograms are defined with *full atomicity*, i.e. changing a |
| 118 | +counter is a potentially costly operation and we usually don't want to pay the |
| 119 | +price for this in tight loops, where we are certain that we are in any case on a |
| 120 | +single thread only. For these cases we use the `buffer` method to get a |
| 121 | +non-atomic version that can be updated quickly and which will automatically |
| 122 | +update its parent once it goes out of scope, e.g. |
| 123 | +
|
| 124 | +```cpp |
| 125 | +auto operator()(/* However your signature looks like */) { |
| 126 | + { // create a scope to limit the liftime of the buffer |
| 127 | + auto histBuffer = m_hist1d.buffer(); |
| 128 | + for (int i = 0; i < 1000; ++i) { |
| 129 | + ++histBuffer[i]; |
| 130 | + } |
| 131 | + } // Here histBuffer goes out of scope and its destructor will update m_hist1d in a threadsafe way |
| 132 | +} |
| 133 | +``` |
| 134 | + |
| 135 | +### Configuring the necessary services |
| 136 | + |
| 137 | +In order to actually store the histograms that are filled it is necessary to |
| 138 | +configure a Gaudi service to persist them, for ROOT histograms this can be done |
| 139 | +as follows |
| 140 | + |
| 141 | +```python |
| 142 | +from Configurables import Gaudi__Histograming__Sink__Root as RootHistoSink |
| 143 | + |
| 144 | +histoSinkSvc = RootHistSink("RootHistoSink") |
| 145 | +histoSinkSvc.FileName = "monitoring_histograms.root" |
| 146 | +``` |
| 147 | + |
| 148 | +**Don't forget to add this service to the `ExtSvc` list in the `ApplicationMgr`** |
| 149 | + |
| 150 | +### Customizing a histogram |
| 151 | + |
| 152 | +Configurable histograms need to be crated in `initialize` as follows in order to |
| 153 | +pick up the configuration from python: |
| 154 | +```cpp |
| 155 | +StatusCode MyAlgorithm::initizlize() { |
| 156 | + m_hist1d.createHistogram(*this); |
| 157 | + m_hist2d.createHistogram(*this); |
| 158 | +} |
| 159 | +``` |
| 160 | + |
| 161 | +To change properties on the python side you first need to instantiate your |
| 162 | +algorithm and you then have access to the histograms of this instance. Note how |
| 163 | +the name of the histogram that was given on the c++ side is used as part of the |
| 164 | +property name on the python side: |
| 165 | + |
| 166 | +```python |
| 167 | +algo = MyAlgorithm("MyAlgorithm") |
| 168 | +algo.Hist2D_Title = "Title changed from python" |
| 169 | +algo.Hist1D_Axis0 = (20, -1.0, 1.0, "X-axis") |
| 170 | +``` |
0 commit comments