use self::LineOp::*;
use crate::MetricValue;
use crate::input::InputKind;
use crate::name::MetricName;
use std::io;
use std::io::Write;
use std::sync::Arc;
pub enum LineOp {
Literal(Vec<u8>),
LabelExists(String, Vec<LabelOp>),
ValueAsText,
ScaledValueAsText(f64),
NewLine,
}
pub enum LabelOp {
Literal(Vec<u8>),
LabelKey,
LabelValue,
}
pub struct LineTemplate {
ops: Vec<LineOp>,
}
impl From<Vec<LineOp>> for LineTemplate {
fn from(ops: Vec<LineOp>) -> Self {
LineTemplate::new(ops)
}
}
impl LineTemplate {
pub fn new(ops: Vec<LineOp>) -> Self {
LineTemplate { ops }
}
pub fn print<L>(&self, output: &mut dyn Write, value: MetricValue, lookup: L) -> io::Result<()>
where
L: Fn(&str) -> Option<Arc<String>>,
{
for cmd in &self.ops {
match cmd {
Literal(src) => output.write_all(src.as_ref())?,
ValueAsText => output.write_all(format!("{value}").as_ref())?,
ScaledValueAsText(scale) => {
let scaled = value as f64 / scale;
output.write_all(format!("{scaled}").as_ref())?
}
NewLine => writeln!(output)?,
LabelExists(label_key, print_label) => {
if let Some(label_value) = lookup(label_key.as_ref()) {
for label_cmd in print_label {
match label_cmd {
LabelOp::LabelValue => output.write_all(label_value.as_bytes())?,
LabelOp::LabelKey => output.write_all(label_key.as_bytes())?,
LabelOp::Literal(src) => output.write_all(src.as_ref())?,
}
}
}
}
};
}
Ok(())
}
}
pub trait Formatting {
fn formatting(&self, format: impl LineFormat + 'static) -> Self;
}
pub trait LineFormat: Send + Sync {
fn template(&self, name: &MetricName, kind: InputKind) -> LineTemplate;
}
#[derive(Default)]
pub struct SimpleFormat {
}
impl LineFormat for SimpleFormat {
fn template(&self, name: &MetricName, _kind: InputKind) -> LineTemplate {
let mut header = name.join(".");
header.push(' ');
LineTemplate {
ops: vec![Literal(header.into_bytes()), ValueAsText, NewLine],
}
}
}
#[cfg(test)]
pub mod test {
use super::*;
use crate::label::Labels;
pub struct TestFormat;
impl LineFormat for TestFormat {
fn template(&self, name: &MetricName, kind: InputKind) -> LineTemplate {
let mut header: String = format!("{:?}", kind);
header.push('/');
header.push_str(&name.join("."));
header.push(' ');
LineTemplate {
ops: vec![
Literal(header.into()),
ValueAsText,
Literal(" ".into()),
ScaledValueAsText(1000.0),
Literal(" ".into()),
LabelExists(
"test_key".into(),
vec![
LabelOp::LabelKey,
LabelOp::Literal("=".into()),
LabelOp::LabelValue,
],
),
NewLine,
],
}
}
}
#[test]
fn print_label_exists() {
let labels: Labels = labels!("test_key" => "456");
let format = TestFormat {};
let mut name = MetricName::from("abc");
name = name.prepend("xyz");
let template = format.template(&name, InputKind::Counter);
let mut out = vec![];
template
.print(&mut out, 123000, |key| labels.lookup(key))
.unwrap();
assert_eq!(
"Counter/xyz.abc 123000 123 test_key=456\n",
String::from_utf8(out).unwrap()
);
}
#[test]
fn print_label_not_exists() {
let format = TestFormat {};
let mut name = MetricName::from("abc");
name = name.prepend("xyz");
let template = format.template(&name, InputKind::Counter);
let mut out = vec![];
template.print(&mut out, 123000, |_key| None).unwrap();
assert_eq!(
"Counter/xyz.abc 123000 123 \n",
String::from_utf8(out).unwrap()
);
}
}