Don't copy static_text in render

This commit is contained in:
2025-09-03 11:54:35 -04:00
parent 13e4039ed6
commit f16cff9126

View File

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