Lightweight 0.20260921.0
Loading...
Searching...
No Matches
SqlStatistics.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5// Built-in statistics collection. Part of the Lightweight public API: any consumer (tests, tools,
6// examples, downstream apps) inherits the `LIGHTWEIGHT_STATS_*` macros.
7//
8// The collector is always compiled in, and is OFF by default at runtime. Call
9// `SqlStatistics::Enable()` / `SqlStatistics::Disable()` to toggle collection while the process is
10// running — e.g. flip it on for a diagnostic window, then off again — without a recompile or
11// restart. Toggling never clears counters; call `Reset()` explicitly for that.
12//
13// When Tracy is *also* enabled (`-DLIGHTWEIGHT_ENABLE_TRACY=ON`), every recorded sample is
14// additionally emitted as a Tracy plot value whenever collection is runtime-enabled, so the same
15// instrumentation feeds both the in-process `Snapshot()` API and the Tracy GUI. Statistics never
16// depends on Tracy: the collector works identically, Tracy present or not.
17//
18// @see docs/statistics.md
19
20#include "Api.hpp"
21
22#include <array>
23#include <atomic>
24#include <bit>
25#include <chrono>
26#include <cstddef>
27#include <cstdint>
28#include <exception>
29#include <limits>
30#include <string_view>
31
32namespace Lightweight
33{
34
35/// @ingroup CoreApi
36/// Identifies the operation class a statistics sample belongs to.
37///
38/// The enumerators are contiguous and `Count` is the array extent used by
39/// @ref SqlStatisticsSnapshot — this is the data-driven table key, not a
40/// switch-ladder discriminator.
41enum class SqlStatisticsOperation : std::uint8_t
42{
43 /// `SQLExecute` of a prepared statement.
44 Execute,
45 /// `SQLExecDirect` of a one-shot statement.
47 /// `SQLExecute` with a bound parameter array (batch insert/update).
49 /// `SQLPrepare` of a statement.
50 Prepare,
51 /// Row / block retrieval (`SQLFetch`, `SQLFetchScroll`).
52 ///
53 /// @note The library does not currently time its fetch paths, so this slot reads back zero unless
54 /// your own code records into it. Row throughput is reported instead by
55 /// @ref SqlStatisticsSnapshot::rowsFetched and @ref SqlStatisticsSnapshot::blockFetches.
56 Fetch,
57 /// Connection acquisition from a @ref Pool.
59
60 /// Number of enumerators; not an operation itself.
61 Count
62};
63
64/// @ingroup CoreApi
65/// Human-readable name of an operation class, for exporters and log output.
66///
67/// @param operation The operation to name.
68/// @return A stable, dot-free identifier (e.g. `"Execute"`), or `"Unknown"` for an out-of-range value.
69[[nodiscard]] LIGHTWEIGHT_API std::string_view ToStringView(SqlStatisticsOperation operation) noexcept;
70
71/// @ingroup CoreApi
72/// A fixed-bucket latency histogram, in microseconds.
73///
74/// Buckets are power-of-two spaced: bucket `i` counts samples in
75/// `[2^(i-1), 2^i)` microseconds, with bucket 0 counting `[0, 1)` and the last
76/// bucket acting as an open-ended overflow. That covers sub-microsecond calls
77/// up to ~35 minutes in @ref BucketCount buckets, and makes bucket selection a
78/// single `std::bit_width` rather than a search — cheap enough to sit on the
79/// hot path.
80///
81/// This is the plain-struct snapshot type. It is a value: copy it, export it,
82/// diff two of them. The live counters are held separately by @ref SqlStatistics.
84{
85 /// Number of histogram buckets.
86 static constexpr std::size_t BucketCount = 32;
87
88 /// Per-bucket sample counts; bucket `i` covers `[2^(i-1), 2^i)` microseconds.
89 std::array<std::uint64_t, BucketCount> buckets {};
90
91 /// Total number of samples recorded.
92 std::uint64_t count {};
93
94 /// Sum of all sample values, in microseconds.
95 std::uint64_t totalMicroseconds {};
96
97 /// Smallest sample recorded, in microseconds; 0 when @ref count is 0.
98 std::uint64_t minMicroseconds {};
99
100 /// Largest sample recorded, in microseconds; 0 when @ref count is 0.
101 std::uint64_t maxMicroseconds {};
102
103 /// Retrieves the arithmetic mean sample value.
104 ///
105 /// @return The mean, in microseconds; 0.0 when no samples were recorded.
106 [[nodiscard]] LIGHTWEIGHT_API double AverageMicroseconds() const noexcept;
107
108 /// Estimates a percentile from the bucket counts.
109 ///
110 /// The result is the upper bound of the bucket the percentile falls into,
111 /// so it is an over-estimate bounded by a factor of two — the standard
112 /// trade-off for a power-of-two histogram, and accurate enough to spot a
113 /// regression without storing every sample.
114 ///
115 /// @param percentile The percentile to estimate, in `[0.0, 1.0]` (e.g. 0.99 for p99).
116 /// @return The estimated latency, in microseconds; 0 when no samples were recorded.
117 [[nodiscard]] LIGHTWEIGHT_API std::uint64_t PercentileMicroseconds(double percentile) const noexcept;
118
119 /// Retrieves the index of the bucket a given sample value falls into.
120 ///
121 /// @param microseconds The sample value.
122 /// @return The bucket index, clamped to `BucketCount - 1`.
123 [[nodiscard]] static constexpr std::size_t BucketOf(std::uint64_t microseconds) noexcept
124 {
125 // bit_width(0) == 0 -> bucket 0; bit_width(1) == 1 -> bucket 1 ([1,2)); etc.
126 auto const width = static_cast<std::size_t>(std::bit_width(microseconds));
127 return width < BucketCount ? width : BucketCount - 1;
128 }
129};
130
131/// @ingroup CoreApi
132/// Aggregated counters and latency for one @ref SqlStatisticsOperation class.
134{
135 /// Number of operations that completed without raising an ODBC error.
136 std::uint64_t succeeded {};
137
138 /// Number of operations that raised an ODBC error.
139 std::uint64_t failed {};
140
141 /// Number of operations that were transparently retried (e.g. a stale prepared statement
142 /// re-prepared and re-executed). A retried operation is counted once here *and* once in
143 /// @ref succeeded or @ref failed according to its final outcome.
144 std::uint64_t retried {};
145
146 /// Latency distribution across both successful and failed operations.
148
149 /// Retrieves the total number of operations recorded.
150 ///
151 /// @return `succeeded + failed`.
152 [[nodiscard]] constexpr std::uint64_t Total() const noexcept
153 {
154 return succeeded + failed;
155 }
156};
157
158/// @ingroup CoreApi
159/// Aggregated connection-pool counters.
160///
161/// Because `Pool` is a class template keyed on a compile-time configuration, a process typically holds
162/// several *distinct* pool types. They all record into the process-wide @ref SqlStatistics::Instance —
163/// a pool cannot be pointed at a collector of its own — so these counters give a combined view across
164/// every pool in the process. The monotonic counters (@ref acquired, @ref reused, @ref waited,
165/// @ref released, @ref discarded) aggregate cleanly; @ref idle and @ref checkedOut are last-writer-wins
166/// and read as "the most recent pool transition" once more than one pool is in play.
168{
169 /// Number of connections handed out (from idle, freshly created, or handed off to a waiter).
170 std::uint64_t acquired {};
171
172 /// Of @ref acquired, how many reused an already-open connection rather than creating one.
173 std::uint64_t reused {};
174
175 /// Of @ref acquired, how many had to block or park because the pool was exhausted.
176 std::uint64_t waited {};
177
178 /// Number of connections returned to the pool.
179 std::uint64_t released {};
180
181 /// Number of returned connections destroyed rather than idled, because the pool was over
182 /// capacity (`GrowthStrategy::BoundedOverflow`).
183 std::uint64_t discarded {};
184
185 /// Connections currently sitting idle in the pool.
186 std::uint64_t idle {};
187
188 /// Connections currently checked out of the pool.
189 ///
190 /// @note Only `GrowthStrategy::BoundedWait` tracks a checked-out count — it is what bounds the
191 /// pool. The non-blocking strategies (`UnboundedGrow`, `BoundedOverflow`) never maintain one, so
192 /// this reads 0 for them; use @ref acquired minus @ref released there instead.
193 std::uint64_t checkedOut {};
194
195 /// Distribution of the time spent waiting for a connection to become available. Only
196 /// acquisitions that actually waited contribute a sample.
198
199 /// Retrieves the fraction of acquisitions served by an already-open connection.
200 ///
201 /// @return The reuse rate in `[0.0, 1.0]`; 0.0 when nothing was acquired yet.
202 [[nodiscard]] LIGHTWEIGHT_API double ReuseRate() const noexcept;
203};
204
205/// @ingroup CoreApi
206/// An immutable, plain-struct view of everything a @ref SqlStatistics collector has observed.
207///
208/// Deliberately free of any exporter-specific concept: read the fields and feed them to Prometheus,
209/// StatsD, a log line, or a test assertion. Obtain one via @ref SqlStatistics::Snapshot.
210///
211/// @note The snapshot is *not* atomic as a whole. Individual counters are read with relaxed
212/// ordering while other threads may still be recording, so two related counters can disagree by a
213/// few samples at the edges. This is intentional: a consistent snapshot would require locking the
214/// hot path. Treat the numbers as monotonically-growing observations, not as a transaction.
216{
217 /// Per-operation-class counters, indexed by @ref SqlStatisticsOperation.
218 std::array<SqlOperationStatistics, static_cast<std::size_t>(SqlStatisticsOperation::Count)> operations {};
219
220 /// Connection-pool counters.
222
223 /// Number of connections opened.
224 std::uint64_t connectionsOpened {};
225
226 /// Number of connections closed.
227 std::uint64_t connectionsClosed {};
228
229 /// Total number of rows fetched across all statements.
230 std::uint64_t rowsFetched {};
231
232 /// Number of block-prefetch round-trips (one per `SQLFetchScroll` that materialized a row block).
233 std::uint64_t blockFetches {};
234
235 /// Retrieves the counters for one operation class.
236 ///
237 /// @param operation The operation class to read.
238 /// @return A reference to the counters for @p operation.
239 [[nodiscard]] constexpr SqlOperationStatistics const& operator[](SqlStatisticsOperation operation) const noexcept
240 {
241 return operations[static_cast<std::size_t>(operation)];
242 }
243};
244
245/// @ingroup CoreApi
246/// Thread-safe, lock-free aggregator of SQL execution and pool statistics.
247///
248/// The collector is always compiled in, but only *populated* while collection is runtime-enabled
249/// (see @ref Enable, @ref Disable, @ref IsEnabled) — OFF by default. While disabled, every recording
250/// method is still callable but does nothing, so downstream code never needs to guard a call site.
251/// Toggling is orthogonal to @ref Reset : disabling and re-enabling preserves whatever was already
252/// collected.
253///
254/// All recording methods use relaxed atomics: they never block, never allocate, and are safe to
255/// call from any thread. See @ref SqlStatisticsSnapshot for the consistency caveat that buys.
256///
257/// @code
258/// Lightweight::SqlStatistics::Enable();
259/// // ... run some workload ...
260/// auto const stats = Lightweight::SqlStatistics::Instance().Snapshot();
261/// std::println("executes: {}, p99: {}us",
262/// stats[Lightweight::SqlStatisticsOperation::Execute].Total(),
263/// stats[Lightweight::SqlStatisticsOperation::Execute].latency.PercentileMicroseconds(0.99));
264/// @endcode
265///
266/// @see SqlStatisticsSnapshot, docs/statistics.md
268{
269 public:
270 /// Constructs a collector with every counter at zero.
271 SqlStatistics() noexcept = default;
272 ~SqlStatistics() = default;
273
274 SqlStatistics(SqlStatistics const&) = delete;
275 SqlStatistics& operator=(SqlStatistics const&) = delete;
276 SqlStatistics(SqlStatistics&&) = delete;
277 SqlStatistics& operator=(SqlStatistics&&) = delete;
278
279 /// Retrieves the process-wide collector that the library's own instrumentation records into.
280 ///
281 /// @return A reference to the singleton collector.
282 [[nodiscard]] LIGHTWEIGHT_API static SqlStatistics& Instance() noexcept;
283
284 /// Records one completed operation.
285 ///
286 /// @param operation The operation class.
287 /// @param duration How long the operation took.
288 /// @param failed Whether the operation raised an ODBC error.
289 LIGHTWEIGHT_API void RecordOperation(SqlStatisticsOperation operation,
290 std::chrono::microseconds duration,
291 bool failed) noexcept;
292
293 /// Records that an operation was transparently retried.
294 ///
295 /// @param operation The operation class being retried.
296 LIGHTWEIGHT_API void RecordRetry(SqlStatisticsOperation operation) noexcept;
297
298 /// Records rows produced by a fetch.
299 ///
300 /// @param rowCount Number of rows materialized.
301 /// @param wasBlockFetch Whether the rows came from a single block-prefetch round-trip.
302 LIGHTWEIGHT_API void RecordRowsFetched(std::uint64_t rowCount, bool wasBlockFetch) noexcept;
303
304 /// Records a connection being opened.
305 LIGHTWEIGHT_API void RecordConnectionOpened() noexcept;
306
307 /// Records a connection being closed.
308 LIGHTWEIGHT_API void RecordConnectionClosed() noexcept;
309
310 /// Records a connection acquisition from a pool.
311 ///
312 /// @param waitDuration Time spent waiting for the connection; zero when it was available immediately.
313 /// @param reused Whether an already-open connection was handed out rather than a fresh one created.
314 /// @param waited Whether the caller actually had to block or park.
315 LIGHTWEIGHT_API void RecordPoolAcquire(std::chrono::microseconds waitDuration, bool reused, bool waited) noexcept;
316
317 /// Records a connection being returned to a pool.
318 ///
319 /// @param discarded Whether the connection was destroyed instead of idled (pool over capacity).
320 LIGHTWEIGHT_API void RecordPoolRelease(bool discarded) noexcept;
321
322 /// Records the pool's current occupancy. Called whenever the pool's composition changes.
323 ///
324 /// @param idle Connections currently idle.
325 /// @param checkedOut Connections currently checked out.
326 LIGHTWEIGHT_API void RecordPoolOccupancy(std::uint64_t idle, std::uint64_t checkedOut) noexcept;
327
328 /// Retrieves a point-in-time copy of every counter.
329 ///
330 /// @return The snapshot; all-zero while collection has never been enabled (see @ref Enable).
331 [[nodiscard]] LIGHTWEIGHT_API SqlStatisticsSnapshot Snapshot() const noexcept;
332
333 /// Resets every counter back to zero. Intended for tests and for exporters that report deltas.
334 /// Orthogonal to @ref Enable / @ref Disable : resetting does not change whether collection runs.
335 LIGHTWEIGHT_API void Reset() noexcept;
336
337 /// Turns statistics collection on. Safe to call from any thread, at any point in the process
338 /// lifetime; takes effect for every subsequent recording call. Counters already collected are
339 /// left untouched.
340 LIGHTWEIGHT_API static void Enable() noexcept;
341
342 /// Turns statistics collection off. Recording methods remain callable but become no-ops;
343 /// counters already collected are left untouched and continue to read back from @ref Snapshot.
344 LIGHTWEIGHT_API static void Disable() noexcept;
345
346 /// Indicates whether statistics collection is currently turned on.
347 ///
348 /// @return `true` after @ref Enable (and before a subsequent @ref Disable); `false` otherwise,
349 /// including at process start.
350 [[nodiscard]] LIGHTWEIGHT_API static bool IsEnabled() noexcept;
351
352 private:
353 /// Lock-free mirror of @ref SqlLatencyHistogram holding the live counters.
354 struct AtomicHistogram
355 {
356 std::array<std::atomic<std::uint64_t>, SqlLatencyHistogram::BucketCount> buckets {};
357 std::atomic<std::uint64_t> count {};
358 std::atomic<std::uint64_t> totalMicroseconds {};
359 std::atomic<std::uint64_t> minMicroseconds { (std::numeric_limits<std::uint64_t>::max)() };
360 std::atomic<std::uint64_t> maxMicroseconds {};
361
362 void Record(std::uint64_t microseconds) noexcept;
363 [[nodiscard]] SqlLatencyHistogram Load() const noexcept;
364 void Reset() noexcept;
365 };
366
367 /// Lock-free mirror of @ref SqlOperationStatistics.
368 struct AtomicOperation
369 {
370 std::atomic<std::uint64_t> succeeded {};
371 std::atomic<std::uint64_t> failed {};
372 std::atomic<std::uint64_t> retried {};
373 AtomicHistogram latency {};
374 };
375
376 std::array<AtomicOperation, static_cast<std::size_t>(SqlStatisticsOperation::Count)> _operations {};
377
378 std::atomic<std::uint64_t> _poolAcquired {};
379 std::atomic<std::uint64_t> _poolReused {};
380 std::atomic<std::uint64_t> _poolWaited {};
381 std::atomic<std::uint64_t> _poolReleased {};
382 std::atomic<std::uint64_t> _poolDiscarded {};
383 std::atomic<std::uint64_t> _poolIdle {};
384 std::atomic<std::uint64_t> _poolCheckedOut {};
385 AtomicHistogram _poolWaitLatency {};
386
387 std::atomic<std::uint64_t> _connectionsOpened {};
388 std::atomic<std::uint64_t> _connectionsClosed {};
389 std::atomic<std::uint64_t> _rowsFetched {};
390 std::atomic<std::uint64_t> _blockFetches {};
391
392 /// The runtime enable/disable flag. Process-wide by design — @ref Enable, @ref Disable, and
393 /// @ref IsEnabled are all static, so this lives independently of @ref Instance rather than as an
394 /// instance member.
395 ///
396 /// @return A reference to the flag, function-local `static` to sidestep static-init-order issues.
397 [[nodiscard]] static std::atomic<bool>& EnabledFlag() noexcept;
398};
399
400/// @ingroup CoreApi
401/// RAII scope that times its enclosing region and records it as one operation.
402///
403/// Construct at the top of the region; the destructor records the elapsed time. Call @ref Failed to
404/// mark the operation as errored (the destructor still records the latency, so a failing statement
405/// contributes to the distribution rather than silently vanishing from it).
406///
407/// Prefer the @c LIGHTWEIGHT_STATS_SCOPE macro over constructing this directly — it keeps
408/// instrumentation call sites uniform, and its destructor's `RecordOperation` call is itself a no-op
409/// whenever collection is runtime-disabled.
411{
412 public:
413 /// Starts timing a region.
414 ///
415 /// @param operation The operation class to record under.
416 explicit SqlStatisticsScope(SqlStatisticsOperation operation) noexcept:
417 _operation { operation },
418 _startedAt { std::chrono::steady_clock::now() },
419 _uncaughtOnEntry { std::uncaught_exceptions() }
420 {
421 }
422
423 SqlStatisticsScope(SqlStatisticsScope const&) = delete;
424 SqlStatisticsScope& operator=(SqlStatisticsScope const&) = delete;
425 SqlStatisticsScope(SqlStatisticsScope&&) = delete;
426 SqlStatisticsScope& operator=(SqlStatisticsScope&&) = delete;
427
428 ~SqlStatisticsScope()
429 {
430 auto const elapsed =
431 std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::steady_clock::now() - _startedAt);
432 // Leaving via an exception *is* the failure signal on this code base: RequireSuccess throws
433 // SqlException on a non-successful ODBC return. Comparing the in-flight exception count
434 // rather than a flag means every throwing path is classified without touching its call site.
435 auto const failed = _failed || std::uncaught_exceptions() > _uncaughtOnEntry;
436 SqlStatistics::Instance().RecordOperation(_operation, elapsed, failed);
437 }
438
439 /// Marks the timed operation as having failed.
440 void Failed() noexcept
441 {
442 _failed = true;
443 }
444
445 /// Records that the timed operation was transparently retried.
446 void Retried() noexcept
447 {
448 SqlStatistics::Instance().RecordRetry(_operation);
449 }
450
451 private:
452 SqlStatisticsOperation _operation;
453 std::chrono::steady_clock::time_point _startedAt;
454 int _uncaughtOnEntry;
455 bool _failed = false;
456};
457
458} // namespace Lightweight
459
460// {{{ Instrumentation macros
461//
462// Collection is a runtime toggle now (SqlStatistics::Enable/Disable), not a compile-time one, so
463// these always expand to the real call — there is no disabled-build variant to fall back to. Each
464// recording method itself checks the runtime flag first and does nothing when collection is off, so
465// a call site pays one relaxed atomic load, never more, when statistics are disabled at runtime.
466//
467// LIGHTWEIGHT_STATS_SCOPE is the one exception: its SqlStatisticsScope object still pays a
468// steady_clock::now() and an uncaught_exceptions() call at construction, unconditionally, because the
469// constructor cannot know whether collection will still be enabled by the time the destructor runs —
470// the flag check happens once, in the destructor's RecordOperation call.
471//
472// The macros remain so call sites read the same either way and so a future instrumentation change
473// only touches this header.
474
475/// Times the enclosing scope and records it under @p op, naming the scope object @p var.
476#define LIGHTWEIGHT_STATS_SCOPE_V(var, op) \
477 ::Lightweight::SqlStatisticsScope var \
478 { \
479 op \
480 }
481
482/// Times the enclosing scope and records it under @p op.
483#define LIGHTWEIGHT_STATS_SCOPE(op) LIGHTWEIGHT_STATS_SCOPE_V(lightweightStatsScope_, op)
484
485/// Marks the scope named @p var as failed.
486#define LIGHTWEIGHT_STATS_FAILED(var) (var).Failed()
487
488/// Marks the scope named @p var as retried.
489#define LIGHTWEIGHT_STATS_RETRIED(var) (var).Retried()
490
491/// Records @p rows fetched; @p isBlock tells whether they came from one block round-trip.
492#define LIGHTWEIGHT_STATS_ROWS(rows, isBlock) ::Lightweight::SqlStatistics::Instance().RecordRowsFetched((rows), (isBlock))
493
494/// Records a connection being opened.
495#define LIGHTWEIGHT_STATS_CONNECTION_OPENED() ::Lightweight::SqlStatistics::Instance().RecordConnectionOpened()
496
497/// Records a connection being closed.
498#define LIGHTWEIGHT_STATS_CONNECTION_CLOSED() ::Lightweight::SqlStatistics::Instance().RecordConnectionClosed()
499
500/// Records a pool acquisition: @p wait duration, whether it @p reused, whether it @p waited.
501#define LIGHTWEIGHT_STATS_POOL_ACQUIRE(wait, reused, waited) \
502 ::Lightweight::SqlStatistics::Instance().RecordPoolAcquire((wait), (reused), (waited))
503
504/// Records a pool release; @p discarded tells whether the connection was destroyed.
505#define LIGHTWEIGHT_STATS_POOL_RELEASE(discarded) ::Lightweight::SqlStatistics::Instance().RecordPoolRelease((discarded))
506
507/// Records current pool occupancy.
508#define LIGHTWEIGHT_STATS_POOL_OCCUPANCY(idle, checkedOut) \
509 ::Lightweight::SqlStatistics::Instance().RecordPoolOccupancy((idle), (checkedOut))
510// }}}
SqlStatisticsScope(SqlStatisticsOperation operation) noexcept
void Retried() noexcept
Records that the timed operation was transparently retried.
void Failed() noexcept
Marks the timed operation as having failed.
SqlStatistics() noexcept=default
Constructs a collector with every counter at zero.
LIGHTWEIGHT_API std::string_view ToStringView(SqlStatisticsOperation operation) noexcept
@ PoolAcquire
Connection acquisition from a Pool.
@ ExecuteBatch
SQLExecute with a bound parameter array (batch insert/update).
@ Execute
SQLExecute of a prepared statement.
@ ExecuteDirect
SQLExecDirect of a one-shot statement.
@ Count
Number of enumerators; not an operation itself.
@ Prepare
SQLPrepare of a statement.
LIGHTWEIGHT_API std::uint64_t PercentileMicroseconds(double percentile) const noexcept
std::uint64_t totalMicroseconds
Sum of all sample values, in microseconds.
std::array< std::uint64_t, BucketCount > buckets
Per-bucket sample counts; bucket i covers [2^(i-1), 2^i) microseconds.
std::uint64_t minMicroseconds
Smallest sample recorded, in microseconds; 0 when count is 0.
std::uint64_t maxMicroseconds
Largest sample recorded, in microseconds; 0 when count is 0.
static constexpr std::size_t BucketCount
Number of histogram buckets.
LIGHTWEIGHT_API double AverageMicroseconds() const noexcept
static constexpr std::size_t BucketOf(std::uint64_t microseconds) noexcept
std::uint64_t count
Total number of samples recorded.
std::uint64_t succeeded
Number of operations that completed without raising an ODBC error.
constexpr std::uint64_t Total() const noexcept
std::uint64_t failed
Number of operations that raised an ODBC error.
SqlLatencyHistogram latency
Latency distribution across both successful and failed operations.
std::uint64_t idle
Connections currently sitting idle in the pool.
std::uint64_t waited
Of acquired, how many had to block or park because the pool was exhausted.
LIGHTWEIGHT_API double ReuseRate() const noexcept
std::uint64_t released
Number of connections returned to the pool.
std::uint64_t reused
Of acquired, how many reused an already-open connection rather than creating one.
std::uint64_t acquired
Number of connections handed out (from idle, freshly created, or handed off to a waiter).
constexpr SqlOperationStatistics const & operator[](SqlStatisticsOperation operation) const noexcept