Accept initializer_list, span, and string_view in api
This commit is contained in:
122
src/metric.cpp
122
src/metric.cpp
@@ -56,22 +56,22 @@ namespace metric {
|
||||
|
||||
// Validation helper that works in both debug and release builds
|
||||
static void validate_or_abort(bool condition, const char *message,
|
||||
const char *value) {
|
||||
std::string_view value) {
|
||||
if (!condition) {
|
||||
std::fprintf(stderr, "WeaselDB metric validation failed: %s: '%s'\n",
|
||||
message, value);
|
||||
std::fprintf(stderr, "WeaselDB metric validation failed: %s: '%.*s'\n",
|
||||
message, static_cast<int>(value.size()), value.data());
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to copy a string into arena memory
|
||||
static std::string_view arena_copy_string(const std::string &str,
|
||||
static std::string_view arena_copy_string(std::string_view str,
|
||||
ArenaAllocator &arena) {
|
||||
if (str.empty()) {
|
||||
return std::string_view{};
|
||||
}
|
||||
char *copied = arena.allocate<char>(str.size() + 1);
|
||||
std::memcpy(copied, str.c_str(), str.size());
|
||||
std::memcpy(copied, str.data(), str.size());
|
||||
copied[str.size()] = '\0';
|
||||
return std::string_view(copied, str.size());
|
||||
}
|
||||
@@ -81,15 +81,14 @@ static std::string_view arena_copy_string(const std::string &str,
|
||||
struct LabelsKey {
|
||||
ArenaVector<std::pair<std::string_view, std::string_view>> labels;
|
||||
|
||||
LabelsKey(const std::vector<std::pair<std::string, std::string>> &l,
|
||||
LabelsKey(std::span<const std::pair<std::string_view, std::string_view>> l,
|
||||
ArenaAllocator &arena)
|
||||
: labels(&arena) {
|
||||
// Copy and validate all label keys and values into arena
|
||||
for (const auto &[key, value] : l) {
|
||||
validate_or_abort(is_valid_label_key(key), "invalid label key",
|
||||
key.c_str());
|
||||
validate_or_abort(is_valid_label_key(key), "invalid label key", key);
|
||||
validate_or_abort(is_valid_label_value(value), "invalid label value",
|
||||
value.c_str());
|
||||
value);
|
||||
|
||||
auto key_view = arena_copy_string(key, arena);
|
||||
auto value_view = arena_copy_string(value, arena);
|
||||
@@ -382,19 +381,18 @@ struct Metric {
|
||||
|
||||
static Counter create_counter_instance(
|
||||
Family<Counter> *family,
|
||||
const std::vector<std::pair<std::string, std::string>> &labels) {
|
||||
std::span<const std::pair<std::string_view, std::string_view>> labels) {
|
||||
// Force thread_local initialization
|
||||
(void)thread_init;
|
||||
|
||||
std::unique_lock<std::mutex> _{mutex};
|
||||
LabelsKey key{labels, get_thread_local_arena()};
|
||||
LabelsKey key{labels, get_global_arena()};
|
||||
|
||||
// Validate that labels aren't already registered as callback
|
||||
validate_or_abort(
|
||||
family->p->callbacks.find(key) == family->p->callbacks.end(),
|
||||
"labels already registered as callback",
|
||||
key.labels.empty() ? "(no labels)"
|
||||
: std::string(key.labels[0].first).c_str());
|
||||
validate_or_abort(family->p->callbacks.find(key) ==
|
||||
family->p->callbacks.end(),
|
||||
"labels already registered as callback",
|
||||
key.labels.empty() ? "(no labels)" : key.labels[0].first);
|
||||
|
||||
// Ensure thread state exists
|
||||
auto thread_id = std::this_thread::get_id();
|
||||
@@ -419,16 +417,15 @@ struct Metric {
|
||||
|
||||
static Gauge create_gauge_instance(
|
||||
Family<Gauge> *family,
|
||||
const std::vector<std::pair<std::string, std::string>> &labels) {
|
||||
std::span<const std::pair<std::string_view, std::string_view>> labels) {
|
||||
std::unique_lock<std::mutex> _{mutex};
|
||||
LabelsKey key{labels, get_global_arena()};
|
||||
|
||||
// Validate that labels aren't already registered as callback
|
||||
validate_or_abort(
|
||||
family->p->callbacks.find(key) == family->p->callbacks.end(),
|
||||
"labels already registered as callback",
|
||||
key.labels.empty() ? "(no labels)"
|
||||
: std::string(key.labels[0].first).c_str());
|
||||
validate_or_abort(family->p->callbacks.find(key) ==
|
||||
family->p->callbacks.end(),
|
||||
"labels already registered as callback",
|
||||
key.labels.empty() ? "(no labels)" : key.labels[0].first);
|
||||
|
||||
auto &ptr = family->p->instances[key];
|
||||
if (!ptr) {
|
||||
@@ -442,12 +439,12 @@ struct Metric {
|
||||
|
||||
static Histogram create_histogram_instance(
|
||||
Family<Histogram> *family,
|
||||
const std::vector<std::pair<std::string, std::string>> &labels) {
|
||||
std::span<const std::pair<std::string_view, std::string_view>> labels) {
|
||||
// Force thread_local initialization
|
||||
(void)thread_init;
|
||||
|
||||
std::unique_lock<std::mutex> _{mutex};
|
||||
LabelsKey key{labels, get_thread_local_arena()};
|
||||
LabelsKey key{labels, get_global_arena()};
|
||||
|
||||
// Ensure thread state exists
|
||||
auto thread_id = std::this_thread::get_id();
|
||||
@@ -512,7 +509,7 @@ void Counter::inc(double x) {
|
||||
// Validate monotonic property (counter never decreases)
|
||||
if (new_value < p->value) [[unlikely]] {
|
||||
validate_or_abort(false, "counter value overflow/wraparound detected",
|
||||
std::to_string(new_value).c_str());
|
||||
std::to_string(new_value));
|
||||
}
|
||||
|
||||
__atomic_store(&p->value, &new_value, __ATOMIC_RELAXED);
|
||||
@@ -605,25 +602,24 @@ template <> Family<Histogram>::Family() = default;
|
||||
|
||||
template <>
|
||||
Counter Family<Counter>::create(
|
||||
std::vector<std::pair<std::string, std::string>> labels) {
|
||||
std::span<const std::pair<std::string_view, std::string_view>> labels) {
|
||||
return Metric::create_counter_instance(this, labels);
|
||||
}
|
||||
|
||||
template <>
|
||||
Gauge Family<Gauge>::create(
|
||||
std::vector<std::pair<std::string, std::string>> labels) {
|
||||
std::span<const std::pair<std::string_view, std::string_view>> labels) {
|
||||
return Metric::create_gauge_instance(this, labels);
|
||||
}
|
||||
|
||||
template <>
|
||||
Histogram Family<Histogram>::create(
|
||||
std::vector<std::pair<std::string, std::string>> labels) {
|
||||
std::span<const std::pair<std::string_view, std::string_view>> labels) {
|
||||
return Metric::create_histogram_instance(this, labels);
|
||||
}
|
||||
|
||||
Family<Counter> create_counter(std::string name, std::string help) {
|
||||
validate_or_abort(is_valid_metric_name(name), "invalid counter name",
|
||||
name.c_str());
|
||||
Family<Counter> create_counter(std::string_view name, std::string_view help) {
|
||||
validate_or_abort(is_valid_metric_name(name), "invalid counter name", name);
|
||||
|
||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||
auto &global_arena = Metric::get_global_arena();
|
||||
@@ -640,17 +636,15 @@ Family<Counter> create_counter(std::string name, std::string help) {
|
||||
} else {
|
||||
validate_or_abort(
|
||||
familyPtr->help == help,
|
||||
"metric family already registered with different help text",
|
||||
name.c_str());
|
||||
"metric family already registered with different help text", name);
|
||||
}
|
||||
Family<Counter> family;
|
||||
family.p = familyPtr;
|
||||
return family;
|
||||
}
|
||||
|
||||
Family<Gauge> create_gauge(std::string name, std::string help) {
|
||||
validate_or_abort(is_valid_metric_name(name), "invalid gauge name",
|
||||
name.c_str());
|
||||
Family<Gauge> create_gauge(std::string_view name, std::string_view help) {
|
||||
validate_or_abort(is_valid_metric_name(name), "invalid gauge name", name);
|
||||
|
||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||
auto &global_arena = Metric::get_global_arena();
|
||||
@@ -667,18 +661,16 @@ Family<Gauge> create_gauge(std::string name, std::string help) {
|
||||
} else {
|
||||
validate_or_abort(
|
||||
familyPtr->help == help,
|
||||
"metric family already registered with different help text",
|
||||
name.c_str());
|
||||
"metric family already registered with different help text", name);
|
||||
}
|
||||
Family<Gauge> family;
|
||||
family.p = familyPtr;
|
||||
return family;
|
||||
}
|
||||
|
||||
Family<Histogram> create_histogram(std::string name, std::string help,
|
||||
Family<Histogram> create_histogram(std::string_view name, std::string_view help,
|
||||
std::span<const double> buckets) {
|
||||
validate_or_abort(is_valid_metric_name(name), "invalid histogram name",
|
||||
name.c_str());
|
||||
validate_or_abort(is_valid_metric_name(name), "invalid histogram name", name);
|
||||
|
||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||
auto &global_arena = Metric::get_global_arena();
|
||||
@@ -709,8 +701,7 @@ Family<Histogram> create_histogram(std::string name, std::string help,
|
||||
} else {
|
||||
validate_or_abort(
|
||||
family_ptr->help == help,
|
||||
"metric family already registered with different help text",
|
||||
name.c_str());
|
||||
"metric family already registered with different help text", name);
|
||||
std::vector<double> new_buckets_vec(buckets.begin(), buckets.end());
|
||||
std::sort(new_buckets_vec.begin(), new_buckets_vec.end());
|
||||
new_buckets_vec.erase(
|
||||
@@ -731,7 +722,7 @@ Family<Histogram> create_histogram(std::string name, std::string help,
|
||||
}
|
||||
validate_or_abort(buckets_match,
|
||||
"metric family already registered with different buckets",
|
||||
name.c_str());
|
||||
name);
|
||||
}
|
||||
Family<Histogram> family;
|
||||
family.p = family_ptr;
|
||||
@@ -740,9 +731,9 @@ Family<Histogram> create_histogram(std::string name, std::string help,
|
||||
|
||||
std::vector<double> linear_buckets(double start, double width, int count) {
|
||||
validate_or_abort(width > 0, "linear bucket width must be positive",
|
||||
std::to_string(width).c_str());
|
||||
std::to_string(width));
|
||||
validate_or_abort(count >= 0, "linear bucket count must be non-negative",
|
||||
std::to_string(count).c_str());
|
||||
std::to_string(count));
|
||||
|
||||
std::vector<double> buckets;
|
||||
buckets.reserve(count);
|
||||
@@ -757,11 +748,11 @@ std::vector<double> linear_buckets(double start, double width, int count) {
|
||||
std::vector<double> exponential_buckets(double start, double factor,
|
||||
int count) {
|
||||
validate_or_abort(start > 0, "exponential bucket start must be positive",
|
||||
std::to_string(start).c_str());
|
||||
std::to_string(start));
|
||||
validate_or_abort(factor > 1, "exponential bucket factor must be > 1",
|
||||
std::to_string(factor).c_str());
|
||||
std::to_string(factor));
|
||||
validate_or_abort(count >= 0, "exponential bucket count must be non-negative",
|
||||
std::to_string(count).c_str());
|
||||
std::to_string(count));
|
||||
|
||||
std::vector<double> buckets;
|
||||
buckets.reserve(count);
|
||||
@@ -777,7 +768,7 @@ std::vector<double> exponential_buckets(double start, double factor,
|
||||
|
||||
// Prometheus validation functions
|
||||
// Metric names must match [a-zA-Z_:][a-zA-Z0-9_:]*
|
||||
bool is_valid_metric_name(const std::string &name) {
|
||||
bool is_valid_metric_name(std::string_view name) {
|
||||
if (name.empty())
|
||||
return false;
|
||||
|
||||
@@ -799,7 +790,7 @@ bool is_valid_metric_name(const std::string &name) {
|
||||
}
|
||||
|
||||
// Label keys must match [a-zA-Z_][a-zA-Z0-9_]*
|
||||
bool is_valid_label_key(const std::string &key) {
|
||||
bool is_valid_label_key(std::string_view key) {
|
||||
if (key.empty())
|
||||
return false;
|
||||
|
||||
@@ -826,10 +817,10 @@ bool is_valid_label_key(const std::string &key) {
|
||||
}
|
||||
|
||||
// Label values can contain any UTF-8 characters (no specific restrictions)
|
||||
bool is_valid_label_value(const std::string &value) {
|
||||
bool is_valid_label_value(std::string_view value) {
|
||||
// Prometheus allows any UTF-8 string as label value
|
||||
// Validate UTF-8 encoding for correctness using simdutf
|
||||
return simdutf::validate_utf8(value.c_str(), value.size());
|
||||
return simdutf::validate_utf8(value.data(), value.size());
|
||||
}
|
||||
|
||||
std::span<std::string_view> render(ArenaAllocator &arena) {
|
||||
@@ -1169,33 +1160,30 @@ std::span<std::string_view> render(ArenaAllocator &arena) {
|
||||
// Template specialization implementations for register_callback
|
||||
template <>
|
||||
void Family<Counter>::register_callback(
|
||||
std::vector<std::pair<std::string, std::string>> labels,
|
||||
std::span<const std::pair<std::string_view, std::string_view>> labels,
|
||||
MetricCallback<Counter> callback) {
|
||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||
LabelsKey key{labels, Metric::get_global_arena()};
|
||||
|
||||
// Validate that labels aren't already in use by create() calls
|
||||
for (const auto &[thread_id, per_thread] : p->per_thread_state) {
|
||||
validate_or_abort(
|
||||
per_thread.instances.find(key) == per_thread.instances.end(),
|
||||
"labels already registered as static instance",
|
||||
key.labels.empty() ? "(no labels)"
|
||||
: std::string(key.labels[0].first).c_str());
|
||||
validate_or_abort(per_thread.instances.find(key) ==
|
||||
per_thread.instances.end(),
|
||||
"labels already registered as static instance",
|
||||
key.labels.empty() ? "(no labels)" : key.labels[0].first);
|
||||
}
|
||||
|
||||
// Validate that callback isn't already registered for these labels
|
||||
validate_or_abort(p->callbacks.find(key) == p->callbacks.end(),
|
||||
"callback already registered for labels",
|
||||
key.labels.empty()
|
||||
? "(no labels)"
|
||||
: std::string(key.labels[0].first).c_str());
|
||||
key.labels.empty() ? "(no labels)" : key.labels[0].first);
|
||||
|
||||
p->callbacks[std::move(key)] = std::move(callback);
|
||||
}
|
||||
|
||||
template <>
|
||||
void Family<Gauge>::register_callback(
|
||||
std::vector<std::pair<std::string, std::string>> labels,
|
||||
std::span<const std::pair<std::string_view, std::string_view>> labels,
|
||||
MetricCallback<Gauge> callback) {
|
||||
std::unique_lock<std::mutex> _{Metric::mutex};
|
||||
LabelsKey key{labels, Metric::get_global_arena()};
|
||||
@@ -1203,16 +1191,12 @@ void Family<Gauge>::register_callback(
|
||||
// Validate that labels aren't already in use by create() calls
|
||||
validate_or_abort(p->instances.find(key) == p->instances.end(),
|
||||
"labels already registered as static instance",
|
||||
key.labels.empty()
|
||||
? "(no labels)"
|
||||
: std::string(key.labels[0].first).c_str());
|
||||
key.labels.empty() ? "(no labels)" : key.labels[0].first);
|
||||
|
||||
// Validate that callback isn't already registered for these labels
|
||||
validate_or_abort(p->callbacks.find(key) == p->callbacks.end(),
|
||||
"callback already registered for labels",
|
||||
key.labels.empty()
|
||||
? "(no labels)"
|
||||
: std::string(key.labels[0].first).c_str());
|
||||
key.labels.empty() ? "(no labels)" : key.labels[0].first);
|
||||
|
||||
p->callbacks[std::move(key)] = std::move(callback);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user