use super::labels::{ComponentLabels, NamespaceRejection, classify_namespace, validate_namespace};
use metrics::{Counter, Gauge, Histogram, SharedString};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum MetricRole {
Source,
Sink,
}
impl MetricRole {
fn segment(self) -> &'static str {
match self {
MetricRole::Source => "source",
MetricRole::Sink => "sink",
}
}
}
#[derive(Clone, Debug)]
pub struct Meter {
labels: ComponentLabels,
prefix: String,
}
impl Meter {
pub fn new(
pipeline: impl Into<SharedString>,
component: impl Into<SharedString>,
component_type: impl Into<SharedString>,
) -> Self {
Self::with_namespace(
super::names::CUSTOM_NAMESPACE,
pipeline,
component,
component_type,
)
}
pub fn with_namespace(
namespace: &str,
pipeline: impl Into<SharedString>,
component: impl Into<SharedString>,
component_type: impl Into<SharedString>,
) -> Self {
validate_namespace(namespace);
Meter {
labels: ComponentLabels::new(pipeline, component, component_type),
prefix: format!("{}{namespace}_", super::names::PREFIX),
}
}
pub(crate) fn for_component(
component_type: &str,
role: MetricRole,
pipeline: impl Into<SharedString>,
component: impl Into<SharedString>,
) -> Option<Meter> {
if component_type == super::names::CUSTOM_NAMESPACE {
return None;
}
match classify_namespace(component_type) {
Ok(()) => Some(Meter {
labels: ComponentLabels::new(pipeline, component, component_type.to_string()),
prefix: format!(
"{}{component_type}_{}_",
super::names::PREFIX,
role.segment()
),
}),
Err(NamespaceRejection::Reserved) => None,
Err(reason) => {
tracing::warn!(
component_type,
role = role.segment(),
reason = reason.reason(),
"component `component_type` is unusable as a metric namespace, \
so this component's custom Meter families are disabled (its \
framework stage metrics are unaffected); use a lowercase \
[a-z][a-z0-9_]* segment to enable them"
);
None
}
}
}
fn qualify(&self, name: &str) -> SharedString {
assert!(
!name.starts_with(super::names::PREFIX),
"pass the metric's local name without the `{}` prefix — the Meter \
adds `{}` for you (got `{name}`)",
super::names::PREFIX,
self.prefix
);
let first = name.split('_').next().unwrap_or_default();
assert!(
!super::names::ROLE_SEGMENTS.contains(&first),
"metric local name `{name}` must not begin with the role segment \
`{first}_`; the runtime reserves `source_`/`sink_` to scope a \
component's source vs sink families"
);
format!("{}{name}", self.prefix).into()
}
pub fn counter(&self, name: &str, extra: &[(&'static str, SharedString)]) -> Counter {
self.labels.register_counter(self.qualify(name), extra)
}
pub fn gauge(&self, name: &str, extra: &[(&'static str, SharedString)]) -> Gauge {
self.labels.register_gauge(self.qualify(name), extra)
}
pub fn histogram(&self, name: &str, extra: &[(&'static str, SharedString)]) -> Histogram {
self.labels.register_histogram(self.qualify(name), extra)
}
#[must_use]
pub fn labels(&self) -> &ComponentLabels {
&self.labels
}
}