Add arena to RenderPlan

This commit is contained in:
2025-09-03 10:43:11 -04:00
parent a30020e960
commit 8763daca8e

View File

@@ -19,7 +19,6 @@
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <variant>
#include <vector>
#include <immintrin.h>
@@ -631,11 +630,11 @@ struct Metric {
// lookups
struct CounterLabelData {
LabelsKey labels_key;
std::vector<Counter::State *> thread_states; // Pre-resolved pointers
ArenaVector<Counter::State *> thread_states; // Pre-resolved pointers
Counter::State *global_state; // Pre-resolved global state pointer
CounterLabelData(const LabelsKey &key)
: labels_key(key), global_state(nullptr) {}
CounterLabelData(const LabelsKey &key, ArenaAllocator &arena)
: labels_key(key), thread_states(&arena), global_state(nullptr) {}
};
struct GaugeLabelData {
@@ -648,19 +647,22 @@ struct Metric {
struct HistogramLabelData {
LabelsKey labels_key;
std::vector<Histogram::State *> thread_states; // Pre-resolved pointers
ArenaVector<Histogram::State *> thread_states; // Pre-resolved pointers
Histogram::State *global_state; // Pre-resolved global state pointer
size_t bucket_count; // Cache bucket count from family
HistogramLabelData(const LabelsKey &key)
: labels_key(key), global_state(nullptr), bucket_count(0) {}
HistogramLabelData(const LabelsKey &key, ArenaAllocator &arena)
: labels_key(key), thread_states(&arena), global_state(nullptr),
bucket_count(0) {}
};
// Pre-computed data for each family type, built once and reused
struct LabelSets {
std::vector<std::vector<CounterLabelData>> counter_data;
std::vector<std::vector<GaugeLabelData>> gauge_data;
std::vector<std::vector<HistogramLabelData>> histogram_data;
ArenaVector<ArenaVector<CounterLabelData>> counter_data;
ArenaVector<ArenaVector<GaugeLabelData>> gauge_data;
ArenaVector<ArenaVector<HistogramLabelData>> histogram_data;
explicit LabelSets(ArenaAllocator &arena)
: counter_data(&arena), gauge_data(&arena), histogram_data(&arena) {}
};
// Instruction types for the execute phase
@@ -737,70 +739,20 @@ struct Metric {
static_assert(std::is_trivially_destructible_v<AggregateCounter>);
static_assert(std::is_trivially_destructible_v<AggregateGauge>);
static_assert(std::is_trivially_destructible_v<AggregateHistogram>);
// Copy constructor and assignment
RenderInstruction(const RenderInstruction &other) : type(other.type) {
switch (type) {
case InstructionType::CALL_COUNTER_CALLBACK:
new (&counter_callback) CallCounterCallback(other.counter_callback);
break;
case InstructionType::CALL_GAUGE_CALLBACK:
new (&gauge_callback) CallGaugeCallback(other.gauge_callback);
break;
case InstructionType::AGGREGATE_COUNTER:
new (&aggregate_counter) AggregateCounter(other.aggregate_counter);
break;
case InstructionType::AGGREGATE_GAUGE:
new (&aggregate_gauge) AggregateGauge(other.aggregate_gauge);
break;
case InstructionType::AGGREGATE_HISTOGRAM:
new (&aggregate_histogram)
AggregateHistogram(other.aggregate_histogram);
break;
}
}
RenderInstruction &operator=(const RenderInstruction &other) {
if (this != &other) {
// All union members are trivially destructible, so no need to call
// destructor. Just reconstruct with new type and data.
type = other.type;
switch (type) {
case InstructionType::CALL_COUNTER_CALLBACK:
new (&counter_callback) CallCounterCallback(other.counter_callback);
break;
case InstructionType::CALL_GAUGE_CALLBACK:
new (&gauge_callback) CallGaugeCallback(other.gauge_callback);
break;
case InstructionType::AGGREGATE_COUNTER:
new (&aggregate_counter) AggregateCounter(other.aggregate_counter);
break;
case InstructionType::AGGREGATE_GAUGE:
new (&aggregate_gauge) AggregateGauge(other.aggregate_gauge);
break;
case InstructionType::AGGREGATE_HISTOGRAM:
new (&aggregate_histogram)
AggregateHistogram(other.aggregate_histogram);
break;
}
}
return *this;
}
};
// Three-phase rendering system
struct RenderPlan {
ArenaVector<std::string_view> static_text;
ArenaVector<RenderInstruction> instructions;
RenderPlan(ArenaAllocator *arena)
: static_text(arena), instructions(arena) {}
ArenaAllocator arena;
ArenaVector<std::string_view> static_text{&arena};
ArenaVector<RenderInstruction> instructions{&arena};
};
// Phase 1: Compile phase - generate static text and instructions
static RenderPlan compile_render_plan(ArenaAllocator &arena,
const LabelSets &label_sets) {
RenderPlan plan(&arena);
static RenderPlan compile_render_plan() {
RenderPlan plan;
Metric::LabelSets label_sets = Metric::build_label_sets(plan.arena);
// Helper function to append an additional label to existing Prometheus
// format
@@ -818,7 +770,7 @@ struct Metric {
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 *buf = plan.arena.allocate<char>(required_size);
char *p = buf;
*p++ = '{';
std::memcpy(p, key.data(), key.length());
@@ -851,7 +803,7 @@ struct Metric {
// 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 *buf = plan.arena.allocate<char>(required_size);
char *p = buf;
// Copy everything except the closing }
std::memcpy(p, base_format.data(), base_format.length() - 1);
@@ -894,7 +846,7 @@ struct Metric {
for (const auto &[name, family] : get_counter_families()) {
// Add HELP line
auto help_line = format(
arena, "%s# HELP %.*s %.*s\n# TYPE %.*s counter",
plan.arena, "%s# HELP %.*s %.*s\n# TYPE %.*s counter",
is_first_static ? "" : "\n", static_cast<int>(name.length()),
name.data(), static_cast<int>(family->help.length()),
family->help.data(), static_cast<int>(name.length()), name.data());
@@ -903,13 +855,11 @@ 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(arena_copy_string(
format(arena, "%.*s\n%.*s%.*s ", static_cast<int>(help_line.size()),
help_line.data(), static_cast<int>(name.length()),
name.data(),
plan.static_text.push_back(format(
plan.arena, "%.*s\n%.*s%.*s ", static_cast<int>(help_line.size()),
help_line.data(), static_cast<int>(name.length()), name.data(),
static_cast<int>(labels_key.prometheus_format.length()),
labels_key.prometheus_format.data()),
arena));
labels_key.prometheus_format.data()));
help_line = "";
}
@@ -918,13 +868,11 @@ struct Metric {
for (const auto &data : family_data) {
plan.instructions.push_back(
AggregateCounter{data.thread_states, data.global_state});
plan.static_text.push_back(arena_copy_string(
format(arena, "%.*s\n%.*s%.*s ", static_cast<int>(help_line.size()),
help_line.data(), static_cast<int>(name.length()),
name.data(),
plan.static_text.push_back(format(
plan.arena, "%.*s\n%.*s%.*s ", static_cast<int>(help_line.size()),
help_line.data(), static_cast<int>(name.length()), name.data(),
static_cast<int>(data.labels_key.prometheus_format.length()),
data.labels_key.prometheus_format.data()),
arena));
data.labels_key.prometheus_format.data()));
help_line = "";
}
}
@@ -934,7 +882,7 @@ struct Metric {
for (const auto &[name, family] : get_gauge_families()) {
// Add HELP line
auto help_line = format(
arena, "%s# HELP %.*s %.*s\n# TYPE %.*s gauge",
plan.arena, "%s# HELP %.*s %.*s\n# TYPE %.*s gauge",
is_first_static ? "" : "\n", static_cast<int>(name.length()),
name.data(), static_cast<int>(family->help.length()),
family->help.data(), static_cast<int>(name.length()), name.data());
@@ -943,13 +891,11 @@ 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(arena_copy_string(
format(arena, "%.*s\n%.*s%.*s ", static_cast<int>(help_line.size()),
help_line.data(), static_cast<int>(name.length()),
name.data(),
plan.static_text.push_back(format(
plan.arena, "%.*s\n%.*s%.*s ", static_cast<int>(help_line.size()),
help_line.data(), static_cast<int>(name.length()), name.data(),
static_cast<int>(labels_key.prometheus_format.length()),
labels_key.prometheus_format.data()),
arena));
labels_key.prometheus_format.data()));
help_line = "";
}
@@ -957,13 +903,11 @@ 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(arena_copy_string(
format(arena, "%.*s\n%.*s%.*s ", static_cast<int>(help_line.size()),
help_line.data(), static_cast<int>(name.length()),
name.data(),
plan.static_text.push_back(format(
plan.arena, "%.*s\n%.*s%.*s ", static_cast<int>(help_line.size()),
help_line.data(), static_cast<int>(name.length()), name.data(),
static_cast<int>(data.labels_key.prometheus_format.length()),
data.labels_key.prometheus_format.data()),
arena));
data.labels_key.prometheus_format.data()));
help_line = "";
}
}
@@ -972,7 +916,7 @@ struct Metric {
size_t histogram_family_idx = 0;
for (const auto &[name, family] : get_histogram_families()) {
auto help_line = format(
arena, "%s# HELP %.*s %.*s\n# TYPE %.*s histogram",
plan.arena, "%s# HELP %.*s %.*s\n# TYPE %.*s histogram",
is_first_static ? "" : "\n", static_cast<int>(name.length()),
name.data(), static_cast<int>(family->help.length()),
family->help.data(), static_cast<int>(name.length()), name.data());
@@ -986,42 +930,38 @@ struct Metric {
// Static text for explicit buckets
for (size_t i = 0; i < data.bucket_count; ++i) {
auto bucket_value = static_format(arena, family->buckets[i]);
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(arena_copy_string(
format(arena, "%.*s\n%.*s_bucket%.*s ",
plan.static_text.push_back(
format(plan.arena, "%.*s\n%.*s_bucket%.*s ",
static_cast<int>(help_line.size()), help_line.data(),
static_cast<int>(name.length()), name.data(),
static_cast<int>(labels.length()), labels.data()),
arena));
static_cast<int>(labels.length()), labels.data()));
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(arena_copy_string(
format(arena, "\n%.*s_bucket%.*s ", static_cast<int>(name.length()),
name.data(), static_cast<int>(inf_labels.length()),
inf_labels.data()),
arena));
plan.static_text.push_back(
format(plan.arena, "\n%.*s_bucket%.*s ",
static_cast<int>(name.length()), name.data(),
static_cast<int>(inf_labels.length()), inf_labels.data()));
// Static text for sum
plan.static_text.push_back(arena_copy_string(
format(arena, "\n%.*s_sum%.*s ", static_cast<int>(name.length()),
name.data(),
plan.static_text.push_back(
format(plan.arena, "\n%.*s_sum%.*s ",
static_cast<int>(name.length()), name.data(),
static_cast<int>(data.labels_key.prometheus_format.length()),
data.labels_key.prometheus_format.data()),
arena));
data.labels_key.prometheus_format.data()));
// Static text for count
plan.static_text.push_back(arena_copy_string(
format(arena, "\n%.*s_count%.*s ", static_cast<int>(name.length()),
name.data(),
plan.static_text.push_back(
format(plan.arena, "\n%.*s_count%.*s ",
static_cast<int>(name.length()), name.data(),
static_cast<int>(data.labels_key.prometheus_format.length()),
data.labels_key.prometheus_format.data()),
arena));
data.labels_key.prometheus_format.data()));
}
}
@@ -1149,7 +1089,7 @@ struct Metric {
// Build label sets once for reuse in both phases
static LabelSets build_label_sets(ArenaAllocator &arena) {
LabelSets label_sets;
LabelSets label_sets{arena};
// Build counter data with pre-resolved pointers
for (const auto &[name, family] : Metric::get_counter_families()) {
@@ -1170,9 +1110,9 @@ struct Metric {
}
// Pre-resolve all pointers for each label set
std::vector<CounterLabelData> family_data;
ArenaVector<CounterLabelData> family_data{&arena};
for (const auto &labels_key : all_labels) {
CounterLabelData data(labels_key);
CounterLabelData data{labels_key, arena};
// Pre-resolve thread-local state pointers
for (const auto &[thread_id, per_thread] : family->per_thread_state) {
@@ -1197,7 +1137,7 @@ struct Metric {
// Build gauge data with pre-resolved pointers
for (const auto &[name, family] : Metric::get_gauge_families()) {
std::vector<GaugeLabelData> family_data;
ArenaVector<GaugeLabelData> family_data{&arena};
// Gauges iterate directly over instances
for (const auto &[labels_key, instance] : family->instances) {
@@ -1228,9 +1168,9 @@ struct Metric {
}
// Pre-resolve all pointers for each label set
std::vector<HistogramLabelData> family_data;
ArenaVector<HistogramLabelData> family_data{&arena};
for (const auto &labels_key : all_labels) {
HistogramLabelData data(labels_key);
HistogramLabelData data(labels_key, arena);
data.bucket_count = family->buckets.size(); // Cache bucket count
// Pre-resolve thread-local state pointers
@@ -1594,141 +1534,13 @@ union MetricValue {
uint64_t as_uint64;
};
// Legacy function kept for reference - will be replaced
static ArenaVector<MetricValue>
compute_metric_values_legacy(ArenaAllocator &arena,
const Metric::LabelSets &label_sets) {
ArenaVector<MetricValue> values(&arena);
// Compute counter values - ITERATION ORDER MUST MATCH FORMAT PHASE
size_t counter_family_idx = 0;
for (const auto &[name, family] : Metric::get_counter_families()) {
// Callback values
for (const auto &[labels_key, callback] : family->callbacks) {
auto value = callback();
values.push_back({.as_double = value});
}
// Use pre-computed data with resolved pointers - no hash lookups!
const auto &family_data = label_sets.counter_data[counter_family_idx++];
for (const auto &data : family_data) {
double total_value = 0.0;
// Sum thread-local values using pre-resolved pointers
for (auto *state_ptr : data.thread_states) {
// Atomic read to match atomic store in Counter::inc()
double value;
__atomic_load(&state_ptr->value, &value, __ATOMIC_RELAXED);
total_value += value;
}
// Add global accumulated value using pre-resolved pointer
if (data.global_state) {
total_value += data.global_state->value;
}
values.push_back({.as_double = total_value});
}
}
// Compute gauge values - ITERATION ORDER MUST MATCH FORMAT PHASE
size_t gauge_family_idx = 0;
for (const auto &[name, family] : Metric::get_gauge_families()) {
// Callback values
for (const auto &[labels_key, callback] : family->callbacks) {
auto value = callback();
values.push_back({.as_double = value});
}
// Use pre-computed data with resolved pointers - no hash lookups!
const auto &family_data = label_sets.gauge_data[gauge_family_idx++];
for (const auto &data : family_data) {
auto value = std::bit_cast<double>(
data.instance_state->value.load(std::memory_order_relaxed));
values.push_back({.as_double = value});
}
}
// Compute histogram values - ITERATION ORDER MUST MATCH FORMAT PHASE
size_t histogram_family_idx = 0;
for ([[maybe_unused]] const auto &[_name, _family] :
Metric::get_histogram_families()) {
// Use pre-computed data with resolved pointers - no hash lookups!
const auto &family_data = label_sets.histogram_data[histogram_family_idx++];
for (const auto &data : family_data) {
size_t bucket_count = data.bucket_count; // Use cached bucket count
uint64_t *total_counts_data = arena.allocate<uint64_t>(bucket_count);
std::memset(total_counts_data, 0, bucket_count * sizeof(uint64_t));
std::span<uint64_t> total_counts(total_counts_data, bucket_count);
double total_sum = 0.0;
uint64_t total_observations = 0;
// Sum thread-local values using pre-resolved pointers
for (auto *instance : data.thread_states) {
// Extract data under lock - minimize critical section
uint64_t *counts_snapshot = arena.allocate<uint64_t>(bucket_count);
double sum_snapshot;
uint64_t observations_snapshot;
{
std::lock_guard<std::mutex> lock(instance->mutex);
for (size_t i = 0; i < instance->counts.size(); ++i) {
counts_snapshot[i] = instance->counts[i];
}
sum_snapshot = instance->sum;
observations_snapshot = instance->observations;
}
// Add to totals
for (size_t i = 0; i < bucket_count; ++i) {
total_counts[i] += counts_snapshot[i];
}
total_sum += sum_snapshot;
total_observations += observations_snapshot;
}
// Add global accumulated value using pre-resolved pointer
if (data.global_state) {
auto *global_state = data.global_state;
for (size_t i = 0; i < global_state->counts.size(); ++i) {
total_counts[i] += global_state->counts[i];
}
total_sum += global_state->sum;
total_observations += global_state->observations;
}
// Store histogram values
// Store explicit bucket counts
for (size_t i = 0; i < total_counts.size(); ++i) {
values.push_back({.as_uint64 = total_counts[i]});
}
// Store +Inf bucket (total observations)
values.push_back({.as_uint64 = total_observations});
// Store sum
values.push_back({.as_double = total_sum});
// Store count
values.push_back({.as_uint64 = total_observations});
}
}
return values;
}
// Forward declaration
std::span<std::string_view> render_legacy(ArenaAllocator &arena);
// New three-phase render implementation
std::span<std::string_view> render(ArenaAllocator &arena) {
// Hold lock throughout all phases to prevent registry changes
std::unique_lock<std::mutex> _{Metric::mutex};
// Build label sets once for all phases
Metric::LabelSets label_sets = Metric::build_label_sets(arena);
// Phase 1: Compile - generate static text and instructions
Metric::RenderPlan plan = Metric::compile_render_plan(arena, label_sets);
Metric::RenderPlan plan = Metric::compile_render_plan();
// Phase 2: Execute - run instructions and generate dynamic text
ArenaVector<std::string_view> dynamic_text =