perfc 0.12.0
Loading...
Searching...
No Matches
register.hpp
Go to the documentation of this file.
1/**
2 * @file
3 * @ingroup perfc_register
4 * @brief Implementation of the counter register perfc::Register
5 * @copyright
6 * SPDX-FileCopyrightText: 2022-2024 European Southern Observatory (ESO)
7 *
8 * SPDX-License-Identifier: LGPL-3.0-only
9 *
10 *
11 * @defgroup perfc_register Counter Register
12 * @brief Keep track of counters.
13 * @ingroup perfc
14 *
15 * Main type is @ref perfc::Register.
16 */
17#ifndef PERFC_REGISTER_HPP_
18#define PERFC_REGISTER_HPP_
19#include <algorithm>
20#include <cstdint>
21#include <functional>
22#include <mutex>
23#include <variant>
24#include <vector>
25
26namespace perfc {
27
28/**
29 * Helper used when declaring counter types in perfc::Register.
30 *
31 * Example:
32 * @code
33 * // Specify the counter types that should be supported by our Register
34 * using Counters = perfc::CounterTypes<perfc::CounterDouble,
35 * perfc::CounterU64>;
36 * // Metadata using simple string for this example.
37 * using Metadata = std::string;
38 *
39 * // Specify the counters as first template parameter.
40 * using Register = perfc::Register<Counters, Metadata>;
41 * @endcode
42 *
43 * @ingroup perfc_register
44 * @headerfile <> <perfc/register.hpp>
45 */
46template <class... CounterType>
48 using CounterVariant = std::variant<std::add_pointer_t<CounterType>...>;
49};
50
51/**
52 * Simple type that provide synchronized access by holding a lock to T container.
53 *
54 * Accessing the locked object is peformed using pointer semantics.
55 * @ingroup perfc_register
56 */
57template <class T, class Mutex>
58class Locked {
59public:
60 using PointerType = std::add_pointer_t<T>;
61 using ReferenceType = std::add_lvalue_reference_t<T>;
62
63 /**
64 * Construct by providing reference to object requiring synchronized access and the mutex
65 * protecting it.
66 *
67 * @param t Object to obtain exclusive access to.
68 * @param mutex Mutex protecting @a t.
69 * @throws Exception originating from `Mutex::lock()` (typically `std::system_error`)
70 */
71 Locked(ReferenceType t, Mutex& mutex) : m_lock(mutex), m_ptr(&t) {
72 }
73
74 /**
75 * Move constructs by taking owneship of internal state of @a other.
76 * @param other Object to move from.
77 */
78 Locked(Locked&& other) noexcept : m_lock(std::move(other.m_lock)), m_ptr(other.m_ptr) {
79 if (m_ptr) {
80 other.m_ptr = nullptr;
81 }
82 }
83
84 /**
85 * Move constructs by taking owneship of internal state of @a other.
86 * @param other Object to move from.
87 */
88 Locked& operator=(Locked&& other) noexcept {
89 if (other.m_ptr) {
90 m_ptr = other.m_ptr;
91 m_lock = std::move(other.m_lock);
92 other.m_ptr = nullptr;
93 }
94 }
95
96 /**
97 * @return Reference to underlying object.
98 */
99 PointerType Get() const noexcept {
100 return m_ptr;
101 }
102 /**
103 * @return Reference to underlying object.
104 */
105 ReferenceType operator*() const noexcept {
106 return *m_ptr;
107 }
108 /**
109 * @return Pointer to underlying object.
110 */
111 PointerType operator->() const noexcept {
112 return m_ptr;
113 }
114
115 /**
116 * @returns true if to underlying object is valid, false otherwise.
117 */
118 operator bool() const noexcept {
119 return m_ptr != nullptr;
120 }
121
122 /**
123 * Unlocks lock.
124 *
125 * @note There is no way to re-acquire lock after this.
126 *
127 * @post `Get() == nullptr`
128 */
129 void Unlock() noexcept {
130 if (m_ptr) {
131 m_ptr = nullptr;
132 m_lock.unlock();
133 }
134 }
135
136private:
137 std::unique_lock<Mutex> m_lock;
138 PointerType m_ptr;
139};
140
141template <class CounterType, class MetadataType, class Mutex>
142class Register;
143
144/**
145 * RAII object to manage automatic deregistration.
146 *
147 * @thread_compatible
148 * @ingroup perfc_register
149 */
151public:
152 ScopedRegistration() noexcept = default;
154 ScopedRegistration& operator=(ScopedRegistration&&) noexcept = default;
155 ~ScopedRegistration() noexcept {
156 Reset();
157 }
158 /**
159 * @name Observers
160 */
161 /// @{
162 /**
163 * @returns true if it still manages a deregistration.
164 */
165 bool IsValid() const noexcept {
166 return m_dereg != nullptr;
167 }
168 /**
169 * @returns true if it still manages a deregistration.
170 */
171 operator bool() const noexcept {
172 return m_dereg != nullptr;
173 }
174 /// @}
175 /**
176 * @name Modifiers
177 */
178 /// @{
179 /**
180 * Release ownership such that destroying object will not deregister counter.
181 *
182 * @post `IsValid() == false`
183 */
184 void Release() noexcept {
185 m_dereg = nullptr;
186 }
187
188 /**
189 * Deregisters counter if object is managing a registration.
190 *
191 * @post `IsValid() == false`
192 */
193 void Reset() noexcept {
194 if (m_dereg) {
195 m_dereg();
196 Release();
197 }
198 }
199 /**
200 * Swaps two entries.
201 *
202 * @param other Exchanges ownership between `*this` and @c other.
203 */
204 void Swap(ScopedRegistration& other) noexcept;
205 /// @}
206protected:
207 /**
208 * @note From C++20 `std::function(function&&)` is noexcept
209 */
210 template <class CounterType, class MetadataType, class Mutex>
211 friend class Register;
212 ScopedRegistration(std::function<void()> dereg) : m_dereg(std::move(dereg)){};
213
214private:
215 std::function<void()> m_dereg = nullptr;
216};
217
218/**
219 * Register of counter variants, used for example to facilitate discovery/monitoring.
220 *
221 * @note A counter is uniquely identified by its address. If a counter is moved it must first be
222 * deregistered and then re-registered afterwards.
223 *
224 * To define a register for `double` and `std::uint64_t` and a `std::string` as metadata:
225 *
226 * @code
227 * #include <string>
228 * #include <perfc/perfc.hpp>
229 *
230 * using Counters = perfc::CounterTypes<perfc::CounterDouble,
231 * perfc::CounterU64>;
232 * using Register = perfc::Register<Counters, std::string>;
233 *
234 * Register register;
235 *
236 * perfc::CounterDouble counter;
237 * ScopedRegistration reg = register.Add(&counter, "metadata for counter");
238 *
239 * @endcode
240 *
241 * @thread_safe{All operations take a mutex lock and are thread safe if Mutex type provides mutual
242 * exclusion (i.e. not a null-opt mutex).
243 * Register::Lock() returns a pointer-like object that holds a lock, providing mutual exclusion.
244 * }
245 *
246 * @headerfile <> <perfc/register.hpp>
247 * @tparam CounterTypes Specifies which counter types can be registered using the helper
248 * perfc::CounterTypes.
249 * @tparam TMetadata Specifies the metadata type used to associate each registered counter with.
250 * Type requirements: *CopyConstructible* and *CopyAssignable*
251 *
252 * @tparam TMutex The mutex type to use, defaults to std::recursive_mutex.
253 *
254 * @ingroup perfc_register
255 *
256 */
257template <class CounterTypes, class TMetadata, class TMutex = std::recursive_mutex>
258class Register {
259public:
260 using Mutex = TMutex;
261 using MetadataType = TMetadata;
263
264 /**
265 * Register entry representing the counter and associated metadata.
266 */
271
272 using Container = std::vector<Entry>;
275
276 /**
277 * @name Access container of counters thread safely
278 * Obtains proxy object to counters that holds lock for synchronized access.
279 *
280 * Prefer a new nested scope to minimize lock duration:
281 * @code
282 * // Minimal scope for Lock()
283 * {
284 * auto counters = register.Lock();
285 * for (auto const& counter : *counters) {
286 * ...
287 * }
288 * }
289 * @endcode
290 * @throws Exception originating from `Mutex::lock()`
291 */
292 /// @{
293 /** @returns locked access to container of counters. */
295 return LockedContainer(m_counters, m_mutex);
296 }
297 /** @returns locked access to *const* container of counters. */
299 return LockedContainerConst(m_counters, m_mutex);
300 }
301 /// @}
302
303 /**
304 * @name Counter registration
305 *
306 * @note Manual de-registration with Register::Remove is not necessary unless ScopedRegistration
307 * is released from managing the registration.
308 */
309 /// @{
310 /**
311 * Add a counter, identified by its *address*, together with metadata to the register.
312 *
313 * @pre counter must point to a valid CounterType object.
314 *
315 * @note The caller must ensure that the @a counter is deregistered before the counter is
316 * deleted. The returned object aims to facilitate this.
317 *
318 * @note May invalidate iterators.
319 *
320 * @param counter Pointer to any of the supported counter types.
321 * @param metadata A metadata object to be associated with @a counter.
322 *
323 * @returns Empty ScopedRegistration if counter already has been registered before.
324 * @returns Non-empty RAII registration management object that automatically deregisters the
325 * counter when destroyed (or manually released).
326 *
327 * @throw std::exception-derived exceptions originating from standard library.
328 */
329 [[nodiscard]] ScopedRegistration Add(CounterVariant counter, MetadataType metadata) {
330 std::unique_lock<Mutex> lk(m_mutex);
331 if (auto it = FindCounter(counter); it != std::end(m_counters)) {
332 return ScopedRegistration();
333 }
334 ScopedRegistration entry([counter, this]() { this->Remove(counter); });
335 m_counters.push_back({std::move(counter), std::move(metadata)});
336 return entry;
337 }
338
339 /**
340 * Remove counter from register.
341 *
342 * @note May invalidate iterators.
343 * @return true if counter was found and erased, false otherwise.
344 *
345 * @throw std::exception-derived exceptions originating from standard library.
346 */
347 bool Remove(CounterVariant counter) {
348 std::unique_lock<Mutex> lk(m_mutex);
349
350 if (auto it = FindCounter(counter); it != std::end(m_counters)) {
351 m_counters.erase(it);
352 return true;
353 }
354 return false;
355 }
356 /// @}
357
358private:
359 typename Container::iterator FindCounter(CounterVariant counter) noexcept {
360 return std::find_if(
361 std::begin(m_counters), std::end(m_counters), [&counter](Entry const& entry) -> bool {
362 return entry.counter == counter;
363 });
364 }
365 mutable Mutex m_mutex;
366 Container m_counters;
367 std::uint64_t m_next_id;
368};
369
370} // namespace perfc
371#endif // #ifndef PERFC_REGISTER_HPP_
Simple type that provide synchronized access by holding a lock to T container.
Definition register.hpp:58
void Unlock() noexcept
Unlocks lock.
Definition register.hpp:129
ReferenceType operator*() const noexcept
Definition register.hpp:105
std::add_lvalue_reference_t< T > ReferenceType
Definition register.hpp:61
std::add_pointer_t< T > PointerType
Definition register.hpp:60
PointerType operator->() const noexcept
Definition register.hpp:111
Locked & operator=(Locked &&other) noexcept
Move constructs by taking owneship of internal state of other.
Definition register.hpp:88
Locked(Locked &&other) noexcept
Move constructs by taking owneship of internal state of other.
Definition register.hpp:78
PointerType Get() const noexcept
Definition register.hpp:99
Locked(ReferenceType t, Mutex &mutex)
Construct by providing reference to object requiring synchronized access and the mutex protecting it.
Definition register.hpp:71
Register of counter variants, used for example to facilitate discovery/monitoring.
Definition register.hpp:258
Locked< Container const, Mutex > LockedContainerConst
Definition register.hpp:274
LockedContainerConst Lock() const
Definition register.hpp:298
typename CounterTypes::CounterVariant CounterVariant
Definition register.hpp:262
ScopedRegistration Add(CounterVariant counter, MetadataType metadata)
Add a counter, identified by its address, together with metadata to the register.
Definition register.hpp:329
bool Remove(CounterVariant counter)
Remove counter from register.
Definition register.hpp:347
Locked< Container, Mutex > LockedContainer
Definition register.hpp:273
std::vector< Entry > Container
Definition register.hpp:272
TMetadata MetadataType
Definition register.hpp:261
LockedContainer Lock()
Definition register.hpp:294
RAII object to manage automatic deregistration.
Definition register.hpp:150
ScopedRegistration(std::function< void()> dereg)
Definition register.hpp:212
bool IsValid() const noexcept
Definition register.hpp:165
void Reset() noexcept
Deregisters counter if object is managing a registration.
Definition register.hpp:193
ScopedRegistration() noexcept=default
void Swap(ScopedRegistration &other) noexcept
Swaps two entries.
void Release() noexcept
Release ownership such that destroying object will not deregister counter.
Definition register.hpp:184
Helper used when declaring counter types in perfc::Register.
Definition register.hpp:47
std::variant< std::add_pointer_t< CounterType >... > CounterVariant
Definition register.hpp:48
Register entry representing the counter and associated metadata.
Definition register.hpp:267
CounterVariant counter
Definition register.hpp:268
MetadataType metadata
Definition register.hpp:269