From a30020e960339e9ff89718fc4e49b57328fa5f7d Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Wed, 3 Sep 2025 10:18:19 -0400 Subject: [PATCH] Add Metric::registration_version For cache invalidation --- src/metric.cpp | 237 +++---------------------------------------------- 1 file changed, 14 insertions(+), 223 deletions(-) diff --git a/src/metric.cpp b/src/metric.cpp index ab9728c..d1a426e 100644 --- a/src/metric.cpp +++ b/src/metric.cpp @@ -352,6 +352,9 @@ struct Metric { static_assert(std::is_trivially_destructible_v); static std::mutex mutex; + // Use to invalidate the render plan cache + static uint64_t registration_version; + // Global arena allocator for metric families and persistent global state static ArenaAllocator &get_global_arena() { static auto *global_arena = @@ -422,6 +425,7 @@ struct Metric { ~ThreadInit() { // Accumulate thread-local state into global state before cleanup std::unique_lock _{mutex}; + ++Metric::registration_version; auto thread_id = std::this_thread::get_id(); // Accumulate counter families @@ -510,6 +514,7 @@ struct Metric { (void)thread_init; std::unique_lock _{mutex}; + ++Metric::registration_version; const LabelsKey &key = intern_labels(labels); // Validate that labels aren't already registered as callback @@ -543,6 +548,7 @@ struct Metric { Family *family, std::span> labels) { std::unique_lock _{mutex}; + ++Metric::registration_version; const LabelsKey &key = intern_labels(labels); // Validate that labels aren't already registered as callback @@ -568,6 +574,7 @@ struct Metric { (void)thread_init; std::unique_lock _{mutex}; + ++Metric::registration_version; const LabelsKey &key = intern_labels(labels); // Ensure thread state exists @@ -1386,6 +1393,7 @@ Family create_counter(std::string_view name, std::string_view help) { validate_or_abort(is_valid_metric_name(name), "invalid counter name", name); std::unique_lock _{Metric::mutex}; + ++Metric::registration_version; auto &global_arena = Metric::get_global_arena(); auto name_view = arena_copy_string(name, global_arena); auto &familyPtr = Metric::get_counter_families()[name_view]; @@ -1407,6 +1415,7 @@ Family create_gauge(std::string_view name, std::string_view help) { validate_or_abort(is_valid_metric_name(name), "invalid gauge name", name); std::unique_lock _{Metric::mutex}; + ++Metric::registration_version; auto &global_arena = Metric::get_global_arena(); auto name_view = arena_copy_string(name, global_arena); auto &familyPtr = Metric::get_gauge_families()[name_view]; @@ -1431,6 +1440,7 @@ Family create_histogram(std::string_view name, std::string_view help, validate_or_abort(is_valid_metric_name(name), "invalid histogram name", name); std::unique_lock _{Metric::mutex}; + ++Metric::registration_version; auto &global_arena = Metric::get_global_arena(); auto name_view = arena_copy_string(name, global_arena); auto &family_ptr = Metric::get_histogram_families()[name_view]; @@ -1731,235 +1741,13 @@ std::span render(ArenaAllocator &arena) { return output; } -// Legacy render function - kept for reference during transition -std::span render_legacy(ArenaAllocator &arena) { - // Hold lock throughout both phases to prevent registry changes - std::unique_lock _{Metric::mutex}; - - // Build label sets once for both phases - Metric::LabelSets label_sets = Metric::build_label_sets(arena); - - // Phase 1: Compute all metric values - ArenaVector metric_values = - compute_metric_values_legacy(arena, label_sets); - const MetricValue *next_value = metric_values.data(); - - ArenaVector output(&arena); - - // Helper function to append an additional label to existing Prometheus format - auto append_label_to_format = - [&](std::string_view base_format, std::string_view key, - std::string_view value) -> std::string_view { - // Calculate size for key="value" with escaping - size_t key_value_size = key.length() + 3 + value.length(); // key="value" - for (char c : value) { - if (c == '\\' || c == '"' || c == '\n') { - key_value_size++; - } - } - - if (base_format.empty()) { - // Create new format: {key="value"} - size_t required_size = 2 + key_value_size; // {} - char *buf = arena.allocate(required_size); - char *p = buf; - *p++ = '{'; - std::memcpy(p, key.data(), key.length()); - p += key.length(); - *p++ = '='; - *p++ = '"'; - for (char c : value) { - switch (c) { - case '\\': - *p++ = '\\'; - *p++ = '\\'; - break; - case '"': - *p++ = '\\'; - *p++ = '"'; - break; - case '\n': - *p++ = '\\'; - *p++ = 'n'; - break; - default: - *p++ = c; - break; - } - } - *p++ = '"'; - *p++ = '}'; - return std::string_view(buf, p - buf); - } else { - // Append to existing format: {existing,key="value"} - size_t required_size = base_format.length() + 1 + - key_value_size; // comma + key="value", replace } - char *buf = arena.allocate(required_size); - char *p = buf; - // Copy everything except the closing } - std::memcpy(p, base_format.data(), base_format.length() - 1); - p += base_format.length() - 1; - *p++ = ','; - std::memcpy(p, key.data(), key.length()); - p += key.length(); - *p++ = '='; - *p++ = '"'; - for (char c : value) { - switch (c) { - case '\\': - *p++ = '\\'; - *p++ = '\\'; - break; - case '"': - *p++ = '\\'; - *p++ = '"'; - break; - case '\n': - *p++ = '\\'; - *p++ = 'n'; - break; - default: - *p++ = c; - break; - } - } - *p++ = '"'; - *p++ = '}'; - return std::string_view(buf, p - buf); - } - }; - - // Format counters - ITERATION ORDER MUST MATCH COMPUTE PHASE - size_t counter_family_idx = 0; - for (const auto &[name, family] : Metric::get_counter_families()) { - output.push_back(format(arena, "# HELP %.*s %.*s\n", - static_cast(name.length()), name.data(), - static_cast(family->help.length()), - family->help.data())); - output.push_back(format(arena, "# TYPE %.*s counter\n", - static_cast(name.length()), name.data())); - - // Format callback values - for (const auto &[labels_key, callback] : family->callbacks) { - auto value = next_value++->as_double; - output.push_back(format( - arena, "%.*s%.*s %.17g\n", static_cast(name.length()), - name.data(), static_cast(labels_key.prometheus_format.length()), - labels_key.prometheus_format.data(), value)); - } - - // Use pre-computed data (same as compute phase) - const auto &family_data = label_sets.counter_data[counter_family_idx++]; - - // Format counter values using pre-computed values - for (const auto &data : family_data) { - auto total_value = next_value++->as_double; - output.push_back( - format(arena, "%.*s%.*s %.17g\n", static_cast(name.length()), - name.data(), - static_cast(data.labels_key.prometheus_format.length()), - data.labels_key.prometheus_format.data(), total_value)); - } - } - - // Format gauges - ITERATION ORDER MUST MATCH COMPUTE PHASE - size_t gauge_family_idx = 0; - for (const auto &[name, family] : Metric::get_gauge_families()) { - output.push_back(format(arena, "# HELP %.*s %.*s\n", - static_cast(name.length()), name.data(), - static_cast(family->help.length()), - family->help.data())); - output.push_back(format(arena, "# TYPE %.*s gauge\n", - static_cast(name.length()), name.data())); - - // Format callback values - for (const auto &[labels_key, callback] : family->callbacks) { - auto value = next_value++->as_double; - output.push_back(format( - arena, "%.*s%.*s %.17g\n", static_cast(name.length()), - name.data(), static_cast(labels_key.prometheus_format.length()), - labels_key.prometheus_format.data(), value)); - } - - // Use pre-computed data (same as compute phase) - const auto &family_data = label_sets.gauge_data[gauge_family_idx++]; - for (const auto &data : family_data) { - auto value = next_value++->as_double; - output.push_back( - format(arena, "%.*s%.*s %.17g\n", static_cast(name.length()), - name.data(), - static_cast(data.labels_key.prometheus_format.length()), - data.labels_key.prometheus_format.data(), value)); - } - } - - // Format histograms - ITERATION ORDER MUST MATCH COMPUTE PHASE - size_t histogram_family_idx = 0; - for (const auto &[name, family] : Metric::get_histogram_families()) { - output.push_back(format(arena, "# HELP %.*s %.*s\n", - static_cast(name.length()), name.data(), - static_cast(family->help.length()), - family->help.data())); - output.push_back(format(arena, "# TYPE %.*s histogram\n", - static_cast(name.length()), name.data())); - - // Use pre-computed data (same as compute phase) - const auto &family_data = label_sets.histogram_data[histogram_family_idx++]; - - // Format histogram data using pre-computed values - for (const auto &data : family_data) { - // Get bucket count from pre-computed data - size_t bucket_count = data.bucket_count; - - // Format explicit bucket counts - for (size_t i = 0; i < bucket_count; ++i) { - auto count = next_value++->as_uint64; - auto bucket_value = static_format(arena, family->buckets[i]); - auto labels = append_label_to_format(data.labels_key.prometheus_format, - "le", bucket_value); - output.push_back(format( - arena, "%.*s_bucket%.*s %llu\n", static_cast(name.length()), - name.data(), static_cast(labels.length()), labels.data(), - static_cast(count))); - } - - // Format +Inf bucket - auto observations = next_value++->as_uint64; - auto inf_labels = append_label_to_format( - data.labels_key.prometheus_format, "le", "+Inf"); - output.push_back(format( - arena, "%.*s_bucket%.*s %llu\n", static_cast(name.length()), - name.data(), static_cast(inf_labels.length()), inf_labels.data(), - static_cast(observations))); - - // Format sum - auto sum = next_value++->as_double; - output.push_back( - format(arena, "%.*s_sum%.*s %.17g\n", static_cast(name.length()), - name.data(), - static_cast(data.labels_key.prometheus_format.length()), - data.labels_key.prometheus_format.data(), sum)); - - // Format count - auto count = next_value++->as_uint64; - output.push_back( - format(arena, "%.*s_count%.*s %llu\n", - static_cast(name.length()), name.data(), - static_cast(data.labels_key.prometheus_format.length()), - data.labels_key.prometheus_format.data(), - static_cast(count))); - } - } - - return output; -} - // Template specialization implementations for register_callback template <> void Family::register_callback( std::span> labels, MetricCallback callback) { std::unique_lock _{Metric::mutex}; + ++Metric::registration_version; const LabelsKey &key = Metric::intern_labels(labels); // Validate that labels aren't already in use by create() calls @@ -1984,6 +1772,7 @@ void Family::register_callback( std::span> labels, MetricCallback callback) { std::unique_lock _{Metric::mutex}; + ++Metric::registration_version; const LabelsKey &key = Metric::intern_labels(labels); // Validate that labels aren't already in use by create() calls @@ -2005,10 +1794,12 @@ void Family::register_callback( // Static member definitions std::mutex Metric::mutex; +uint64_t Metric::registration_version; thread_local Metric::ThreadInit Metric::thread_init; void reset_metrics_for_testing() { std::lock_guard _{Metric::mutex}; + ++Metric::registration_version; // WARNING: This function assumes no metric objects are in use! // Clear all family maps - this will leak the Family::State objects but