Validation + callback api

This commit is contained in:
2025-08-29 11:31:06 -04:00
parent b6d4ae2862
commit e3a2ddbbfb
2 changed files with 198 additions and 19 deletions

View File

@@ -8,12 +8,23 @@
// - Single-writer semantics: Each metric instance bound to creating thread
// - Lock-free operations using atomic<uint64_t> storage for doubles
// - Full IEEE 754 double precision preservation via bit reinterpretation
// - Single global registry: All metrics registered in one global namespace
//
// CRITICAL THREAD SAFETY CONSTRAINT:
// Each metric instance has exactly ONE writer thread (the creating thread).
// It is undefined behavior to call inc()/dec()/set()/observe() from a different
// thread.
//
// REGISTRY MODEL:
// This implementation uses a single global registry for all metrics, unlike
// typical Prometheus client libraries that support multiple registries.
// This design choice prioritizes simplicity and performance over flexibility.
//
// METRIC LIFECYCLE:
// Metrics are created once and persist for the application lifetime. There is
// no unregistration mechanism - this prevents accidental metric loss and
// simplifies the implementation.
//
// USAGE:
// auto counter_family = metric::create_counter("requests_total", "Total
// requests"); auto counter = counter_family.create({{"method", "GET"}}); //
@@ -100,6 +111,8 @@ template <class T> struct Family {
// Create metric instance with specific labels
// 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<std::pair<std::string, std::string>> labels);
private:
@@ -129,6 +142,42 @@ Family<Histogram> create_histogram(std::string name, std::string help,
std::initializer_list<double> buckets);
// Render all metrics in Prometheus text format
std::span<std::string> render(ArenaAllocator &arena);
// TODO: Implement Prometheus text exposition format
// THREAD SAFETY: Serialized by global mutex - callbacks need not be thread-safe
std::span<std::string_view> 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);
// Callback function type for dynamic metric values
// Called during render() to get current metric value
// THREAD SAFETY: May be called from arbitrary thread, but serialized by
// render() mutex
// - no need to be thread-safe internally
template <typename T> using MetricCallback = std::function<double()>;
// Register callback-based metric to Family
// Validates that label set isn't already taken by either:
// - A previous register_callback() call (callbacks must be unique)
// - A create() call (static and callback metrics cannot coexist for same
// labels)
//
// Similarly, create() will validate that label set isn't already registered as
// callback Note: create() can be called multiple times with same labels
// (returns same instance)
template <>
void Family<Counter>::register_callback(
std::vector<std::pair<std::string, std::string>> labels,
MetricCallback<Counter> callback);
template <>
void Family<Gauge>::register_callback(
std::vector<std::pair<std::string, std::string>> labels,
MetricCallback<Gauge> callback);
// Note: Histograms do not support callbacks due to their multi-value nature
// (buckets + sum + count). Use static histogram metrics only.
} // namespace metric