Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/metrics/src/label/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ impl LabelSet {
pub fn upsert(&mut self, key: LabelName, value: LabelValue) {
self.items.insert(key, value);
}

pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}

impl Display for LabelSet {
Expand Down Expand Up @@ -157,6 +161,10 @@ impl<'de> Deserialize<'de> for LabelSet {

impl PrometheusSerializable for LabelSet {
fn to_prometheus(&self) -> String {
if self.is_empty() {
return String::new();
}

let items = self.items.iter().fold(String::new(), |mut output, label_pair| {
if !output.is_empty() {
output.push(',');
Expand Down
6 changes: 3 additions & 3 deletions packages/metrics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ pub const METRICS_TARGET: &str = "METRICS";

#[cfg(test)]
mod tests {
/// It removes leading and trailing whitespace from each line, and empty lines.
/// It removes leading and trailing whitespace from each line.
pub fn format_prometheus_output(output: &str) -> String {
output
.lines()
.map(str::trim)
.filter(|line| !line.is_empty())
.map(str::trim_start)
.map(str::trim_end)
.collect::<Vec<_>>()
.join("\n")
}
Expand Down
102 changes: 28 additions & 74 deletions packages/metrics/src/metric/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,6 @@ impl Metric<Gauge> {
}
}

/// `PrometheusMetricSample` is a wrapper around types that provides methods to
/// convert the metric and its measurement into a Prometheus-compatible format.
///
/// In Prometheus, a metric is a time series that consists of a name, a set of
/// labels, and a value. The sample value needs data from the `Metric` and
/// `Measurement` structs, as well as the `LabelSet` that defines the labels for
/// the metric.
struct PrometheusMetricSample<'a, T> {
metric: &'a Metric<T>,
measurement: &'a Measurement<T>,
label_set: &'a LabelSet,
}

enum PrometheusType {
Counter,
Gauge,
Expand All @@ -130,91 +117,58 @@ impl PrometheusSerializable for PrometheusType {
}
}

impl<T: PrometheusSerializable> PrometheusMetricSample<'_, T> {
fn to_prometheus(&self, prometheus_type: &PrometheusType) -> String {
format!(
// Format:
// # HELP <metric_name> <description>
// # TYPE <metric_name> <type>
// <metric_name>{label_set} <value>
"{}{}{}",
self.help_line(),
self.type_line(prometheus_type),
self.metric_line()
)
}

fn help_line(&self) -> String {
if let Some(description) = &self.metric.opt_description {
format!(
// Format: # HELP <metric_name> <description>
"# HELP {} {}\n",
self.metric.name().to_prometheus(),
description.to_prometheus()
)
impl<T: PrometheusSerializable> Metric<T> {
#[must_use]
fn prometheus_help_line(&self) -> String {
if let Some(description) = &self.opt_description {
format!("# HELP {} {}", self.name.to_prometheus(), description.to_prometheus())
} else {
String::new()
}
}

fn type_line(&self, kind: &PrometheusType) -> String {
format!("# TYPE {} {}\n", self.metric.name().to_prometheus(), kind.to_prometheus())
#[must_use]
fn prometheus_type_line(&self, prometheus_type: &PrometheusType) -> String {
format!("# TYPE {} {}", self.name.to_prometheus(), prometheus_type.to_prometheus())
}

fn metric_line(&self) -> String {
#[must_use]
fn prometheus_sample_line(&self, label_set: &LabelSet, measurement: &Measurement<T>) -> String {
format!(
// Format: <metric_name>{label_set} <value>
"{}{} {}",
self.metric.name.to_prometheus(),
self.label_set.to_prometheus(),
self.measurement.value().to_prometheus()
self.name.to_prometheus(),
label_set.to_prometheus(),
measurement.to_prometheus()
)
}
}

impl<'a> PrometheusMetricSample<'a, Counter> {
pub fn new(metric: &'a Metric<Counter>, measurement: &'a Measurement<Counter>, label_set: &'a LabelSet) -> Self {
Self {
metric,
measurement,
label_set,
}
#[must_use]
fn prometheus_samples(&self) -> String {
self.sample_collection
.iter()
.map(|(label_set, measurement)| self.prometheus_sample_line(label_set, measurement))
.collect::<Vec<_>>()
.join("\n")
}
}

impl<'a> PrometheusMetricSample<'a, Gauge> {
pub fn new(metric: &'a Metric<Gauge>, measurement: &'a Measurement<Gauge>, label_set: &'a LabelSet) -> Self {
Self {
metric,
measurement,
label_set,
}
fn to_prometheus(&self, prometheus_type: &PrometheusType) -> String {
let help_line = self.prometheus_help_line();
let type_line = self.prometheus_type_line(prometheus_type);
let samples = self.prometheus_samples();

format!("{help_line}\n{type_line}\n{samples}")
}
}

impl PrometheusSerializable for Metric<Counter> {
fn to_prometheus(&self) -> String {
let samples: Vec<String> = self
.sample_collection
.iter()
.map(|(label_set, measurement)| {
PrometheusMetricSample::<Counter>::new(self, measurement, label_set).to_prometheus(&PrometheusType::Counter)
})
.collect();
samples.join("\n")
self.to_prometheus(&PrometheusType::Counter)
}
}

impl PrometheusSerializable for Metric<Gauge> {
fn to_prometheus(&self) -> String {
let samples: Vec<String> = self
.sample_collection
.iter()
.map(|(label_set, measurement)| {
PrometheusMetricSample::<Gauge>::new(self, measurement, label_set).to_prometheus(&PrometheusType::Gauge)
})
.collect();
samples.join("\n")
self.to_prometheus(&PrometheusType::Gauge)
}
}

Expand Down
31 changes: 15 additions & 16 deletions packages/metrics/src/metric_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ impl PrometheusSerializable for MetricCollection {
.map(Metric::<Gauge>::to_prometheus),
)
.collect::<Vec<String>>()
.join("\n")
.join("\n\n")
}
}

Expand Down Expand Up @@ -629,14 +629,14 @@ mod tests {

fn prometheus() -> String {
format_prometheus_output(
r#"
# HELP http_tracker_core_announce_requests_received_total The number of announce requests received.
# TYPE http_tracker_core_announce_requests_received_total counter
http_tracker_core_announce_requests_received_total{server_binding_ip="0.0.0.0",server_binding_port="7070",server_binding_protocol="http"} 1
# HELP udp_tracker_server_performance_avg_announce_processing_time_ns The average announce processing time in nanoseconds.
# TYPE udp_tracker_server_performance_avg_announce_processing_time_ns gauge
udp_tracker_server_performance_avg_announce_processing_time_ns{server_binding_ip="0.0.0.0",server_binding_port="7070",server_binding_protocol="http"} 1
"#,
r#"# HELP http_tracker_core_announce_requests_received_total The number of announce requests received.
# TYPE http_tracker_core_announce_requests_received_total counter
http_tracker_core_announce_requests_received_total{server_binding_ip="0.0.0.0",server_binding_port="7070",server_binding_protocol="http"} 1

# HELP udp_tracker_server_performance_avg_announce_processing_time_ns The average announce processing time in nanoseconds.
# TYPE udp_tracker_server_performance_avg_announce_processing_time_ns gauge
udp_tracker_server_performance_avg_announce_processing_time_ns{server_binding_ip="0.0.0.0",server_binding_port="7070",server_binding_protocol="http"} 1
"#,
)
}
}
Expand Down Expand Up @@ -750,7 +750,7 @@ mod tests {
MetricKindCollection::new(vec![Metric::new(
metric_name!("http_tracker_core_announce_requests_received_total"),
None,
None,
Some(MetricDescription::new("The number of announce requests received.")),
SampleCollection::new(vec![
Sample::new(Counter::new(1), time, label_set_1.clone()),
Sample::new(Counter::new(2), time, label_set_2.clone()),
Expand All @@ -765,12 +765,11 @@ mod tests {
let prometheus_output = metric_collection.to_prometheus();

let expected_prometheus_output = format_prometheus_output(
r#"
# TYPE http_tracker_core_announce_requests_received_total counter
http_tracker_core_announce_requests_received_total{server_binding_ip="0.0.0.0",server_binding_port="7171",server_binding_protocol="http"} 2
# TYPE http_tracker_core_announce_requests_received_total counter
http_tracker_core_announce_requests_received_total{server_binding_ip="0.0.0.0",server_binding_port="7070",server_binding_protocol="http"} 1
"#,
r#"# HELP http_tracker_core_announce_requests_received_total The number of announce requests received.
# TYPE http_tracker_core_announce_requests_received_total counter
http_tracker_core_announce_requests_received_total{server_binding_ip="0.0.0.0",server_binding_port="7070",server_binding_protocol="http"} 1
http_tracker_core_announce_requests_received_total{server_binding_ip="0.0.0.0",server_binding_port="7171",server_binding_protocol="http"} 2
"#,
);

// code-review: samples are not serialized in the same order as they are created.
Expand Down
6 changes: 5 additions & 1 deletion packages/metrics/src/sample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ impl<T> Sample<T> {

impl<T: PrometheusSerializable> PrometheusSerializable for Sample<T> {
fn to_prometheus(&self) -> String {
format!("{} {}", self.label_set.to_prometheus(), self.measurement.to_prometheus())
if self.label_set.is_empty() {
format!(" {}", self.measurement.to_prometheus())
} else {
format!("{} {}", self.label_set.to_prometheus(), self.measurement.to_prometheus())
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion packages/metrics/src/sample_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ impl<T: PrometheusSerializable> PrometheusSerializable for SampleCollection<T> {
let mut output = String::new();

for (label_set, sample_data) in &self.samples {
let _ = write!(output, "{} {}", label_set.to_prometheus(), sample_data.to_prometheus());
if label_set.is_empty() {
let _ = write!(output, "{}", sample_data.to_prometheus());
} else {
let _ = write!(output, "{} {}", label_set.to_prometheus(), sample_data.to_prometheus());
}
}

output
Expand Down
Loading