Using spdlog Stopwatch for Timing Measurements: A Complete Guide

TLDR: spdlog provides a lightweight, header-only spdlog::stopwatch class that captures timing via std::chrono::steady_clock and formats elapsed seconds directly into log messages through built-in fmt integration.

The gabime/spdlog repository includes a purpose-built stopwatch utility designed for zero-overhead timing instrumentation. Using spdlog stopwatch for timing measurements requires no additional linking, as the entire implementation resides in include/spdlog/stopwatch.h. This tool integrates seamlessly with the library's formatting engine, allowing you to stream elapsed time directly into log records without manual string conversion.

How spdlog::stopwatch Works Internally

Monotonic Clock Selection

According to the source code in include/spdlog/stopwatch.h (line 32), the stopwatch uses std::chrono::steady_clock as its underlying time source. This clock is monotonic, meaning it always moves forward and is not subject to system-time adjustments or daylight saving changes, guaranteeing reliable interval measurement even if the system clock is modified during execution.

State Management and Construction

The class stores a single private member start_tp_ representing the start time point. The constructor (lines 36-38) captures the current time via std::chrono::steady_clock::now(), establishing the baseline for all subsequent elapsed calculations.

Elapsed Time Accessors

The stopwatch provides two primary methods for retrieving elapsed duration:

  • elapsed() (lines 39-41): Returns a std::chrono::duration<double> representing the elapsed time in seconds as a floating-point value.
  • elapsed_ms() (lines 43-45): Returns a std::chrono::milliseconds value via duration_cast, useful when you need whole-millisecond precision without floating-point overhead.

Reset Capability

To begin a new measurement without destroying the object, the reset() method (line 47) re-captures the current time into start_tp_, effectively zeroing the timer while preserving the object instance.

Automatic Formatter Integration

The stopwatch leverages a template specialization of fmt::formatter (or std::formatter when SPDLOG_USE_STD_FORMAT is defined) to enable direct insertion into log macros. This formatter specialization (lines 60-66) forwards the stopwatch's elapsed seconds to the underlying double formatter. Consequently, you can pass the stopwatch object directly to any spdlog function:

spdlog::stopwatch sw;
// ... perform work ...
spdlog::info("Operation completed in {}", sw);

The output automatically renders as a floating-point number of seconds (e.g., 0.005116733), respecting any format specifiers you provide (such as {:.6} for six-digit precision).

Practical Code Examples

Basic Usage: Logging Elapsed Seconds

Instantiate the stopwatch at the scope entry point and log it when work completes. The object formats itself as seconds by default:

#include <spdlog/spdlog.h>
#include <spdlog/stopwatch.h>

void process_data() {
    spdlog::stopwatch sw;  // starts timing immediately
    perform_computation();
    spdlog::debug("Processing took {} seconds", sw);
}

Example output: Processing took 0.005116733 seconds

Custom Precision Formatting

Control the floating-point precision using standard fmt format specifiers:

spdlog::stopwatch sw;
std::this_thread::sleep_for(std::chrono::milliseconds(12));
spdlog::info("Elapsed: {:.6} seconds", sw);   // limits to six digits

Example output: Elapsed: 0.012345 seconds

Measuring Milliseconds and Other Units

For discrete units like milliseconds, use elapsed_ms() or cast the duration manually. Include <spdlog/fmt/chrono.h> to enable chrono-aware formatting helpers:

#include <spdlog/fmt/chrono.h>   // enables fmt::chrono formatting

spdlog::stopwatch sw;
std::this_thread::sleep_for(std::chrono::milliseconds(150));
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(sw.elapsed());
spdlog::info("Elapsed: {} ms", ms);

Example output: Elapsed: 150 ms

The comment block at lines 21-28 of stopwatch.h explicitly documents this pattern for users requiring unit flexibility beyond the default seconds formatting.

Resetting for Multiple Intervals

Measure distinct phases of execution by resetting the stopwatch between operations:

spdlog::stopwatch sw;

phase_one();
spdlog::info("Phase 1 duration: {}", sw);

sw.reset();  // restart timer from now (line 47)

phase_two();
spdlog::info("Phase 2 duration: {}", sw);

Integration with Custom Loggers

The stopwatch works with any spdlog sink or logger instance, not just the default registry:

auto sink = std::make_shared<spdlog::sinks::test_sink_st>();
spdlog::logger logger("perf_logger", sink);
logger.set_pattern("%v");   // output value only

spdlog::stopwatch sw;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
logger.info("{}", sw);      // formats automatically via formatter specialization

Advanced Formatting with spdlog/fmt/chrono.h

While the built-in formatter outputs seconds, the header <spdlog/fmt/chrono.h> provides comprehensive chrono formatting support. This allows you to format raw durations using time-specific specifiers (e.g., %S for seconds, %M for minutes) when you need human-readable timestamps rather than raw floating-point intervals.

Summary

  • spdlog::stopwatch is a header-only utility in include/spdlog/stopwatch.h requiring no additional compilation steps.
  • It uses std::chrono::steady_clock (line 32) to guarantee monotonic, system-adjustment-proof timing.
  • The class provides elapsed() for floating-point seconds and elapsed_ms() for integer milliseconds (lines 39-45).
  • A specialized fmt::formatter (lines 60-66) enables direct insertion into log messages without manual conversion.
  • The reset() method (line 47) allows object reuse across multiple measurement phases.
  • For custom unit display, combine duration_cast with the chrono formatting helpers in <spdlog/fmt/chrono.h>.

Frequently Asked Questions

What clock source does spdlog::stopwatch use?

The stopwatch uses std::chrono::steady_clock as implemented on line 32 of include/spdlog/stopwatch.h. This clock is monotonic and not subject to system time adjustments, ensuring that timing measurements remain accurate even if the system clock is modified during program execution.

How do I reset the stopwatch to measure a new interval?

Call the reset() method (line 47), which re-captures the current time into the internal start_tp_ member. This allows you to reuse the same stopwatch instance for consecutive timing measurements without reconstruction overhead.

Can I display the stopwatch value in milliseconds instead of seconds?

Yes. While the default formatter outputs seconds, you can call elapsed_ms() (lines 43-45) to retrieve a std::chrono::milliseconds value, or manually cast the result of elapsed() using std::chrono::duration_cast. Include <spdlog/fmt/chrono.h> to format these duration types directly in log messages.

Is spdlog::stopwatch thread-safe?

The stopwatch itself is a lightweight value type storing only a single time point. Reading elapsed time via elapsed() or elapsed_ms() is safe from multiple threads concurrently, but calling reset() (which mutates the internal state) requires external synchronization if the same instance is shared across threads. For per-thread timing, simply create a local stopwatch instance on the stack.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →