use super::{Begin, Chart, Dimension, Instruction, Set};
use std::collections::HashMap;
use std::time::Instant;
use thiserror::Error;
use validator::{Validate, ValidationErrors};
use std::io::Write;
#[derive(Error, Debug)]
pub enum CollectorError {
#[error("unknown Chart: type.id = {0}")]
UnkownChart(String),
#[error("unknown Dimension: id = {0}")]
UnkownDimension(String),
#[error(transparent)]
ValidationErrors(#[from] ValidationErrors),
#[error(transparent)]
IOError(#[from] std::io::Error),
}
#[derive(Default)]
struct CollectedDimensionInfo {
value: i64,
commited: bool,
}
#[derive(Default)]
struct CollectedChartInfo {
dimensions: HashMap<String, CollectedDimensionInfo>,
last_commit: Option<Instant>,
}
#[derive()]
pub struct Collector<'a, W: Write> {
charts: HashMap<String, CollectedChartInfo>,
writer: &'a mut W,
}
impl<'a, W: Write> Collector<'a, W> {
pub fn new(writer: &'a mut W) -> Self {
Collector {
charts: HashMap::new(),
writer: writer,
}
}
pub fn add_chart(&mut self, chart: &Chart) -> Result<(), CollectorError> {
chart.validate()?;
let cci = CollectedChartInfo {
..Default::default()
};
self.charts.insert(format!("{}", chart.type_id), cci);
writeln!(self.writer, "{}", chart)?;
Ok(())
}
pub fn add_dimension(
&mut self,
chart_id: &str,
dimension: &Dimension,
) -> Result<(), CollectorError> {
dimension.validate()?;
let cci = self
.charts
.get_mut(chart_id)
.ok_or(CollectorError::UnkownChart(chart_id.to_owned()))?;
let cdi = CollectedDimensionInfo {
..Default::default()
};
cci.dimensions.insert(dimension.id.to_owned(), cdi);
writeln!(self.writer, "{}", dimension)?;
Ok(())
}
pub fn prepare_value(
&mut self,
chart_id: &str,
dimension_id: &str,
value: i64,
) -> Result<(), CollectorError> {
let cci = self
.charts
.get_mut(chart_id)
.ok_or(CollectorError::UnkownChart(chart_id.to_owned()))?;
let mut cdi = cci
.dimensions
.get_mut(dimension_id)
.ok_or(CollectorError::UnkownDimension(dimension_id.to_owned()))?;
cdi.value = value;
cdi.commited = false;
Ok(())
}
pub fn commit_chart(&mut self, chart_id: &str) -> Result<(), CollectorError> {
let mut cci = self
.charts
.get_mut(chart_id)
.ok_or(CollectorError::UnkownChart(chart_id.to_owned()))?;
let now = Instant::now();
let begin = Begin {
type_id: chart_id,
microseconds: match cci.last_commit {
Some(t) => Some(now.duration_since(t).as_micros()),
_ => None,
},
};
cci.last_commit = Some(now);
writeln!(self.writer, "{}", begin).unwrap();
for (id, cdi) in cci.dimensions.iter_mut() {
if !cdi.commited {
writeln!(
self.writer,
"{}",
Set {
id: id,
value: Some(cdi.value)
}
)
.unwrap();
cdi.commited = true;
}
}
writeln!(self.writer, "{}", Instruction::END)?;
Ok(())
}
}
#[cfg(test)]
mod collector_tests {
use super::{Chart, Collector, Dimension};
use pretty_assertions::assert_eq;
#[test]
fn collector_test() {
let mut redirect_buf = Vec::new();
let mut collector = Collector::new(&mut redirect_buf);
collector
.add_chart(&mut Chart {
type_id: "olsr.test_id",
name: "test_name",
title: "captions",
units: "ms",
..Default::default()
})
.unwrap();
collector
.add_dimension(
"olsr.test_id",
&Dimension {
id: "test_dim_id",
name: "test_dim_name",
..Default::default()
},
)
.unwrap();
collector
.prepare_value("olsr.test_id", "test_dim_id", 4242)
.unwrap();
collector.commit_chart("olsr.test_id").unwrap();
collector
.prepare_value("olsr.test_id", "test_dim_id", 4343)
.unwrap();
collector.commit_chart("olsr.test_id").unwrap();
let should_be = r#"CHART "olsr.test_id" "test_name" "captions" "ms"
DIMENSION "test_dim_id" "test_dim_name"
BEGIN "olsr.test_id"
SET "test_dim_id" = 00
END
BEGIN "olsr.test_id" 0
SET "test_dim_id" = 00
END
"#;
let mut output = String::from_utf8(redirect_buf).unwrap();
output = output
.chars()
.map(|x| if x.is_numeric() { '0' } else { x })
.collect::<String>()
.replace("00", "0");
assert_eq!(output, should_be);
}
}