Add Metric::registration_version
For cache invalidation
This commit is contained in:
237
src/metric.cpp
237
src/metric.cpp
@@ -352,6 +352,9 @@ struct Metric {
|
|||||||
static_assert(std::is_trivially_destructible_v<Histogram::State>);
|
static_assert(std::is_trivially_destructible_v<Histogram::State>);
|
||||||
static std::mutex mutex;
|
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
|
// Global arena allocator for metric families and persistent global state
|
||||||
static ArenaAllocator &get_global_arena() {
|
static ArenaAllocator &get_global_arena() {
|
||||||
static auto *global_arena =
|
static auto *global_arena =
|
||||||
@@ -422,6 +425,7 @@ struct Metric {
|
|||||||
~ThreadInit() {
|
~ThreadInit() {
|
||||||
// Accumulate thread-local state into global state before cleanup
|
// Accumulate thread-local state into global state before cleanup
|
||||||
std::unique_lock<std::mutex> _{mutex};
|
std::unique_lock<std::mutex> _{mutex};
|
||||||
|
++Metric::registration_version;
|
||||||
auto thread_id = std::this_thread::get_id();
|
auto thread_id = std::this_thread::get_id();
|
||||||
|
|
||||||
// Accumulate counter families
|
// Accumulate counter families
|
||||||
@@ -510,6 +514,7 @@ struct Metric {
|
|||||||
(void)thread_init;
|
(void)thread_init;
|
||||||
|
|
||||||
std::unique_lock<std::mutex> _{mutex};
|
std::unique_lock<std::mutex> _{mutex};
|
||||||
|
++Metric::registration_version;
|
||||||
const LabelsKey &key = intern_labels(labels);
|
const LabelsKey &key = intern_labels(labels);
|
||||||
|
|
||||||
// Validate that labels aren't already registered as callback
|
// Validate that labels aren't already registered as callback
|
||||||
@@ -543,6 +548,7 @@ struct Metric {
|
|||||||
Family<Gauge> *family,
|
Family<Gauge> *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) {
|
||||||
std::unique_lock<std::mutex> _{mutex};
|
std::unique_lock<std::mutex> _{mutex};
|
||||||
|
++Metric::registration_version;
|
||||||
const LabelsKey &key = intern_labels(labels);
|
const LabelsKey &key = intern_labels(labels);
|
||||||
|
|
||||||
// Validate that labels aren't already registered as callback
|
// Validate that labels aren't already registered as callback
|
||||||
@@ -568,6 +574,7 @@ struct Metric {
|
|||||||
(void)thread_init;
|
(void)thread_init;
|
||||||
|
|
||||||
std::unique_lock<std::mutex> _{mutex};
|
std::unique_lock<std::mutex> _{mutex};
|
||||||
|
++Metric::registration_version;
|
||||||
const LabelsKey &key = intern_labels(labels);
|
const LabelsKey &key = intern_labels(labels);
|
||||||
|
|
||||||
// Ensure thread state exists
|
// Ensure thread state exists
|
||||||
@@ -1386,6 +1393,7 @@ Family<Counter> create_counter(std::string_view name, std::string_view help) {
|
|||||||
validate_or_abort(is_valid_metric_name(name), "invalid counter name", name);
|
validate_or_abort(is_valid_metric_name(name), "invalid counter name", name);
|
||||||
|
|
||||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||||
|
++Metric::registration_version;
|
||||||
auto &global_arena = Metric::get_global_arena();
|
auto &global_arena = Metric::get_global_arena();
|
||||||
auto name_view = arena_copy_string(name, global_arena);
|
auto name_view = arena_copy_string(name, global_arena);
|
||||||
auto &familyPtr = Metric::get_counter_families()[name_view];
|
auto &familyPtr = Metric::get_counter_families()[name_view];
|
||||||
@@ -1407,6 +1415,7 @@ Family<Gauge> create_gauge(std::string_view name, std::string_view help) {
|
|||||||
validate_or_abort(is_valid_metric_name(name), "invalid gauge name", name);
|
validate_or_abort(is_valid_metric_name(name), "invalid gauge name", name);
|
||||||
|
|
||||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||||
|
++Metric::registration_version;
|
||||||
auto &global_arena = Metric::get_global_arena();
|
auto &global_arena = Metric::get_global_arena();
|
||||||
auto name_view = arena_copy_string(name, global_arena);
|
auto name_view = arena_copy_string(name, global_arena);
|
||||||
auto &familyPtr = Metric::get_gauge_families()[name_view];
|
auto &familyPtr = Metric::get_gauge_families()[name_view];
|
||||||
@@ -1431,6 +1440,7 @@ Family<Histogram> create_histogram(std::string_view name, std::string_view help,
|
|||||||
validate_or_abort(is_valid_metric_name(name), "invalid histogram name", name);
|
validate_or_abort(is_valid_metric_name(name), "invalid histogram name", name);
|
||||||
|
|
||||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||||
|
++Metric::registration_version;
|
||||||
auto &global_arena = Metric::get_global_arena();
|
auto &global_arena = Metric::get_global_arena();
|
||||||
auto name_view = arena_copy_string(name, global_arena);
|
auto name_view = arena_copy_string(name, global_arena);
|
||||||
auto &family_ptr = Metric::get_histogram_families()[name_view];
|
auto &family_ptr = Metric::get_histogram_families()[name_view];
|
||||||
@@ -1731,235 +1741,13 @@ std::span<std::string_view> render(ArenaAllocator &arena) {
|
|||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy render function - kept for reference during transition
|
|
||||||
std::span<std::string_view> render_legacy(ArenaAllocator &arena) {
|
|
||||||
// Hold lock throughout both phases to prevent registry changes
|
|
||||||
std::unique_lock<std::mutex> _{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<MetricValue> metric_values =
|
|
||||||
compute_metric_values_legacy(arena, label_sets);
|
|
||||||
const MetricValue *next_value = metric_values.data();
|
|
||||||
|
|
||||||
ArenaVector<std::string_view> 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<char>(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<char>(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<int>(name.length()), name.data(),
|
|
||||||
static_cast<int>(family->help.length()),
|
|
||||||
family->help.data()));
|
|
||||||
output.push_back(format(arena, "# TYPE %.*s counter\n",
|
|
||||||
static_cast<int>(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<int>(name.length()),
|
|
||||||
name.data(), static_cast<int>(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<int>(name.length()),
|
|
||||||
name.data(),
|
|
||||||
static_cast<int>(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<int>(name.length()), name.data(),
|
|
||||||
static_cast<int>(family->help.length()),
|
|
||||||
family->help.data()));
|
|
||||||
output.push_back(format(arena, "# TYPE %.*s gauge\n",
|
|
||||||
static_cast<int>(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<int>(name.length()),
|
|
||||||
name.data(), static_cast<int>(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<int>(name.length()),
|
|
||||||
name.data(),
|
|
||||||
static_cast<int>(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<int>(name.length()), name.data(),
|
|
||||||
static_cast<int>(family->help.length()),
|
|
||||||
family->help.data()));
|
|
||||||
output.push_back(format(arena, "# TYPE %.*s histogram\n",
|
|
||||||
static_cast<int>(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<int>(name.length()),
|
|
||||||
name.data(), static_cast<int>(labels.length()), labels.data(),
|
|
||||||
static_cast<unsigned long long>(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<int>(name.length()),
|
|
||||||
name.data(), static_cast<int>(inf_labels.length()), inf_labels.data(),
|
|
||||||
static_cast<unsigned long long>(observations)));
|
|
||||||
|
|
||||||
// Format sum
|
|
||||||
auto sum = next_value++->as_double;
|
|
||||||
output.push_back(
|
|
||||||
format(arena, "%.*s_sum%.*s %.17g\n", static_cast<int>(name.length()),
|
|
||||||
name.data(),
|
|
||||||
static_cast<int>(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<int>(name.length()), name.data(),
|
|
||||||
static_cast<int>(data.labels_key.prometheus_format.length()),
|
|
||||||
data.labels_key.prometheus_format.data(),
|
|
||||||
static_cast<unsigned long long>(count)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Template specialization implementations for register_callback
|
// Template specialization implementations for register_callback
|
||||||
template <>
|
template <>
|
||||||
void Family<Counter>::register_callback(
|
void Family<Counter>::register_callback(
|
||||||
std::span<const std::pair<std::string_view, std::string_view>> labels,
|
std::span<const std::pair<std::string_view, std::string_view>> labels,
|
||||||
MetricCallback<Counter> callback) {
|
MetricCallback<Counter> callback) {
|
||||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||||
|
++Metric::registration_version;
|
||||||
const LabelsKey &key = Metric::intern_labels(labels);
|
const LabelsKey &key = Metric::intern_labels(labels);
|
||||||
|
|
||||||
// Validate that labels aren't already in use by create() calls
|
// Validate that labels aren't already in use by create() calls
|
||||||
@@ -1984,6 +1772,7 @@ void Family<Gauge>::register_callback(
|
|||||||
std::span<const std::pair<std::string_view, std::string_view>> labels,
|
std::span<const std::pair<std::string_view, std::string_view>> labels,
|
||||||
MetricCallback<Gauge> callback) {
|
MetricCallback<Gauge> callback) {
|
||||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||||
|
++Metric::registration_version;
|
||||||
const LabelsKey &key = Metric::intern_labels(labels);
|
const LabelsKey &key = Metric::intern_labels(labels);
|
||||||
|
|
||||||
// Validate that labels aren't already in use by create() calls
|
// Validate that labels aren't already in use by create() calls
|
||||||
@@ -2005,10 +1794,12 @@ void Family<Gauge>::register_callback(
|
|||||||
|
|
||||||
// Static member definitions
|
// Static member definitions
|
||||||
std::mutex Metric::mutex;
|
std::mutex Metric::mutex;
|
||||||
|
uint64_t Metric::registration_version;
|
||||||
thread_local Metric::ThreadInit Metric::thread_init;
|
thread_local Metric::ThreadInit Metric::thread_init;
|
||||||
|
|
||||||
void reset_metrics_for_testing() {
|
void reset_metrics_for_testing() {
|
||||||
std::lock_guard _{Metric::mutex};
|
std::lock_guard _{Metric::mutex};
|
||||||
|
++Metric::registration_version;
|
||||||
|
|
||||||
// WARNING: This function assumes no metric objects are in use!
|
// WARNING: This function assumes no metric objects are in use!
|
||||||
// Clear all family maps - this will leak the Family::State objects but
|
// Clear all family maps - this will leak the Family::State objects but
|
||||||
|
|||||||
Reference in New Issue
Block a user