65 return count == 0 ? Duration::zero() :
total /
static_cast<Duration::rep
>(
count);
87 auto& statistics = values_[event_index(event)];
88 if (statistics.count == 0)
90 statistics.minimum = duration;
91 statistics.maximum = duration;
95 statistics.minimum = std::min(statistics.minimum, duration);
96 statistics.maximum = std::max(statistics.maximum, duration);
99 statistics.total += duration;
108 return values_[event_index(event)];
112 constexpr void reset() noexcept { values_ = {}; }
115 [[nodiscard]]
static constexpr auto event_index(Event event)
noexcept -> std::size_t
117 return static_cast<std::size_t
>(event);
120 std::array<DurationStatistics, EventCount> values_{};
156template <
class Event, std::
size_t EventCount>
167 batches_[
static_cast<std::size_t
>(event)].push_back(std::move(measurement));
174 [[nodiscard]]
auto batches(Event event)
const noexcept -> std::span<BatchMeasurement const>
176 return batches_[
static_cast<std::size_t
>(event)];
187 std::array<std::vector<BatchMeasurement>, EventCount> batches_{};
191template <
class Measurements>
195template <
class Measurements,
class Event>
198 requires(Measurements& measurements, Event event,
Duration duration) {
199 { measurements.record_duration(event, duration) }
noexcept -> std::same_as<void>;
203template <
class Measurements,
class Event>
206 requires(Measurements& measurements, Event event, BatchMeasurement batch) {
207 { measurements.record_batch(event, std::move(batch)) } -> std::same_as<void>;
213template <
class Measurements,
class Event>
class DurationGuard {
215 DurationGuard(Measurements& measurements, Event event) noexcept
216 : measurements_(measurements), event_(event), start_(WallClock::now())
219 DurationGuard(DurationGuard
const&) =
delete;
220 DurationGuard(DurationGuard&&) =
delete;
221 auto operator=(DurationGuard
const&) -> DurationGuard& =
delete;
222 auto operator=(DurationGuard&&) -> DurationGuard& =
delete;
224 ~DurationGuard() noexcept
226 auto const elapsed = std::chrono::duration_cast<Duration>(WallClock::now() - start_);
227 measurements_.record_duration(event_, elapsed);
231 Measurements& measurements_;
233 WallClock::time_point start_;
249template <
class Measurements,
class Event,
class Function>
250 requires DurationMeasurementPolicy<Measurements, Event> && std::invocable<Function>
251decltype(
auto)
measure_duration(Measurements& measurements, Event event, Function&& function)
255 static_cast<void>(measurements);
256 static_cast<void>(event);
257 return std::invoke(std::forward<Function>(function));
261 detail::DurationGuard guard(measurements, event);
262 return std::invoke(std::forward<Function>(function));
269struct BatchItemInterval
271 bool started =
false;
272 bool completed =
false;
273 WallClock::time_point start = {};
274 WallClock::time_point finish = {};
275 std::size_t start_order = 0;
276 std::size_t finish_order = 0;
279inline auto summarize_batch(std::span<BatchItemInterval const> items, WallClock::time_point batch_start,
280 WallClock::time_point batch_finish) -> BatchMeasurement
282 BatchMeasurement result{.requested_items = items.size(),
283 .wall_duration = std::chrono::duration_cast<Duration>(batch_finish - batch_start)};
284 using boundary_type = std::pair<std::size_t, int>;
285 std::vector<boundary_type> boundaries;
286 boundaries.reserve(items.size() * 2);
287 std::optional<WallClock::time_point> first_start;
288 std::optional<WallClock::time_point> last_start;
289 std::optional<WallClock::time_point> first_finish;
290 std::optional<WallClock::time_point> last_finish;
291 for (
auto const& item : items)
293 if (!item.started)
continue;
294 ++result.started_items;
295 first_start = first_start ? std::min(*first_start, item.start) : item.start;
296 last_start = last_start ? std::max(*last_start, item.start) : item.start;
297 if (!item.completed)
continue;
298 ++result.completed_items;
299 first_finish = first_finish ? std::min(*first_finish, item.finish) : item.
finish;
300 last_finish = last_finish ? std::max(*last_finish, item.finish) : item.
finish;
301 auto const duration = std::chrono::duration_cast<Duration>(item.finish - item.start);
302 result.total_item_duration += duration;
303 result.maximum_item_duration = std::max(result.maximum_item_duration, duration);
304 boundaries.emplace_back(item.start_order, 1);
305 boundaries.emplace_back(item.finish_order, -1);
310 result.first_item_start_delay = std::chrono::duration_cast<Duration>(*first_start - batch_start);
311 result.item_start_spread = std::chrono::duration_cast<Duration>(*last_start - *first_start);
315 result.item_finish_spread = std::chrono::duration_cast<Duration>(*last_finish - *first_finish);
316 result.return_after_last_finish = std::chrono::duration_cast<Duration>(batch_finish - *last_finish);
319 std::ranges::sort(boundaries, {}, &boundary_type::first);
321 for (
auto const& [order, delta] : boundaries)
323 static_cast<void>(order);
327 result.peak_concurrency = std::max(result.peak_concurrency, ++active);
352template <
class Measurements,
class Event,
class Executor,
class Function>
353 requires BatchMeasurementPolicy<Measurements, Event> && std::invocable<Function&, std::size_t>
354void measure_batch(Measurements& measurements, Event event, std::size_t size, Executor&& executor, Function&& function)
358 static_cast<void>(measurements);
359 static_cast<void>(event);
360 std::invoke(std::forward<Executor>(executor), function);
364 measure_duration(measurements, event, [&] { std::invoke(std::forward<Executor>(executor), function); });
368 std::vector<detail::BatchItemInterval> items(size);
369 std::atomic_size_t next_boundary_order = 0;
370 auto wrapped = [&](std::size_t index) {
371 auto& item = items[index];
373 item.start_order = next_boundary_order.fetch_add(1, std::memory_order_relaxed);
374 item.start = WallClock::now();
377 std::invoke(function, index);
381 item.finish = WallClock::now();
382 item.finish_order = next_boundary_order.fetch_add(1, std::memory_order_relaxed);
383 item.completed =
true;
386 item.finish = WallClock::now();
387 item.finish_order = next_boundary_order.fetch_add(1, std::memory_order_relaxed);
388 item.completed =
true;
391 auto const start = WallClock::now();
392 std::exception_ptr failure;
395 std::invoke(std::forward<Executor>(executor), wrapped);
399 failure = std::current_exception();
401 auto const finish = WallClock::now();
402 auto batch = detail::summarize_batch(items, start,
finish);
403 measurements.record_duration(event, batch.wall_duration);
404 measurements.record_batch(event, std::move(batch));
405 if (failure) std::rethrow_exception(failure);