More precompute
This commit is contained in:
367
src/metric.cpp
367
src/metric.cpp
@@ -585,6 +585,152 @@ struct Metric {
|
|||||||
result.p = ptr;
|
result.p = ptr;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pre-computed data structures with resolved pointers to eliminate hash
|
||||||
|
// lookups
|
||||||
|
struct CounterLabelData {
|
||||||
|
LabelsKey labels_key;
|
||||||
|
std::vector<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) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GaugeLabelData {
|
||||||
|
LabelsKey labels_key;
|
||||||
|
Gauge::State *instance_state; // Direct pointer to gauge instance
|
||||||
|
|
||||||
|
GaugeLabelData(const LabelsKey &key)
|
||||||
|
: labels_key(key), instance_state(nullptr) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HistogramLabelData {
|
||||||
|
LabelsKey labels_key;
|
||||||
|
std::vector<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) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Build label sets once for reuse in both phases
|
||||||
|
static LabelSets build_label_sets(ArenaAllocator &arena) {
|
||||||
|
LabelSets label_sets;
|
||||||
|
|
||||||
|
// Build counter data with pre-resolved pointers
|
||||||
|
for (const auto &[name, family] : Metric::get_counter_families()) {
|
||||||
|
// Collect all unique labels first
|
||||||
|
std::set<LabelsKey, std::less<LabelsKey>, ArenaStlAllocator<LabelsKey>>
|
||||||
|
all_labels{ArenaStlAllocator<LabelsKey>(&arena)};
|
||||||
|
|
||||||
|
for (const auto &[thread_id, per_thread] : family->per_thread_state) {
|
||||||
|
for (const auto &[labels_key, instance] : per_thread.instances) {
|
||||||
|
all_labels.insert(labels_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &[labels_key, global_state] :
|
||||||
|
family->global_accumulated_values) {
|
||||||
|
if (global_state) {
|
||||||
|
all_labels.insert(labels_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-resolve all pointers for each label set
|
||||||
|
std::vector<CounterLabelData> family_data;
|
||||||
|
for (const auto &labels_key : all_labels) {
|
||||||
|
CounterLabelData data(labels_key);
|
||||||
|
|
||||||
|
// Pre-resolve thread-local state pointers
|
||||||
|
for (const auto &[thread_id, per_thread] : family->per_thread_state) {
|
||||||
|
auto it = per_thread.instances.find(labels_key);
|
||||||
|
if (it != per_thread.instances.end()) {
|
||||||
|
data.thread_states.push_back(it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-resolve global accumulated state pointer
|
||||||
|
auto global_it = family->global_accumulated_values.find(labels_key);
|
||||||
|
data.global_state =
|
||||||
|
(global_it != family->global_accumulated_values.end() &&
|
||||||
|
global_it->second)
|
||||||
|
? global_it->second
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
|
family_data.push_back(std::move(data));
|
||||||
|
}
|
||||||
|
label_sets.counter_data.push_back(std::move(family_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build gauge data with pre-resolved pointers
|
||||||
|
for (const auto &[name, family] : Metric::get_gauge_families()) {
|
||||||
|
std::vector<GaugeLabelData> family_data;
|
||||||
|
|
||||||
|
// Gauges iterate directly over instances
|
||||||
|
for (const auto &[labels_key, instance] : family->instances) {
|
||||||
|
GaugeLabelData data(labels_key);
|
||||||
|
data.instance_state = instance;
|
||||||
|
family_data.push_back(std::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
label_sets.gauge_data.push_back(std::move(family_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build histogram data with pre-resolved pointers
|
||||||
|
for (const auto &[name, family] : Metric::get_histogram_families()) {
|
||||||
|
// Collect all unique labels first
|
||||||
|
std::set<LabelsKey, std::less<LabelsKey>, ArenaStlAllocator<LabelsKey>>
|
||||||
|
all_labels{ArenaStlAllocator<LabelsKey>(&arena)};
|
||||||
|
|
||||||
|
for (const auto &[thread_id, per_thread] : family->per_thread_state) {
|
||||||
|
for (const auto &[labels_key, instance] : per_thread.instances) {
|
||||||
|
all_labels.insert(labels_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &[labels_key, global_state] :
|
||||||
|
family->global_accumulated_values) {
|
||||||
|
if (global_state) {
|
||||||
|
all_labels.insert(labels_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-resolve all pointers for each label set
|
||||||
|
std::vector<HistogramLabelData> family_data;
|
||||||
|
for (const auto &labels_key : all_labels) {
|
||||||
|
HistogramLabelData data(labels_key);
|
||||||
|
data.bucket_count = family->buckets.size(); // Cache bucket count
|
||||||
|
|
||||||
|
// Pre-resolve thread-local state pointers
|
||||||
|
for (const auto &[thread_id, per_thread] : family->per_thread_state) {
|
||||||
|
auto it = per_thread.instances.find(labels_key);
|
||||||
|
if (it != per_thread.instances.end()) {
|
||||||
|
data.thread_states.push_back(it->second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-resolve global accumulated state pointer
|
||||||
|
auto global_it = family->global_accumulated_values.find(labels_key);
|
||||||
|
data.global_state =
|
||||||
|
(global_it != family->global_accumulated_values.end() &&
|
||||||
|
global_it->second)
|
||||||
|
? global_it->second
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
|
family_data.push_back(std::move(data));
|
||||||
|
}
|
||||||
|
label_sets.histogram_data.push_back(std::move(family_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
return label_sets;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Counter::Counter() = default;
|
Counter::Counter() = default;
|
||||||
@@ -928,79 +1074,10 @@ union MetricValue {
|
|||||||
uint64_t as_uint64;
|
uint64_t as_uint64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Label sets for each family type, built once and reused
|
|
||||||
struct LabelSets {
|
|
||||||
std::vector<
|
|
||||||
std::set<LabelsKey, std::less<LabelsKey>, ArenaStlAllocator<LabelsKey>>>
|
|
||||||
counter_labels;
|
|
||||||
std::vector<
|
|
||||||
std::set<LabelsKey, std::less<LabelsKey>, ArenaStlAllocator<LabelsKey>>>
|
|
||||||
gauge_labels;
|
|
||||||
std::vector<
|
|
||||||
std::set<LabelsKey, std::less<LabelsKey>, ArenaStlAllocator<LabelsKey>>>
|
|
||||||
histogram_labels;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Build label sets once for reuse in both phases
|
|
||||||
static LabelSets build_label_sets(ArenaAllocator &arena) {
|
|
||||||
LabelSets label_sets;
|
|
||||||
|
|
||||||
// Build counter label sets
|
|
||||||
for (const auto &[name, family] : Metric::get_counter_families()) {
|
|
||||||
std::set<LabelsKey, std::less<LabelsKey>, ArenaStlAllocator<LabelsKey>>
|
|
||||||
all_labels{ArenaStlAllocator<LabelsKey>(&arena)};
|
|
||||||
|
|
||||||
for (const auto &[thread_id, per_thread] : family->per_thread_state) {
|
|
||||||
for (const auto &[labels_key, instance] : per_thread.instances) {
|
|
||||||
all_labels.insert(labels_key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const auto &[labels_key, global_state] :
|
|
||||||
family->global_accumulated_values) {
|
|
||||||
if (global_state) {
|
|
||||||
all_labels.insert(labels_key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label_sets.counter_labels.push_back(std::move(all_labels));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build gauge label sets (none needed - gauges iterate directly over
|
|
||||||
// instances)
|
|
||||||
for (const auto &[name, family] : Metric::get_gauge_families()) {
|
|
||||||
(void)name;
|
|
||||||
(void)family; // Suppress unused variable warnings
|
|
||||||
std::set<LabelsKey, std::less<LabelsKey>, ArenaStlAllocator<LabelsKey>>
|
|
||||||
empty_set{ArenaStlAllocator<LabelsKey>(&arena)};
|
|
||||||
label_sets.gauge_labels.push_back(std::move(empty_set));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build histogram label sets
|
|
||||||
for (const auto &[name, family] : Metric::get_histogram_families()) {
|
|
||||||
std::set<LabelsKey, std::less<LabelsKey>, ArenaStlAllocator<LabelsKey>>
|
|
||||||
all_labels{ArenaStlAllocator<LabelsKey>(&arena)};
|
|
||||||
|
|
||||||
for (const auto &[thread_id, per_thread] : family->per_thread_state) {
|
|
||||||
for (const auto &[labels_key, instance] : per_thread.instances) {
|
|
||||||
all_labels.insert(labels_key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const auto &[labels_key, global_state] :
|
|
||||||
family->global_accumulated_values) {
|
|
||||||
if (global_state) {
|
|
||||||
all_labels.insert(labels_key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label_sets.histogram_labels.push_back(std::move(all_labels));
|
|
||||||
}
|
|
||||||
|
|
||||||
return label_sets;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 1: Compute all metric values in deterministic order
|
// Phase 1: Compute all metric values in deterministic order
|
||||||
static ArenaVector<MetricValue>
|
static ArenaVector<MetricValue>
|
||||||
compute_metric_values(ArenaAllocator &arena, const LabelSets &label_sets) {
|
compute_metric_values(ArenaAllocator &arena,
|
||||||
|
const Metric::LabelSets &label_sets) {
|
||||||
ArenaVector<MetricValue> values(&arena);
|
ArenaVector<MetricValue> values(&arena);
|
||||||
|
|
||||||
// Compute counter values - ITERATION ORDER MUST MATCH FORMAT PHASE
|
// Compute counter values - ITERATION ORDER MUST MATCH FORMAT PHASE
|
||||||
@@ -1012,29 +1089,22 @@ compute_metric_values(ArenaAllocator &arena, const LabelSets &label_sets) {
|
|||||||
values.push_back({.as_double = value});
|
values.push_back({.as_double = value});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use pre-built label sets
|
// Use pre-computed data with resolved pointers - no hash lookups!
|
||||||
const auto &all_labels = label_sets.counter_labels[counter_family_idx++];
|
const auto &family_data = label_sets.counter_data[counter_family_idx++];
|
||||||
|
for (const auto &data : family_data) {
|
||||||
// Iterate by label, lookup per thread (O(1) unordered_map lookup)
|
|
||||||
for (const auto &labels_key : all_labels) {
|
|
||||||
double total_value = 0.0;
|
double total_value = 0.0;
|
||||||
|
|
||||||
// Sum thread-local values for this label set
|
// Sum thread-local values using pre-resolved pointers
|
||||||
for (const auto &[thread_id, per_thread] : family->per_thread_state) {
|
for (auto *state_ptr : data.thread_states) {
|
||||||
auto it = per_thread.instances.find(labels_key);
|
// Atomic read to match atomic store in Counter::inc()
|
||||||
if (it != per_thread.instances.end()) {
|
double value;
|
||||||
// Atomic read to match atomic store in Counter::inc()
|
__atomic_load(&state_ptr->value, &value, __ATOMIC_RELAXED);
|
||||||
double value;
|
total_value += value;
|
||||||
__atomic_load(&it->second->value, &value, __ATOMIC_RELAXED);
|
|
||||||
total_value += value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add global accumulated value for this label set
|
// Add global accumulated value using pre-resolved pointer
|
||||||
auto global_it = family->global_accumulated_values.find(labels_key);
|
if (data.global_state) {
|
||||||
if (global_it != family->global_accumulated_values.end() &&
|
total_value += data.global_state->value;
|
||||||
global_it->second) {
|
|
||||||
total_value += global_it->second->value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
values.push_back({.as_double = total_value});
|
values.push_back({.as_double = total_value});
|
||||||
@@ -1042,6 +1112,7 @@ compute_metric_values(ArenaAllocator &arena, const LabelSets &label_sets) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute gauge values - ITERATION ORDER MUST MATCH FORMAT PHASE
|
// Compute gauge values - ITERATION ORDER MUST MATCH FORMAT PHASE
|
||||||
|
size_t gauge_family_idx = 0;
|
||||||
for (const auto &[name, family] : Metric::get_gauge_families()) {
|
for (const auto &[name, family] : Metric::get_gauge_families()) {
|
||||||
// Callback values
|
// Callback values
|
||||||
for (const auto &[labels_key, callback] : family->callbacks) {
|
for (const auto &[labels_key, callback] : family->callbacks) {
|
||||||
@@ -1049,25 +1120,23 @@ compute_metric_values(ArenaAllocator &arena, const LabelSets &label_sets) {
|
|||||||
values.push_back({.as_double = value});
|
values.push_back({.as_double = value});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instance values (gauges don't aggregate, just direct values)
|
// Use pre-computed data with resolved pointers - no hash lookups!
|
||||||
for (const auto &[labels_key, instance] : family->instances) {
|
const auto &family_data = label_sets.gauge_data[gauge_family_idx++];
|
||||||
|
for (const auto &data : family_data) {
|
||||||
auto value = std::bit_cast<double>(
|
auto value = std::bit_cast<double>(
|
||||||
instance->value.load(std::memory_order_relaxed));
|
data.instance_state->value.load(std::memory_order_relaxed));
|
||||||
values.push_back({.as_double = value});
|
values.push_back({.as_double = value});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute histogram values - ITERATION ORDER MUST MATCH FORMAT PHASE
|
// Compute histogram values - ITERATION ORDER MUST MATCH FORMAT PHASE
|
||||||
size_t histogram_family_idx = 0;
|
size_t histogram_family_idx = 0;
|
||||||
for (const auto &[name, family] : Metric::get_histogram_families()) {
|
for (const auto &family_pair : Metric::get_histogram_families()) {
|
||||||
// Use pre-built label sets
|
// Use pre-computed data with resolved pointers - no hash lookups!
|
||||||
const auto &all_labels =
|
const auto &family_data = label_sets.histogram_data[histogram_family_idx++];
|
||||||
label_sets.histogram_labels[histogram_family_idx++];
|
|
||||||
|
|
||||||
// Iterate by label, lookup per thread (O(1) unordered_map lookup)
|
for (const auto &data : family_data) {
|
||||||
for (const auto &labels_key : all_labels) {
|
size_t bucket_count = data.bucket_count; // Use cached bucket count
|
||||||
// Get bucket count from family config or first instance
|
|
||||||
size_t bucket_count = family->buckets.size();
|
|
||||||
|
|
||||||
ArenaVector<uint64_t> total_counts(&arena);
|
ArenaVector<uint64_t> total_counts(&arena);
|
||||||
for (size_t i = 0; i < bucket_count; ++i) {
|
for (size_t i = 0; i < bucket_count; ++i) {
|
||||||
@@ -1076,40 +1145,33 @@ compute_metric_values(ArenaAllocator &arena, const LabelSets &label_sets) {
|
|||||||
double total_sum = 0.0;
|
double total_sum = 0.0;
|
||||||
uint64_t total_observations = 0;
|
uint64_t total_observations = 0;
|
||||||
|
|
||||||
// Sum thread-local values for this label set
|
// Sum thread-local values using pre-resolved pointers
|
||||||
for (const auto &[thread_id, per_thread] : family->per_thread_state) {
|
for (auto *instance : data.thread_states) {
|
||||||
auto it = per_thread.instances.find(labels_key);
|
// Extract data under lock - minimize critical section
|
||||||
if (it != per_thread.instances.end()) {
|
uint64_t *counts_snapshot = arena.allocate<uint64_t>(bucket_count);
|
||||||
auto *instance = it->second;
|
double sum_snapshot;
|
||||||
|
uint64_t observations_snapshot;
|
||||||
|
|
||||||
// Extract data under lock - minimize critical section
|
{
|
||||||
uint64_t *counts_snapshot = arena.allocate<uint64_t>(bucket_count);
|
std::lock_guard<std::mutex> lock(instance->mutex);
|
||||||
double sum_snapshot;
|
for (size_t i = 0; i < instance->counts.size(); ++i) {
|
||||||
uint64_t observations_snapshot;
|
counts_snapshot[i] = instance->counts[i];
|
||||||
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
sum_snapshot = instance->sum;
|
||||||
// Add to totals
|
observations_snapshot = instance->observations;
|
||||||
for (size_t i = 0; i < bucket_count; ++i) {
|
|
||||||
total_counts[i] += counts_snapshot[i];
|
|
||||||
}
|
|
||||||
total_sum += sum_snapshot;
|
|
||||||
total_observations += observations_snapshot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 for this label set
|
// Add global accumulated value using pre-resolved pointer
|
||||||
auto global_it = family->global_accumulated_values.find(labels_key);
|
if (data.global_state) {
|
||||||
if (global_it != family->global_accumulated_values.end() &&
|
auto *global_state = data.global_state;
|
||||||
global_it->second) {
|
|
||||||
auto *global_state = global_it->second;
|
|
||||||
for (size_t i = 0; i < global_state->counts.size(); ++i) {
|
for (size_t i = 0; i < global_state->counts.size(); ++i) {
|
||||||
total_counts[i] += global_state->counts[i];
|
total_counts[i] += global_state->counts[i];
|
||||||
}
|
}
|
||||||
@@ -1140,7 +1202,7 @@ std::span<std::string_view> render(ArenaAllocator &arena) {
|
|||||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||||
|
|
||||||
// Build label sets once for both phases
|
// Build label sets once for both phases
|
||||||
LabelSets label_sets = build_label_sets(arena);
|
Metric::LabelSets label_sets = Metric::build_label_sets(arena);
|
||||||
|
|
||||||
// Phase 1: Compute all metric values
|
// Phase 1: Compute all metric values
|
||||||
ArenaVector<MetricValue> metric_values =
|
ArenaVector<MetricValue> metric_values =
|
||||||
@@ -1232,15 +1294,15 @@ std::span<std::string_view> render(ArenaAllocator &arena) {
|
|||||||
value));
|
value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use pre-built label sets (same as compute phase)
|
// Use pre-computed data (same as compute phase)
|
||||||
const auto &all_labels = label_sets.counter_labels[counter_family_idx++];
|
const auto &family_data = label_sets.counter_data[counter_family_idx++];
|
||||||
|
|
||||||
// Format counter values using pre-computed values
|
// Format counter values using pre-computed values
|
||||||
for (const auto &labels_key : all_labels) {
|
for (const auto &data : family_data) {
|
||||||
auto total_value = next_value++->as_double;
|
auto total_value = next_value++->as_double;
|
||||||
labels_sv.clear();
|
labels_sv.clear();
|
||||||
for (size_t i = 0; i < labels_key.labels.size(); ++i) {
|
for (size_t i = 0; i < data.labels_key.labels.size(); ++i) {
|
||||||
labels_sv.push_back(labels_key.labels[i]);
|
labels_sv.push_back(data.labels_key.labels[i]);
|
||||||
}
|
}
|
||||||
auto labels = format_labels(labels_sv);
|
auto labels = format_labels(labels_sv);
|
||||||
output.push_back(format(arena, "%.*s%.*s %.17g\n",
|
output.push_back(format(arena, "%.*s%.*s %.17g\n",
|
||||||
@@ -1251,6 +1313,7 @@ std::span<std::string_view> render(ArenaAllocator &arena) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Format gauges - ITERATION ORDER MUST MATCH COMPUTE PHASE
|
// Format gauges - ITERATION ORDER MUST MATCH COMPUTE PHASE
|
||||||
|
size_t gauge_family_idx = 0;
|
||||||
for (const auto &[name, family] : Metric::get_gauge_families()) {
|
for (const auto &[name, family] : Metric::get_gauge_families()) {
|
||||||
output.push_back(format(arena, "# HELP %.*s %.*s\n",
|
output.push_back(format(arena, "# HELP %.*s %.*s\n",
|
||||||
static_cast<int>(name.length()), name.data(),
|
static_cast<int>(name.length()), name.data(),
|
||||||
@@ -1276,12 +1339,13 @@ std::span<std::string_view> render(ArenaAllocator &arena) {
|
|||||||
value));
|
value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format instance values
|
// Use pre-computed data (same as compute phase)
|
||||||
for (const auto &[labels_key, instance] : family->instances) {
|
const auto &family_data = label_sets.gauge_data[gauge_family_idx++];
|
||||||
|
for (const auto &data : family_data) {
|
||||||
auto value = next_value++->as_double;
|
auto value = next_value++->as_double;
|
||||||
labels_sv.clear();
|
labels_sv.clear();
|
||||||
for (size_t i = 0; i < labels_key.labels.size(); ++i) {
|
for (size_t i = 0; i < data.labels_key.labels.size(); ++i) {
|
||||||
labels_sv.push_back(labels_key.labels[i]);
|
labels_sv.push_back(data.labels_key.labels[i]);
|
||||||
}
|
}
|
||||||
auto labels = format_labels(labels_sv);
|
auto labels = format_labels(labels_sv);
|
||||||
output.push_back(format(arena, "%.*s%.*s %.17g\n",
|
output.push_back(format(arena, "%.*s%.*s %.17g\n",
|
||||||
@@ -1301,24 +1365,23 @@ std::span<std::string_view> render(ArenaAllocator &arena) {
|
|||||||
output.push_back(format(arena, "# TYPE %.*s histogram\n",
|
output.push_back(format(arena, "# TYPE %.*s histogram\n",
|
||||||
static_cast<int>(name.length()), name.data()));
|
static_cast<int>(name.length()), name.data()));
|
||||||
|
|
||||||
// Use pre-built label sets (same as compute phase)
|
// Use pre-computed data (same as compute phase)
|
||||||
const auto &all_labels =
|
const auto &family_data = label_sets.histogram_data[histogram_family_idx++];
|
||||||
label_sets.histogram_labels[histogram_family_idx++];
|
|
||||||
|
|
||||||
ArenaVector<std::pair<std::string_view, std::string_view>> bucket_labels_sv(
|
ArenaVector<std::pair<std::string_view, std::string_view>> bucket_labels_sv(
|
||||||
&arena);
|
&arena);
|
||||||
|
|
||||||
// Format histogram data using pre-computed values
|
// Format histogram data using pre-computed values
|
||||||
for (const auto &labels_key : all_labels) {
|
for (const auto &data : family_data) {
|
||||||
// Get bucket thresholds from family config
|
// Get bucket count from pre-computed data
|
||||||
size_t bucket_count = family->buckets.size();
|
size_t bucket_count = data.bucket_count;
|
||||||
|
|
||||||
// Format explicit bucket counts
|
// Format explicit bucket counts
|
||||||
for (size_t i = 0; i < bucket_count; ++i) {
|
for (size_t i = 0; i < bucket_count; ++i) {
|
||||||
auto count = next_value++->as_uint64;
|
auto count = next_value++->as_uint64;
|
||||||
bucket_labels_sv.clear();
|
bucket_labels_sv.clear();
|
||||||
for (size_t j = 0; j < labels_key.labels.size(); ++j) {
|
for (size_t j = 0; j < data.labels_key.labels.size(); ++j) {
|
||||||
bucket_labels_sv.push_back(labels_key.labels[j]);
|
bucket_labels_sv.push_back(data.labels_key.labels[j]);
|
||||||
}
|
}
|
||||||
bucket_labels_sv.push_back(
|
bucket_labels_sv.push_back(
|
||||||
{"le", static_format(arena, family->buckets[i])});
|
{"le", static_format(arena, family->buckets[i])});
|
||||||
@@ -1332,8 +1395,8 @@ std::span<std::string_view> render(ArenaAllocator &arena) {
|
|||||||
// Format +Inf bucket
|
// Format +Inf bucket
|
||||||
auto observations = next_value++->as_uint64;
|
auto observations = next_value++->as_uint64;
|
||||||
bucket_labels_sv.clear();
|
bucket_labels_sv.clear();
|
||||||
for (size_t j = 0; j < labels_key.labels.size(); ++j) {
|
for (size_t j = 0; j < data.labels_key.labels.size(); ++j) {
|
||||||
bucket_labels_sv.push_back(labels_key.labels[j]);
|
bucket_labels_sv.push_back(data.labels_key.labels[j]);
|
||||||
}
|
}
|
||||||
bucket_labels_sv.push_back({"le", "+Inf"});
|
bucket_labels_sv.push_back({"le", "+Inf"});
|
||||||
auto inf_labels = format_labels(bucket_labels_sv);
|
auto inf_labels = format_labels(bucket_labels_sv);
|
||||||
@@ -1345,8 +1408,8 @@ std::span<std::string_view> render(ArenaAllocator &arena) {
|
|||||||
// Format sum
|
// Format sum
|
||||||
auto sum = next_value++->as_double;
|
auto sum = next_value++->as_double;
|
||||||
bucket_labels_sv.clear();
|
bucket_labels_sv.clear();
|
||||||
for (size_t j = 0; j < labels_key.labels.size(); ++j) {
|
for (size_t j = 0; j < data.labels_key.labels.size(); ++j) {
|
||||||
bucket_labels_sv.push_back(labels_key.labels[j]);
|
bucket_labels_sv.push_back(data.labels_key.labels[j]);
|
||||||
}
|
}
|
||||||
auto labels = format_labels(bucket_labels_sv);
|
auto labels = format_labels(bucket_labels_sv);
|
||||||
output.push_back(format(
|
output.push_back(format(
|
||||||
|
|||||||
Reference in New Issue
Block a user