///|
/// Metric kinds defined by OpenMetrics 1.0.
///
/// `Unknown` is useful for untyped input where no `# TYPE` directive was
/// present. `GaugeHistogram` is kept distinct from a regular histogram because
/// its `_gsum`/`_gcount` suffixes have different validation rules.
pub(all) enum MetricType {
Unknown
Counter
Gauge
Histogram
GaugeHistogram
Summary
Info
StateSet
} derive(Eq)
///|
/// Convert a metric type into its wire-format keyword.
pub fn MetricType::to_keyword(self : MetricType) -> String {
match self {
Unknown => "unknown"
Counter => "counter"
Gauge => "gauge"
Histogram => "histogram"
GaugeHistogram => "gaugehistogram"
Summary => "summary"
Info => "info"
StateSet => "stateset"
}
}
///|
/// Parse a `# TYPE` keyword.
pub fn MetricType::from_keyword(
keyword : StringView,
) -> Result[MetricType, String] {
match keyword {
"unknown" => Ok(Unknown)
"counter" => Ok(Counter)
"gauge" => Ok(Gauge)
"histogram" => Ok(Histogram)
"gaugehistogram" => Ok(GaugeHistogram)
"summary" => Ok(Summary)
"info" => Ok(Info)
"stateset" => Ok(StateSet)
other => Err("unsupported metric type '\{other}'")
}
}
///|
/// A label name/value pair attached to a sample or exemplar.
pub(all) struct Label {
name : String
value : String
} derive(Eq)
///|
/// Create a label.
pub fn Label::new(name : String, value : String) -> Label {
{ name, value }
}
///|
/// An OpenMetrics exemplar attached to a sample.
///
/// An exemplar contains its own label set, a numeric observation and an
/// optional Unix timestamp in seconds.
pub(all) struct Exemplar {
labels : Array[Label]
value : Double
timestamp : Double?
} derive(Eq)
///|
/// Create an exemplar without a timestamp.
pub fn Exemplar::new(
labels : Array[Label],
value : Double,
timestamp? : Double,
) -> Exemplar {
{ labels, value, timestamp }
}
///|
/// One sample line in an exposition document.
pub(all) struct Sample {
name : String
labels : Array[Label]
value : Double
timestamp : Double?
exemplar : Exemplar?
} derive(Eq)
///|
/// Create a sample.
pub fn Sample::new(
name : String,
value : Double,
labels? : Array[Label] = [],
timestamp? : Double,
exemplar? : Exemplar,
) -> Sample {
{ name, labels, value, timestamp, exemplar }
}
///|
/// Return the first value for a label name.
pub fn Sample::label(self : Sample, name : StringView) -> String? {
for label in self.labels {
if name.equal_to_string(label.name) {
return Some(label.value)
}
}
None
}
///|
/// Metadata and samples belonging to one metric family.
pub(all) struct MetricFamily {
name : String
help : String?
metric_type : MetricType
unit : String?
samples : Array[Sample]
} derive(Eq)
///|
/// Create an empty metric family.
pub fn MetricFamily::new(
name : String,
metric_type? : MetricType = Unknown,
help? : String,
unit? : String,
) -> MetricFamily {
let help = match help {
Some(value) => if value == "" { None } else { Some(value) }
None => None
}
let unit = match unit {
Some(value) => if value == "" { None } else { Some(value) }
None => None
}
{ name, help, metric_type, unit, samples: [] }
}
///|
/// Return a copy of a family with one extra sample.
pub fn MetricFamily::add_sample(
self : MetricFamily,
sample : Sample,
) -> MetricFamily {
let samples = self.samples.copy()
samples.push(sample)
{ ..self, samples, }
}
///|
/// A parsed OpenMetrics document.
///
/// `has_eof` records whether the mandatory `# EOF` marker was present. The
/// parser preserves a document without the marker so callers can choose
/// between lenient inspection and strict validation.
pub(all) struct Document {
families : Array[MetricFamily]
has_eof : Bool
} derive(Eq)
///|
/// Create an empty document.
pub fn Document::new() -> Document {
{ families: [], has_eof: false }
}
///|
/// Find a family by its declared name.
pub fn Document::family(self : Document, name : StringView) -> MetricFamily? {
for family in self.families {
if name.equal_to_string(family.name) {
return Some(family)
}
}
None
}
///|
/// Severity of a semantic validation issue.
pub(all) enum Severity {
Warning
Error
} derive(Eq)
///|
/// A machine-readable semantic validation issue.
pub(all) struct ValidationIssue {
severity : Severity
code : String
family : String
sample : String?
message : String
} derive(Eq)
///|
/// Create a validation error.
pub fn ValidationIssue::error(
code : String,
family : String,
message : String,
sample? : String,
) -> ValidationIssue {
{ severity: Error, code, family, sample, message }
}
///|
/// Create a validation warning.
pub fn ValidationIssue::warning(
code : String,
family : String,
message : String,
sample? : String,
) -> ValidationIssue {
{ severity: Warning, code, family, sample, message }
}
///|
/// Syntax failure returned by the line-oriented parser.
pub(all) struct ParseError {
line : Int
column : Int
message : String
} derive(Eq)
///|
/// Create a parser error at a one-based line and column.
pub fn ParseError::new(
line : Int,
column : Int,
message : String,
) -> ParseError {
{ line, column, message }
}
///|
/// Render a compact human-readable parser error.
pub fn ParseError::to_string(self : ParseError) -> String {
"line \{self.line}, column \{self.column}: \{self.message}"
}