mirror of https://github.com/fralalonde/dipstick
Dispatcher done
This commit is contained in:
parent
928704233d
commit
00e6505dd4
|
@ -5,7 +5,7 @@ members = [
|
|||
"examples/multi_outs/",
|
||||
"examples/macro_outs/",
|
||||
"examples/aggregate_print/",
|
||||
"examples/summary_print/",
|
||||
"examples/dispatch_print/",
|
||||
"examples/async_print/",
|
||||
"examples/custom_publish/",
|
||||
"examples/raw_log/",
|
||||
|
@ -36,10 +36,11 @@ build = "build.rs"
|
|||
travis-ci = { repository = "fralalonde/dipstick", branch = "master" }
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.3" }
|
||||
log = "0.3"
|
||||
time = "0.1"
|
||||
lazy_static = "0.2"
|
||||
lazy_static = "1.0"
|
||||
derivative = "1.0"
|
||||
atomic_refcell = "0.1"
|
||||
|
||||
[build-dependencies]
|
||||
skeptic = "0.13"
|
||||
|
|
|
@ -87,7 +87,7 @@ Outputs can use sample rate to expand or format published data.
|
|||
|
||||
Metrics can be recorded asynchronously:
|
||||
```rust,skt-run
|
||||
let _app_metrics = app_metrics(to_stdout()).with_async_queue(64);
|
||||
let _app_metrics = app_metrics(to_stdout().with_async_queue(64));
|
||||
```
|
||||
The async queue uses a Rust channel and a standalone thread.
|
||||
The current behavior is to block when full.
|
||||
|
|
|
@ -8,7 +8,7 @@ use std::time::Duration;
|
|||
use dipstick::*;
|
||||
|
||||
fn main() {
|
||||
let metrics = app_metrics(to_stdout()).with_async_queue(0);
|
||||
let metrics = app_metrics(to_stdout().with_async_queue(0));
|
||||
|
||||
let counter = metrics.counter("counter_a");
|
||||
let timer = metrics.timer("timer_b");
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "dispatch_print"
|
||||
version = "0.0.0"
|
||||
workspace = "../../"
|
||||
|
||||
[dependencies]
|
||||
dipstick = { path = '../../' }
|
|
@ -0,0 +1,38 @@
|
|||
//! A sample application continuously aggregating metrics,
|
||||
//! printing the summary stats every three seconds
|
||||
|
||||
extern crate dipstick;
|
||||
|
||||
use std::time::Duration;
|
||||
use dipstick::*;
|
||||
|
||||
fn main() {
|
||||
let dispatch = dispatch();
|
||||
|
||||
let app_metrics = app_metrics(dispatch.clone());
|
||||
let counter = app_metrics.counter("counter_a");
|
||||
let timer = app_metrics.timer("timer_a");
|
||||
let gauge = app_metrics.gauge("gauge_a");
|
||||
let marker = app_metrics.marker("marker_a");
|
||||
app_metrics.flush_every(Duration::from_secs(3));
|
||||
|
||||
let to_aggregate = aggregate(summary, to_stdout());
|
||||
dispatch.set_receiver(to_aggregate);
|
||||
|
||||
loop {
|
||||
// add counts forever, non-stop
|
||||
counter.count(11);
|
||||
counter.count(12);
|
||||
counter.count(13);
|
||||
|
||||
timer.interval_us(11_000_000);
|
||||
timer.interval_us(12_000_000);
|
||||
timer.interval_us(13_000_000);
|
||||
|
||||
gauge.value(11);
|
||||
gauge.value(12);
|
||||
gauge.value(13);
|
||||
|
||||
marker.mark();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
//! Maintain aggregated metrics for deferred reporting,
|
||||
//!
|
||||
use core::*;
|
||||
use scope_metrics::*;
|
||||
use app_metrics::*;
|
||||
|
||||
use scores::*;
|
||||
use publish::*;
|
||||
|
||||
|
@ -18,46 +21,37 @@ use std::sync::{Arc, RwLock};
|
|||
/// metrics.marker("my_event").mark();
|
||||
/// metrics.marker("my_event").mark();
|
||||
/// ```
|
||||
pub fn aggregate<E, M>(stat_fn: E, to_chain: Chain<M>) -> Chain<Aggregate>
|
||||
pub fn aggregate<E, M>(stat_fn: E, to_chain: ScopeMetrics<M>) -> Aggregator
|
||||
where
|
||||
E: Fn(Kind, &str, ScoreType) -> Option<(Kind, Vec<&str>, Value)> + Send + Sync + 'static,
|
||||
M: Clone + Send + Sync + Debug + 'static,
|
||||
{
|
||||
let metrics = Arc::new(RwLock::new(HashMap::new()));
|
||||
let metrics0 = metrics.clone();
|
||||
Aggregator {
|
||||
metrics: Arc::new(RwLock::new(HashMap::new())),
|
||||
publish: Arc::new(Publisher::new(stat_fn, to_chain))
|
||||
}
|
||||
}
|
||||
|
||||
let publish = Arc::new(Publisher::new(stat_fn, to_chain));
|
||||
|
||||
Chain::new(
|
||||
move |kind, name, _rate| {
|
||||
metrics
|
||||
.write()
|
||||
.unwrap()
|
||||
.entry(name.to_string())
|
||||
.or_insert_with(|| Arc::new(Scoreboard::new(kind, name.to_string())))
|
||||
.clone()
|
||||
},
|
||||
move |_buffered| {
|
||||
let metrics = metrics0.clone();
|
||||
let publish = publish.clone();
|
||||
ControlScopeFn::new(move |cmd| match cmd {
|
||||
impl From<Aggregator> for AppMetrics<Aggregate> {
|
||||
fn from(agg: Aggregator) -> AppMetrics<Aggregate> {
|
||||
let agg_1 = agg.clone();
|
||||
AppMetrics::new(
|
||||
Arc::new(move |kind, name, rate| agg.define_metric(kind, name, rate)),
|
||||
control_scope(move |cmd| match cmd {
|
||||
ScopeCmd::Write(metric, value) => {
|
||||
let metric: &Aggregate = metric;
|
||||
metric.update(value)
|
||||
},
|
||||
ScopeCmd::Flush => {
|
||||
let metrics = metrics.read().expect("Locking metrics scoreboards");
|
||||
let snapshot = metrics.values().flat_map(|score| score.reset()).collect();
|
||||
publish.publish(snapshot);
|
||||
agg_1.flush()
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Central aggregation structure.
|
||||
/// Since `AggregateKey`s themselves contain scores, the aggregator simply maintains
|
||||
/// a shared list of metrics for enumeration when used as source.
|
||||
/// Maintains a list of metrics for enumeration when used as source.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Aggregator {
|
||||
metrics: Arc<RwLock<HashMap<String, Arc<Scoreboard>>>>,
|
||||
|
@ -77,6 +71,7 @@ impl Aggregator {
|
|||
pub fn cleanup(&self) {
|
||||
let orphans: Vec<String> = self.metrics.read().unwrap().iter()
|
||||
// is aggregator now the sole owner?
|
||||
// TODO use weak ref + impl Drop to mark abandoned metrics (see dispatch)
|
||||
.filter(|&(_k, v)| Arc::strong_count(v) == 1)
|
||||
.map(|(k, _v)| k.to_string())
|
||||
.collect();
|
||||
|
@ -87,6 +82,22 @@ impl Aggregator {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup or create a scoreboard for the requested metric.
|
||||
pub fn define_metric(&self, kind: Kind, name: &str, _rate: Rate) -> Aggregate {
|
||||
self.metrics.write().expect("Locking aggregator")
|
||||
.entry(name.to_string())
|
||||
.or_insert_with(|| Arc::new(Scoreboard::new(kind, name.to_string())))
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Collect and reset aggregated data.
|
||||
/// Publish statistics
|
||||
pub fn flush(&self) {
|
||||
let metrics = self.metrics.read().expect("Locking metrics scoreboards");
|
||||
let snapshot = metrics.values().flat_map(|score| score.reset()).collect();
|
||||
self.publish.publish(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of metric created by the Aggregator.
|
||||
|
@ -101,7 +112,7 @@ mod bench {
|
|||
use output::*;
|
||||
|
||||
#[bench]
|
||||
fn time_bench_write_event(b: &mut test::Bencher) {
|
||||
fn aggregate_marker(b: &mut test::Bencher) {
|
||||
let sink = aggregate(summary, to_void());
|
||||
let metric = sink.define_metric(Marker, "event_a", 1.0);
|
||||
let scope = sink.open_scope(false);
|
||||
|
@ -109,7 +120,7 @@ mod bench {
|
|||
}
|
||||
|
||||
#[bench]
|
||||
fn time_bench_write_count(b: &mut test::Bencher) {
|
||||
fn aggregate_counter(b: &mut test::Bencher) {
|
||||
let sink = aggregate(summary, to_void());
|
||||
let metric = sink.define_metric(Counter, "count_a", 1.0);
|
||||
let scope = sink.open_scope(false);
|
||||
|
@ -117,14 +128,14 @@ mod bench {
|
|||
}
|
||||
|
||||
#[bench]
|
||||
fn time_bench_read_event(b: &mut test::Bencher) {
|
||||
fn reset_marker(b: &mut test::Bencher) {
|
||||
let sink = aggregate(summary, to_void());
|
||||
let metric = sink.define_metric(Marker, "marker_a", 1.0);
|
||||
b.iter(|| test::black_box(metric.reset()));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn time_bench_read_count(b: &mut test::Bencher) {
|
||||
fn reset_counter(b: &mut test::Bencher) {
|
||||
let sink = aggregate(summary, to_void());
|
||||
let metric = sink.define_metric(Counter, "count_a", 1.0);
|
||||
b.iter(|| test::black_box(metric.reset()));
|
||||
|
|
|
@ -8,54 +8,25 @@
|
|||
//! If multiple [AppMetrics] are defined, they'll each have their scope.
|
||||
//!
|
||||
use core::*;
|
||||
use core::Kind::*;
|
||||
use namespace::*;
|
||||
use cache::*;
|
||||
use async_queue::*;
|
||||
use sample::*;
|
||||
use core::Kind::*;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use schedule::*;
|
||||
use dispatch::*;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
// TODO define an 'AsValue' trait + impl for supported number types, then drop 'num' crate
|
||||
pub use num::ToPrimitive;
|
||||
|
||||
/// Wrap the metrics backend to provide an application-friendly interface.
|
||||
/// Open a metric scope to share across the application.
|
||||
#[deprecated(since = "0.5.0", note = "Use `app_metrics` instead.")]
|
||||
pub fn metrics<M, IC>(chain: IC) -> AppMetrics<M>
|
||||
where
|
||||
M: Clone + Send + Sync + 'static,
|
||||
IC: Into<Chain<M>>,
|
||||
{
|
||||
app_metrics(chain)
|
||||
}
|
||||
|
||||
|
||||
/// Wrap the metrics backend to provide an application-friendly interface.
|
||||
/// Open a metric scope to share across the application.
|
||||
pub fn app_metrics<M, IC>(chain: IC) -> AppMetrics<M>
|
||||
pub fn app_metrics<M, AM>(app_metrics: AM) -> AppMetrics<M>
|
||||
where
|
||||
M: Clone + Send + Sync + 'static,
|
||||
IC: Into<Chain<M>>,
|
||||
AM: Into<AppMetrics<M>>,
|
||||
{
|
||||
let chain = chain.into();
|
||||
let static_scope = chain.open_scope(false);
|
||||
AppMetrics {
|
||||
scope: static_scope,
|
||||
chain: Arc::new(chain),
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> From<Chain<M>> for AppMetrics<M> {
|
||||
fn from(chain: Chain<M>) -> AppMetrics<M> {
|
||||
let static_scope = chain.open_scope(false);
|
||||
AppMetrics {
|
||||
scope: static_scope,
|
||||
chain: Arc::new(chain),
|
||||
}
|
||||
}
|
||||
app_metrics.into()
|
||||
}
|
||||
|
||||
/// A monotonic counter metric.
|
||||
|
@ -166,21 +137,35 @@ impl<M> AppTimer<M> {
|
|||
}
|
||||
}
|
||||
|
||||
//// AppMetrics proper
|
||||
|
||||
/// Variations of this should also provide control of the metric recording scope.
|
||||
#[derive(Derivative, Clone)]
|
||||
#[derivative(Debug)]
|
||||
pub struct AppMetrics<M> {
|
||||
chain: Arc<Chain<M>>,
|
||||
#[derivative(Debug = "ignore")] define_metric_fn: DefineMetricFn<M>,
|
||||
#[derivative(Debug = "ignore")] scope: ControlScopeFn<M>,
|
||||
}
|
||||
|
||||
impl<M> AppMetrics<M> {
|
||||
/// Create new application metrics instance.
|
||||
pub fn new(define_metric_fn: DefineMetricFn<M>, scope: ControlScopeFn<M>, ) -> Self {
|
||||
AppMetrics { define_metric_fn, scope }
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> AppMetrics<M>
|
||||
where
|
||||
M: Clone + Send + Sync + 'static,
|
||||
where
|
||||
M: Clone + Send + Sync + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn define_metric(&self, kind: Kind, name: &str, rate: Rate) -> M {
|
||||
(self.define_metric_fn)(kind, name, rate)
|
||||
}
|
||||
|
||||
/// Get an event counter of the provided name.
|
||||
pub fn marker<AS: AsRef<str>>(&self, name: AS) -> AppMarker<M> {
|
||||
let metric = self.chain.define_metric(Marker, name.as_ref(), 1.0);
|
||||
let metric = self.define_metric(Marker, name.as_ref(), 1.0);
|
||||
AppMarker {
|
||||
metric,
|
||||
scope: self.scope.clone(),
|
||||
|
@ -189,7 +174,7 @@ where
|
|||
|
||||
/// Get a counter of the provided name.
|
||||
pub fn counter<AS: AsRef<str>>(&self, name: AS) -> AppCounter<M> {
|
||||
let metric = self.chain.define_metric(Counter, name.as_ref(), 1.0);
|
||||
let metric = self.define_metric(Counter, name.as_ref(), 1.0);
|
||||
AppCounter {
|
||||
metric,
|
||||
scope: self.scope.clone(),
|
||||
|
@ -198,7 +183,7 @@ where
|
|||
|
||||
/// Get a timer of the provided name.
|
||||
pub fn timer<AS: AsRef<str>>(&self, name: AS) -> AppTimer<M> {
|
||||
let metric = self.chain.define_metric(Timer, name.as_ref(), 1.0);
|
||||
let metric = self.define_metric(Timer, name.as_ref(), 1.0);
|
||||
AppTimer {
|
||||
metric,
|
||||
scope: self.scope.clone(),
|
||||
|
@ -207,7 +192,7 @@ where
|
|||
|
||||
/// Get a gauge of the provided name.
|
||||
pub fn gauge<AS: AsRef<str>>(&self, name: AS) -> AppGauge<M> {
|
||||
let metric = self.chain.define_metric(Gauge, name.as_ref(), 1.0);
|
||||
let metric = self.define_metric(Gauge, name.as_ref(), 1.0);
|
||||
AppGauge {
|
||||
metric,
|
||||
scope: self.scope.clone(),
|
||||
|
@ -229,10 +214,42 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
//// Dispatch / Receiver impl
|
||||
|
||||
struct AppReceiverMetric<M> {
|
||||
metric: M,
|
||||
scope: ControlScopeFn<M>,
|
||||
}
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> Receiver for AppMetrics<M> {
|
||||
fn box_metric(&self, kind: Kind, name: &str, rate: Rate) -> Box<ReceiverMetric + Send + Sync> {
|
||||
let scope: ControlScopeFn<M> = self.scope.clone();
|
||||
let metric: M = self.define_metric(kind, name, rate);
|
||||
|
||||
Box::new(AppReceiverMetric {
|
||||
metric,
|
||||
scope,
|
||||
})
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
self.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> ReceiverMetric for AppReceiverMetric<M> {
|
||||
fn write(&self, value: Value) {
|
||||
self.scope.write(&self.metric, value);
|
||||
}
|
||||
}
|
||||
|
||||
//// Mutators impl
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> WithNamespace for AppMetrics<M> {
|
||||
fn with_name<IN: Into<Namespace>>(&self, names: IN) -> Self {
|
||||
let ref ns = names.into();
|
||||
AppMetrics {
|
||||
chain: Arc::new(self.chain.with_name(names)),
|
||||
define_metric_fn: add_namespace(ns, self.define_metric_fn.clone()),
|
||||
scope: self.scope.clone(),
|
||||
}
|
||||
}
|
||||
|
@ -241,25 +258,7 @@ impl<M: Send + Sync + Clone + 'static> WithNamespace for AppMetrics<M> {
|
|||
impl<M: Send + Sync + Clone + 'static> WithCache for AppMetrics<M> {
|
||||
fn with_cache(&self, cache_size: usize) -> Self {
|
||||
AppMetrics {
|
||||
chain: Arc::new(self.chain.with_cache(cache_size)),
|
||||
scope: self.scope.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> WithSamplingRate for AppMetrics<M> {
|
||||
fn with_sampling_rate(&self, sampling_rate: Rate) -> Self {
|
||||
AppMetrics {
|
||||
chain: Arc::new(self.chain.with_sampling_rate(sampling_rate)),
|
||||
scope: self.scope.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> WithAsyncQueue for AppMetrics<M> {
|
||||
fn with_async_queue(&self, queue_size: usize) -> Self {
|
||||
AppMetrics {
|
||||
chain: Arc::new(self.chain.with_async_queue(queue_size)),
|
||||
define_metric_fn: add_cache(cache_size, self.define_metric_fn.clone()),
|
||||
scope: self.scope.clone(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//! If queue size is exceeded, calling code reverts to blocking.
|
||||
//!
|
||||
use core::*;
|
||||
use scope_metrics::*;
|
||||
use self_metrics::*;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
@ -21,7 +22,7 @@ where
|
|||
fn with_async_queue(&self, queue_size: usize) -> Self;
|
||||
}
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> WithAsyncQueue for Chain<M> {
|
||||
impl<M: Send + Sync + Clone + 'static> WithAsyncQueue for ScopeMetrics<M> {
|
||||
fn with_async_queue(&self, queue_size: usize) -> Self {
|
||||
self.mod_scope(|next| {
|
||||
// setup channel
|
||||
|
@ -49,7 +50,7 @@ impl<M: Send + Sync + Clone + 'static> WithAsyncQueue for Chain<M> {
|
|||
let sender = sender.clone();
|
||||
|
||||
// forward any scope command through the channel
|
||||
ControlScopeFn::new(move |cmd| {
|
||||
control_scope(move |cmd| {
|
||||
let send_cmd = match cmd {
|
||||
ScopeCmd::Write(metric, value) => {
|
||||
let metric: &M = metric;
|
||||
|
@ -74,10 +75,10 @@ impl<M: Send + Sync + Clone + 'static> WithAsyncQueue for Chain<M> {
|
|||
|
||||
/// Enqueue collected metrics for dispatch on background thread.
|
||||
#[deprecated(since = "0.5.0", note = "Use `with_async_queue` instead.")]
|
||||
pub fn async<M, IC>(queue_size: usize, chain: IC) -> Chain<M>
|
||||
pub fn async<M, IC>(queue_size: usize, chain: IC) -> ScopeMetrics<M>
|
||||
where
|
||||
M: Clone + Send + Sync + 'static,
|
||||
IC: Into<Chain<M>>,
|
||||
IC: Into<ScopeMetrics<M>>,
|
||||
{
|
||||
let chain = chain.into();
|
||||
chain.with_async_queue(queue_size)
|
||||
|
|
48
src/cache.rs
48
src/cache.rs
|
@ -17,37 +17,23 @@ where
|
|||
|
||||
// TODO add selfmetrics cache stats
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> WithCache for Chain<M> {
|
||||
fn with_cache(&self, cache_size: usize) -> Self {
|
||||
self.mod_metric(|next| {
|
||||
let cache: RwLock<LRUCache<String, M>> =
|
||||
RwLock::new(LRUCache::with_capacity(cache_size));
|
||||
Arc::new(move |kind, name, rate| {
|
||||
let mut cache = cache.write().expect("Locking metric cache");
|
||||
let name_str = String::from(name);
|
||||
|
||||
// FIXME lookup should use straight &str
|
||||
if let Some(value) = cache.get(&name_str) {
|
||||
return value.clone();
|
||||
}
|
||||
|
||||
let new_value = (next)(kind, name, rate).clone();
|
||||
cache.insert(name_str, new_value.clone());
|
||||
new_value
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache metrics to prevent them from being re-defined on every use.
|
||||
/// Use of this should be transparent, this has no effect on the values.
|
||||
/// Stateful sinks (i.e. Aggregate) may naturally cache their definitions.
|
||||
#[deprecated(since = "0.5.0", note = "Use `with_cache` instead.")]
|
||||
pub fn cache<M, IC>(cache_size: usize, chain: IC) -> Chain<M>
|
||||
/// Add a caching decorator to a metric definition function.
|
||||
pub fn add_cache<M>(cache_size: usize, next: DefineMetricFn<M>) -> DefineMetricFn<M>
|
||||
where
|
||||
M: Clone + Send + Sync + 'static,
|
||||
IC: Into<Chain<M>>,
|
||||
M: Clone + Send + Sync + 'static
|
||||
{
|
||||
let chain = chain.into();
|
||||
chain.with_cache(cache_size)
|
||||
let cache: RwLock<LRUCache<String, M>> = RwLock::new(LRUCache::with_capacity(cache_size));
|
||||
Arc::new(move |kind, name, rate| {
|
||||
let mut cache = cache.write().expect("Locking metric cache");
|
||||
let name_str = String::from(name);
|
||||
|
||||
// FIXME lookup should use straight &str
|
||||
if let Some(value) = cache.get(&name_str) {
|
||||
return value.clone();
|
||||
}
|
||||
|
||||
let new_value = (next)(kind, name, rate).clone();
|
||||
cache.insert(name_str, new_value.clone());
|
||||
new_value
|
||||
})
|
||||
}
|
||||
|
|
292
src/core.rs
292
src/core.rs
|
@ -2,7 +2,6 @@
|
|||
//! This is mostly centered around the backend.
|
||||
//! Application-facing types are in the `app` module.
|
||||
|
||||
use self::Kind::*;
|
||||
use self::ScopeCmd::*;
|
||||
|
||||
use time;
|
||||
|
@ -44,7 +43,7 @@ pub type Rate = f64;
|
|||
pub const FULL_SAMPLING_RATE: Rate = 1.0;
|
||||
|
||||
/// Used to differentiate between metric kinds in the backend.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Kind {
|
||||
/// Handling one item at a time.
|
||||
Marker,
|
||||
|
@ -66,6 +65,9 @@ pub type DefineMetricFn<M> = Arc<Fn(Kind, &str, Rate) -> M + Send + Sync>;
|
|||
/// A function trait that opens a new metric capture scope.
|
||||
pub type OpenScopeFn<M> = Arc<Fn(bool) -> ControlScopeFn<M> + Send + Sync>;
|
||||
|
||||
/// A function trait that writes to or flushes a certain scope.
|
||||
pub type ControlScopeFn<M> = Arc<InnerControlScopeFn<M>>;
|
||||
|
||||
/// Returns a callback function to send commands to the metric scope.
|
||||
/// Writes can be performed by passing Some((&Metric, Value))
|
||||
/// Flushes can be performed by passing None
|
||||
|
@ -74,14 +76,14 @@ pub type OpenScopeFn<M> = Arc<Fn(bool) -> ControlScopeFn<M> + Send + Sync>;
|
|||
/// Complex applications may define a new scope fo each operation or request.
|
||||
/// Scopes can be moved acrossed threads (Send) but are not required to be thread-safe (Sync).
|
||||
/// Some implementations _may_ be 'Sync', otherwise queue()ing or threadlocal() can be used.
|
||||
#[derive(Clone)]
|
||||
pub struct ControlScopeFn<M> {
|
||||
pub struct InnerControlScopeFn<M> {
|
||||
flush_on_drop: bool,
|
||||
scope_fn: Arc<Fn(ScopeCmd<M>)>,
|
||||
scope_fn: Box<Fn(ScopeCmd<M>)>,
|
||||
}
|
||||
|
||||
unsafe impl<M> Sync for ControlScopeFn<M> {}
|
||||
unsafe impl<M> Send for ControlScopeFn<M> {}
|
||||
// TODO why is this necessary?
|
||||
unsafe impl<M> Sync for InnerControlScopeFn<M> {}
|
||||
unsafe impl<M> Send for InnerControlScopeFn<M> {}
|
||||
|
||||
/// An method dispatching command enum to manipulate metric scopes.
|
||||
/// Replaces a potential `Writer` trait that would have methods `write` and `flush`.
|
||||
|
@ -95,22 +97,17 @@ pub enum ScopeCmd<'a, M: 'a> {
|
|||
Flush,
|
||||
}
|
||||
|
||||
impl<M> ControlScopeFn<M> {
|
||||
/// Create a new metric scope based on the provided scope function.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dipstick::ControlScopeFn;
|
||||
/// let ref mut scope: ControlScopeFn<String> = ControlScopeFn::new(|_cmd| { /* match cmd {} */ });
|
||||
/// ```
|
||||
///
|
||||
pub fn new<F>(scope_fn: F) -> Self
|
||||
where F: Fn(ScopeCmd<M>) + 'static
|
||||
{
|
||||
ControlScopeFn {
|
||||
flush_on_drop: true,
|
||||
scope_fn: Arc::new(scope_fn)
|
||||
}
|
||||
}
|
||||
/// Create a new metric scope based on the provided scope function.
|
||||
pub fn control_scope<M, F>(scope_fn: F) -> ControlScopeFn<M>
|
||||
where F: Fn(ScopeCmd<M>) + 'static
|
||||
{
|
||||
Arc::new(InnerControlScopeFn {
|
||||
flush_on_drop: true,
|
||||
scope_fn: Box::new(scope_fn)
|
||||
})
|
||||
}
|
||||
|
||||
impl<M> InnerControlScopeFn<M> {
|
||||
|
||||
/// Write a value to this scope.
|
||||
///
|
||||
|
@ -136,20 +133,9 @@ impl<M> ControlScopeFn<M> {
|
|||
(self.scope_fn)(Flush)
|
||||
}
|
||||
|
||||
/// If scope is buffered, controls whether to flush the scope one last time when it is dropped.
|
||||
/// The default is true.
|
||||
///
|
||||
/// ```rust
|
||||
/// let ref mut scope = dipstick::to_log().open_scope(true).flush_on_drop(false);
|
||||
/// ```
|
||||
///
|
||||
pub fn flush_on_drop(mut self, enable: bool) -> Self {
|
||||
self.flush_on_drop = enable;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> Drop for ControlScopeFn<M> {
|
||||
impl<M> Drop for InnerControlScopeFn<M> {
|
||||
fn drop(&mut self) {
|
||||
if self.flush_on_drop {
|
||||
self.flush()
|
||||
|
@ -157,239 +143,3 @@ impl<M> Drop for ControlScopeFn<M> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A pair of functions composing a twin "chain of command".
|
||||
/// This is the building block for the metrics backend.
|
||||
#[derive(Derivative, Clone)]
|
||||
#[derivative(Debug)]
|
||||
pub struct Chain<M> {
|
||||
#[derivative(Debug = "ignore")] define_metric_fn: DefineMetricFn<M>,
|
||||
|
||||
#[derivative(Debug = "ignore")] scope_metric_fn: OpenScopeFn<M>,
|
||||
}
|
||||
|
||||
impl<M> Chain<M> {
|
||||
/// Define a new metric.
|
||||
#[allow(unused_variables)]
|
||||
pub fn define_metric(&self, kind: Kind, name: &str, sampling: Rate) -> M {
|
||||
(self.define_metric_fn)(kind, name, sampling)
|
||||
}
|
||||
|
||||
/// Open a new metric scope.
|
||||
/// Scope metrics allow an application to emit per-operation statistics,
|
||||
/// For example, producing a per-request performance log.
|
||||
///
|
||||
/// Although the scope metrics can be predefined like in ['AppMetrics'], the application needs to
|
||||
/// create a scope that will be passed back when reporting scoped metric values.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dipstick::*;
|
||||
/// let scope_metrics = to_log();
|
||||
/// let request_counter = scope_metrics.counter("scope_counter");
|
||||
/// {
|
||||
/// let ref mut request_scope = scope_metrics.open_scope(true);
|
||||
/// request_counter.count(request_scope, 42);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
pub fn open_scope(&self, buffered: bool) -> ControlScopeFn<M> {
|
||||
(self.scope_metric_fn)(buffered)
|
||||
}
|
||||
|
||||
/// Open a buffered scope.
|
||||
#[inline]
|
||||
pub fn buffered_scope(&self) -> ControlScopeFn<M> {
|
||||
self.open_scope(true)
|
||||
}
|
||||
|
||||
/// Open an unbuffered scope.
|
||||
#[inline]
|
||||
pub fn unbuffered_scope(&self) -> ControlScopeFn<M> {
|
||||
self.open_scope(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> Chain<M> {
|
||||
/// Create a new metric chain with the provided metric definition and scope creation functions.
|
||||
pub fn new<MF, WF>(make_metric: MF, make_scope: WF) -> Self
|
||||
where
|
||||
MF: Fn(Kind, &str, Rate) -> M + Send + Sync + 'static,
|
||||
WF: Fn(bool) -> ControlScopeFn<M> + Send + Sync + 'static,
|
||||
{
|
||||
Chain {
|
||||
// capture the provided closures in Arc to provide cheap clones
|
||||
define_metric_fn: Arc::new(make_metric),
|
||||
scope_metric_fn: Arc::new(make_scope),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an event counter of the provided name.
|
||||
pub fn marker<AS: AsRef<str>>(&self, name: AS) -> ScopeMarker<M> {
|
||||
let metric = self.define_metric(Marker, name.as_ref(), 1.0);
|
||||
ScopeMarker { metric }
|
||||
}
|
||||
|
||||
/// Get a counter of the provided name.
|
||||
pub fn counter<AS: AsRef<str>>(&self, name: AS) -> ScopeCounter<M> {
|
||||
let metric = self.define_metric(Counter, name.as_ref(), 1.0);
|
||||
ScopeCounter { metric }
|
||||
}
|
||||
|
||||
/// Get a timer of the provided name.
|
||||
pub fn timer<AS: AsRef<str>>(&self, name: AS) -> ScopeTimer<M> {
|
||||
let metric = self.define_metric(Timer, name.as_ref(), 1.0);
|
||||
ScopeTimer { metric }
|
||||
}
|
||||
|
||||
/// Get a gauge of the provided name.
|
||||
pub fn gauge<AS: AsRef<str>>(&self, name: AS) -> ScopeGauge<M> {
|
||||
let metric = self.define_metric(Gauge, name.as_ref(), 1.0);
|
||||
ScopeGauge { metric }
|
||||
}
|
||||
|
||||
/// Intercept metric definition without changing the metric type.
|
||||
pub fn mod_metric<MF>(&self, mod_fn: MF) -> Chain<M>
|
||||
where
|
||||
MF: Fn(DefineMetricFn<M>) -> DefineMetricFn<M>,
|
||||
{
|
||||
Chain {
|
||||
define_metric_fn: mod_fn(self.define_metric_fn.clone()),
|
||||
scope_metric_fn: self.scope_metric_fn.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Intercept both metric definition and scope creation, possibly changing the metric type.
|
||||
pub fn mod_both<MF, N>(&self, mod_fn: MF) -> Chain<N>
|
||||
where
|
||||
MF: Fn(DefineMetricFn<M>, OpenScopeFn<M>) -> (DefineMetricFn<N>, OpenScopeFn<N>),
|
||||
N: Clone + Send + Sync,
|
||||
{
|
||||
let (metric_fn, scope_fn) =
|
||||
mod_fn(self.define_metric_fn.clone(), self.scope_metric_fn.clone());
|
||||
Chain {
|
||||
define_metric_fn: metric_fn,
|
||||
scope_metric_fn: scope_fn,
|
||||
}
|
||||
}
|
||||
|
||||
/// Intercept scope creation.
|
||||
pub fn mod_scope<MF>(&self, mod_fn: MF) -> Self
|
||||
where
|
||||
MF: Fn(OpenScopeFn<M>) -> OpenScopeFn<M>,
|
||||
{
|
||||
Chain {
|
||||
define_metric_fn: self.define_metric_fn.clone(),
|
||||
scope_metric_fn: mod_fn(self.scope_metric_fn.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A monotonic counter metric.
|
||||
/// Since value is only ever increased by one, no value parameter is provided,
|
||||
/// preventing programming errors.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ScopeMarker<M> {
|
||||
metric: M,
|
||||
}
|
||||
|
||||
impl<M> ScopeMarker<M> {
|
||||
/// Record a single event occurence.
|
||||
#[inline]
|
||||
pub fn mark(&self, scope: &mut ControlScopeFn<M>) {
|
||||
scope.write(&self.metric, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// A counter that sends values to the metrics backend
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ScopeCounter<M> {
|
||||
metric: M,
|
||||
}
|
||||
|
||||
impl<M> ScopeCounter<M> {
|
||||
/// Record a value count.
|
||||
#[inline]
|
||||
pub fn count<V>(&self, scope: &mut ControlScopeFn<M>, count: V)
|
||||
where
|
||||
V: ToPrimitive,
|
||||
{
|
||||
scope.write(&self.metric, count.to_u64().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
/// A gauge that sends values to the metrics backend
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ScopeGauge<M> {
|
||||
metric: M,
|
||||
}
|
||||
|
||||
impl<M: Clone> ScopeGauge<M> {
|
||||
/// Record a value point for this gauge.
|
||||
#[inline]
|
||||
pub fn value<V>(&self, scope: &mut ControlScopeFn<M>, value: V)
|
||||
where
|
||||
V: ToPrimitive,
|
||||
{
|
||||
scope.write(&self.metric, value.to_u64().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
/// A timer that sends values to the metrics backend
|
||||
/// Timers can record time intervals in multiple ways :
|
||||
/// - with the time! macro which wraps an expression or block with start() and stop() calls.
|
||||
/// - with the time(Fn) method which wraps a closure with start() and stop() calls.
|
||||
/// - with start() and stop() methods wrapping around the operation to time
|
||||
/// - with the interval_us() method, providing an externally determined microsecond interval
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ScopeTimer<M> {
|
||||
metric: M,
|
||||
}
|
||||
|
||||
impl<M: Clone> ScopeTimer<M> {
|
||||
/// Record a microsecond interval for this timer
|
||||
/// Can be used in place of start()/stop() if an external time interval source is used
|
||||
#[inline]
|
||||
pub fn interval_us<V>(&self, scope: &mut ControlScopeFn<M>, interval_us: V) -> V
|
||||
where
|
||||
V: ToPrimitive,
|
||||
{
|
||||
scope.write(&self.metric, interval_us.to_u64().unwrap());
|
||||
interval_us
|
||||
}
|
||||
|
||||
/// Obtain a opaque handle to the current time.
|
||||
/// The handle is passed back to the stop() method to record a time interval.
|
||||
/// This is actually a convenience method to the TimeHandle::now()
|
||||
/// Beware, handles obtained here are not bound to this specific timer instance
|
||||
/// _for now_ but might be in the future for safety.
|
||||
/// If you require safe multi-timer handles, get them through TimeType::now()
|
||||
#[inline]
|
||||
pub fn start(&self) -> TimeHandle {
|
||||
TimeHandle::now()
|
||||
}
|
||||
|
||||
/// Record the time elapsed since the start_time handle was obtained.
|
||||
/// This call can be performed multiple times using the same handle,
|
||||
/// reporting distinct time intervals each time.
|
||||
/// Returns the microsecond interval value that was recorded.
|
||||
#[inline]
|
||||
pub fn stop(&self, scope: &mut ControlScopeFn<M>, start_time: TimeHandle) -> u64 {
|
||||
let elapsed_us = start_time.elapsed_us();
|
||||
self.interval_us(scope, elapsed_us)
|
||||
}
|
||||
|
||||
/// Record the time taken to execute the provided closure
|
||||
#[inline]
|
||||
pub fn time<F, R>(&self, scope: &mut ControlScopeFn<M>, operations: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
let start_time = self.start();
|
||||
let value: R = operations();
|
||||
self.stop(scope, start_time);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
|
224
src/dispatch.rs
224
src/dispatch.rs
|
@ -1,102 +1,174 @@
|
|||
//! Decouple metric definition from configuration with trait objects.
|
||||
|
||||
use core::*;
|
||||
use chain::*;
|
||||
use std::collections::{HashMap, LinkedList};
|
||||
use app_metrics::*;
|
||||
use output::*;
|
||||
|
||||
pub struct MetricHandle (usize);
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, RwLock, Weak};
|
||||
|
||||
pub struct ScopeHandle (usize);
|
||||
use atomic_refcell::*;
|
||||
|
||||
pub trait Observer {
|
||||
/// Create a new dispatcher.
|
||||
// TODO add dispatch name for registry
|
||||
pub fn dispatch() -> DispatchPoint {
|
||||
DispatchPoint {
|
||||
inner: Arc::new(RwLock::new(InnerDispatcher {
|
||||
metrics: HashMap::new(),
|
||||
receiver: Box::new(app_metrics(to_void())),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
/// Dynamic counterpart of a `Dispatcher`.
|
||||
/// Adapter to AppMetrics<_> of unknown type.
|
||||
pub trait Receiver {
|
||||
/// Register a new metric.
|
||||
/// Only one metric of a certain name will be defined.
|
||||
/// Observer must return a MetricHandle that uniquely identifies the metric.
|
||||
fn metric_create(&self, kind: Kind, name: &str, rate: Rate) -> MetricHandle;
|
||||
fn box_metric(&self, kind: Kind, name: &str, rate: Rate) -> Box<ReceiverMetric + Send + Sync>;
|
||||
|
||||
/// Drop a previously registered metric.
|
||||
/// Drop is called once per handle.
|
||||
/// Dropped handle will never be used again.
|
||||
/// Drop is only called with previously registered handles.
|
||||
fn metric_drop(&self, metric: MetricHandle);
|
||||
|
||||
/// Open a new scope.
|
||||
/// Observer must return a new ScopeHandle that uniquely identifies the scope.
|
||||
fn scope_open(&self, buffered: bool) -> ScopeHandle;
|
||||
/// Flush the receiver's scope.
|
||||
fn flush(&self);
|
||||
}
|
||||
|
||||
/// Dynamic counterpart of the `DispatcherMetric`.
|
||||
/// Adapter to a metric of unknown type.
|
||||
pub trait ReceiverMetric {
|
||||
/// Write metric value to a scope.
|
||||
/// Observers only receive previously registered handles.
|
||||
fn scope_write(&self, scope: ScopeHandle, metric: MetricHandle, value:Value);
|
||||
|
||||
/// Flush a scope.
|
||||
/// Observers only receive previously registered handles.
|
||||
fn scope_flush(&self, scope: ScopeHandle);
|
||||
|
||||
/// Drop a previously registered scope.
|
||||
/// Drop is called once per handle.
|
||||
/// Dropped handle will never be used again.
|
||||
/// Drop is only called with previously registered handles.
|
||||
fn scope_close(&self, scope: ScopeHandle);
|
||||
fn write(&self, value: Value);
|
||||
}
|
||||
|
||||
pub struct ChainObserver<T> {
|
||||
chain: Chain<T>
|
||||
/// Shortcut name because `AppMetrics<Dispatch>`
|
||||
/// looks better than `AppMetrics<Arc<DispatcherMetric>>`.
|
||||
pub type Dispatch = Arc<DispatcherMetric>;
|
||||
|
||||
/// A dynamically dispatched metric.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct DispatcherMetric {
|
||||
kind: Kind,
|
||||
name: String,
|
||||
rate: Rate,
|
||||
#[derivative(Debug = "ignore")]
|
||||
receiver: AtomicRefCell<Box<ReceiverMetric + Send + Sync>>,
|
||||
#[derivative(Debug = "ignore")]
|
||||
dispatcher: DispatchPoint,
|
||||
}
|
||||
|
||||
impl<T> Observer for ChainObserver<T> {
|
||||
fn metric_create(&self, kind: Kind, name: &str, rate: Rate) -> MetricHandle {
|
||||
self.chain.define_metric(kind, name, rate)
|
||||
/// Dispatcher weak ref does not prevent dropping but still needs to be cleaned out.
|
||||
impl Drop for DispatcherMetric {
|
||||
fn drop(&mut self) {
|
||||
self.dispatcher.drop_metric(self)
|
||||
}
|
||||
|
||||
fn metric_drop(&self, metric: MetricHandle) {}
|
||||
|
||||
fn scope_open(&self, buffered: bool) -> ScopeHandle {}
|
||||
fn scope_write(&self, scope: ScopeHandle, metric: MetricHandle, value:Value) {}
|
||||
fn scope_flush(&self, scope: ScopeHandle) {}
|
||||
fn scope_close(&self, scope: ScopeHandle) {}
|
||||
}
|
||||
|
||||
pub struct Dispatcher {
|
||||
active_observers: usize,
|
||||
metrics: HashMap<String, Dispatch>,
|
||||
observers: RwLock<Vec<Observer>>
|
||||
/// A dynamic dispatch point for app and lib metrics.
|
||||
/// Decouples metrics definition from backend configuration.
|
||||
/// Allows defining metrics before a concrete type has been selected.
|
||||
/// Allows replacing metrics backend on the fly at runtime.
|
||||
#[derive(Clone)]
|
||||
pub struct DispatchPoint {
|
||||
inner: Arc<RwLock<InnerDispatcher>>,
|
||||
}
|
||||
|
||||
/// Aggregate metrics in memory.
|
||||
/// Depending on the type of metric, count, sum, minimum and maximum of values will be tracked.
|
||||
/// Needs to be connected to a publish to be useful.
|
||||
/// ```
|
||||
/// use dipstick::*;
|
||||
/// let sink = aggregate(4, summary, to_stdout());
|
||||
/// let metrics = global_metrics(sink);
|
||||
/// metrics.marker("my_event").mark();
|
||||
/// metrics.marker("my_event").mark();
|
||||
/// ```
|
||||
pub fn dispatch<E, M>(stat_fn: E, to_chain: Chain<M>) -> Chain<Dispatch>
|
||||
where
|
||||
E: Fn(Kind, &str, ScoreType) -> Option<(Kind, Vec<&str>, Value)> + Send + Sync + 'static,
|
||||
M: Clone + Send + Sync + Debug + 'static,
|
||||
{
|
||||
let metrics = Arc::new(RwLock::new(HashMap::new()));
|
||||
let metrics0 = metrics.clone();
|
||||
struct InnerDispatcher {
|
||||
metrics: HashMap<String, Weak<DispatcherMetric>>,
|
||||
receiver: Box<Receiver + Send + Sync>,
|
||||
}
|
||||
|
||||
let publish = Arc::new(Publisher::new(stat_fn, to_chain));
|
||||
impl From<DispatchPoint> for AppMetrics<Dispatch> {
|
||||
fn from(dispatcher: DispatchPoint) -> AppMetrics<Dispatch> {
|
||||
let dispatcher_1 = dispatcher.clone();
|
||||
AppMetrics::new(
|
||||
// define metric
|
||||
Arc::new(move |kind, name, rate| dispatcher.define_metric(kind, name, rate)),
|
||||
|
||||
Chain::new(
|
||||
move |kind, name, _rate| {
|
||||
// add metric
|
||||
},
|
||||
move |_buffered| {
|
||||
// open scope
|
||||
ControlScopeFn::new(move |cmd| match cmd {
|
||||
// write / flush metric
|
||||
control_scope(move |cmd| match cmd {
|
||||
ScopeCmd::Write(metric, value) => {
|
||||
let metric: &Aggregate = metric;
|
||||
metric.update(value)
|
||||
let dispatch: &Arc<DispatcherMetric> = metric;
|
||||
let receiver_metric: AtomicRef<Box<ReceiverMetric + Send + Sync>> = dispatch.receiver.borrow();
|
||||
receiver_metric.write(value)
|
||||
},
|
||||
ScopeCmd::Flush => {
|
||||
let metrics = metrics.read().expect("Locking metrics scoreboards");
|
||||
let snapshot = metrics.values().flat_map(|score| score.reset()).collect();
|
||||
publish.publish(snapshot);
|
||||
}
|
||||
dispatcher_1.inner.write().expect("Locking dispatcher").receiver.flush()
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl DispatchPoint {
|
||||
|
||||
/// Install a new metric receiver, replacing the previous one.
|
||||
pub fn set_receiver<IS: Into<AppMetrics<T>>, T: Send + Sync + Clone + 'static>(&self, receiver: IS) {
|
||||
let receiver: Box<Receiver + Send + Sync> = Box::new(receiver.into());
|
||||
let inner: &mut InnerDispatcher = &mut *self.inner.write().expect("Locking dispatcher");
|
||||
|
||||
for mut metric in inner.metrics.values() {
|
||||
if let Some(metric) = metric.upgrade() {
|
||||
let receiver_metric = receiver.box_metric(metric.kind, metric.name.as_ref(), metric.rate);
|
||||
*metric.receiver.borrow_mut() = receiver_metric;
|
||||
}
|
||||
}
|
||||
// TODO return old receiver (swap, how?)
|
||||
inner.receiver = receiver;
|
||||
}
|
||||
|
||||
/// Define a dispatch metric, registering it with the current receiver.
|
||||
/// A weak ref is kept to update receiver metric if receiver is replaced.
|
||||
pub fn define_metric(&self, kind: Kind, name: &str, rate: Rate) -> Dispatch {
|
||||
let mut inner = self.inner.write().expect("Locking dispatcher");
|
||||
|
||||
let receiver_metric = inner.receiver.box_metric(kind, name, rate);
|
||||
|
||||
let dispatcher_metric = Arc::new(DispatcherMetric {
|
||||
kind,
|
||||
name: name.to_string(),
|
||||
rate,
|
||||
receiver: AtomicRefCell::new(receiver_metric),
|
||||
dispatcher: self.clone(),
|
||||
});
|
||||
|
||||
inner.metrics.insert(dispatcher_metric.name.clone(), Arc::downgrade(&dispatcher_metric));
|
||||
dispatcher_metric
|
||||
}
|
||||
|
||||
fn drop_metric(&self, metric: &DispatcherMetric) {
|
||||
let mut inner = self.inner.write().expect("Locking dispatcher");
|
||||
if let None = inner.metrics.remove(&metric.name) {
|
||||
panic!("Could not remove DispatchMetric weak ref from Dispatcher")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
mod bench {
|
||||
|
||||
use super::*;
|
||||
use test;
|
||||
use core::Kind::*;
|
||||
use aggregate::*;
|
||||
use publish::*;
|
||||
|
||||
#[bench]
|
||||
fn dispatch_marker_to_aggregate(b: &mut test::Bencher) {
|
||||
let dispatch = dispatch();
|
||||
let sink: AppMetrics<Dispatch> = dispatch.clone().into();
|
||||
dispatch.set_receiver(aggregate(summary, to_void()));
|
||||
let metric = sink.marker("event_a");
|
||||
b.iter(|| test::black_box(metric.mark()));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn dispatch_marker_to_void(b: &mut test::Bencher) {
|
||||
let dispatch = dispatch();
|
||||
let sink: AppMetrics<Dispatch> = dispatch.into();
|
||||
let metric = sink.marker("event_a");
|
||||
b.iter(|| test::black_box(metric.mark()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Send metrics to a graphite server.
|
||||
|
||||
use core::*;
|
||||
use scope_metrics::*;
|
||||
use error;
|
||||
use self_metrics::*;
|
||||
|
||||
|
@ -22,13 +23,13 @@ mod_counter!(Aggregate, GRAPHITE_METRICS, { SENT_BYTES: "sent_bytes" });
|
|||
|
||||
|
||||
/// Send metrics to a graphite server at the address and port provided.
|
||||
pub fn to_graphite<ADDR>(address: ADDR) -> error::Result<Chain<Graphite>>
|
||||
pub fn to_graphite<ADDR>(address: ADDR) -> error::Result<ScopeMetrics<Graphite>>
|
||||
where
|
||||
ADDR: ToSocketAddrs + Debug + Clone,
|
||||
{
|
||||
debug!("Connecting to graphite {:?}", address);
|
||||
let socket = Arc::new(RwLock::new(RetrySocket::new(address.clone())?));
|
||||
Ok(Chain::new(
|
||||
Ok(ScopeMetrics::new(
|
||||
move |kind, name, rate| {
|
||||
let mut prefix = String::with_capacity(32);
|
||||
prefix.push_str(name);
|
||||
|
@ -59,7 +60,7 @@ where
|
|||
socket: socket.clone(),
|
||||
buffered,
|
||||
};
|
||||
ControlScopeFn::new(move |cmd| match cmd {
|
||||
control_scope(move |cmd| match cmd {
|
||||
ScopeCmd::Write(metric, value) => buf.write(metric, value),
|
||||
ScopeCmd::Flush => buf.flush(),
|
||||
})
|
||||
|
|
15
src/lib.rs
15
src/lib.rs
|
@ -1,18 +1,13 @@
|
|||
/*!
|
||||
A quick, modular metrics toolkit for Rust applications.
|
||||
|
||||
*/
|
||||
//! A quick, modular metrics toolkit for Rust applications.
|
||||
|
||||
#![cfg_attr(feature = "bench", feature(test))]
|
||||
#![warn(
|
||||
missing_copy_implementations,
|
||||
missing_docs,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unused_extern_crates,
|
||||
unused_import_braces,
|
||||
unused_qualifications,
|
||||
// variant_size_differences,
|
||||
)]
|
||||
|
||||
#[cfg(feature = "bench")]
|
||||
|
@ -27,6 +22,7 @@ extern crate derivative;
|
|||
extern crate lazy_static;
|
||||
extern crate num;
|
||||
extern crate time;
|
||||
extern crate atomic_refcell;
|
||||
|
||||
mod pcg32;
|
||||
mod lru_cache;
|
||||
|
@ -40,8 +36,11 @@ pub mod macros;
|
|||
pub mod core;
|
||||
pub use core::*;
|
||||
|
||||
//pub mod dispatch;
|
||||
//pub use dispatch::*;
|
||||
pub mod scope_metrics;
|
||||
pub use scope_metrics::*;
|
||||
|
||||
pub mod dispatch;
|
||||
pub use dispatch::*;
|
||||
|
||||
mod output;
|
||||
pub use output::*;
|
||||
|
|
39
src/multi.rs
39
src/multi.rs
|
@ -1,20 +1,22 @@
|
|||
//! Dispatch metrics to multiple sinks.
|
||||
|
||||
use core::*;
|
||||
use scope_metrics::*;
|
||||
use app_metrics::*;
|
||||
|
||||
/// Two chains of different types can be combined in a tuple.
|
||||
/// The chains will act as one, each receiving calls in the order the appear in the tuple.
|
||||
/// For more than two types, make tuples of tuples, "Yo Dawg" style.
|
||||
impl<M1, M2> From<(Chain<M1>, Chain<M2>)> for Chain<(M1, M2)>
|
||||
impl<M1, M2> From<(ScopeMetrics<M1>, ScopeMetrics<M2>)> for ScopeMetrics<(M1, M2)>
|
||||
where
|
||||
M1: 'static + Clone + Send + Sync,
|
||||
M2: 'static + Clone + Send + Sync,
|
||||
{
|
||||
fn from(combo: (Chain<M1>, Chain<M2>)) -> Chain<(M1, M2)> {
|
||||
fn from(combo: (ScopeMetrics<M1>, ScopeMetrics<M2>)) -> ScopeMetrics<(M1, M2)> {
|
||||
let combo0 = combo.0.clone();
|
||||
let combo1 = combo.1.clone();
|
||||
|
||||
Chain::new(
|
||||
ScopeMetrics::new(
|
||||
move |kind, name, rate| {
|
||||
(
|
||||
combo.0.define_metric(kind, name, rate),
|
||||
|
@ -25,7 +27,7 @@ where
|
|||
let scope0 = combo0.open_scope(buffered);
|
||||
let scope1 = combo1.open_scope(buffered);
|
||||
|
||||
ControlScopeFn::new(move |cmd| match cmd {
|
||||
control_scope(move |cmd| match cmd {
|
||||
ScopeCmd::Write(metric, value) => {
|
||||
let metric: &(M1, M2) = metric;
|
||||
scope0.write(&metric.0, value);
|
||||
|
@ -41,17 +43,28 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<M1, M2> From<(ScopeMetrics<M1>, ScopeMetrics<M2>)> for AppMetrics<(M1, M2)>
|
||||
where
|
||||
M1: 'static + Clone + Send + Sync,
|
||||
M2: 'static + Clone + Send + Sync,
|
||||
{
|
||||
fn from(combo: (ScopeMetrics<M1>, ScopeMetrics<M2>)) -> AppMetrics<(M1, M2)> {
|
||||
let chain: ScopeMetrics<(M1, M2)> = combo.into();
|
||||
app_metrics(chain)
|
||||
}
|
||||
}
|
||||
|
||||
/// Multiple chains of the same type can be combined in a slice.
|
||||
/// The chains will act as one, each receiving calls in the order the appear in the slice.
|
||||
impl<'a, M> From<&'a [Chain<M>]> for Chain<Vec<M>>
|
||||
impl<'a, M> From<&'a [ScopeMetrics<M>]> for ScopeMetrics<Vec<M>>
|
||||
where
|
||||
M: 'static + Clone + Send + Sync,
|
||||
{
|
||||
fn from(chains: &'a [Chain<M>]) -> Chain<Vec<M>> {
|
||||
fn from(chains: &'a [ScopeMetrics<M>]) -> ScopeMetrics<Vec<M>> {
|
||||
let chains = chains.to_vec();
|
||||
let chains2 = chains.clone();
|
||||
|
||||
Chain::new(
|
||||
ScopeMetrics::new(
|
||||
move |kind, name, rate| {
|
||||
let mut metric = Vec::with_capacity(chains.len());
|
||||
for chain in &chains {
|
||||
|
@ -65,7 +78,7 @@ where
|
|||
scopes.push(chain.open_scope(buffered));
|
||||
}
|
||||
|
||||
ControlScopeFn::new(move |cmd| match cmd {
|
||||
control_scope(move |cmd| match cmd {
|
||||
ScopeCmd::Write(metric, value) => {
|
||||
let metric: &Vec<M> = metric;
|
||||
for (i, scope) in scopes.iter().enumerate() {
|
||||
|
@ -80,3 +93,13 @@ where
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, M> From<&'a [ScopeMetrics<M>]> for AppMetrics<Vec<M>>
|
||||
where
|
||||
M: 'static + Clone + Send + Sync,
|
||||
{
|
||||
fn from(chains: &'a [ScopeMetrics<M>]) -> AppMetrics<Vec<M>> {
|
||||
let chain: ScopeMetrics<Vec<M>> = chains.into();
|
||||
app_metrics(chain)
|
||||
}
|
||||
}
|
|
@ -39,7 +39,6 @@ impl<'a, 'b: 'a> From<&'b [&'a str]> for Namespace {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// Prepend metric names with custom prefix.
|
||||
pub trait WithNamespace
|
||||
where
|
||||
|
@ -62,33 +61,11 @@ where
|
|||
|
||||
}
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> WithNamespace for Chain<M> {
|
||||
fn with_name<IN: Into<Namespace>>(&self, names: IN) -> Self {
|
||||
let ninto = names.into();
|
||||
self.mod_metric(|next| {
|
||||
let nspace = ninto.join(DEFAULT_SEPARATOR);
|
||||
Arc::new(move |kind, name, rate| {
|
||||
let name = [nspace.as_ref(), name].join(DEFAULT_SEPARATOR);
|
||||
(next)(kind, name.as_ref(), rate)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// deprecated, use with_prefix() omitting any previously supplied separator
|
||||
#[deprecated(since = "0.5.0",
|
||||
note = "Use `with_name` instead, omitting any previously supplied separator.")]
|
||||
pub fn prefix<M, IC>(prefix: &str, chain: IC) -> Chain<M>
|
||||
where
|
||||
M: Clone + Send + Sync + 'static,
|
||||
IC: Into<Chain<M>>,
|
||||
{
|
||||
let chain = chain.into();
|
||||
chain.mod_metric(|next| {
|
||||
let prefix = prefix.to_string();
|
||||
Arc::new(move |kind, name, rate| {
|
||||
let name = [&prefix, name].concat();
|
||||
(next)(kind, name.as_ref(), rate)
|
||||
})
|
||||
/// Add a namespace decorator to a metric definition function.
|
||||
pub fn add_namespace<M: 'static>(names: &Namespace, next: DefineMetricFn<M>) -> DefineMetricFn<M> {
|
||||
let nspace = names.join(DEFAULT_SEPARATOR);
|
||||
Arc::new(move |kind, name, rate| {
|
||||
let name = [nspace.as_ref(), name].join(DEFAULT_SEPARATOR);
|
||||
(next)(kind, name.as_ref(), rate)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
//! Standard stateless metric outputs.
|
||||
// TODO parameterize templates
|
||||
use core::*;
|
||||
use scope_metrics::*;
|
||||
use std::sync::RwLock;
|
||||
|
||||
/// Write metric values to stdout using `println!`.
|
||||
pub fn to_stdout() -> Chain<String> {
|
||||
Chain::new(
|
||||
pub fn to_stdout() -> ScopeMetrics<String> {
|
||||
ScopeMetrics::new(
|
||||
|_kind, name, _rate| String::from(name),
|
||||
|buffered| {
|
||||
if !buffered {
|
||||
ControlScopeFn::new(|cmd| {
|
||||
control_scope(|cmd| {
|
||||
if let ScopeCmd::Write(m, v) = cmd {
|
||||
println!("{}: {}", m, v)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let buf = RwLock::new(String::new());
|
||||
ControlScopeFn::new(move |cmd| {
|
||||
control_scope(move |cmd| {
|
||||
let mut buf = buf.write().expect("Locking stdout buffer");
|
||||
match cmd {
|
||||
ScopeCmd::Write(metric, value) => buf.push_str(format!("{}: {}\n", metric, value).as_ref()),
|
||||
|
@ -33,19 +34,19 @@ pub fn to_stdout() -> Chain<String> {
|
|||
|
||||
/// Write metric values to the standard log using `info!`.
|
||||
// TODO parameterize log level
|
||||
pub fn to_log() -> Chain<String> {
|
||||
Chain::new(
|
||||
pub fn to_log() -> ScopeMetrics<String> {
|
||||
ScopeMetrics::new(
|
||||
|_kind, name, _rate| String::from(name),
|
||||
|buffered| {
|
||||
if !buffered {
|
||||
ControlScopeFn::new(|cmd| {
|
||||
control_scope(|cmd| {
|
||||
if let ScopeCmd::Write(m, v) = cmd {
|
||||
info!("{}: {}", m, v)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let buf = RwLock::new(String::new());
|
||||
ControlScopeFn::new(move |cmd| {
|
||||
control_scope(move |cmd| {
|
||||
let mut buf = buf.write().expect("Locking string buffer");
|
||||
match cmd {
|
||||
ScopeCmd::Write(metric, value) => buf.push_str(format!("{}: {}\n", metric, value).as_ref()),
|
||||
|
@ -61,10 +62,10 @@ pub fn to_log() -> Chain<String> {
|
|||
}
|
||||
|
||||
/// Discard all metric values sent to it.
|
||||
pub fn to_void() -> Chain<String> {
|
||||
Chain::new(
|
||||
pub fn to_void() -> ScopeMetrics<String> {
|
||||
ScopeMetrics::new(
|
||||
move |_kind, name, _rate| String::from(name),
|
||||
|_buffered| ControlScopeFn::new(|_cmd| {}),
|
||||
|_buffered| control_scope(|_cmd| {}),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
//! ```
|
||||
|
||||
use core::*;
|
||||
use scope_metrics::*;
|
||||
use core::Kind::*;
|
||||
use scores::{ScoreSnapshot, ScoreType};
|
||||
use scores::ScoreType::*;
|
||||
|
@ -38,7 +39,7 @@ pub trait Publish: Send + Sync + Debug {
|
|||
#[derivative(Debug)]
|
||||
pub struct Publisher<E, M> {
|
||||
#[derivative(Debug = "ignore")] statistics: Box<E>,
|
||||
target_chain: Chain<M>,
|
||||
target_chain: ScopeMetrics<M>,
|
||||
}
|
||||
|
||||
impl<E, M> Publisher<E, M>
|
||||
|
@ -48,7 +49,7 @@ where
|
|||
{
|
||||
/// Define a new metrics publishing strategy, from a transformation
|
||||
/// function and a target metric chain.
|
||||
pub fn new(stat_fn: E, target_chain: Chain<M>) -> Self {
|
||||
pub fn new(stat_fn: E, target_chain: ScopeMetrics<M>) -> Self {
|
||||
Publisher {
|
||||
statistics: Box::new(stat_fn),
|
||||
target_chain,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! Reduce the amount of data to process or transfer by statistically dropping some of it.
|
||||
|
||||
use core::*;
|
||||
use scope_metrics::*;
|
||||
|
||||
use pcg32;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
@ -14,7 +16,7 @@ where
|
|||
fn with_sampling_rate(&self, sampling_rate: Rate) -> Self;
|
||||
}
|
||||
|
||||
impl<M: Send + Sync + 'static + Clone> WithSamplingRate for Chain<M> {
|
||||
impl<M: Send + Sync + 'static + Clone> WithSamplingRate for ScopeMetrics<M> {
|
||||
fn with_sampling_rate(&self, sampling_rate: Rate) -> Self {
|
||||
let int_sampling_rate = pcg32::to_int_rate(sampling_rate);
|
||||
|
||||
|
@ -34,7 +36,7 @@ impl<M: Send + Sync + 'static + Clone> WithSamplingRate for Chain<M> {
|
|||
}),
|
||||
Arc::new(move |buffered| {
|
||||
let next_scope = scope_fn(buffered);
|
||||
ControlScopeFn::new(move |cmd| {
|
||||
control_scope(move |cmd| {
|
||||
match cmd {
|
||||
ScopeCmd::Write(metric, value) => {
|
||||
if pcg32::accept_sample(int_sampling_rate) {
|
||||
|
@ -52,10 +54,10 @@ impl<M: Send + Sync + 'static + Clone> WithSamplingRate for Chain<M> {
|
|||
|
||||
/// Perform random sampling of values according to the specified rate.
|
||||
#[deprecated(since = "0.5.0", note = "Use `with_sampling_rate` instead.")]
|
||||
pub fn sample<M, IC>(sampling_rate: Rate, chain: IC) -> Chain<M>
|
||||
pub fn sample<M, IC>(sampling_rate: Rate, chain: IC) -> ScopeMetrics<M>
|
||||
where
|
||||
M: Clone + Send + Sync + 'static,
|
||||
IC: Into<Chain<M>>,
|
||||
IC: Into<ScopeMetrics<M>>,
|
||||
{
|
||||
let chain = chain.into();
|
||||
chain.with_sampling_rate(sampling_rate)
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
//! Chain of command for unscoped metrics.
|
||||
|
||||
use core::*;
|
||||
use core::Kind::*;
|
||||
use app_metrics::AppMetrics;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use cache::*;
|
||||
use namespace::*;
|
||||
|
||||
|
||||
/// A pair of functions composing a twin "chain of command".
|
||||
/// This is the building block for the metrics backend.
|
||||
#[derive(Derivative, Clone)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ScopeMetrics<M> {
|
||||
#[derivative(Debug = "ignore")] define_metric_fn: DefineMetricFn<M>,
|
||||
|
||||
#[derivative(Debug = "ignore")] scope_metric_fn: OpenScopeFn<M>,
|
||||
}
|
||||
|
||||
impl<M> ScopeMetrics<M> {
|
||||
/// Define a new metric.
|
||||
#[allow(unused_variables)]
|
||||
pub fn define_metric(&self, kind: Kind, name: &str, sampling: Rate) -> M {
|
||||
(self.define_metric_fn)(kind, name, sampling)
|
||||
}
|
||||
|
||||
/// Open a new metric scope.
|
||||
/// Scope metrics allow an application to emit per-operation statistics,
|
||||
/// For example, producing a per-request performance log.
|
||||
///
|
||||
/// Although the scope metrics can be predefined like in ['AppMetrics'], the application needs to
|
||||
/// create a scope that will be passed back when reporting scoped metric values.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dipstick::*;
|
||||
/// let scope_metrics = to_log();
|
||||
/// let request_counter = scope_metrics.counter("scope_counter");
|
||||
/// {
|
||||
/// let ref mut request_scope = scope_metrics.open_scope(true);
|
||||
/// request_counter.count(request_scope, 42);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
pub fn open_scope(&self, buffered: bool) -> ControlScopeFn<M> {
|
||||
(self.scope_metric_fn)(buffered)
|
||||
}
|
||||
|
||||
/// Open a buffered scope.
|
||||
#[inline]
|
||||
pub fn buffered_scope(&self) -> ControlScopeFn<M> {
|
||||
self.open_scope(true)
|
||||
}
|
||||
|
||||
/// Open an unbuffered scope.
|
||||
#[inline]
|
||||
pub fn unbuffered_scope(&self) -> ControlScopeFn<M> {
|
||||
self.open_scope(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> ScopeMetrics<M> {
|
||||
/// Create a new metric chain with the provided metric definition and scope creation functions.
|
||||
pub fn new<MF, WF>(make_metric: MF, make_scope: WF) -> Self
|
||||
where
|
||||
MF: Fn(Kind, &str, Rate) -> M + Send + Sync + 'static,
|
||||
WF: Fn(bool) -> ControlScopeFn<M> + Send + Sync + 'static,
|
||||
{
|
||||
ScopeMetrics {
|
||||
// capture the provided closures in Arc to provide cheap clones
|
||||
define_metric_fn: Arc::new(make_metric),
|
||||
scope_metric_fn: Arc::new(make_scope),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an event counter of the provided name.
|
||||
pub fn marker<AS: AsRef<str>>(&self, name: AS) -> ScopeMarker<M> {
|
||||
let metric = self.define_metric(Marker, name.as_ref(), 1.0);
|
||||
ScopeMarker { metric }
|
||||
}
|
||||
|
||||
/// Get a counter of the provided name.
|
||||
pub fn counter<AS: AsRef<str>>(&self, name: AS) -> ScopeCounter<M> {
|
||||
let metric = self.define_metric(Counter, name.as_ref(), 1.0);
|
||||
ScopeCounter { metric }
|
||||
}
|
||||
|
||||
/// Get a timer of the provided name.
|
||||
pub fn timer<AS: AsRef<str>>(&self, name: AS) -> ScopeTimer<M> {
|
||||
let metric = self.define_metric(Timer, name.as_ref(), 1.0);
|
||||
ScopeTimer { metric }
|
||||
}
|
||||
|
||||
/// Get a gauge of the provided name.
|
||||
pub fn gauge<AS: AsRef<str>>(&self, name: AS) -> ScopeGauge<M> {
|
||||
let metric = self.define_metric(Gauge, name.as_ref(), 1.0);
|
||||
ScopeGauge { metric }
|
||||
}
|
||||
|
||||
/// Intercept both metric definition and scope creation, possibly changing the metric type.
|
||||
pub fn mod_both<MF, N>(&self, mod_fn: MF) -> ScopeMetrics<N>
|
||||
where
|
||||
MF: Fn(DefineMetricFn<M>, OpenScopeFn<M>) -> (DefineMetricFn<N>, OpenScopeFn<N>),
|
||||
N: Clone + Send + Sync,
|
||||
{
|
||||
let (metric_fn, scope_fn) =
|
||||
mod_fn(self.define_metric_fn.clone(), self.scope_metric_fn.clone());
|
||||
ScopeMetrics {
|
||||
define_metric_fn: metric_fn,
|
||||
scope_metric_fn: scope_fn,
|
||||
}
|
||||
}
|
||||
|
||||
/// Intercept scope creation.
|
||||
pub fn mod_scope<MF>(&self, mod_fn: MF) -> Self
|
||||
where
|
||||
MF: Fn(OpenScopeFn<M>) -> OpenScopeFn<M>,
|
||||
{
|
||||
ScopeMetrics {
|
||||
define_metric_fn: self.define_metric_fn.clone(),
|
||||
scope_metric_fn: mod_fn(self.scope_metric_fn.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> From<ScopeMetrics<M>> for AppMetrics<M> {
|
||||
fn from(chain: ScopeMetrics<M>) -> AppMetrics<M> {
|
||||
AppMetrics::new(chain.define_metric_fn.clone(), chain.open_scope(false))
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> WithCache for ScopeMetrics<M> {
|
||||
fn with_cache(&self, cache_size: usize) -> Self {
|
||||
ScopeMetrics {
|
||||
define_metric_fn: add_cache(cache_size, self.define_metric_fn.clone()),
|
||||
scope_metric_fn: self.scope_metric_fn.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<M: Send + Sync + Clone + 'static> WithNamespace for ScopeMetrics<M> {
|
||||
fn with_name<IN: Into<Namespace>>(&self, names: IN) -> Self {
|
||||
let ref ninto = names.into();
|
||||
ScopeMetrics {
|
||||
define_metric_fn: add_namespace(ninto, self.define_metric_fn.clone()),
|
||||
scope_metric_fn: self.scope_metric_fn.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A monotonic counter metric.
|
||||
/// Since value is only ever increased by one, no value parameter is provided,
|
||||
/// preventing programming errors.
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ScopeMarker<M> {
|
||||
metric: M,
|
||||
}
|
||||
|
||||
impl<M> ScopeMarker<M> {
|
||||
/// Record a single event occurence.
|
||||
#[inline]
|
||||
pub fn mark(&self, scope: &mut ControlScopeFn<M>) {
|
||||
scope.write(&self.metric, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// A counter that sends values to the metrics backend
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ScopeCounter<M> {
|
||||
metric: M,
|
||||
}
|
||||
|
||||
impl<M> ScopeCounter<M> {
|
||||
/// Record a value count.
|
||||
#[inline]
|
||||
pub fn count<V>(&self, scope: &mut ControlScopeFn<M>, count: V)
|
||||
where
|
||||
V: ToPrimitive,
|
||||
{
|
||||
scope.write(&self.metric, count.to_u64().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
/// A gauge that sends values to the metrics backend
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ScopeGauge<M> {
|
||||
metric: M,
|
||||
}
|
||||
|
||||
impl<M: Clone> ScopeGauge<M> {
|
||||
/// Record a value point for this gauge.
|
||||
#[inline]
|
||||
pub fn value<V>(&self, scope: &mut ControlScopeFn<M>, value: V)
|
||||
where
|
||||
V: ToPrimitive,
|
||||
{
|
||||
scope.write(&self.metric, value.to_u64().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
/// A timer that sends values to the metrics backend
|
||||
/// Timers can record time intervals in multiple ways :
|
||||
/// - with the time! macro which wraps an expression or block with start() and stop() calls.
|
||||
/// - with the time(Fn) method which wraps a closure with start() and stop() calls.
|
||||
/// - with start() and stop() methods wrapping around the operation to time
|
||||
/// - with the interval_us() method, providing an externally determined microsecond interval
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug)]
|
||||
pub struct ScopeTimer<M> {
|
||||
metric: M,
|
||||
}
|
||||
|
||||
impl<M: Clone> ScopeTimer<M> {
|
||||
/// Record a microsecond interval for this timer
|
||||
/// Can be used in place of start()/stop() if an external time interval source is used
|
||||
#[inline]
|
||||
pub fn interval_us<V>(&self, scope: &mut ControlScopeFn<M>, interval_us: V) -> V
|
||||
where
|
||||
V: ToPrimitive,
|
||||
{
|
||||
scope.write(&self.metric, interval_us.to_u64().unwrap());
|
||||
interval_us
|
||||
}
|
||||
|
||||
/// Obtain a opaque handle to the current time.
|
||||
/// The handle is passed back to the stop() method to record a time interval.
|
||||
/// This is actually a convenience method to the TimeHandle::now()
|
||||
/// Beware, handles obtained here are not bound to this specific timer instance
|
||||
/// _for now_ but might be in the future for safety.
|
||||
/// If you require safe multi-timer handles, get them through TimeType::now()
|
||||
#[inline]
|
||||
pub fn start(&self) -> TimeHandle {
|
||||
TimeHandle::now()
|
||||
}
|
||||
|
||||
/// Record the time elapsed since the start_time handle was obtained.
|
||||
/// This call can be performed multiple times using the same handle,
|
||||
/// reporting distinct time intervals each time.
|
||||
/// Returns the microsecond interval value that was recorded.
|
||||
#[inline]
|
||||
pub fn stop(&self, scope: &mut ControlScopeFn<M>, start_time: TimeHandle) -> u64 {
|
||||
let elapsed_us = start_time.elapsed_us();
|
||||
self.interval_us(scope, elapsed_us)
|
||||
}
|
||||
|
||||
/// Record the time taken to execute the provided closure
|
||||
#[inline]
|
||||
pub fn time<F, R>(&self, scope: &mut ControlScopeFn<M>, operations: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
let start_time = self.start();
|
||||
let value: R = operations();
|
||||
self.stop(scope, start_time);
|
||||
value
|
||||
}
|
||||
}
|
|
@ -2,17 +2,26 @@
|
|||
//! Collect statistics about various metrics modules at runtime.
|
||||
//! Stats can can be obtained for publication from `selfstats::SOURCE`.
|
||||
|
||||
pub use core::*;
|
||||
|
||||
pub use app_metrics::*;
|
||||
pub use aggregate::*;
|
||||
pub use publish::*;
|
||||
pub use scores::*;
|
||||
pub use core::*;
|
||||
pub use namespace::*;
|
||||
|
||||
use output::to_void;
|
||||
|
||||
// TODO send to_dispatch()
|
||||
fn build_aggregator() -> Chain<Aggregate> {
|
||||
lazy_static! {
|
||||
static ref DIPSTICK_AGGREGATOR: Aggregator = build_aggregator();
|
||||
}
|
||||
|
||||
/// Application metrics are collected to the aggregator
|
||||
|
||||
app_metrics!(Aggregate, DIPSTICK_METRICS = build_self_metrics());
|
||||
|
||||
fn build_aggregator() -> Aggregator {
|
||||
// TODO make publishable
|
||||
aggregate(summary, to_void())
|
||||
}
|
||||
|
||||
|
@ -22,10 +31,8 @@ pub fn snapshot() -> Vec<ScoreSnapshot> {
|
|||
}
|
||||
|
||||
fn build_self_metrics() -> AppMetrics<Aggregate> {
|
||||
app_metrics(AGGREGATOR.clone()).with_prefix("dipstick")
|
||||
let mug: &Aggregator = &DIPSTICK_AGGREGATOR;
|
||||
let am: AppMetrics<Aggregate> = mug.clone().into();
|
||||
am.with_prefix("dipstick")
|
||||
}
|
||||
|
||||
lazy_static! { static ref AGGREGATOR: Chain<Aggregate> = build_aggregator(); }
|
||||
|
||||
/// Application metrics are collected to the aggregator
|
||||
app_metrics!(Aggregate, DIPSTICK_METRICS = build_self_metrics());
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Send metrics to a statsd server.
|
||||
|
||||
use core::*;
|
||||
use scope_metrics::*;
|
||||
use error;
|
||||
use self_metrics::*;
|
||||
|
||||
|
@ -14,7 +15,7 @@ mod_marker!(Aggregate, STATSD_METRICS, { SEND_ERR: "send_failed" });
|
|||
mod_counter!(Aggregate, STATSD_METRICS, { SENT_BYTES: "sent_bytes" });
|
||||
|
||||
/// Send metrics to a statsd server at the address and port provided.
|
||||
pub fn to_statsd<ADDR>(address: ADDR) -> error::Result<Chain<Statsd>>
|
||||
pub fn to_statsd<ADDR>(address: ADDR) -> error::Result<ScopeMetrics<Statsd>>
|
||||
where
|
||||
ADDR: ToSocketAddrs,
|
||||
{
|
||||
|
@ -22,7 +23,7 @@ where
|
|||
socket.set_nonblocking(true)?;
|
||||
socket.connect(address)?;
|
||||
|
||||
Ok(Chain::new(
|
||||
Ok(ScopeMetrics::new(
|
||||
move |kind, name, rate| {
|
||||
let mut prefix = String::with_capacity(32);
|
||||
prefix.push_str(name);
|
||||
|
@ -59,7 +60,7 @@ where
|
|||
socket: socket.clone(),
|
||||
buffered,
|
||||
});
|
||||
ControlScopeFn::new(move |cmd| {
|
||||
control_scope(move |cmd| {
|
||||
if let Ok(mut buf) = buf.write() {
|
||||
match cmd {
|
||||
ScopeCmd::Write(metric, value) => buf.write(metric, value),
|
||||
|
|
Loading…
Reference in New Issue