///|
pub(all) suberror RegistryError {
/// A family name is already owned by this registry.
DuplicateMetric(String)
/// Retained for source compatibility with the initial skeleton.
MetricNotFound(String)
/// A custom collector's descriptor list is structurally invalid.
InvalidCollector(String)
/// Two descriptors use the same name but disagree on a stable identity field.
DescriptorConflict(name~ : String, field~ : String)
} derive(Debug, Eq)
///|
/// Registry owns collector registrations and creates deterministic detached
/// snapshots. Native builds protect its entry table with storage's pthread
/// mutex; portable targets keep the same copy-and-release protocol as no-op.
pub struct Registry {
entries : Array[CollectorEntry]
mut next_token : Int
lock : @storage.Mutex
}
///|
pub fn Registry::new() -> Registry {
{ entries: [], next_token: 1, lock: @storage.Mutex::new() }
}
///|
fn Registry::next_registration(self : Registry) -> Registration {
let token = self.next_token
self.next_token += 1
{ token, }
}
///|
fn Registry::validate_descriptors(
self : Registry,
descriptors : Array[DescriptorInfo],
) -> Unit raise RegistryError {
if descriptors.is_empty() {
raise InvalidCollector(
"a collector must describe at least one metric family",
)
}
for index, descriptor in descriptors {
for previous in 0.. CollectorEntry,
descriptors : Array[DescriptorInfo],
) -> Registration raise RegistryError {
self.lock.lock()
defer self.lock.unlock()
self.validate_descriptors(descriptors)
let registration = self.next_registration()
self.entries.push(entry_from_token(registration.token))
registration
}
///|
/// Register a Counter and return a handle for identity-safe unregistration.
pub fn Registry::register_counter_with_handle(
self : Registry,
metric : @metric.Counter,
) -> Registration raise RegistryError {
let descriptor = DescriptorInfo::from_counter(metric)
self.insert(token => CounterEntry(token, metric, descriptor), [descriptor])
}
///|
/// Register a Gauge and return a handle for identity-safe unregistration.
pub fn Registry::register_gauge_with_handle(
self : Registry,
metric : @metric.Gauge,
) -> Registration raise RegistryError {
let descriptor = DescriptorInfo::from_gauge(metric)
self.insert(token => GaugeEntry(token, metric, descriptor), [descriptor])
}
///|
/// Register a Histogram and return a handle for identity-safe unregistration.
pub fn Registry::register_histogram_with_handle(
self : Registry,
metric : @metric.Histogram,
) -> Registration raise RegistryError {
let descriptor = DescriptorInfo::from_histogram(metric)
self.insert(token => HistogramEntry(token, metric, descriptor), [descriptor])
}
///|
/// Register a CounterVec as one dynamic metric family.
///
/// Children are read at gather time, so adding or deleting a label set changes
/// the next snapshot without requiring another registry registration.
pub fn Registry::register_counter_vec_with_handle(
self : Registry,
vector : @vector.CounterVec,
) -> Registration raise RegistryError {
let descriptor = DescriptorInfo::from_descriptor(vector.descriptor())
self.insert(token => CounterVecEntry(token, vector, descriptor), [descriptor])
}
///|
/// Register a GaugeVec as one dynamic metric family.
pub fn Registry::register_gauge_vec_with_handle(
self : Registry,
vector : @vector.GaugeVec,
) -> Registration raise RegistryError {
let descriptor = DescriptorInfo::from_descriptor(vector.descriptor())
self.insert(token => GaugeVecEntry(token, vector, descriptor), [descriptor])
}
///|
/// Register a HistogramVec as one dynamic metric family.
pub fn Registry::register_histogram_vec_with_handle(
self : Registry,
vector : @vector.HistogramVec,
) -> Registration raise RegistryError {
let descriptor = DescriptorInfo::from_descriptor(vector.descriptor())
self.insert(token => HistogramVecEntry(token, vector, descriptor), [
descriptor,
])
}
///|
/// Register a framework-neutral custom collector adapter.
pub fn Registry::register_collector(
self : Registry,
collector : CollectorAdapter,
) -> Registration raise RegistryError {
let descriptors : Array[DescriptorInfo] = []
for descriptor in collector.descriptors() {
descriptors.push(DescriptorInfo::from_descriptor(descriptor))
}
self.insert(token => AdapterEntry(token, collector, descriptors), descriptors)
}
///|
/// Backwards-compatible Counter registration. Prefer
/// `register_counter_with_handle` when lifecycle management is needed.
pub fn Registry::register_counter(
self : Registry,
metric : @metric.Counter,
) -> Unit raise RegistryError {
ignore(self.register_counter_with_handle(metric))
}
///|
/// Backwards-compatible Gauge registration. Prefer
/// `register_gauge_with_handle` when lifecycle management is needed.
pub fn Registry::register_gauge(
self : Registry,
metric : @metric.Gauge,
) -> Unit raise RegistryError {
ignore(self.register_gauge_with_handle(metric))
}
///|
/// Backwards-compatible Histogram registration. Prefer
/// `register_histogram_with_handle` when lifecycle management is needed.
pub fn Registry::register_histogram(
self : Registry,
metric : @metric.Histogram,
) -> Unit raise RegistryError {
ignore(self.register_histogram_with_handle(metric))
}
///|
/// Backwards-compatible CounterVec registration. Prefer the handle-returning
/// form when the vector must later be unregistered.
pub fn Registry::register_counter_vec(
self : Registry,
vector : @vector.CounterVec,
) -> Unit raise RegistryError {
ignore(self.register_counter_vec_with_handle(vector))
}
///|
/// Backwards-compatible GaugeVec registration. Prefer the handle-returning
/// form when the vector must later be unregistered.
pub fn Registry::register_gauge_vec(
self : Registry,
vector : @vector.GaugeVec,
) -> Unit raise RegistryError {
ignore(self.register_gauge_vec_with_handle(vector))
}
///|
/// Backwards-compatible HistogramVec registration. Prefer the handle-returning
/// form when the vector must later be unregistered.
pub fn Registry::register_histogram_vec(
self : Registry,
vector : @vector.HistogramVec,
) -> Unit raise RegistryError {
ignore(self.register_histogram_vec_with_handle(vector))
}
///|
/// Remove exactly the registration represented by `registration`.
///
/// Returning `false` for a stale or already removed handle is intentional;
/// the method never removes a newer family with the same descriptor name.
pub fn Registry::unregister(
self : Registry,
registration : Registration,
) -> Bool {
self.lock.lock()
defer self.lock.unlock()
match self.entries.search_by(entry => entry.token() == registration.token) {
Some(index) => {
ignore(self.entries.remove(index))
true
}
None => false
}
}
///|
pub fn Registry::registered_count(self : Registry) -> Int {
self.lock.lock()
defer self.lock.unlock()
self.entries.length()
}
///|
/// Copy the collection set under the table mutex and immediately release it.
/// Collector callbacks are consequently never invoked while the Registry lock
/// is held, which preserves re-entry and lock-order safety.
fn Registry::entries_snapshot(self : Registry) -> Array[CollectorEntry] {
self.lock.lock()
defer self.lock.unlock()
self.entries.copy()
}
///|
/// Return a copy of currently registered Counter references. This is a
/// compatibility view; exporters should consume `gather()` instead.
pub fn Registry::counters(self : Registry) -> Array[@metric.Counter] {
self.lock.lock()
defer self.lock.unlock()
let result : Array[@metric.Counter] = []
for entry in self.entries {
match entry {
CounterEntry(_, counter, _) => result.push(counter)
_ => ()
}
}
result
}
///|
/// Return a copy of currently registered Gauge references. This is a
/// compatibility view; exporters should consume `gather()` instead.
pub fn Registry::gauges(self : Registry) -> Array[@metric.Gauge] {
self.lock.lock()
defer self.lock.unlock()
let result : Array[@metric.Gauge] = []
for entry in self.entries {
match entry {
GaugeEntry(_, gauge, _) => result.push(gauge)
_ => ()
}
}
result
}
///|
/// Return a copy of currently registered Histogram references. This is a
/// compatibility view; exporters should consume `gather()` instead.
pub fn Registry::histograms(self : Registry) -> Array[@metric.Histogram] {
self.lock.lock()
defer self.lock.unlock()
let result : Array[@metric.Histogram] = []
for entry in self.entries {
match entry {
HistogramEntry(_, histogram, _) => result.push(histogram)
_ => ()
}
}
result
}
///|
/// Collect every registered family into a deeply detached, deterministic
/// snapshot. Collection is fail-closed: no partial result is returned when a
/// custom collector fails or violates its descriptor contract.
pub fn Registry::gather(self : Registry) -> GatherSnapshot raise GatherError {
// A shallow copy establishes the collection set before invoking user
// callbacks. `entries_snapshot` already released the Registry mutex.
let entries = self.entries_snapshot()
let families : Array[FamilySnapshot] = []
for entry in entries {
let collected = match entry.collect() {
Ok(value) => value
Err(Failed(message)) =>
raise CollectorFailed(token=entry.token(), message~)
}
for family in normalize_entry_families(entry, collected) {
for previous in families {
if previous.name == family.name {
raise InvalidFamilySamples(family.name)
}
}
families.push(family)
}
}
families.sort_by((left, right) => left.name.lexical_compare(right.name))
{ families, }
}