pub mod chassis;
pub mod cpu;
pub mod disk;
pub mod energy;
pub mod exporter_status;
pub mod gpu;
pub mod hardware;
pub mod memory;
pub mod mig;
pub mod npu;
pub mod process;
pub mod render;
pub mod runtime;
pub mod vgpu;
pub trait MetricExporter {
fn export_metrics(&self) -> String;
}
pub struct MetricBuilder {
metrics: String,
}
impl MetricBuilder {
pub fn new() -> Self {
Self {
metrics: String::new(),
}
}
#[allow(dead_code)]
pub fn comment(&mut self, text: &str) -> &mut Self {
self.metrics.push_str("# ");
self.metrics.push_str(text);
self.metrics.push('\n');
self
}
pub fn help(&mut self, name: &str, description: &str) -> &mut Self {
self.metrics
.push_str(&format!("# HELP {name} {description}\n"));
self
}
pub fn type_(&mut self, name: &str, metric_type: &str) -> &mut Self {
self.metrics
.push_str(&format!("# TYPE {name} {metric_type}\n"));
self
}
pub fn metric(
&mut self,
name: &str,
labels: &[(&str, &str)],
value: impl ToString,
) -> &mut Self {
self.metrics.push_str(name);
if !labels.is_empty() {
self.metrics.push('{');
for (i, (key, value)) in labels.iter().enumerate() {
if i > 0 {
self.metrics.push_str(", ");
}
let escaped_value: String = value
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
.replace('\r', "\\r")
.chars()
.filter(|c| !c.is_control())
.collect();
self.metrics.push_str(&format!("{key}=\"{escaped_value}\""));
}
self.metrics.push('}');
}
self.metrics.push(' ');
self.metrics.push_str(&value.to_string());
self.metrics.push('\n');
self
}
pub fn build(self) -> String {
self.metrics
}
}
impl Default for MetricBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metric_builder_label_escaping_backslash() {
let mut builder = MetricBuilder::new();
builder.metric("test_metric", &[("path", "C:\\Windows\\System32")], "1");
let output = builder.build();
assert!(output.contains(r#"path="C:\\Windows\\System32""#));
}
#[test]
fn test_metric_builder_label_escaping_double_quote() {
let mut builder = MetricBuilder::new();
builder.metric("test_metric", &[("label", r#"say "hello""#)], "1");
let output = builder.build();
assert!(output.contains(r#"label="say \"hello\"""#));
}
#[test]
fn test_metric_builder_label_escaping_newline() {
let mut builder = MetricBuilder::new();
builder.metric("test_metric", &[("value", "line1\nline2")], "1");
let output = builder.build();
assert!(output.contains(r#"value="line1\nline2""#));
}
#[test]
fn test_metric_builder_label_escaping_carriage_return() {
let mut builder = MetricBuilder::new();
builder.metric("test_metric", &[("value", "line1\rline2")], "1");
let output = builder.build();
assert!(output.contains(r#"value="line1\rline2""#));
}
#[test]
fn test_metric_builder_label_strips_control_characters() {
let mut builder = MetricBuilder::new();
builder.metric("test_metric", &[("gpu", "NVIDIA\x1b[2JEvil")], "1");
let output = builder.build();
assert!(!output.contains('\x1b'), "control char leaked: {output}");
assert!(output.contains("NVIDIA[2JEvil"));
}
#[test]
fn test_metric_builder_no_labels() {
let mut builder = MetricBuilder::new();
builder.metric("test_metric", &[], "42.5");
let output = builder.build();
assert_eq!(output, "test_metric 42.5\n");
}
#[test]
fn test_metric_builder_help_and_type() {
let mut builder = MetricBuilder::new();
builder
.help("my_metric", "A test metric")
.type_("my_metric", "gauge")
.metric("my_metric", &[("host", "server1")], "99");
let output = builder.build();
assert!(output.contains("# HELP my_metric A test metric\n"));
assert!(output.contains("# TYPE my_metric gauge\n"));
assert!(output.contains("my_metric{host=\"server1\"} 99\n"));
}
}