TLS Mock Clock for tests

This commit is contained in:
Francis Lalonde 2018-05-25 12:28:01 -04:00
parent c3c7329c3d
commit 838c121fcc
5 changed files with 126 additions and 156 deletions

View File

@ -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

View File

@ -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);
}
}

View File

@ -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()
})
}

View File

@ -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]

View File

@ -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);
}
}