diff --git a/src/metric.cpp b/src/metric.cpp index 9aa8c6f..836360b 100644 --- a/src/metric.cpp +++ b/src/metric.cpp @@ -425,10 +425,22 @@ struct Metric { return *internedStaticText; } + // Registry of all thread arenas for memory tracking + static auto &get_thread_arenas() { + using ThreadArenaMap = + std::unordered_map; + static ThreadArenaMap *threadArenas = new ThreadArenaMap(); + return *threadArenas; + } + // Thread cleanup for per-family thread-local storage struct ThreadInit { ArenaAllocator arena; - ThreadInit() {} + ThreadInit() { + // Register this thread's arena for memory tracking + std::unique_lock _{mutex}; + get_thread_arenas()[std::this_thread::get_id()] = &arena; + } ~ThreadInit() { // Accumulate thread-local state into global state before cleanup // THREAD SAFETY: All operations below are protected by the global mutex, @@ -442,6 +454,9 @@ struct Metric { ++Metric::registration_version; auto thread_id = std::this_thread::get_id(); + // Unregister this thread's arena from memory tracking + get_thread_arenas().erase(thread_id); + // Accumulate counter families for (auto &[name, family] : Metric::get_counter_families()) { auto thread_it = family->per_thread_state.find(thread_id); @@ -1609,8 +1624,38 @@ union MetricValue { uint64_t as_uint64; }; +// Memory usage calculation callback for metrics system self-monitoring +static double calculate_metrics_memory_usage() { + std::size_t total_bytes = 0; + + // 1. Global arena memory + total_bytes += Metric::get_global_arena().total_allocated(); + + // 2. Per-thread arenas (safe because we're already holding the global mutex) + for (const auto &[thread_id, arena_ptr] : Metric::get_thread_arenas()) { + total_bytes += arena_ptr->total_allocated(); + } + + // 3. Cached plan arena + if (Metric::cached_plan) { + total_bytes += Metric::cached_plan->arena.total_allocated(); + } + + return static_cast(total_bytes); +} + // New three-phase render implementation std::span render(ArenaAllocator &arena) { + // Initialize self-monitoring metrics (before taking global lock) + static auto memory_gauge = []() { + auto gauge = create_gauge("weaseldb_metrics_memory_bytes", + "Total memory usage of the metrics system " + "including global and per-thread arenas"); + gauge.register_callback({}, calculate_metrics_memory_usage); + return gauge; + }(); + (void)memory_gauge; // Suppress unused variable warning + // Hold lock throughout all phases to prevent registry changes // THREAD SAFETY: Global mutex protects cached_plan initialization and access, // prevents races during static member initialization at program startup