distributed 4.0.2

CQRS/ES framework for Rust using Plain Old Rust Structs — append-only events, replay, snapshots, outbox, service bus, and pluggable infrastructure
Documentation
use crate::telemetry::metric_labels;

use super::registry::{MetricFamily, MetricSampleValue, MetricsSnapshot};

pub(super) fn render_prometheus(snapshot: &MetricsSnapshot) -> String {
    let mut output = String::new();
    for family_snapshot in snapshot.families() {
        write_family_header(&mut output, family_snapshot.family);
        for sample in &family_snapshot.samples {
            match &sample.value {
                MetricSampleValue::Counter(value) => {
                    push_metric(
                        &mut output,
                        family_snapshot.family.name,
                        &sample.labels,
                        &value.to_string(),
                    );
                }
                MetricSampleValue::Gauge(value) => {
                    push_metric(
                        &mut output,
                        family_snapshot.family.name,
                        &sample.labels,
                        &format_float(*value),
                    );
                }
                MetricSampleValue::Histogram(histogram) => {
                    let bucket_name = format!("{}_bucket", family_snapshot.family.name);
                    for bucket in &histogram.buckets {
                        let mut labels = sample.labels.clone();
                        labels.push((
                            metric_labels::LE.to_string(),
                            bucket.upper_bound.to_string(),
                        ));
                        push_metric(
                            &mut output,
                            &bucket_name,
                            &labels,
                            &bucket.count.to_string(),
                        );
                    }
                    let mut labels = sample.labels.clone();
                    labels.push((metric_labels::LE.to_string(), "+Inf".to_string()));
                    push_metric(
                        &mut output,
                        &bucket_name,
                        &labels,
                        &histogram.count.to_string(),
                    );
                    push_metric(
                        &mut output,
                        &format!("{}_sum", family_snapshot.family.name),
                        &sample.labels,
                        &format_float(histogram.sum),
                    );
                    push_metric(
                        &mut output,
                        &format!("{}_count", family_snapshot.family.name),
                        &sample.labels,
                        &histogram.count.to_string(),
                    );
                }
            }
        }
    }
    output.push_str("# EOF\n");
    output
}

fn write_family_header(output: &mut String, family: MetricFamily) {
    output.push_str("# HELP ");
    output.push_str(family.name);
    output.push(' ');
    output.push_str(family.help);
    output.push('\n');
    output.push_str("# TYPE ");
    output.push_str(family.name);
    output.push(' ');
    output.push_str(family.kind.as_prometheus_type());
    output.push('\n');
}

fn push_metric(output: &mut String, name: &str, labels: &[(String, String)], value: &str) {
    output.push_str(name);
    if !labels.is_empty() {
        output.push('{');
        for (index, (key, value)) in labels.iter().enumerate() {
            if index > 0 {
                output.push(',');
            }
            output.push_str(key.as_str());
            output.push_str("=\"");
            push_escaped_label_value(output, value);
            output.push('"');
        }
        output.push('}');
    }
    output.push(' ');
    output.push_str(value);
    output.push('\n');
}

fn push_escaped_label_value(output: &mut String, value: &str) {
    for ch in value.chars() {
        match ch {
            '\\' => output.push_str("\\\\"),
            '"' => output.push_str("\\\""),
            '\n' => output.push_str("\\n"),
            _ => output.push(ch),
        }
    }
}

fn format_float(value: f64) -> String {
    if value.fract() == 0.0 {
        format!("{value:.0}")
    } else {
        value.to_string()
    }
}