From 4b2c5b8ce8da16a42c2ad43ec7b484ef811e16e5 Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Sun, 31 Aug 2025 12:31:29 -0400 Subject: [PATCH] Accept initializer_list, span, and string_view in api --- src/metric.cpp | 122 +++++++++++++++++++++---------------------------- src/metric.hpp | 50 ++++++++++++++------ 2 files changed, 89 insertions(+), 83 deletions(-) diff --git a/src/metric.cpp b/src/metric.cpp index b490cb8..87ef826 100644 --- a/src/metric.cpp +++ b/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(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(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> labels; - LabelsKey(const std::vector> &l, + LabelsKey(std::span> 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 *family, - const std::vector> &labels) { + std::span> labels) { // Force thread_local initialization (void)thread_init; std::unique_lock _{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 *family, - const std::vector> &labels) { + std::span> labels) { std::unique_lock _{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 *family, - const std::vector> &labels) { + std::span> labels) { // Force thread_local initialization (void)thread_init; std::unique_lock _{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::Family() = default; template <> Counter Family::create( - std::vector> labels) { + std::span> labels) { return Metric::create_counter_instance(this, labels); } template <> Gauge Family::create( - std::vector> labels) { + std::span> labels) { return Metric::create_gauge_instance(this, labels); } template <> Histogram Family::create( - std::vector> labels) { + std::span> labels) { return Metric::create_histogram_instance(this, labels); } -Family create_counter(std::string name, std::string help) { - validate_or_abort(is_valid_metric_name(name), "invalid counter name", - name.c_str()); +Family 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 _{Metric::mutex}; auto &global_arena = Metric::get_global_arena(); @@ -640,17 +636,15 @@ Family 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 family; family.p = familyPtr; return family; } -Family create_gauge(std::string name, std::string help) { - validate_or_abort(is_valid_metric_name(name), "invalid gauge name", - name.c_str()); +Family 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 _{Metric::mutex}; auto &global_arena = Metric::get_global_arena(); @@ -667,18 +661,16 @@ Family 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 family; family.p = familyPtr; return family; } -Family create_histogram(std::string name, std::string help, +Family create_histogram(std::string_view name, std::string_view help, std::span 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 _{Metric::mutex}; auto &global_arena = Metric::get_global_arena(); @@ -709,8 +701,7 @@ Family 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 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 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 family; family.p = family_ptr; @@ -740,9 +731,9 @@ Family create_histogram(std::string name, std::string help, std::vector 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 buckets; buckets.reserve(count); @@ -757,11 +748,11 @@ std::vector linear_buckets(double start, double width, int count) { std::vector 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 buckets; buckets.reserve(count); @@ -777,7 +768,7 @@ std::vector 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 render(ArenaAllocator &arena) { @@ -1169,33 +1160,30 @@ std::span render(ArenaAllocator &arena) { // Template specialization implementations for register_callback template <> void Family::register_callback( - std::vector> labels, + std::span> labels, MetricCallback callback) { std::unique_lock _{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::register_callback( - std::vector> labels, + std::span> labels, MetricCallback callback) { std::unique_lock _{Metric::mutex}; LabelsKey key{labels, Metric::get_global_arena()}; @@ -1203,16 +1191,12 @@ void Family::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); } diff --git a/src/metric.hpp b/src/metric.hpp index 093e283..1770db5 100644 --- a/src/metric.hpp +++ b/src/metric.hpp @@ -36,8 +36,8 @@ // histogram.observe(0.25); // ONLY call from creating thread #include +#include #include -#include #include #include @@ -127,20 +127,36 @@ template struct Family { // Labels are sorted by key for Prometheus compatibility // ERROR: Will abort if labels already registered via register_callback() // OK: Multiple calls with same labels return same instance (idempotent) - T create(std::vector> labels); + T create(std::initializer_list> + labels) { + return create( + std::span>( + labels.begin(), labels.end())); + } + T create( + std::span> labels); // Register callback-based metric (Counter and Gauge only) // Validates that label set isn't already taken - void - register_callback(std::vector> labels, - MetricCallback callback); + void register_callback( + std::initializer_list> + labels, + MetricCallback callback) { + register_callback( + std::span>( + labels.begin(), labels.end()), + callback); + } + void register_callback( + std::span> labels, + MetricCallback callback); private: Family(); friend struct Metric; - friend Family create_counter(std::string, std::string); - friend Family create_gauge(std::string, std::string); - friend Family create_histogram(std::string, std::string, + friend Family create_counter(std::string_view, std::string_view); + friend Family create_gauge(std::string_view, std::string_view); + friend Family create_histogram(std::string_view, std::string_view, std::span); struct State; @@ -153,19 +169,25 @@ private: // Create counter family (monotonically increasing values) // ERROR: Aborts if family with same name is registered with different help // text. -Family create_counter(std::string name, std::string help); +Family create_counter(std::string_view name, std::string_view help); // Create gauge family (can increase/decrease) // ERROR: Aborts if family with same name is registered with different help // text. -Family create_gauge(std::string name, std::string help); +Family create_gauge(std::string_view name, std::string_view help); // Create histogram family with custom buckets // Buckets will be sorted, deduplicated, and +Inf will be added automatically // ERROR: Aborts if family with same name is registered with different help text // or buckets. -Family create_histogram(std::string name, std::string help, +Family create_histogram(std::string_view name, std::string_view help, std::span buckets); +inline Family +create_histogram(std::string_view name, std::string_view help, + std::initializer_list buckets) { + return create_histogram( + name, help, std::span(buckets.begin(), buckets.end())); +} // Helper functions for generating standard histogram buckets // Following Prometheus client library conventions @@ -189,9 +211,9 @@ std::vector exponential_buckets(double start, double factor, int count); std::span render(ArenaAllocator &arena); // Validation functions for Prometheus compatibility -bool is_valid_metric_name(const std::string &name); -bool is_valid_label_key(const std::string &key); -bool is_valid_label_value(const std::string &value); +bool is_valid_metric_name(std::string_view name); +bool is_valid_label_key(std::string_view key); +bool is_valid_label_value(std::string_view value); // Note: Histograms do not support callbacks due to their multi-value nature // (buckets + sum + count). Use static histogram metrics only.