Understanding the Performance Impact of move_events() in tui-logger
The move_events() function in tui-logger incurs a linear O(N) performance cost relative to the number of pending log events, but executes in a dedicated background thread every 10ms or when the hot buffer reaches 50% capacity, ensuring zero blocking of the main logging path.
In the gin66/tui-logger crate, move_events() serves as the critical bridge between the high-performance hot buffer and the long-term main storage. This function determines how quickly log records transition from temporary memory to persistent circular buffers, directly impacting both latency and throughput in terminal user interface applications.
What Does move_events() Do?
move_events() implements the hot-buffer → main-buffer pipeline, transferring pending log records from the temporary hot storage to the permanent circular buffer. According to the implementation in src/logger/inner.rs (lines 128-140), the function performs several distinct operations with varying computational costs.
Step-by-Step Execution Flow
The function executes the following sequence every time it runs:
-
Early-exit check – Verifies if
self.hot_log.lock().eventscontains any pending records. This costs O(1) and returns immediately if empty. -
Buffer swap – Replaces the hot circular buffer with a fresh instance via
CircularBuffer::new(hot_depth). This O(1) swap operation is memory-efficient and atomic. -
Consume and reverse – Drains all pending
ExtLogRecordinstances into a temporaryVec, reversing their order so older records appear first. This step costs O(N) where N equals the number of pending events, requiring allocation of a temporary vector of size N. -
Overrun detection – If event production exceeded hot buffer capacity, generates a synthetic "events lost" record. Cost remains O(1).
-
Target-filter resolution – For each record, performs target lookup in
tli.targets, queries optionalenv_filter, and computesfast_str_hashto cache the resolvedLevelFilterinself.hot_select.lock().hashtable. This costs O(N × Cfilter), where Cfilter represents iterating over fiveLevelFiltervariants when filters are present. -
Optional file dump – Formats and writes records according to
TuiLoggerFileconfiguration. This costs O(N × Cwrite) and depends entirely on I/O subsystem performance. -
Main buffer insertion – Pushes processed records into the long-term circular buffer (
tli.events) at O(N) cost.
Performance Characteristics
Understanding the computational complexity helps predict behavior under load.
Time Complexity and Big-O Analysis
move_events() exhibits linear time complexity O(N) relative to the number of pending events. In steady-state operation where the hot buffer contains only a few dozen entries, execution completes within microseconds. The dominant costs emerge during the consume-reverse phase and target-filter resolution, both scaling proportionally with event volume.
Memory Allocation Patterns
The function allocates a temporary Vec sized to the exact number of pending events during the consume phase. While this prevents unnecessary memory bloat, burst logging patterns trigger corresponding allocation spikes. The buffer swap mechanism (CircularBuffer::new) pre-allocates fresh storage, ensuring subsequent log calls maintain O(1) insertion performance.
Background Thread Isolation
As implemented in src/logger/api.rs (lines 44-50), move_events() executes exclusively within the "tui-logger::move_events" background thread. This architectural choice isolates all O(N) processing costs from the application threads, preventing latency spikes in time-sensitive code paths.
When Is move_events() Triggered?
Three distinct mechanisms invoke move_events():
-
Periodic background execution – The dedicated thread sleeps 10ms between iterations, creating a default processing interval of approximately every 10 milliseconds.
-
Hot-buffer back-pressure – When the hot buffer reaches 50% capacity,
raw_log()(insrc/logger/inner.rslines 98-103) sets aneed_signalflag and unparks the background thread. This ensures prompt processing during log bursts without waiting for the next 10ms interval. -
Explicit user invocation – The public wrapper function
tui_logger::move_events()(defined insrc/logger/api.rslines 32-35) allows synchronous execution. Test suites utilize this capability to enforce deterministic behavior during assertions.
Code Example: Monitoring Performance
use tui_logger;
// Initialize the logger and spawn the background thread
tui_logger::init_logger(log::LevelFilter::Info).unwrap();
// These calls return immediately (O(1) hot buffer insertion)
log::info!("Application starting");
log::debug!("Configuration loaded");
// Background thread automatically processes these every ~10ms
// or sooner if buffer reaches 50% capacity
// Force synchronous processing for deterministic tests
tui_logger::move_events();
Implementation Details
The performance characteristics derive from specific implementation choices across the codebase:
-
src/logger/inner.rs(lines 128-140): Contains the coreTuiLogger::move_events()implementation with the hot buffer swap logic and event reversal algorithm. -
src/logger/api.rs(lines 44-50, 32-35): Defines the background thread spawn logic and the public API wrapper that exposesmove_events()to external callers. -
src/logger/fast_hash.rs: Implementsfast_str_hash, the hashing algorithm used during target-filter resolution to minimize cache misses in the hot path. -
src/logger/mod.rs: Re-exports the public API surface used by consuming applications.
Summary
move_events()operates in O(N) time where N equals pending log events, with linear costs for reversal, filtering, and buffer insertion.- The function runs in a dedicated background thread every 10ms by default, ensuring application threads never block on log processing.
- Back-pressure handling triggers immediate execution when the hot buffer reaches 50% capacity, preventing overflow during logging bursts.
- Memory allocation occurs proportionally to pending event counts during the temporary vector creation phase.
- File I/O costs only apply when
TuiLoggerFiledumping is enabled, and remain isolated to the background thread.
Frequently Asked Questions
Does move_events() block the main application thread?
No. The function executes exclusively within the "tui-logger::move_events" background thread spawned by init_logger(). Application threads calling log::info!() or similar macros perform only O(1) hot buffer insertions and return immediately, while the background thread handles all O(N) processing asynchronously.
How often does move_events() run in production?
By default, the background thread invokes move_events() approximately every 10 milliseconds during normal operation. However, if the hot buffer fills to 50% capacity, the system triggers immediate execution through thread unparking (see raw_log() in src/logger/inner.rs lines 98-103), ensuring rapid response to logging bursts.
What happens if the hot buffer overflows?
When event production exceeds hot buffer capacity before move_events() processes them, the system generates a synthetic "events lost" record during the next execution cycle. This mechanism alerts developers to buffer exhaustion while maintaining system stability, though it indicates the 10ms polling interval proved insufficient for the current log volume.
Can I manually trigger event processing?
Yes. The public API exposes tui_logger::move_events() (defined in src/logger/api.rs lines 32-35), which simply forwards to TUI_LOGGER.move_events(). This is particularly useful in test suites requiring deterministic log ordering or when applications need to ensure all pending events are visible in the TUI before rendering specific frames.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →