use std::{collections::HashMap, error, fmt, iter, num, sync::LazyLock};
use chrono::{DateTime, Utc};
use crate::event::metric::{Metric, MetricKind, MetricTags, MetricValue};
static SCOREBOARD: LazyLock<HashMap<char, &'static str>> = LazyLock::new(|| {
vec![
('_', "waiting"),
('S', "starting"),
('R', "reading"),
('W', "sending"),
('K', "keepalive"),
('D', "dnslookup"),
('C', "closing"),
('L', "logging"),
('G', "finishing"),
('I', "idle_cleanup"),
('.', "open"),
]
.into_iter()
.collect()
});
enum StatusFieldStatistic<'a> {
ServerUptimeSeconds(u64),
TotalAccesses(u64),
TotalKBytes(u64),
TotalDuration(u64),
CpuUser(f64),
CpuSystem(f64),
CpuChildrenUser(f64),
CpuChildrenSystem(f64),
CpuLoad(f64),
IdleWorkers(u64),
BusyWorkers(u64),
ConnsTotal(u64),
ConnsAsyncWriting(u64),
ConnsAsyncKeepAlive(u64),
ConnsAsyncClosing(u64),
Scoreboard(&'a str),
}
impl<'a> StatusFieldStatistic<'a> {
fn from_key_value(
key: &str,
value: &'a str,
) -> Option<Result<StatusFieldStatistic<'a>, ParseError>> {
match key {
"ServerUptimeSeconds" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::ServerUptimeSeconds))
}
"Total Accesses" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::TotalAccesses))
}
"Total kBytes" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::TotalKBytes))
}
"Total Duration" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::TotalDuration))
}
"CPUUser" => Some(parse_numeric_value(key, value).map(StatusFieldStatistic::CpuUser)),
"CPUSystem" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::CpuSystem))
}
"CPUChildrenUser" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::CpuChildrenUser))
}
"CPUChildrenSystem" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::CpuChildrenSystem))
}
"CPULoad" => Some(parse_numeric_value(key, value).map(StatusFieldStatistic::CpuLoad)),
"IdleWorkers" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::IdleWorkers))
}
"BusyWorkers" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::BusyWorkers))
}
"ConnsTotal" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::ConnsTotal))
}
"ConnsAsyncWriting" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::ConnsAsyncWriting))
}
"ConnsAsyncClosing" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::ConnsAsyncClosing))
}
"ConnsAsyncKeepAlive" => {
Some(parse_numeric_value(key, value).map(StatusFieldStatistic::ConnsAsyncKeepAlive))
}
"Scoreboard" => Some(Ok(StatusFieldStatistic::Scoreboard(value))),
_ => None,
}
}
}
pub fn parse(
payload: &str,
namespace: Option<&str>,
now: DateTime<Utc>,
tags: Option<&MetricTags>,
) -> impl Iterator<Item = Result<Metric, ParseError>> {
let parsed = payload
.lines()
.filter_map(|l| {
let mut parts = l.splitn(2, ':');
let key = parts.next();
let value = parts.next();
match (key, value) {
(Some(k), Some(v)) => Some((k, v.trim())),
_ => None,
}
})
.collect::<HashMap<_, _>>();
parsed
.iter()
.filter_map(|(key, value)| line_to_metrics(key, value, namespace, now, tags))
.fold(vec![], |mut acc, v| {
match v {
Ok(metrics) => metrics.for_each(|v| acc.push(Ok(v))),
Err(error) => acc.push(Err(error)),
};
acc
})
.into_iter()
}
fn line_to_metrics<'a>(
key: &str,
value: &str,
namespace: Option<&'a str>,
now: DateTime<Utc>,
tags: Option<&'a MetricTags>,
) -> Option<Result<Box<dyn Iterator<Item = Metric> + 'a>, ParseError>> {
StatusFieldStatistic::from_key_value(key, value).map(move |result| {
result.map(move |statistic| match statistic {
StatusFieldStatistic::ServerUptimeSeconds(value) => Box::new(iter::once(
Metric::new(
"uptime_seconds_total",
MetricKind::Absolute,
MetricValue::Counter {
value: value as f64,
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags(tags.cloned())
.with_timestamp(Some(now)),
)),
StatusFieldStatistic::TotalAccesses(value) => Box::new(iter::once(
Metric::new(
"access_total",
MetricKind::Absolute,
MetricValue::Counter {
value: value as f64,
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags(tags.cloned())
.with_timestamp(Some(now)),
)),
StatusFieldStatistic::TotalKBytes(value) => Box::new(iter::once(
Metric::new(
"sent_bytes_total",
MetricKind::Absolute,
MetricValue::Counter {
value: (value * 1024) as f64,
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags(tags.cloned())
.with_timestamp(Some(now)),
)),
StatusFieldStatistic::TotalDuration(value) => Box::new(iter::once(
Metric::new(
"duration_seconds_total",
MetricKind::Absolute,
MetricValue::Counter {
value: value as f64,
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags(tags.cloned())
.with_timestamp(Some(now)),
)),
StatusFieldStatistic::CpuUser(value) => Box::new(iter::once(
Metric::new(
"cpu_seconds_total",
MetricKind::Absolute,
MetricValue::Gauge { value },
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("type".to_string(), "user".to_string());
Some(tags)
})
.with_timestamp(Some(now)),
))
as Box<dyn Iterator<Item = Metric>>,
StatusFieldStatistic::CpuSystem(value) => Box::new(iter::once(
Metric::new(
"cpu_seconds_total",
MetricKind::Absolute,
MetricValue::Gauge { value },
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("type".to_string(), "system".to_string());
Some(tags)
})
.with_timestamp(Some(now)),
))
as Box<dyn Iterator<Item = Metric>>,
StatusFieldStatistic::CpuChildrenUser(value) => Box::new(iter::once(
Metric::new(
"cpu_seconds_total",
MetricKind::Absolute,
MetricValue::Gauge { value },
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("type".to_string(), "children_user".to_string());
Some(tags)
})
.with_timestamp(Some(now)),
))
as Box<dyn Iterator<Item = Metric>>,
StatusFieldStatistic::CpuChildrenSystem(value) => Box::new(iter::once(
Metric::new(
"cpu_seconds_total",
MetricKind::Absolute,
MetricValue::Gauge { value },
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("type".to_string(), "children_system".to_string());
Some(tags)
})
.with_timestamp(Some(now)),
))
as Box<dyn Iterator<Item = Metric>>,
StatusFieldStatistic::CpuLoad(value) => Box::new(iter::once(
Metric::new(
"cpu_load",
MetricKind::Absolute,
MetricValue::Gauge { value },
)
.with_namespace(namespace.map(str::to_string))
.with_tags(tags.cloned())
.with_timestamp(Some(now)),
))
as Box<dyn Iterator<Item = Metric>>,
StatusFieldStatistic::IdleWorkers(value) => Box::new(iter::once(
Metric::new(
"workers",
MetricKind::Absolute,
MetricValue::Gauge {
value: value as f64,
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("state".to_string(), "idle".to_string());
Some(tags)
})
.with_timestamp(Some(now)),
))
as Box<dyn Iterator<Item = Metric>>,
StatusFieldStatistic::BusyWorkers(value) => Box::new(iter::once(
Metric::new(
"workers",
MetricKind::Absolute,
MetricValue::Gauge {
value: value as f64,
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("state".to_string(), "busy".to_string());
Some(tags)
})
.with_timestamp(Some(now)),
)),
StatusFieldStatistic::ConnsTotal(value) => Box::new(iter::once(
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge {
value: value as f64,
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("state".to_string(), "total".to_string());
Some(tags)
})
.with_timestamp(Some(now)),
)),
StatusFieldStatistic::ConnsAsyncWriting(value) => Box::new(iter::once(
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge {
value: value as f64,
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("state".to_string(), "writing".to_string());
Some(tags)
})
.with_timestamp(Some(now)),
)),
StatusFieldStatistic::ConnsAsyncClosing(value) => Box::new(iter::once(
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge {
value: value as f64,
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("state".to_string(), "closing".to_string());
Some(tags)
})
.with_timestamp(Some(now)),
)),
StatusFieldStatistic::ConnsAsyncKeepAlive(value) => Box::new(iter::once(
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge {
value: value as f64,
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("state".to_string(), "keepalive".to_string());
Some(tags)
})
.with_timestamp(Some(now)),
)),
StatusFieldStatistic::Scoreboard(value) => {
let scores = value.chars().fold(HashMap::new(), |mut m, c| {
*m.entry(c).or_insert(0u32) += 1;
m
});
Box::new(SCOREBOARD.iter().map(move |(c, name)| {
score_to_metric(
namespace,
now,
tags,
name,
scores.get(c).copied().unwrap_or_default(),
)
})) as Box<dyn Iterator<Item = Metric>>
}
})
})
}
fn parse_numeric_value<T: std::str::FromStr>(key: &str, value: &str) -> Result<T, ParseError>
where
T::Err: Into<ValueParseError> + 'static,
{
value.parse::<T>().map_err(|error| ParseError {
key: key.to_string(),
error: error.into(),
})
}
fn score_to_metric(
namespace: Option<&str>,
now: DateTime<Utc>,
tags: Option<&MetricTags>,
state: &str,
count: u32,
) -> Metric {
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge {
value: count.into(),
},
)
.with_namespace(namespace.map(str::to_string))
.with_tags({
let mut tags = tags.cloned().unwrap_or_default();
tags.replace("state".to_string(), state.to_string());
Some(tags)
})
.with_timestamp(Some(now))
}
#[derive(Debug)]
enum ValueParseError {
Float(num::ParseFloatError),
Int(num::ParseIntError),
}
impl From<num::ParseFloatError> for ValueParseError {
fn from(error: num::ParseFloatError) -> ValueParseError {
ValueParseError::Float(error)
}
}
impl From<num::ParseIntError> for ValueParseError {
fn from(error: num::ParseIntError) -> ValueParseError {
ValueParseError::Int(error)
}
}
impl error::Error for ValueParseError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
ValueParseError::Float(ref e) => Some(e),
ValueParseError::Int(ref e) => Some(e),
}
}
}
impl fmt::Display for ValueParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ValueParseError::Float(ref e) => e.fmt(f),
ValueParseError::Int(ref e) => e.fmt(f),
}
}
}
#[derive(Debug)]
pub struct ParseError {
key: String,
error: ValueParseError,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "could not parse value for {}: {}", self.key, self.error)
}
}
impl error::Error for ParseError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
Some(&self.error)
}
}
#[cfg(test)]
mod test {
use chrono::{DateTime, Utc};
use similar_asserts::assert_eq;
use vector_lib::assert_event_data_eq;
use vector_lib::metric_tags;
use super::*;
use crate::event::metric::{Metric, MetricKind, MetricValue};
#[test]
fn test_not_extended() {
let payload = r"
localhost
ServerVersion: Apache/2.4.46 (Unix)
ServerMPM: event
Server Built: Aug 5 2020 23:20:17
CurrentTime: Thursday, 03-Sep-2020 20:48:54 UTC
RestartTime: Thursday, 03-Sep-2020 20:48:41 UTC
ParentServerConfigGeneration: 1
ParentServerMPMGeneration: 0
ServerUptimeSeconds: 12
ServerUptime: 12 seconds
Load1: 0.75
Load5: 0.59
Load15: 0.76
BusyWorkers: 1
IdleWorkers: 74
Processes: 3
Stopping: 0
BusyWorkers: 1
IdleWorkers: 74
ConnsTotal: 1
ConnsAsyncWriting: 0
ConnsAsyncKeepAlive: 0
ConnsAsyncClosing: 0
Scoreboard: ____S_____I______R____I_______KK___D__C__G_L____________W__________________.....................................................................................................................................................................................................................................................................................................................................
";
let (now, metrics, errors) = parse_sort(payload);
assert_event_data_eq!(
metrics,
vec![
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "closing")))
.with_timestamp(Some(now)),
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "keepalive")))
.with_timestamp(Some(now)),
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "total")))
.with_timestamp(Some(now)),
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "writing")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "closing")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "dnslookup")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "finishing")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 2.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "idle_cleanup")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 2.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "keepalive")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "logging")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 325.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "open")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "reading")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "sending")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "starting")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 64.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "waiting")))
.with_timestamp(Some(now)),
Metric::new(
"uptime_seconds_total",
MetricKind::Absolute,
MetricValue::Counter { value: 12.0 },
)
.with_namespace(Some("apache"))
.with_timestamp(Some(now)),
Metric::new(
"workers",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "busy")))
.with_timestamp(Some(now)),
Metric::new(
"workers",
MetricKind::Absolute,
MetricValue::Gauge { value: 74.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "idle")))
.with_timestamp(Some(now)),
]
);
assert_eq!(errors.len(), 0);
}
#[test]
fn test_extended() {
let payload = r"
localhost
ServerVersion: Apache/2.4.46 (Unix)
ServerMPM: event
Server Built: Aug 5 2020 23:20:17
CurrentTime: Friday, 21-Aug-2020 18:41:34 UTC
RestartTime: Friday, 21-Aug-2020 18:41:08 UTC
ParentServerConfigGeneration: 1
ParentServerMPMGeneration: 0
ServerUptimeSeconds: 26
ServerUptime: 26 seconds
Load1: 0.00
Load5: 0.03
Load15: 0.03
Total Accesses: 30
Total kBytes: 217
Total Duration: 11
CPUUser: .2
CPUSystem: .02
CPUChildrenUser: 0
CPUChildrenSystem: 0
CPULoad: .846154
Uptime: 26
ReqPerSec: 1.15385
BytesPerSec: 8546.46
BytesPerReq: 7406.93
DurationPerReq: .366667
BusyWorkers: 1
IdleWorkers: 74
Processes: 3
Stopping: 0
BusyWorkers: 1
IdleWorkers: 74
ConnsTotal: 1
ConnsAsyncWriting: 0
ConnsAsyncKeepAlive: 0
ConnsAsyncClosing: 0
Scoreboard: ____S_____I______R____I_______KK___D__C__G_L____________W__________________.....................................................................................................................................................................................................................................................................................................................................
";
let (now, metrics, errors) = parse_sort(payload);
assert_event_data_eq!(
metrics,
vec![
Metric::new(
"access_total",
MetricKind::Absolute,
MetricValue::Counter { value: 30.0 },
)
.with_namespace(Some("apache"))
.with_timestamp(Some(now)),
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "closing")))
.with_timestamp(Some(now)),
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "keepalive")))
.with_timestamp(Some(now)),
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "total")))
.with_timestamp(Some(now)),
Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "writing")))
.with_timestamp(Some(now)),
Metric::new(
"cpu_load",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.846154 },
)
.with_namespace(Some("apache"))
.with_timestamp(Some(now)),
Metric::new(
"cpu_seconds_total",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("type" => "children_system")))
.with_timestamp(Some(now)),
Metric::new(
"cpu_seconds_total",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("type" => "children_user")))
.with_timestamp(Some(now)),
Metric::new(
"cpu_seconds_total",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.02 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("type" => "system")))
.with_timestamp(Some(now)),
Metric::new(
"cpu_seconds_total",
MetricKind::Absolute,
MetricValue::Gauge { value: 0.2 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("type" => "user")))
.with_timestamp(Some(now)),
Metric::new(
"duration_seconds_total",
MetricKind::Absolute,
MetricValue::Counter { value: 11.0 },
)
.with_namespace(Some("apache"))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "closing")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "dnslookup")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "finishing")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 2.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "idle_cleanup")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 2.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "keepalive")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "logging")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 325.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "open")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "reading")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "sending")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "starting")))
.with_timestamp(Some(now)),
Metric::new(
"scoreboard",
MetricKind::Absolute,
MetricValue::Gauge { value: 64.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "waiting")))
.with_timestamp(Some(now)),
Metric::new(
"sent_bytes_total",
MetricKind::Absolute,
MetricValue::Counter { value: 222208.0 },
)
.with_namespace(Some("apache"))
.with_timestamp(Some(now)),
Metric::new(
"uptime_seconds_total",
MetricKind::Absolute,
MetricValue::Counter { value: 26.0 },
)
.with_namespace(Some("apache"))
.with_timestamp(Some(now)),
Metric::new(
"workers",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "busy")))
.with_timestamp(Some(now)),
Metric::new(
"workers",
MetricKind::Absolute,
MetricValue::Gauge { value: 74.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "idle")))
.with_timestamp(Some(now)),
]
);
assert_eq!(errors.len(), 0);
}
#[test]
fn test_parse_failure() {
let payload = r"
ServerUptimeSeconds: not a number
ConnsTotal: 1
";
let (now, metrics, errors) = parse_sort(payload);
assert_event_data_eq!(
metrics,
vec![Metric::new(
"connections",
MetricKind::Absolute,
MetricValue::Gauge { value: 1.0 },
)
.with_namespace(Some("apache"))
.with_tags(Some(metric_tags!("state" => "total")))
.with_timestamp(Some(now)),]
);
assert_eq!(errors.len(), 1);
}
fn parse_sort(payload: &str) -> (DateTime<Utc>, Vec<Metric>, Vec<ParseError>) {
let now: DateTime<Utc> = Utc::now();
let (mut metrics, errors) = parse(payload, Some("apache"), now, None).fold(
(vec![], vec![]),
|(mut metrics, mut errors), v| {
match v {
Ok(m) => metrics.push(m),
Err(e) => errors.push(e),
}
(metrics, errors)
},
);
metrics.sort_by_key(|metric| metric.series().to_string());
(now, metrics, errors)
}
}