dipstick/src/output/format.rs

165 lines
5.1 KiB
Rust
Raw Normal View History

2018-10-09 20:46:15 +00:00
use core::name::Name;
use core::input::Kind;
use core::Value;
2018-10-18 13:31:53 +00:00
use self::LineOp::*;
2018-10-09 20:46:15 +00:00
use std::io;
2018-10-12 20:44:39 +00:00
use std::sync::Arc;
2018-10-09 20:46:15 +00:00
2018-10-10 18:29:30 +00:00
/// Print commands are steps in the execution of output templates.
2018-10-18 13:31:53 +00:00
pub enum LineOp {
2018-10-10 18:29:30 +00:00
/// Print a string.
2018-10-18 14:10:23 +00:00
Literal(Vec<u8>),
2018-10-11 16:32:01 +00:00
/// Lookup and print label value for key, if it exists.
2018-10-18 13:31:53 +00:00
LabelExists(String, Vec<LabelOp>),
2018-10-10 18:29:30 +00:00
/// Print metric value as text.
2018-10-09 20:46:15 +00:00
ValueAsText,
2018-10-10 18:29:30 +00:00
/// Print metric value, divided by the given scale, as text.
ScaledValueAsText(Value),
2018-10-12 20:44:39 +00:00
/// Print the newline character.labels.lookup(key)
2018-10-10 18:29:30 +00:00
NewLine,
2018-10-09 20:46:15 +00:00
}
2018-10-11 16:32:01 +00:00
/// Print commands are steps in the execution of output templates.
2018-10-18 13:31:53 +00:00
pub enum LabelOp {
2018-10-12 20:44:39 +00:00
/// Print a string.
2018-10-18 14:10:23 +00:00
Literal(Vec<u8>),
2018-10-12 20:44:39 +00:00
/// Print the label key.
LabelKey,
/// Print the label value.
LabelValue,
2018-10-11 16:32:01 +00:00
}
2018-10-10 18:29:30 +00:00
/// An sequence of print commands, embodying an output strategy for a single metric.
2018-10-12 20:44:39 +00:00
pub struct LineTemplate {
2018-10-18 13:31:53 +00:00
ops: Vec<LineOp>
}
impl From<Vec<LineOp>> for LineTemplate {
fn from(ops: Vec<LineOp>) -> Self {
LineTemplate { ops }
}
2018-10-09 20:46:15 +00:00
}
2018-10-12 20:44:39 +00:00
impl LineTemplate {
2018-10-10 18:29:30 +00:00
/// Template execution applies commands in turn, writing to the output.
2018-10-12 20:44:39 +00:00
pub fn print<L>(&self, output: &mut io::Write, value: Value, lookup: L) -> Result<(), io::Error>
where L: Fn(&str) -> Option<Arc<String>>
{
2018-10-18 13:31:53 +00:00
for cmd in &self.ops {
2018-10-09 20:46:15 +00:00
match cmd {
2018-10-10 18:29:30 +00:00
Literal(src) => output.write_all(src.as_ref())?,
2018-10-09 20:46:15 +00:00
ValueAsText => output.write_all(format!("{}", value).as_ref())?,
2018-10-10 18:29:30 +00:00
ScaledValueAsText(scale) => {
let scaled = value / scale;
output.write_all(format!("{}", scaled).as_ref())?
},
NewLine => writeln!(output)?,
2018-10-12 20:44:39 +00:00
LabelExists(label_key, print_label) => {
2018-10-11 16:32:01 +00:00
if let Some(label_value) = lookup(label_key.as_ref()) {
2018-10-12 20:44:39 +00:00
for label_cmd in print_label {
match label_cmd {
2018-10-18 13:31:53 +00:00
LabelOp::LabelValue =>
2018-10-12 20:44:39 +00:00
output.write_all(label_value.as_bytes())?,
2018-10-18 13:31:53 +00:00
LabelOp::LabelKey =>
2018-10-12 20:44:39 +00:00
output.write_all(label_key.as_bytes())?,
2018-10-18 13:31:53 +00:00
LabelOp::Literal(src) =>
2018-10-12 20:44:39 +00:00
output.write_all(src.as_ref())?,
}
}
2018-10-11 16:32:01 +00:00
}
2018-10-12 20:44:39 +00:00
},
2018-10-09 20:46:15 +00:00
};
}
Ok(())
}
}
2018-10-12 20:44:39 +00:00
/// Format output config support.
pub trait Formatting {
/// Specify formatting of output.
fn formatting(&self, format: impl LineFormat + 'static) -> Self;
}
2018-10-09 20:46:15 +00:00
/// Forges metric-specific printers
2018-10-12 20:44:39 +00:00
pub trait LineFormat: Send + Sync {
2018-10-09 20:46:15 +00:00
/// Prepare a template for output of metric values.
2018-10-12 20:44:39 +00:00
fn template(&self, name: &Name, kind: Kind) -> LineTemplate;
2018-10-09 20:46:15 +00:00
}
/// A simple metric output format of "MetricName {Value}"
#[derive(Default)]
2018-10-12 20:44:39 +00:00
pub struct SimpleFormat {
// TODO make separator configurable
2018-10-10 18:29:30 +00:00
// separator: String,
}
2018-10-09 20:46:15 +00:00
2018-10-12 20:44:39 +00:00
impl LineFormat for SimpleFormat {
fn template(&self, name: &Name, _kind: Kind) -> LineTemplate {
2018-10-09 20:46:15 +00:00
let mut header = name.join(".");
header.push(' ');
2018-10-12 20:44:39 +00:00
LineTemplate {
2018-10-18 13:31:53 +00:00
ops: vec![
2018-10-18 14:10:23 +00:00
Literal(header.into_bytes()),
2018-10-09 20:46:15 +00:00
ValueAsText,
2018-10-10 18:29:30 +00:00
NewLine,
2018-10-09 20:46:15 +00:00
]
}
}
}
2018-10-10 18:29:30 +00:00
2018-10-12 20:44:39 +00:00
#[cfg(test)]
pub mod test {
use super::*;
use ::Labels;
pub struct TestFormat;
impl LineFormat for TestFormat {
fn template(&self, name: &Name, kind: Kind) -> LineTemplate {
let mut header: String = format!("{:?}", kind);
header.push('/');
header.push_str(&name.join("."));
header.push(' ');
LineTemplate {
2018-10-18 13:31:53 +00:00
ops: vec![
2018-10-18 14:10:23 +00:00
Literal(header.into()),
2018-10-12 20:44:39 +00:00
ValueAsText,
Literal(" ".into()),
ScaledValueAsText(1000),
Literal(" ".into()),
LabelExists("test_key".into(), vec![
2018-10-18 13:31:53 +00:00
LabelOp::LabelKey,
LabelOp::Literal("=".into()),
LabelOp::LabelValue]),
2018-10-12 20:44:39 +00:00
NewLine,
]
}
}
}
#[test]
fn print_label_exists() {
let labels: Labels = labels!("test_key" => "456");
let format = TestFormat {};
let mut name = Name::from("abc");
name = name.prepend("xyz");
let template = format.template(&name, Kind::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 = Name::from("abc");
name = name.prepend("xyz");
let template = format.template(&name, Kind::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());
}
}