ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
caliper_utils.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2026 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#pragma once
21
22/**
23 * @file caliper_utils.hpp
24 * @brief Zero-overhead Caliper guards for the inactive (no @c CALI_CONFIG)
25 * case.
26 *
27 * When Caliper is compiled in but @c CALI_CONFIG is not set (the common
28 * production case), the standard @c CALI_MARK_BEGIN/END and
29 * @c CALI_CXX_MARK_FUNCTION macros still pay the full Caliper entry cost
30 * (thread-blackboard update, siglock acquisition). On a hot path with
31 * many markers per step this amounts to measurable wall-clock overhead.
32 *
33 * This header provides guarded replacements:
34 * - @c espresso_cali_active() — checked once per process; in steady state this
35 * is a single byte load + branch.
36 * - @c ESPRESSO_CALI_MARK_FUNCTION — RAII guard equivalent to
37 * @c CALI_CXX_MARK_FUNCTION but zero-cost when inactive.
38 * - @c ESPRESSO_CALI_MARK_BEGIN(name) / @c ESPRESSO_CALI_MARK_END(name)
39 * - @c EspressoCaliLoop — RAII loop wrapper replacing the
40 * @c ESPRESSO_CALI_MARK_LOOP_BEGIN / @c ESPRESSO_CALI_MARK_LOOP_ITERATION /
41 * @c ESPRESSO_CALI_MARK_LOOP_END macro triplet.
42 *
43 * All macros and types are no-ops / empty when @c ESPRESSO_CALIPER is not
44 * defined.
45 *
46 * @par Activation path limitation
47 * @c espresso_cali_active() detects Caliper activation **solely** via the
48 * @c CALI_CONFIG environment variable. Activation through
49 * @c CALI_SERVICES_ENABLE, Caliper config files, or programmatic
50 * @c cali::ConfigManager::start() is **not** detected — hot-path regions will
51 * silently produce no output for those activation paths. @c CALI_CONFIG is
52 * the supported activation path for ESPResSo profiling (used by
53 * @c testsuite/python/caliper.py). This restriction is intentional: checking
54 * @c CALI_CONFIG once and caching the result eliminates the Caliper entry cost
55 * (siglock + thread-blackboard update) on every hot-path marker when profiling
56 * is inactive.
57 */
58
59#include <config/config.hpp>
60
61#ifdef ESPRESSO_CALIPER
62
63#include <caliper/cali.h>
64#include <cstdlib>
65
66/**
67 * @brief Return true if Caliper is configured for this process.
68 *
69 * Reads @c CALI_CONFIG from the environment exactly once (on the first call)
70 * and caches the result. Subsequent calls pay only one byte load + branch.
71 *
72 * @c inline ensures a single shared static across all translation units
73 * (C++ ODR for inline functions with static locals).
74 *
75 * @note Only @c CALI_CONFIG activation is detected; see the file-level
76 * documentation for the rationale and limitation.
77 */
78inline bool espresso_cali_active() noexcept {
79 static const bool active = (std::getenv("CALI_CONFIG") != nullptr);
80 return active;
81}
82
83/**
84 * @brief RAII region guard: begin on construction, end on destruction.
85 *
86 * Conditionally calls @c cali_begin_region / @c cali_end_region only when
87 * @c espresso_cali_active() is true. Correctly handles all exit paths.
88 */
90 const char *name_;
91 bool active_;
92
93 EspressoCaliRegion(const char *name, bool active)
94 : name_(name), active_(active) {
95 if (active_)
96 cali_begin_region(name_);
97 }
99 if (active_)
100 cali_end_region(name_);
101 }
104};
105
106/**
107 * @brief RAII iteration annotation for one pass through a loop body.
108 *
109 * Returned by @c EspressoCaliLoop::iteration(). When active, calls
110 * @c cali_begin_int on the loop's iteration attribute on construction and
111 * @c cali_end_byid on destruction — identical to what @c cali::Loop::Iteration
112 * does internally. When inactive, both operations are no-ops.
113 *
114 * The object must be stored at the top of the loop body so that its lifetime
115 * spans the loop body:
116 * @code
117 * for (int step = 0; ...) {
118 * auto cali_iter = cali_loop.iteration(step);
119 * // ...
120 * } // cali_iter destroyed here, closing the iteration region
121 * @endcode
122 */
124 cali_id_t iter_attr_;
126
127 EspressoCaliIteration(cali_id_t iter_attr, int iter, bool active)
128 : iter_attr_(iter_attr), active_(active) {
129 if (active_)
130 cali_begin_int(iter_attr_, iter);
131 }
133 if (active_)
134 cali_end(iter_attr_);
135 }
138 // Moveable so it can be returned from iteration() with NRVO/move.
140 : iter_attr_(other.iter_attr_), active_(other.active_) {
141 other.active_ = false;
142 }
144};
145
146/**
147 * @brief RAII loop wrapper replacing the @c CALI_CXX_MARK_LOOP_BEGIN /
148 * @c CALI_CXX_MARK_LOOP_ITERATION / @c CALI_CXX_MARK_LOOP_END macro triplet.
149 *
150 * When @c espresso_cali_active() is true at construction time, creates the
151 * same @c cali.loop region and @c iteration#name attribute as the raw Caliper
152 * macros would. When inactive, construction, iteration(), and destruction are
153 * all no-ops with no Caliper calls.
154 *
155 * Usage:
156 * @code
157 * EspressoCaliLoop cali_loop("Integration loop");
158 * for (int step = 0; step < n_steps; ++step) {
159 * auto cali_iter = cali_loop.iteration(step); // RAII: closed at }
160 * // ... loop body ...
161 * }
162 * // cali_loop destructor ends the loop region
163 * @endcode
164 */
166 cali::Loop *loop_;
167 cali_id_t iter_attr_;
168
169 explicit EspressoCaliLoop(const char *name)
170 : loop_(nullptr), iter_attr_(CALI_INV_ID) {
171 if (espresso_cali_active()) {
172 loop_ = new cali::Loop(name);
173 iter_attr_ = cali_make_loop_iteration_attribute(name);
174 }
175 }
177 if (loop_)
178 loop_->end();
179 delete loop_;
180 }
183
184 /**
185 * @brief Return an RAII iteration annotation for the current step.
186 *
187 * The returned @c EspressoCaliIteration must be kept alive for the duration
188 * of the loop body; store it in a named local variable.
189 */
191 return EspressoCaliIteration(iter_attr_, iter, loop_ != nullptr);
192 }
193};
194
195// NOLINTBEGIN(cppcoreguidelines-macro-usage)
196
197/**
198 * @brief Guarded drop-in replacement for @c CALI_CXX_MARK_FUNCTION.
199 *
200 * Captures @c __func__ at the call site so that the region name is the
201 * enclosing function name, not the destructor name.
202 */
203#define ESPRESSO_CALI_MARK_FUNCTION \
204 EspressoCaliRegion CALI_CREATE_VAR_NAME(__espresso_cali_fn, __LINE__)( \
205 __func__, ::espresso_cali_active())
206
207/**
208 * @brief Guarded @c CALI_MARK_BEGIN — no-op when inactive.
209 */
210#define ESPRESSO_CALI_MARK_BEGIN(name) \
211 do { \
212 if (::espresso_cali_active()) \
213 CALI_MARK_BEGIN(name); \
214 } while (false)
215
216/**
217 * @brief Guarded @c CALI_MARK_END — no-op when inactive.
218 */
219#define ESPRESSO_CALI_MARK_END(name) \
220 do { \
221 if (::espresso_cali_active()) \
222 CALI_MARK_END(name); \
223 } while (false)
224
225// NOLINTEND(cppcoreguidelines-macro-usage)
226
227#endif // ESPRESSO_CALIPER
bool espresso_cali_active() noexcept
Return true if Caliper is configured for this process.
RAII iteration annotation for one pass through a loop body.
EspressoCaliIteration & operator=(const EspressoCaliIteration &)=delete
EspressoCaliIteration(EspressoCaliIteration &&other) noexcept
EspressoCaliIteration & operator=(EspressoCaliIteration &&)=delete
EspressoCaliIteration(const EspressoCaliIteration &)=delete
EspressoCaliIteration(cali_id_t iter_attr, int iter, bool active)
RAII loop wrapper replacing the CALI_CXX_MARK_LOOP_BEGIN / CALI_CXX_MARK_LOOP_ITERATION / CALI_CXX_MA...
EspressoCaliLoop & operator=(const EspressoCaliLoop &)=delete
EspressoCaliIteration iteration(int iter) const
Return an RAII iteration annotation for the current step.
EspressoCaliLoop(const char *name)
EspressoCaliLoop(const EspressoCaliLoop &)=delete
RAII region guard: begin on construction, end on destruction.
EspressoCaliRegion & operator=(const EspressoCaliRegion &)=delete
EspressoCaliRegion(const char *name, bool active)
EspressoCaliRegion(const EspressoCaliRegion &)=delete