Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,12 @@ BENCHMARK(BM_SetInsert_With_Timer_Control)->Ranges({{1<<10, 8<<10}, {128, 512}})
```
<!-- {% endraw %} -->

Both calls are only valid inside the benchmark loop. Calling them outside it
stops a timer that was never started, which adds an absolute clock reading to
the reported time instead of a duration. This is asserted, and the assertion is
compiled into the benchmark rather than into the library, so it fires whenever
the benchmark itself is built without `NDEBUG`.

For convenience, a `ScopedPauseTiming` class is provided to manage pausing and
resuming timers within a scope. This is less error-prone than manually calling
`PauseTiming` and `ResumeTiming`.
Expand Down
20 changes: 18 additions & 2 deletions include/benchmark/state.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,19 @@ class BENCHMARK_EXPORT BENCHMARK_INTERNAL_CACHELINE_ALIGNED State {

inline bool KeepRunningBatch(IterationCount n);

void PauseTiming();
// Only valid while the benchmark loop is running.
void PauseTiming() {
assert(started_ && !finished_ && !skipped() &&
"PauseTiming() called outside of the benchmark loop");
PauseTimingImpl();
}

void ResumeTiming();
// Only valid while the benchmark loop is running.
void ResumeTiming() {
assert(started_ && !finished_ && !skipped() &&
"ResumeTiming() called outside of the benchmark loop");
ResumeTimingImpl();
}

void SkipWithMessage(const std::string& msg);

Expand Down Expand Up @@ -164,6 +174,12 @@ class BENCHMARK_EXPORT BENCHMARK_INTERNAL_CACHELINE_ALIGNED State {
inline bool KeepRunningInternal(IterationCount n, bool is_batch);
void FinishKeepRunning();

// The checked entry points above forward here; keeping the bodies out of
// line keeps the assertions with the caller, whose NDEBUG decides whether
// they are compiled in.
void PauseTimingImpl();
void ResumeTimingImpl();

const std::string name_;
const int thread_index_;
const int threads_;
Expand Down
6 changes: 2 additions & 4 deletions src/benchmark.cc
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,8 @@ State::State(std::string name, IterationCount max_iters,
#endif
}

void State::PauseTiming() {
void State::PauseTimingImpl() {
// Add in time accumulated so far
BM_CHECK(started_ && !finished_ && !skipped());
timer_->StopTimer();
if (perf_counters_measurement_ != nullptr) {
std::vector<std::pair<std::string, double>> measurements;
Expand All @@ -279,8 +278,7 @@ void State::PauseTiming() {
}
}

void State::ResumeTiming() {
BM_CHECK(started_ && !finished_ && !skipped());
void State::ResumeTimingImpl() {
timer_->StartTimer();
if (perf_counters_measurement_ != nullptr) {
perf_counters_measurement_->Start();
Expand Down
3 changes: 1 addition & 2 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,6 @@ benchmark_add_test(NAME basic_benchmark COMMAND basic_test --benchmark_min_time=
compile_output_test(repetitions_test)
benchmark_add_test(NAME repetitions_benchmark COMMAND repetitions_test --benchmark_min_time=0.01s --benchmark_repetitions=3)

compile_benchmark_test(diagnostics_test)
benchmark_add_test(NAME diagnostics_test COMMAND diagnostics_test --benchmark_min_time=0.01s)

compile_benchmark_test(skip_with_error_test)
benchmark_add_test(NAME skip_with_error_test COMMAND skip_with_error_test --benchmark_min_time=0.01s)
Expand Down Expand Up @@ -263,6 +261,7 @@ if (BENCHMARK_ENABLE_GTEST_TESTS)
add_gtest(benchmark_setup_teardown_cb_types_gtest)
add_gtest(memory_results_gtest)
add_gtest(memory_manager_ordering_gtest)
add_gtest(diagnostics_gtest)
endif(BENCHMARK_ENABLE_GTEST_TESTS)

###############################################################################
Expand Down
98 changes: 98 additions & 0 deletions test/diagnostics_gtest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Testing:
// State::PauseTiming()
// State::ResumeTiming()
// Test that the assertions in these functions diagnose calls made outside of
// the benchmark loop, and that a run they do not diagnose still reports a
// sane time.

#include <string>
#include <vector>

#include "benchmark/benchmark.h"
#include "gtest/gtest.h"

namespace {

void BM_pause_before_loop(benchmark::State& state) {
state.PauseTiming();
for (auto _ : state) {
}
}
BENCHMARK(BM_pause_before_loop)->Iterations(1);

void BM_resume_before_loop(benchmark::State& state) {
state.ResumeTiming();
for (auto _ : state) {
}
}
BENCHMARK(BM_resume_before_loop)->Iterations(1);

void BM_pause_after_loop(benchmark::State& state) {
for (auto _ : state) {
}
state.PauseTiming();
}
BENCHMARK(BM_pause_after_loop)->Iterations(1);

void BM_resume_after_loop(benchmark::State& state) {
for (auto _ : state) {
}
state.ResumeTiming();
}
BENCHMARK(BM_resume_after_loop)->Iterations(1);

void BM_pause_and_resume_in_loop(benchmark::State& state) {
for (auto _ : state) {
state.PauseTiming();
state.ResumeTiming();
}
}
BENCHMARK(BM_pause_and_resume_in_loop)->Iterations(1);

class CapturingReporter : public benchmark::BenchmarkReporter {
public:
bool ReportContext(const Context& /*context*/) override { return true; }
void ReportRuns(const std::vector<Run>& runs) override {
runs_.insert(runs_.end(), runs.begin(), runs.end());
}

const std::vector<Run>& runs() const { return runs_; }

private:
std::vector<Run> runs_;
};

std::vector<benchmark::BenchmarkReporter::Run> RunOne(const std::string& name) {
CapturingReporter reporter;
benchmark::RunSpecifiedBenchmarks(&reporter, name);
return reporter.runs();
}

TEST(Diagnostics, PauseOutsideOfTheLoopIsDiagnosed) {
#ifndef NDEBUG
ASSERT_DEATH_IF_SUPPORTED(RunOne("BM_pause_before_loop"), "PauseTiming");
ASSERT_DEATH_IF_SUPPORTED(RunOne("BM_pause_after_loop"), "PauseTiming");
#endif
}

TEST(Diagnostics, ResumeOutsideOfTheLoopIsDiagnosed) {
#ifndef NDEBUG
ASSERT_DEATH_IF_SUPPORTED(RunOne("BM_resume_before_loop"), "ResumeTiming");
ASSERT_DEATH_IF_SUPPORTED(RunOne("BM_resume_after_loop"), "ResumeTiming");
#endif
}

TEST(Diagnostics, PauseAndResumeInsideTheLoopReportASaneTime) {
const std::vector<benchmark::BenchmarkReporter::Run> runs =
RunOne("BM_pause_and_resume_in_loop");
ASSERT_EQ(runs.size(), 1u);
EXPECT_EQ(runs[0].skipped, 0u);
// One iteration of an empty loop. A whole second would mean an absolute
// clock reading was accumulated instead of a duration.
EXPECT_GE(runs[0].real_accumulated_time, 0.0);
EXPECT_LT(runs[0].real_accumulated_time, 1.0);
EXPECT_GE(runs[0].cpu_accumulated_time, 0.0);
EXPECT_LT(runs[0].cpu_accumulated_time, 1.0);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about pause of paused, resume of resumed, pause of skipped, resume of skipped, etc?

@LebedevRI LebedevRI Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And note that skipping can happen before/during/after the loop, and time may be either running or paused, so some more variations to test.
Oh, and we should actually test that the timings reported aren't absurd in the cases that aren't diagnosed.

} // namespace
107 changes: 0 additions & 107 deletions test/diagnostics_test.cc

This file was deleted.