mirror of https://github.com/fralalonde/dipstick
TLS Mock Clock for tests
This commit is contained in:
parent
c3c7329c3d
commit
838c121fcc
|
@ -13,7 +13,6 @@ repository = "https://github.com/fralalonde/dipstick"
|
|||
readme = "README.md"
|
||||
keywords = ["metrics", "statsd", "graphite", "timer", "monitoring"]
|
||||
license = "MIT/Apache-2.0"
|
||||
build = "build.rs"
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "fralalonde/dipstick", branch = "master" }
|
||||
|
@ -32,9 +31,6 @@ skeptic = { version = "0.13", optional = true }
|
|||
|
||||
[features]
|
||||
bench = []
|
||||
# enables the use of the mock metric clock outside of dipstick's own tests
|
||||
# this disables real-time metrics clock and should not be used outside of tests
|
||||
mock_clock = []
|
||||
|
||||
[package.metadata.release]
|
||||
#sign-commit = true
|
||||
|
|
|
@ -383,3 +383,89 @@ mod bench {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use Value;
|
||||
use std::time::Duration;
|
||||
use std::collections::BTreeMap;
|
||||
use aggregate::{MetricAggregator, all_stats, summary, average, StatsFn};
|
||||
use scope::MetricInput;
|
||||
use clock::{mock_clock_advance, mock_clock_reset};
|
||||
use local::StatsMap;
|
||||
|
||||
fn make_stats(stats_fn: &StatsFn) -> BTreeMap<String, Value> {
|
||||
mock_clock_reset();
|
||||
|
||||
let metrics = MetricAggregator::new().with_suffix("test");
|
||||
|
||||
let counter = metrics.counter("counter_a");
|
||||
let timer = metrics.timer("timer_a");
|
||||
let gauge = metrics.gauge("gauge_a");
|
||||
let marker = metrics.marker("marker_a");
|
||||
|
||||
marker.mark();
|
||||
marker.mark();
|
||||
marker.mark();
|
||||
|
||||
counter.count(10);
|
||||
counter.count(20);
|
||||
|
||||
timer.interval_us(10_000_000);
|
||||
timer.interval_us(20_000_000);
|
||||
|
||||
gauge.value(10);
|
||||
gauge.value(20);
|
||||
|
||||
mock_clock_advance(Duration::from_secs(3));
|
||||
|
||||
// TODO expose & use flush_to()
|
||||
let stats = StatsMap::new();
|
||||
metrics.flush_to(&stats, stats_fn);
|
||||
stats.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_aggregate_all_stats() {
|
||||
let map = make_stats(&all_stats);
|
||||
|
||||
assert_eq!(map["test.counter_a.count"], 2);
|
||||
assert_eq!(map["test.counter_a.sum"], 30);
|
||||
assert_eq!(map["test.counter_a.mean"], 15);
|
||||
assert_eq!(map["test.counter_a.rate"], 10);
|
||||
|
||||
assert_eq!(map["test.timer_a.count"], 2);
|
||||
assert_eq!(map["test.timer_a.sum"], 30_000_000);
|
||||
assert_eq!(map["test.timer_a.min"], 10_000_000);
|
||||
assert_eq!(map["test.timer_a.max"], 20_000_000);
|
||||
assert_eq!(map["test.timer_a.mean"], 15_000_000);
|
||||
assert_eq!(map["test.timer_a.rate"], 1);
|
||||
|
||||
assert_eq!(map["test.gauge_a.mean"], 15);
|
||||
assert_eq!(map["test.gauge_a.min"], 10);
|
||||
assert_eq!(map["test.gauge_a.max"], 20);
|
||||
|
||||
assert_eq!(map["test.marker_a.count"], 3);
|
||||
assert_eq!(map["test.marker_a.rate"], 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_aggregate_summary() {
|
||||
let map = make_stats(&summary);
|
||||
|
||||
assert_eq!(map["test.counter_a"], 30);
|
||||
assert_eq!(map["test.timer_a"], 30_000_000);
|
||||
assert_eq!(map["test.gauge_a"], 15);
|
||||
assert_eq!(map["test.marker_a"], 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_aggregate_average() {
|
||||
let map = make_stats(&average);
|
||||
|
||||
assert_eq!(map["test.counter_a"], 15);
|
||||
assert_eq!(map["test.timer_a"], 15_000_000);
|
||||
assert_eq!(map["test.gauge_a"], 15);
|
||||
assert_eq!(map["test.marker_a"], 3);
|
||||
}
|
||||
}
|
||||
|
|
128
src/clock.rs
128
src/clock.rs
|
@ -1,4 +1,5 @@
|
|||
use std::time::Instant;
|
||||
use std::ops::Add;
|
||||
use std::time::{Duration, Instant};
|
||||
use core::Value;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
|
@ -10,12 +11,12 @@ impl TimeHandle {
|
|||
/// Get a handle on current time.
|
||||
/// Used by the TimerMetric start_time() method.
|
||||
pub fn now() -> TimeHandle {
|
||||
TimeHandle(self::inner::now())
|
||||
TimeHandle(now())
|
||||
}
|
||||
|
||||
/// Get the elapsed time in microseconds since TimeHanduule was obtained.
|
||||
pub fn elapsed_us(self) -> Value {
|
||||
let duration = self::inner::now() - self.0;
|
||||
let duration = now() - self.0;
|
||||
duration.as_secs() * 1000000 + (duration.subsec_nanos() / 1000) as Value
|
||||
}
|
||||
|
||||
|
@ -25,99 +26,48 @@ impl TimeHandle {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(mock_clock, test)))]
|
||||
mod inner {
|
||||
use std::time::Instant;
|
||||
|
||||
pub fn now() -> Instant {
|
||||
Instant::now()
|
||||
}
|
||||
/// The mock clock is thread local so that tests can run in parallel without affecting each other.
|
||||
use std::cell::RefCell;
|
||||
thread_local! {
|
||||
static MOCK_CLOCK: RefCell<Instant> = RefCell::new(Instant::now());
|
||||
}
|
||||
|
||||
#[cfg(any(mock_clock, test))]
|
||||
pub mod inner {
|
||||
use std::ops::Add;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::sync::RwLock;
|
||||
|
||||
lazy_static!{
|
||||
static ref MOCK_CLOCK: RwLock<Instant> = RwLock::new(Instant::now());
|
||||
/// Set the mock clock to the current time.
|
||||
/// Enables writing reproducible metrics tests in combination with #mock_clock_advance()
|
||||
/// Should be called at beginning of test, before the metric scope is created.
|
||||
/// Not feature-gated so it stays visible to outside crates but may not be used outside of tests.
|
||||
pub fn mock_clock_reset() {
|
||||
if !cfg!(not(test)) {
|
||||
warn!("Mock clock used outside of cfg[]tests has no effect")
|
||||
}
|
||||
MOCK_CLOCK.with(|now| {
|
||||
*now.borrow_mut() = Instant::now();
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/// Metrics mock_clock enabled!
|
||||
/// thread::sleep will have no effect on metrics.
|
||||
/// Use advance_time() to simulate passing time.
|
||||
pub fn now() -> Instant {
|
||||
MOCK_CLOCK.read().unwrap().clone()
|
||||
}
|
||||
/// Advance the mock clock by a certain amount of time.
|
||||
/// Enables writing reproducible metrics tests in combination with #mock_clock_reset()
|
||||
/// Should be after metrics have been produced but before they are published.
|
||||
/// Not feature-gated so it stays visible to outside crates but may not be used outside of tests.
|
||||
pub fn mock_clock_advance(period: Duration) {
|
||||
MOCK_CLOCK.with(|now| {
|
||||
let mut now = now.borrow_mut();
|
||||
*now = now.add(period);
|
||||
})
|
||||
}
|
||||
|
||||
/// Advance the mock clock by a certain amount of time.
|
||||
/// Enables writing reproducible metrics tests.
|
||||
pub fn advance_time(period: Duration) {
|
||||
let mut now = MOCK_CLOCK.write().unwrap();
|
||||
let new_now = now.add(period);
|
||||
*now = new_now;
|
||||
}
|
||||
#[cfg(not(test))]
|
||||
fn now() -> Instant {
|
||||
Instant::now()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use Value;
|
||||
use clock::inner;
|
||||
|
||||
#[test]
|
||||
fn aggregate_all_stats() {
|
||||
use std::time::Duration;
|
||||
use std::collections::BTreeMap;
|
||||
use aggregate::{MetricAggregator, all_stats};
|
||||
use scope::MetricInput;
|
||||
use local::StatsMap;
|
||||
|
||||
let metrics = MetricAggregator::new().with_suffix("test");
|
||||
|
||||
let counter = metrics.counter("counter_a");
|
||||
let timer = metrics.timer("timer_a");
|
||||
let gauge = metrics.gauge("gauge_a");
|
||||
let marker = metrics.marker("marker_a");
|
||||
|
||||
marker.mark();
|
||||
marker.mark();
|
||||
marker.mark();
|
||||
|
||||
counter.count(10);
|
||||
counter.count(20);
|
||||
|
||||
timer.interval_us(10_000_000);
|
||||
timer.interval_us(20_000_000);
|
||||
|
||||
gauge.value(10);
|
||||
gauge.value(20);
|
||||
|
||||
inner::advance_time(Duration::from_secs(3));
|
||||
|
||||
// TODO expose & use flush_to()
|
||||
let stats = StatsMap::new();
|
||||
metrics.flush_to(&stats, &all_stats);
|
||||
let map: BTreeMap<String, Value> = stats.into();
|
||||
|
||||
assert_eq!(map["test.counter_a.count"], 2);
|
||||
assert_eq!(map["test.counter_a.sum"], 30);
|
||||
assert_eq!(map["test.counter_a.mean"], 15);
|
||||
assert_eq!(map["test.counter_a.rate"], 10);
|
||||
|
||||
assert_eq!(map["test.timer_a.count"], 2);
|
||||
assert_eq!(map["test.timer_a.sum"], 30_000_000);
|
||||
assert_eq!(map["test.timer_a.min"], 10_000_000);
|
||||
assert_eq!(map["test.timer_a.max"], 20_000_000);
|
||||
assert_eq!(map["test.timer_a.mean"], 15_000_000);
|
||||
assert_eq!(map["test.timer_a.rate"], 1);
|
||||
|
||||
assert_eq!(map["test.gauge_a.mean"], 15);
|
||||
assert_eq!(map["test.gauge_a.min"], 10);
|
||||
assert_eq!(map["test.gauge_a.max"], 20);
|
||||
|
||||
assert_eq!(map["test.marker_a.count"], 3);
|
||||
assert_eq!(map["test.marker_a.rate"], 1);
|
||||
}
|
||||
/// Metrics mock_clock enabled!
|
||||
/// thread::sleep will have no effect on metrics.
|
||||
/// Use advance_time() to simulate passing time.
|
||||
fn now() -> Instant {
|
||||
MOCK_CLOCK.with(|now| {
|
||||
*now.borrow()
|
||||
})
|
||||
}
|
||||
|
|
|
@ -73,9 +73,7 @@ mod self_metrics;
|
|||
pub use self_metrics::DIPSTICK_METRICS;
|
||||
|
||||
mod clock;
|
||||
pub use clock::TimeHandle;
|
||||
#[cfg(mock_clock)]
|
||||
pub use clock::inner::advance_time;
|
||||
pub use clock::{TimeHandle, mock_clock_advance, mock_clock_reset};
|
||||
|
||||
// FIXME using * to prevent "use of deprecated" warnings. #[allow(dead_code)] doesnt work?
|
||||
#[macro_use]
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
//! A sample application continuously aggregating metrics,
|
||||
//! printing the summary stats every three seconds
|
||||
|
||||
extern crate dipstick;
|
||||
|
||||
#[cfg(mock_clock)]
|
||||
mod test {
|
||||
use std::time::Duration;
|
||||
use std::collections::BTreeMap;
|
||||
use dipstick::*;
|
||||
|
||||
#[test]
|
||||
fn external_aggregate_all_stats() {
|
||||
let metrics = MetricAggregator::new().with_suffix("test");
|
||||
|
||||
let counter = metrics.counter("counter_a");
|
||||
let timer = metrics.timer("timer_a");
|
||||
let gauge = metrics.gauge("gauge_a");
|
||||
let marker = metrics.marker("marker_a");
|
||||
|
||||
marker.mark();
|
||||
marker.mark();
|
||||
marker.mark();
|
||||
|
||||
counter.count(10);
|
||||
counter.count(20);
|
||||
|
||||
timer.interval_us(10_000_000);
|
||||
timer.interval_us(20_000_000);
|
||||
|
||||
gauge.value(10);
|
||||
gauge.value(20);
|
||||
|
||||
advance_time(Duration::from_secs(3));
|
||||
|
||||
// TODO expose & use flush_to()
|
||||
let stats = StatsMap::new();
|
||||
metrics.flush_to(&stats, &all_stats);
|
||||
let map: BTreeMap<String, Value> = stats.into();
|
||||
|
||||
assert_eq!(map["test.counter_a.count"], 2);
|
||||
assert_eq!(map["test.counter_a.sum"], 30);
|
||||
assert_eq!(map["test.counter_a.mean"], 15);
|
||||
assert_eq!(map["test.counter_a.rate"], 10);
|
||||
|
||||
assert_eq!(map["test.timer_a.count"], 2);
|
||||
assert_eq!(map["test.timer_a.sum"], 30_000_000);
|
||||
assert_eq!(map["test.timer_a.min"], 10_000_000);
|
||||
assert_eq!(map["test.timer_a.max"], 20_000_000);
|
||||
assert_eq!(map["test.timer_a.mean"], 15_000_000);
|
||||
assert_eq!(map["test.timer_a.rate"], 1);
|
||||
|
||||
assert_eq!(map["test.gauge_a.mean"], 15);
|
||||
assert_eq!(map["test.gauge_a.min"], 10);
|
||||
assert_eq!(map["test.gauge_a.max"], 20);
|
||||
|
||||
assert_eq!(map["test.marker_a.count"], 3);
|
||||
assert_eq!(map["test.marker_a.rate"], 1);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue