Skip to content

Commit 4699511

Browse files
committed
Add a first version of documentation about histograms
1 parent 7efa33f commit 4699511

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

doc/monitoring.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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

Comments
 (0)