include/DetourModKit/async_logger_config.hpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #ifndef DETOURMODKIT_ASYNC_LOGGER_CONFIG_HPP | ||
| 2 | #define DETOURMODKIT_ASYNC_LOGGER_CONFIG_HPP | ||
| 3 | |||
| 4 | /** | ||
| 5 | * @file async_logger_config.hpp | ||
| 6 | * @brief Lightweight, public async-logger configuration surface. | ||
| 7 | * @details Carries only the control-plane configuration types plus their default constants, and none of the | ||
| 8 | * async-logger plumbing, so ModInfo can embed an AsyncLoggerConfig by value without forcing every consumer | ||
| 9 | * translation unit to compile the queue and pool. | ||
| 10 | */ | ||
| 11 | |||
| 12 | #include <chrono> | ||
| 13 | #include <cstddef> | ||
| 14 | #include <cstdint> | ||
| 15 | #include <string> | ||
| 16 | #include <string_view> | ||
| 17 | |||
| 18 | namespace DetourModKit | ||
| 19 | { | ||
| 20 | /// Default strftime-style timestamp format for the async sink. | ||
| 21 | inline constexpr std::string_view DEFAULT_ASYNC_TIMESTAMP_FORMAT{"%Y-%m-%d %H:%M:%S"}; | ||
| 22 | /// Default capacity (slot count) of the bounded MPMC message queue. | ||
| 23 | inline constexpr std::size_t DEFAULT_QUEUE_CAPACITY = 8192; | ||
| 24 | /// Default number of messages the writer drains per write batch. | ||
| 25 | inline constexpr std::size_t DEFAULT_BATCH_SIZE = 64; | ||
| 26 | /// Default interval between periodic writer flushes. | ||
| 27 | inline constexpr auto DEFAULT_FLUSH_INTERVAL = std::chrono::milliseconds(100); | ||
| 28 | /// Default spin-backoff iteration count before a producer yields/parks. | ||
| 29 | inline constexpr std::size_t DEFAULT_SPIN_BACKOFF_ITERATIONS = 32; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * @enum OverflowPolicy | ||
| 33 | * @brief Defines the action that AsyncLogger::enqueue takes when the bounded queue is full. | ||
| 34 | * @warning On a callback path, use only DropNewest. For callback-safe use, keep the new record within | ||
| 35 | * @ref LOG_INLINE_MESSAGE_SIZE. The other policies have these hazards: | ||
| 36 | * - DropOldest can take the string-pool lock when it evicts an older long record. | ||
| 37 | * - Block parks the producer. | ||
| 38 | * - SyncFallback writes synchronously. | ||
| 39 | * @note The drop policies change acceptance, not producer latency. Under a saturating burst, DropNewest | ||
| 40 | * accepted under 3 percent of the records and DropOldest accepted almost all, at the same p50 | ||
| 41 | * (`docs/analysis/hot_path_bench_v4/`). | ||
| 42 | */ | ||
| 43 | enum class OverflowPolicy : std::uint8_t | ||
| 44 | { | ||
| 45 | /// Drops the new message when the queue is full and performs no wait at the overflow step. | ||
| 46 | DropNewest, | ||
| 47 | /** | ||
| 48 | * @brief Evicts the oldest queued message and performs no queue-capacity wait. | ||
| 49 | * @note The eviction can take the string-pool lock when the old record exceeds the inline bound. | ||
| 50 | */ | ||
| 51 | DropOldest, | ||
| 52 | /// Parks the producer until space frees or block_timeout_ms elapses and can stall the caller. | ||
| 53 | Block, | ||
| 54 | /** | ||
| 55 | * @brief Applies the synchronous fallback policy. | ||
| 56 | * @details The policy has these effects: | ||
| 57 | * - It writes on the producer thread. | ||
| 58 | * - It takes the sink lock. | ||
| 59 | * - It performs I/O. | ||
| 60 | */ | ||
| 61 | SyncFallback | ||
| 62 | }; | ||
| 63 | |||
| 64 | /** | ||
| 65 | * @struct AsyncLoggerConfig | ||
| 66 | * @brief Configuration for the async logger. | ||
| 67 | * @details The default queue holds DEFAULT_QUEUE_CAPACITY (8192) slots; each slot embeds a LogMessage with a | ||
| 68 | * LOG_INLINE_MESSAGE_SIZE (512) byte inline buffer, so the ring buffer's resident footprint is on the | ||
| 69 | * order of a few MiB at the default capacity (queue_capacity must stay a power of two). The overflow | ||
| 70 | * string pool (for messages larger than the inline buffer) is a separate, lazily grown allocation behind | ||
| 71 | * the AsyncLogger pimpl (detail::StringPool). Shrink queue_capacity for memory-constrained hosts. | ||
| 72 | */ | ||
| 73 | struct AsyncLoggerConfig | ||
| 74 | { | ||
| 75 | std::size_t queue_capacity = DEFAULT_QUEUE_CAPACITY; | ||
| 76 | /// Records the writer drains per write batch; clamped to @ref queue_capacity, which is all the queue can hold. | ||
| 77 | std::size_t batch_size = DEFAULT_BATCH_SIZE; | ||
| 78 | std::chrono::milliseconds flush_interval = DEFAULT_FLUSH_INTERVAL; | ||
| 79 | OverflowPolicy overflow_policy = OverflowPolicy::DropOldest; | ||
| 80 | std::size_t spin_backoff_iterations = DEFAULT_SPIN_BACKOFF_ITERATIONS; | ||
| 81 | std::chrono::milliseconds block_timeout_ms{16}; | ||
| 82 | std::size_t block_max_spin_iterations{1000}; | ||
| 83 | /** | ||
| 84 | * @brief strftime-style date/time format for the async sink; empty selects | ||
| 85 | * @ref DEFAULT_ASYNC_TIMESTAMP_FORMAT. | ||
| 86 | * @details The empty default keeps value construction allocation-free. The async writer materializes the | ||
| 87 | * effective format in its owned configuration before starting; Logger::enable_async_mode replaces it | ||
| 88 | * with the Logger's format so both sinks stay identical. The writer appends the millisecond fraction. | ||
| 89 | */ | ||
| 90 | std::string timestamp_format{}; | ||
| 91 | |||
| 92 | /** | ||
| 93 | * @brief Reports whether every field satisfies its documented bound. | ||
| 94 | * @return true when queue_capacity is a power of two of at least 2 and each duration or count is positive. | ||
| 95 | */ | ||
| 96 | 417 | [[nodiscard]] constexpr bool validate() const noexcept | |
| 97 | { | ||
| 98 |
4/4✓ Branch 2 → 3 taken 415 times.
✓ Branch 2 → 4 taken 2 times.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 414 times.
|
417 | if (queue_capacity < 2 || (queue_capacity & (queue_capacity - 1)) != 0) |
| 99 | 3 | return false; | |
| 100 |
2/2✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 413 times.
|
414 | if (batch_size == 0) |
| 101 | 1 | return false; | |
| 102 |
2/2✓ Branch 8 → 9 taken 2 times.
✓ Branch 8 → 10 taken 411 times.
|
413 | if (flush_interval.count() <= 0) |
| 103 | 2 | return false; | |
| 104 |
2/2✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 410 times.
|
411 | if (spin_backoff_iterations == 0) |
| 105 | 1 | return false; | |
| 106 |
2/2✓ Branch 13 → 14 taken 2 times.
✓ Branch 13 → 15 taken 408 times.
|
410 | if (block_timeout_ms.count() <= 0) |
| 107 | 2 | return false; | |
| 108 |
2/2✓ Branch 15 → 16 taken 1 time.
✓ Branch 15 → 17 taken 407 times.
|
408 | if (block_max_spin_iterations == 0) |
| 109 | 1 | return false; | |
| 110 | 407 | return true; | |
| 111 | } | ||
| 112 | }; | ||
| 113 | |||
| 114 | // Compile-time validation: the default queue capacity must be a power of 2 and at least 2. | ||
| 115 | static_assert( | ||
| 116 | DEFAULT_QUEUE_CAPACITY >= 2 && (DEFAULT_QUEUE_CAPACITY & (DEFAULT_QUEUE_CAPACITY - 1)) == 0, | ||
| 117 | "DEFAULT_QUEUE_CAPACITY must be a power of 2 and at least 2" | ||
| 118 | ); | ||
| 119 | |||
| 120 | } // namespace DetourModKit | ||
| 121 | |||
| 122 | #endif // DETOURMODKIT_ASYNC_LOGGER_CONFIG_HPP | ||
| 123 |