diff --git a/src/metric.cpp b/src/metric.cpp index 14a9866..5be150e 100644 --- a/src/metric.cpp +++ b/src/metric.cpp @@ -414,6 +414,17 @@ struct Metric { return *internedLabels; } + // Global static text interning set to avoid duplicate text allocations + static auto &get_interned_static_text() { + using StaticTextSet = + std::unordered_set, + std::equal_to, + ArenaStlAllocator>; + static StaticTextSet *internedStaticText = new StaticTextSet( + ArenaStlAllocator(&get_global_arena())); + return *internedStaticText; + } + // Thread cleanup for per-family thread-local storage struct ThreadInit { ArenaAllocator arena; @@ -528,6 +539,22 @@ struct Metric { return *result.first; } + // Intern static text to avoid duplicate allocations + static std::string_view intern_static_text(std::string_view text) { + auto &interned_set = get_interned_static_text(); + + // Check if text is already interned + auto it = interned_set.find(text); + if (it != interned_set.end()) { + return *it; + } + + // Not found - copy to global arena and intern + auto interned_text = arena_copy_string(text, get_global_arena()); + auto result = interned_set.emplace(interned_text); + return *result.first; + } + static Counter create_counter_instance( Family *family, std::span> labels) { @@ -883,11 +910,12 @@ struct Metric { // Callback instructions and static text for (const auto &[labels_key, callback] : family->callbacks) { plan.instructions.push_back(CallCounterCallback{&callback}); - plan.static_text.push_back(format( + auto static_text = format( plan.arena, "%.*s\n%.*s%.*s ", static_cast(help_line.size()), help_line.data(), static_cast(name.length()), name.data(), static_cast(labels_key.prometheus_format.length()), - labels_key.prometheus_format.data())); + labels_key.prometheus_format.data()); + plan.static_text.push_back(intern_static_text(static_text)); help_line = ""; } @@ -896,11 +924,12 @@ struct Metric { for (const auto &data : family_data) { plan.instructions.push_back( AggregateCounter{data.thread_states, data.global_state}); - plan.static_text.push_back(format( + auto static_text = format( plan.arena, "%.*s\n%.*s%.*s ", static_cast(help_line.size()), help_line.data(), static_cast(name.length()), name.data(), static_cast(data.labels_key.prometheus_format.length()), - data.labels_key.prometheus_format.data())); + data.labels_key.prometheus_format.data()); + plan.static_text.push_back(intern_static_text(static_text)); help_line = ""; } } @@ -919,11 +948,12 @@ struct Metric { // Callback instructions and static text for (const auto &[labels_key, callback] : family->callbacks) { plan.instructions.push_back(CallGaugeCallback{&callback}); - plan.static_text.push_back(format( + auto static_text = format( plan.arena, "%.*s\n%.*s%.*s ", static_cast(help_line.size()), help_line.data(), static_cast(name.length()), name.data(), static_cast(labels_key.prometheus_format.length()), - labels_key.prometheus_format.data())); + labels_key.prometheus_format.data()); + plan.static_text.push_back(intern_static_text(static_text)); help_line = ""; } @@ -931,11 +961,12 @@ struct Metric { const auto &family_data = label_sets.gauge_data[gauge_family_idx++]; for (const auto &data : family_data) { plan.instructions.push_back(AggregateGauge{data.instance_state}); - plan.static_text.push_back(format( + auto static_text = format( plan.arena, "%.*s\n%.*s%.*s ", static_cast(help_line.size()), help_line.data(), static_cast(name.length()), name.data(), static_cast(data.labels_key.prometheus_format.length()), - data.labels_key.prometheus_format.data())); + data.labels_key.prometheus_format.data()); + plan.static_text.push_back(intern_static_text(static_text)); help_line = ""; } } @@ -961,35 +992,39 @@ struct Metric { auto bucket_value = static_format(plan.arena, family->buckets[i]); auto labels = append_label_to_format( data.labels_key.prometheus_format, "le", bucket_value); - plan.static_text.push_back( + auto static_text = format(plan.arena, "%.*s\n%.*s_bucket%.*s ", static_cast(help_line.size()), help_line.data(), static_cast(name.length()), name.data(), - static_cast(labels.length()), labels.data())); + static_cast(labels.length()), labels.data()); + plan.static_text.push_back(intern_static_text(static_text)); help_line = ""; } // Static text for +Inf bucket auto inf_labels = append_label_to_format( data.labels_key.prometheus_format, "le", "+Inf"); - plan.static_text.push_back( + auto inf_static_text = format(plan.arena, "\n%.*s_bucket%.*s ", static_cast(name.length()), name.data(), - static_cast(inf_labels.length()), inf_labels.data())); + static_cast(inf_labels.length()), inf_labels.data()); + plan.static_text.push_back(intern_static_text(inf_static_text)); // Static text for sum - plan.static_text.push_back( + auto sum_static_text = format(plan.arena, "\n%.*s_sum%.*s ", static_cast(name.length()), name.data(), static_cast(data.labels_key.prometheus_format.length()), - data.labels_key.prometheus_format.data())); + data.labels_key.prometheus_format.data()); + plan.static_text.push_back(intern_static_text(sum_static_text)); // Static text for count - plan.static_text.push_back( + auto count_static_text = format(plan.arena, "\n%.*s_count%.*s ", static_cast(name.length()), name.data(), static_cast(data.labels_key.prometheus_format.length()), - data.labels_key.prometheus_format.data())); + data.labels_key.prometheus_format.data()); + plan.static_text.push_back(intern_static_text(count_static_text)); } } @@ -1104,16 +1139,13 @@ struct Metric { ArenaVector output(&arena); for (size_t i = 0; i < static_text.size(); ++i) { - // Copy static text into caller's arena - // NOTE: This copying is REQUIRED for memory safety: - // - static_text lives in cached_plan's arena (persistent across renders) - // - dynamic_text lives in caller's arena (single render lifetime) - // - output must live entirely in caller's arena for consistent lifetime - // Without copying, output would have mixed arena ownership causing - // use-after-free - output.push_back(arena_copy_string(static_text[i], arena)); + // Static text is interned in global arena (application lifetime) + // Safe to reference directly without copying since: + // - global arena lifetime > caller arena lifetime ≥ output usage lifetime + // - static text is truly static for a given registry state + output.push_back(static_text[i]); - // Add corresponding dynamic text + // Add corresponding dynamic text (already in caller's arena) output.push_back(dynamic_text[i]); } // Trailing newline @@ -1679,6 +1711,10 @@ void reset_metrics_for_testing() { histogram_families.clear(); interned_labels.clear(); + // Clear interned static text + auto &interned_static_text = Metric::get_interned_static_text(); + interned_static_text.clear(); + // Reset the global arena - this will invalidate all arena-allocated strings // but since we're clearing everything, that's OK Metric::get_global_arena().reset();