linkprobe_core/export/
prometheus.rs1use crate::result::RunResult;
2
3fn escape_label(s: &str) -> String {
4 s.replace('\\', "\\\\")
5 .replace('"', "\\\"")
6 .replace('\n', "\\n")
7}
8
9pub fn format_openmetrics(result: &RunResult) -> String {
16 let backend = escape_label(&result.backend);
17 let server = escape_label(&result.server.name);
18 let labels = format!("backend=\"{backend}\", server=\"{server}\"");
19 let mut out = String::new();
20
21 out.push_str("# HELP linkprobe_ok 1 if the last probe succeeded.\n");
22 out.push_str("# TYPE linkprobe_ok gauge\n");
23 out.push_str(&format!("linkprobe_ok{{{labels}}} 1\n"));
24
25 if let Some(ms) = result.measurement.latency_ms {
26 out.push_str("# HELP linkprobe_latency_milliseconds Round-trip latency.\n");
27 out.push_str("# TYPE linkprobe_latency_milliseconds gauge\n");
28 out.push_str(&format!(
29 "linkprobe_latency_milliseconds{{{labels}}} {ms}\n"
30 ));
31 }
32 if let Some(ms) = result.measurement.jitter_ms {
33 out.push_str("# HELP linkprobe_jitter_milliseconds Jitter.\n");
34 out.push_str("# TYPE linkprobe_jitter_milliseconds gauge\n");
35 out.push_str(&format!("linkprobe_jitter_milliseconds{{{labels}}} {ms}\n"));
36 }
37 if let Some(ref dl) = result.measurement.download {
38 out.push_str("# HELP linkprobe_download_bits_per_second Download throughput.\n");
39 out.push_str("# TYPE linkprobe_download_bits_per_second gauge\n");
40 out.push_str(&format!(
41 "linkprobe_download_bits_per_second{{{labels}}} {}\n",
42 dl.bps
43 ));
44 }
45 if let Some(ref ul) = result.measurement.upload {
46 out.push_str("# HELP linkprobe_upload_bits_per_second Upload throughput.\n");
47 out.push_str("# TYPE linkprobe_upload_bits_per_second gauge\n");
48 out.push_str(&format!(
49 "linkprobe_upload_bits_per_second{{{labels}}} {}\n",
50 ul.bps
51 ));
52 }
53 if let Some(loss) = result.measurement.packet_loss {
54 out.push_str("# HELP linkprobe_packet_loss Packet loss ration in [0,1].\n");
55 out.push_str("# TYPE linkprobe_packet_loss gauge\n");
56 out.push_str(&format!("linkprobe_packet_loss{{{labels}}} {loss}\n"));
57 }
58
59 out.push('\n');
60 out
61}
62
63pub fn format_openmetrics_failed(backend: &str, server: &str, err: &str) -> String {
67 let backend = escape_label(backend);
68 let server = escape_label(server);
69 let err = escape_label(err);
70 let labels = format!("backend=\"{backend}\", server=\"{server}\"");
71 let mut out = String::new();
72 out.push_str("# HELP linkprobe_ok 1 if the last probe succeeded.\n");
73 out.push_str("# TYPE linkprobe_ok gauge\n");
74 out.push_str(&format!("linkprobe_ok{{{labels}}} 0\n"));
75 out.push_str("# HELP linkprobe_last_error Last probe error message.\n");
76 out.push_str("# TYPE linkprobe_last_error gauge\n");
77 out.push_str(&format!(
78 "linkprobe_last_error{{{labels},error=\"{err}\"}} 1\n"
79 ));
80 out.push('\n');
81 out
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::measurement::{Measurement, Throughput};
88 use crate::server::Server;
89
90 #[test]
91 fn formats_gauges() {
92 let result = RunResult::new(
93 "librespeed",
94 Server::librespeed("https://example/"),
95 Measurement {
96 latency_ms: Some(12.5),
97 jitter_ms: Some(1.0),
98 download: Some(Throughput::from_bps(100_000_000.0)),
99 upload: Some(Throughput::from_bps(50_000_000.0)),
100 packet_loss: None,
101 },
102 );
103 let text = format_openmetrics(&result);
104 assert!(text.contains("linkprobe_ok{"));
105 assert!(text.contains("linkprobe_latency_milliseconds{"));
106 assert!(text.contains("12.5"));
107 assert!(text.contains("linkprobe_download_bits_per_second{"));
108 assert!(!text.contains("linkprobe_packet_loss_ratio"));
109 }
110
111 #[test]
112 fn formats_failure_gauges() {
113 let text = format_openmetrics_failed("librespeed", "example", "timeout");
114 assert!(text.contains("linkprobe_ok{"));
115 assert!(text.contains("} 0\n"));
116 }
117}