Registry dissolved

This commit is contained in:
Francis Lalonde 2018-03-27 16:30:12 -04:00
parent 2681c7b342
commit d055270d84
22 changed files with 450 additions and 431 deletions

View File

@ -4,4 +4,6 @@
- Add `Delegate` mechanism to allow runtime (re)configuration of metrics
- Enhance macros to allow metrics of different types within a single block
- Additional pre-typed 'delegate' and 'aggregate' macros

View File

@ -57,7 +57,8 @@ Aggregate metrics and schedule to be periodical publication in the background:
```rust,skt-run
use std::time::Duration;
let app_metrics = metrics(aggregate(all_stats, to_stdout()));
let app_metrics = metrics(aggregate());
route_aggregate_metrics(to_stdout());
app_metrics.flush_every(Duration::from_secs(3));
```
@ -68,14 +69,13 @@ Published statistics can be selected with presets such as `all_stats` (see previ
For more control over published statistics, provide your own strategy:
```rust,skt-run
metrics(aggregate(
|_kind, name, score|
match score {
ScoreType::Count(count) =>
Some((Kind::Counter, vec![name, ".per_thousand"], count / 1000)),
_ => None
},
to_log()));
metrics(aggregate());
set_default_aggregate_fn(|_kind, name, score|
match score {
ScoreType::Count(count) =>
Some((Kind::Counter, vec![name, ".per_thousand"], count / 1000)),
_ => None
});
```
Apply statistical sampling to metrics:
@ -98,12 +98,12 @@ For speed and easier maintenance, metrics are usually defined statically:
#[macro_use] extern crate lazy_static;
use dipstick::*;
metrics!("my_app" => {
delegate_metrics!("my_app" => {
@Counter COUNTER_A: "counter_a";
});
fn main() {
send_metrics(to_stdout());
route_aggregate_metrics(to_stdout());
COUNTER_A.count(11);
}
```

View File

@ -7,10 +7,12 @@ use std::time::Duration;
use dipstick::*;
fn main() {
let to_aggregate = aggregate(all_stats, to_stdout());
let to_aggregate = aggregate();
let app_metrics = metrics(to_aggregate);
route_aggregate_metrics(to_stdout());
app_metrics.flush_every(Duration::from_secs(3));
let counter = app_metrics.counter("counter_a");

View File

@ -35,7 +35,9 @@ fn main() {
}
// send application metrics to aggregator
let to_aggregate = aggregate(custom_statistics, to_stdout());
let to_aggregate = aggregate();
route_aggregate_metrics(to_stdout());
let app_metrics = metrics(to_aggregate);

View File

@ -0,0 +1,57 @@
//! A sample application sending ad-hoc counter values both to statsd _and_ to stdout.
extern crate dipstick;
#[macro_use]
extern crate lazy_static;
use dipstick::*;
use std::time::Duration;
// undeclared root (un-prefixed) metrics
aggregate_metrics!(() => {
// create counter "some_counter"
pub @Counter ROOT_COUNTER: "root_counter";
// create counter "root_counter"
pub @Gauge ROOT_GAUGE: "root_gauge";
// create counter "root_timer"
pub @Timer ROOT_TIMER: "root_timer";
});
// public source
aggregate_metrics!(pub PUB_METRICS ="pub_lib_prefix" => {
// create counter "lib_prefix.some_counter"
pub @Counter PUB_COUNTER: "some_counter";
});
// undeclared (private) prefixed metrics
//app_metrics!("closed_lib_prefix" => {
// // create counter "lib_prefix.some_counter"
// pub @Counter MY_COUNTER: "some_counter";
//});
// declare mod source
aggregate_metrics!(LIB_METRICS ="mod_lib_prefix" => {
// create counter "mod_lib_prefix.some_counter"
pub @Counter SOME_COUNTER: "some_counter";
});
// reuse declared source
aggregate_metrics!(LIB_METRICS => {
// create counter "mod_lib_prefix.another_counter"
@Counter ANOTHER_COUNTER: "another_counter";
});
fn main() {
route_aggregate_metrics(to_stdout());
loop {
PUB_COUNTER.count(978);
ROOT_COUNTER.count(123);
ANOTHER_COUNTER.count(456);
ROOT_TIMER.interval_us(2000000);
ROOT_GAUGE.value(34534);
PUB_METRICS.flush();
std::thread::sleep(Duration::from_millis(40));
}
}

View File

@ -8,17 +8,17 @@ use dipstick::*;
use std::time::Duration;
// undeclared root (un-prefixed) metrics
metrics!( => {
delegate_metrics! { () => {
// create counter "some_counter"
pub @Counter ROOT_COUNTER: "root_counter";
// create counter "root_counter"
pub @Gauge ROOT_GAUGE: "root_gauge";
// create counter "root_timer"
pub @Timer ROOT_TIMER: "root_timer";
});
}}
// public source
metrics!(pub PUB_METRICS ="pub_lib_prefix" => {
delegate_metrics!(pub PUB_METRICS ="pub_lib_prefix" => {
// create counter "lib_prefix.some_counter"
pub @Counter PUB_COUNTER: "some_counter";
});
@ -30,19 +30,19 @@ metrics!(pub PUB_METRICS ="pub_lib_prefix" => {
//});
// declare mod source
metrics!(LIB_METRICS ="mod_lib_prefix" => {
delegate_metrics!(LIB_METRICS ="mod_lib_prefix" => {
// create counter "mod_lib_prefix.some_counter"
pub @Counter SOME_COUNTER: "some_counter";
});
// reuse declared source
metrics!(LIB_METRICS => {
delegate_metrics!(LIB_METRICS => {
// create counter "mod_lib_prefix.another_counter"
@Counter ANOTHER_COUNTER: "another_counter";
});
fn main() {
send_metrics(to_stdout());
set_default_metric_scope(to_stdout());
loop {
ROOT_COUNTER.count(123);

View File

@ -11,16 +11,16 @@ metrics!(<(Statsd, String)> DIFFERENT_TYPES = (
// combine outputs of different types by using a tuple
to_statsd("localhost:8125").expect("Connecting"),
to_stdout(),
););
));
metrics!(<Vec<String>> SAME_TYPE = [
// combine multiple outputs of the same type by using an array
to_stdout().with_prefix("yeah"),
to_stdout().with_prefix("ouch"),
to_stdout().with_sampling_rate(0.5),
][..];);
][..]);
metrics!(<Vec<String>> MUTANT_CHILD = SAME_TYPE.with_prefix("super").with_prefix("duper"););
metrics!(<Vec<String>> MUTANT_CHILD = SAME_TYPE.with_prefix("super").with_prefix("duper"));
fn main() {
loop {

View File

@ -7,7 +7,7 @@ use dipstick::*;
fn main() {
// print only 1 out of every 10000 metrics recorded
let app_metrics: Metrics<String> = metrics(to_stdout().with_sampling_rate(0.0001));
let app_metrics: MetricScope<String> = metrics(to_stdout().with_sampling_rate(0.0001));
let marker = app_metrics.marker("marker_a");

View File

@ -7,7 +7,9 @@ use std::time::Duration;
use dipstick::*;
fn main() {
let to_aggregate = aggregate(summary, to_stdout());
let to_aggregate = aggregate();
route_aggregate_metrics(to_stdout());
let app_metrics = metrics(to_aggregate);

View File

@ -1,42 +1,69 @@
//! Maintain aggregated metrics for deferred reporting,
//!
use core::*;
use context::*;
use metrics::*;
use namespace::*;
use output::to_void;
use core::{Value, Kind, control_scope, ScopeCmd, Sampling};
use core::Kind::*;
use context::DEFAULT_CONTEXT;
use metrics::MetricScope;
use namespace::WithNamespace;
use scores::*;
use publish::*;
use scores::{ScoreSnapshot, ScoreType, Scoreboard};
use scores::ScoreType::*;
use std::fmt::Debug;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
/// Define aggregate metrics.
#[macro_export]
macro_rules! aggregate_metrics {
(pub $METRIC_ID:ident = $e:expr $(;)*) => { metrics! {<Aggregate> pub $METRIC_ID = $e } };
(pub $METRIC_ID:ident = $e:expr => { $($REMAINING:tt)+ }) => { metrics! {<Aggregate> pub $METRIC_ID = $e => { $($REMAINING)* } } };
($METRIC_ID:ident = $e:expr $(;)*) => { metrics! {<Aggregate> $METRIC_ID = $e } };
($METRIC_ID:ident = $e:expr => { $($REMAINING:tt)+ }) => { metrics! {<Aggregate> $METRIC_ID = $e => { $($REMAINING)* } } };
($METRIC_ID:ident => { $($REMAINING:tt)+ }) => { metrics! {<Aggregate> $METRIC_ID => { $($REMAINING)* } } };
($e:expr => { $($REMAINING:tt)+ }) => { metrics! {<Aggregate> $e => { $($REMAINING)* } } };
}
lazy_static! {
static ref DEFAULT_PUBLISH_FN: RwLock<Arc<Fn(Kind, &str, ScoreType) ->
Option<(Kind, Vec<&str>, Value)> + Send + Sync + 'static>> =
RwLock::new(Arc::new(summary));
static ref AGGREGATE_REGISTRY: RwLock<Vec<Aggregator>> = RwLock::new(vec![]);
}
pub fn set_default_aggregate_fn<F>(func: F)
where
F: Fn(Kind, &str, ScoreType) -> Option<(Kind, Vec<&str>, Value)> + Send + Sync + 'static
{
*DEFAULT_PUBLISH_FN.write().unwrap() = Arc::new(func)
}
/// Get the default metrics summary.
pub fn get_default_publish_fn() -> Arc<Fn(Kind, &str, ScoreType) ->
Option<(Kind, Vec<&str>, Value)> + Send + Sync + 'static> {
DEFAULT_PUBLISH_FN.read().unwrap().clone()
}
/// 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::*;
/// use dipstick;
/// let metrics: AppMetrics<_> = aggregate(summary, to_stdout()).into();
/// metrics.marker("my_event").mark();
/// metrics.marker("my_event").mark();
/// ```
pub fn aggregate<E, M>(stat_fn: E, to_chain: MetricContext<M>) -> Aggregator
where
E: Fn(Kind, &str, ScoreType) -> Option<(Kind, Vec<&str>, Value)> + Send + Sync + 'static,
M: Clone + Send + Sync + Debug + 'static,
{
pub fn aggregate() -> Aggregator {
Aggregator {
metrics: Arc::new(RwLock::new(HashMap::new())),
publish: Arc::new(Publisher::new(stat_fn, to_chain)),
}
}
impl From<Aggregator> for Metrics<Aggregate> {
fn from(agg: Aggregator) -> Metrics<Aggregate> {
impl From<Aggregator> for MetricScope<Aggregate> {
fn from(agg: Aggregator) -> MetricScope<Aggregate> {
let agg_1 = agg.clone();
Metrics::new(
MetricScope::new(
Arc::new(move |kind, name, rate| agg.define_metric(kind, name, rate)),
control_scope(move |cmd| match cmd {
ScopeCmd::Write(metric, value) => {
@ -44,15 +71,25 @@ impl From<Aggregator> for Metrics<Aggregate> {
metric.update(value)
}
ScopeCmd::Flush => agg_1.flush(),
}),
)
}))
}
}
impl From<&'static str> for Metrics<Aggregate> {
fn from(prefix: &'static str) -> Metrics<Aggregate> {
let app_metrics: Metrics<Aggregate> = aggregate(summary, to_void()).into();
app_metrics.with_prefix(prefix)
impl From<&'static str> for MetricScope<Aggregate> {
fn from(prefix: &'static str) -> MetricScope<Aggregate> {
let app_metrics: MetricScope<Aggregate> = aggregate().into();
if !prefix.is_empty() {
app_metrics.with_prefix(prefix)
} else {
app_metrics
}
}
}
impl From<()> for MetricScope<Aggregate> {
fn from(_: ()) -> MetricScope<Aggregate> {
let scope: MetricScope<Aggregate> = aggregate().into();
scope
}
}
@ -61,15 +98,13 @@ impl From<&'static str> for Metrics<Aggregate> {
#[derive(Debug, Clone)]
pub struct Aggregator {
metrics: Arc<RwLock<HashMap<String, Arc<Scoreboard>>>>,
publish: Arc<Publish>,
}
impl Aggregator {
/// Build a new metric aggregation point with specified initial capacity of metrics to aggregate.
pub fn with_capacity(size: usize, publish: Arc<Publish>) -> Aggregator {
pub fn with_capacity(size: usize) -> Aggregator {
Aggregator {
metrics: Arc::new(RwLock::new(HashMap::with_capacity(size))),
publish: publish.clone(),
}
}
@ -90,7 +125,7 @@ impl Aggregator {
}
/// Lookup or create a scoreboard for the requested metric.
pub fn define_metric(&self, kind: Kind, name: &str, _rate: Rate) -> Aggregate {
pub fn define_metric(&self, kind: Kind, name: &str, _rate: Sampling) -> Aggregate {
self.metrics
.write()
.expect("Locking aggregator")
@ -102,46 +137,122 @@ impl Aggregator {
/// 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);
let snapshot: Vec<ScoreSnapshot> = {
let metrics = self.metrics.read().expect("Locking metrics scoreboards");
metrics.values().flat_map(|score| score.reset()).collect()
};
let publish_scope = DEFAULT_CONTEXT.read().unwrap().open_scope();
if snapshot.is_empty() {
// no data was collected for this period
// TODO repeat previous frame min/max ?
// TODO update some canary metric ?
} else {
for metric in snapshot {
for score in metric.2 {
if let Some(ex) = (get_default_publish_fn())(metric.0, metric.1.as_ref(), score) {
publish_scope.define_metric(ex.0, &ex.1.concat(), 1.0).write(ex.2);
}
}
}
}
// TODO parameterize whether to keep ad-hoc metrics after publish
// source.cleanup();
publish_scope.flush()
}
}
/// The type of metric created by the Aggregator.
pub type Aggregate = Arc<Scoreboard>;
/// A predefined export strategy reporting all aggregated stats for all metric types.
/// Resulting stats are named by appending a short suffix to each metric's name.
pub fn all_stats(kind: Kind, name: &str, score: ScoreType) -> Option<(Kind, Vec<&str>, Value)> {
match score {
Count(hit) => Some((Counter, vec![name, ".count"], hit)),
Sum(sum) => Some((kind, vec![name, ".sum"], sum)),
Mean(mean) => Some((kind, vec![name, ".mean"], mean.round() as Value)),
Max(max) => Some((Gauge, vec![name, ".max"], max)),
Min(min) => Some((Gauge, vec![name, ".min"], min)),
Rate(rate) => Some((Gauge, vec![name, ".rate"], rate.round() as Value)),
}
}
/// A predefined export strategy reporting the average value for every non-marker metric.
/// Marker metrics export their hit count instead.
/// Since there is only one stat per metric, there is no risk of collision
/// and so exported stats copy their metric's name.
pub fn average(kind: Kind, name: &str, score: ScoreType) -> Option<(Kind, Vec<&str>, Value)> {
match kind {
Marker => match score {
Count(count) => Some((Counter, vec![name], count)),
_ => None,
},
_ => match score {
Mean(avg) => Some((Gauge, vec![name], avg.round() as Value)),
_ => None,
},
}
}
/// A predefined single-stat-per-metric export strategy:
/// - Timers and Counters each export their sums
/// - Markers each export their hit count
/// - Gauges each export their average
/// Since there is only one stat per metric, there is no risk of collision
/// and so exported stats copy their metric's name.
pub fn summary(kind: Kind, name: &str, score: ScoreType) -> Option<(Kind, Vec<&str>, Value)> {
match kind {
Marker => match score {
Count(count) => Some((Counter, vec![name], count)),
_ => None,
},
Counter | Timer => match score {
Sum(sum) => Some((kind, vec![name], sum)),
_ => None,
},
Gauge => match score {
Mean(mean) => Some((Gauge, vec![name], mean.round() as Value)),
_ => None,
},
}
}
#[cfg(feature = "bench")]
mod bench {
use super::*;
use test;
use core::Kind::*;
use core::Kind::{Marker, Counter};
use metrics::MetricScope;
use aggregate::{Aggregate, aggregate};
#[bench]
fn aggregate_marker(b: &mut test::Bencher) {
let sink: Metrics<Aggregate> = aggregate(summary, to_void()).into();
let sink: MetricScope<Aggregate> = aggregate().into();
let metric = sink.define_metric(Marker, "event_a", 1.0);
b.iter(|| test::black_box(sink.write(&metric, 1)));
}
#[bench]
fn aggregate_counter(b: &mut test::Bencher) {
let sink: Metrics<Aggregate> = aggregate(summary, to_void()).into();
let sink: MetricScope<Aggregate> = aggregate().into();
let metric = sink.define_metric(Counter, "count_a", 1.0);
b.iter(|| test::black_box(sink.write(&metric, 1)));
}
#[bench]
fn reset_marker(b: &mut test::Bencher) {
let sink: Metrics<Aggregate> = aggregate(summary, to_void()).into();
let sink: MetricScope<Aggregate> = aggregate().into();
let metric = sink.define_metric(Marker, "marker_a", 1.0);
b.iter(|| test::black_box(metric.reset()));
}
#[bench]
fn reset_counter(b: &mut test::Bencher) {
let sink: Metrics<Aggregate> = aggregate(summary, to_void()).into();
let sink: MetricScope<Aggregate> = aggregate().into();
let metric = sink.define_metric(Counter, "count_a", 1.0);
b.iter(|| test::black_box(metric.reset()));
}

View File

@ -1,11 +1,31 @@
//! Chain of command for unscoped metrics.
use core::*;
use metrics::Metrics;
use metrics::MetricScope;
use std::sync::Arc;
use namespace::{WithNamespace, add_namespace, Namespace};
use std::sync::{Arc, RwLock};
use metrics::DefineMetric;
use output;
lazy_static! {
pub static ref NO_RECV_CONTEXT: Arc<OpenScope + Send + Sync> = Arc::new(output::to_void());
pub static ref DEFAULT_CONTEXT: RwLock<Arc<OpenScope + Send + Sync>> = RwLock::new(NO_RECV_CONTEXT.clone());
}
/// Wrap a MetricContext in a non-generic trait.
pub trait OpenScope {
/// Open a new metrics scope
fn open_scope(&self) -> Arc<DefineMetric + Send + Sync>;
}
/// Install a new receiver for all dispatched metrics, replacing any previous receiver.
pub fn route_aggregate_metrics<IS: Into<MetricContext<T>>, T: Send + Sync + Clone + 'static>(into_ctx: IS) {
let ctx = Arc::new(into_ctx.into());
*DEFAULT_CONTEXT.write().unwrap() = ctx;
}
use namespace::*;
/// A pair of functions composing a twin "chain of command".
/// This is the building block for the metrics backend.
@ -13,10 +33,10 @@ use namespace::*;
#[derivative(Debug)]
pub struct MetricContext<M> {
#[derivative(Debug = "ignore")]
define_metric_fn: DefineMetricFn<M>,
prototype_metric_fn: DefineMetricFn<M>,
#[derivative(Debug = "ignore")]
scope_metric_fn: OpenScopeFn<M>,
open_scope_fn: OpenScopeFn<M>,
}
impl<M> MetricContext<M> {
@ -33,21 +53,21 @@ impl<M> MetricContext<M> {
/// let request_counter = scope_metrics.counter("scope_counter");
/// ```
///
pub fn open_scope(&self) -> Metrics<M> {
Metrics::new(self.define_metric_fn.clone(), (self.scope_metric_fn)())
pub fn open_scope(&self) -> MetricScope<M> {
MetricScope::new(self.prototype_metric_fn.clone(), (self.open_scope_fn)())
}
}
/// Create a new metric chain with the provided metric definition and scope creation functions.
pub fn metrics_context<MF, WF, M>(make_metric: MF, make_scope: WF) -> MetricContext<M>
pub fn metrics_context<MF, WF, M>(define_fn: MF, open_scope_fn: WF) -> MetricContext<M>
where
MF: Fn(Kind, &str, Rate) -> M + Send + Sync + 'static,
MF: Fn(Kind, &str, Sampling) -> M + Send + Sync + 'static,
WF: Fn() -> WriteFn<M> + Send + Sync + 'static,
{
MetricContext {
define_metric_fn: Arc::new(make_metric),
scope_metric_fn: Arc::new(make_scope),
prototype_metric_fn: Arc::new(define_fn),
open_scope_fn: Arc::new(open_scope_fn),
}
}
@ -60,10 +80,10 @@ impl<M: Send + Sync + Clone + 'static> MetricContext<M> {
N: Clone + Send + Sync,
{
let (metric_fn, scope_fn) =
mod_fn(self.define_metric_fn.clone(), self.scope_metric_fn.clone());
mod_fn(self.prototype_metric_fn.clone(), self.open_scope_fn.clone());
MetricContext {
define_metric_fn: metric_fn,
scope_metric_fn: scope_fn,
prototype_metric_fn: metric_fn,
open_scope_fn: scope_fn,
}
}
@ -73,15 +93,21 @@ impl<M: Send + Sync + Clone + 'static> MetricContext<M> {
MF: Fn(OpenScopeFn<M>) -> OpenScopeFn<M>,
{
MetricContext {
define_metric_fn: self.define_metric_fn.clone(),
scope_metric_fn: mod_fn(self.scope_metric_fn.clone()),
prototype_metric_fn: self.prototype_metric_fn.clone(),
open_scope_fn: mod_fn(self.open_scope_fn.clone()),
}
}
}
impl<M> From<MetricContext<M>> for Metrics<M> {
fn from(metrics: MetricContext<M>) -> Metrics<M> {
impl<M: Send + Sync + Clone + 'static> OpenScope for MetricContext<M> {
fn open_scope(&self) -> Arc<DefineMetric + Send + Sync> {
Arc::new(self.open_scope())
}
}
impl<M> From<MetricContext<M>> for MetricScope<M> {
fn from(metrics: MetricContext<M>) -> MetricScope<M> {
metrics.open_scope()
}
}
@ -90,8 +116,8 @@ impl<M: Send + Sync + Clone + 'static> WithNamespace for MetricContext<M> {
fn with_name<IN: Into<Namespace>>(&self, names: IN) -> Self {
let ref ninto = names.into();
MetricContext {
define_metric_fn: add_namespace(ninto, self.define_metric_fn.clone()),
scope_metric_fn: self.scope_metric_fn.clone(),
prototype_metric_fn: add_namespace(ninto, self.prototype_metric_fn.clone()),
open_scope_fn: self.open_scope_fn.clone(),
}
}
}

View File

@ -37,10 +37,10 @@ impl TimeHandle {
/// - 0.5 records one of two values
/// - 0.0 records nothing
/// The actual distribution (random, fixed-cycled, etc) depends on selected sampling method.
pub type Rate = f64;
pub type Sampling = f64;
/// Do not sample, use all data.
pub const FULL_SAMPLING_RATE: Rate = 1.0;
pub const FULL_SAMPLING_RATE: Sampling = 1.0;
/// Used to differentiate between metric kinds in the backend.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
@ -60,7 +60,7 @@ pub enum Kind {
/// The resulting metrics themselves can be also be safely shared across threads (<M> is Send + Sync).
/// Concurrent usage of a metric is done using threaded scopes.
/// Shared concurrent scopes may be provided by some backends (aggregate).
pub type DefineMetricFn<M> = Arc<Fn(Kind, &str, Rate) -> M + Send + Sync>;
pub type DefineMetricFn<M> = Arc<Fn(Kind, &str, Sampling) -> M + Send + Sync>;
/// A function trait that opens a new metric capture scope.
pub type OpenScopeFn<M> = Arc<Fn() -> WriteFn<M> + Send + Sync>;

View File

@ -1,15 +1,38 @@
//! Decouple metric definition from configuration with trait objects.
use core::*;
use metrics::*;
use metrics::{MetricScope, DefineMetric, WriteMetric, NO_RECV_METRICS};
use namespace::*;
use registry;
use std::collections::HashMap;
use std::sync::{Arc, RwLock, Weak};
use atomic_refcell::*;
/// Define delegate metrics.
#[macro_export]
macro_rules! delegate_metrics {
(pub $METRIC_ID:ident = $e:expr $(;)*) => { metrics! {<Delegate> pub $METRIC_ID = $e; } };
(pub $METRIC_ID:ident = $e:expr => { $($REMAINING:tt)+ }) => { metrics! {<Delegate> pub $METRIC_ID = $e => { $($REMAINING)* } } };
($METRIC_ID:ident = $e:expr $(;)*) => { metrics! {<Delegate> $METRIC_ID = $e; } };
($METRIC_ID:ident = $e:expr => { $($REMAINING:tt)+ }) => { metrics! {<Delegate> $METRIC_ID = $e => { $($REMAINING)* } } };
($METRIC_ID:ident => { $($REMAINING:tt)+ }) => { metrics! {<Delegate> $METRIC_ID => { $($REMAINING)* } } };
($e:expr => { $($REMAINING:tt)+ }) => { metrics! {<Delegate> $e => { $($REMAINING)* } } };
}
lazy_static! {
pub static ref DELEGATE_REGISTRY: RwLock<Vec<MetricsSend>> = RwLock::new(vec![]);
pub static ref DEFAULT_METRICS: RwLock<Arc<DefineMetric + Sync + Send>> = RwLock::new(NO_RECV_METRICS.clone());
}
/// Install a new receiver for all dispatched metrics, replacing any previous receiver.
pub fn set_default_metric_scope<IS: Into<MetricScope<T>>, T: Send + Sync + Clone + 'static>(into_recv: IS) {
let recv = Arc::new(into_recv.into());
for d in DELEGATE_REGISTRY.read().unwrap().iter() {
d.set_receiver(recv.clone());
}
*DEFAULT_METRICS.write().unwrap() = recv;
}
/// Create a new dispatch point for metrics.
/// All dispatch points are automatically entered in the dispatch registry.
@ -17,33 +40,13 @@ pub fn delegate_metrics() -> MetricsSend {
let send = MetricsSend {
inner: Arc::new(RwLock::new(InnerMetricsSend {
metrics: HashMap::new(),
recv: registry::get_default_metrics_recv(),
recv: DEFAULT_METRICS.read().unwrap().clone(),
})),
};
registry::add_metrics_send(send.clone());
DELEGATE_REGISTRY.write().unwrap().push(send.clone());
send
}
/// Dynamic counterpart of a `Dispatcher`.
/// Adapter to AppMetrics<_> of unknown type.
pub trait MetricsRecv {
/// 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 define_metric(&self, kind: Kind, name: &str, rate: Rate) -> Box<RecvMetric + Send + Sync>;
/// Flush the receiver's scope.
fn flush(&self);
}
/// Dynamic counterpart of the `DispatcherMetric`.
/// Adapter to a metric of unknown type.
pub trait RecvMetric {
/// Write metric value to a scope.
/// Observers only receive previously registered handles.
fn write(&self, value: Value);
}
/// Shortcut name because `AppMetrics<Dispatch>`
/// looks better than `AppMetrics<Arc<DispatcherMetric>>`.
pub type Delegate = Arc<SendMetric>;
@ -54,9 +57,9 @@ pub type Delegate = Arc<SendMetric>;
pub struct SendMetric {
kind: Kind,
name: String,
rate: Rate,
rate: Sampling,
#[derivative(Debug = "ignore")]
recv_metric: AtomicRefCell<Box<RecvMetric + Send + Sync>>,
recv_metric: AtomicRefCell<Box<WriteMetric + Send + Sync>>,
#[derivative(Debug = "ignore")]
send: MetricsSend,
}
@ -79,30 +82,33 @@ pub struct MetricsSend {
struct InnerMetricsSend {
metrics: HashMap<String, Weak<SendMetric>>,
recv: Arc<MetricsRecv + Send + Sync>,
recv: Arc<DefineMetric + Send + Sync>,
}
/// Allow turning a 'static str into a Delegate, where str is the prefix.
impl From<&'static str> for Metrics<Delegate> {
fn from(prefix: &'static str) -> Metrics<Delegate> {
let app_metrics: Metrics<Delegate> = delegate_metrics().into();
app_metrics.with_prefix(prefix)
impl From<&'static str> for MetricScope<Delegate> {
fn from(prefix: &'static str) -> MetricScope<Delegate> {
let app_metrics: MetricScope<Delegate> = delegate_metrics().into();
if !prefix.is_empty() {
app_metrics.with_prefix(prefix)
} else {
app_metrics
}
}
}
/// Allow turning a 'static str into a Delegate, where str is the prefix.
impl From<()> for Metrics<Delegate> {
fn from(_: ()) -> Metrics<Delegate> {
let app_metrics: Metrics<Delegate> = delegate_metrics().into();
impl From<()> for MetricScope<Delegate> {
fn from(_: ()) -> MetricScope<Delegate> {
let app_metrics: MetricScope<Delegate> = delegate_metrics().into();
app_metrics
}
}
impl From<MetricsSend> for Metrics<Delegate> {
fn from(send: MetricsSend) -> Metrics<Delegate> {
impl From<MetricsSend> for MetricScope<Delegate> {
fn from(send: MetricsSend) -> MetricScope<Delegate> {
let send_cmd = send.clone();
Metrics::new(
MetricScope::new(
// define metric
Arc::new(move |kind, name, rate| send.define_metric(kind, name, rate)),
@ -120,7 +126,7 @@ impl From<MetricsSend> for Metrics<Delegate> {
impl MetricsSend {
/// Install a new metric receiver, replacing the previous one.
pub fn set_receiver<R: MetricsRecv + Send + Sync + 'static>(&self, recv: Arc<R>) {
pub fn set_receiver<R: DefineMetric + Send + Sync + 'static>(&self, recv: Arc<R>) {
let inner = &mut self.inner.write().expect("Lock Metrics Send");
for mut metric in inner.metrics.values() {
@ -133,7 +139,7 @@ impl MetricsSend {
inner.recv = recv.clone()
}
fn define_metric(&self, kind: Kind, name: &str, rate: Rate) -> Delegate {
fn define_metric(&self, kind: Kind, name: &str, rate: Sampling) -> Delegate {
let mut inner = self.inner.write().expect("Lock Metrics Send");
inner.metrics.get(name)
.and_then(|metric_ref| Weak::upgrade(metric_ref))
@ -165,25 +171,23 @@ impl MetricsSend {
#[cfg(feature = "bench")]
mod bench {
use super::*;
use delegate::{delegate_metrics, set_default_metric_scope, Delegate};
use test;
use aggregate::*;
use publish::*;
use output::*;
use metrics::MetricScope;
use aggregate::aggregate;
#[bench]
fn dispatch_marker_to_aggregate(b: &mut test::Bencher) {
let dispatch = delegate_metrics();
let sink: Metrics<Delegate> = dispatch.clone().into();
dispatch.set_receiver(aggregate(summary, to_void()));
set_default_metric_scope(aggregate());
let sink: MetricScope<Delegate> = delegate_metrics().into();
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 = delegate_metrics();
let sink: Metrics<Delegate> = dispatch.into();
let metrics = delegate_metrics();
let sink: MetricScope<Delegate> = metrics.into();
let metric = sink.marker("event_a");
b.iter(|| test::black_box(metric.mark()));
}

View File

@ -65,7 +65,7 @@ pub fn to_buffered_graphite<ADDR>(address: ADDR) -> error::Result<MetricContext<
))
}
fn graphite_metric(kind: Kind, name: &str, rate: Rate) -> Graphite {
fn graphite_metric(kind: Kind, name: &str, rate: Sampling) -> Graphite {
let mut prefix = String::with_capacity(32);
prefix.push_str(name);
prefix.push(' ');

View File

@ -33,12 +33,14 @@ pub use core::*;
pub mod context;
pub use context::*;
//pub mod local_delegate;
//pub use local_delegate::*;
#[macro_use]
pub mod delegate;
pub use delegate::*;
#[macro_use]
mod aggregate;
pub use aggregate::*;
mod output;
pub use output::*;
@ -51,11 +53,6 @@ pub use sample::*;
mod scores;
pub use scores::*;
mod aggregate;
pub use aggregate::*;
mod publish;
pub use publish::*;
mod statsd;
pub use statsd::*;
@ -84,8 +81,4 @@ pub use async_queue::*;
mod schedule;
pub use schedule::*;
mod registry;
pub use registry::*;
mod self_metrics;
pub use self_metrics::snapshot;

View File

@ -19,25 +19,24 @@ macro_rules! time {
/// Metrics can be used from anywhere (public), does not need to declare metrics in this block.
#[macro_export]
#[doc(hidden)]
macro_rules! metrics {
// TYPED
// typed, public, no metrics
(<$METRIC_TYPE:ty> pub $METRIC_ID:ident = $e:expr;) => {
lazy_static! { pub static ref $METRIC_ID: Metrics<$METRIC_TYPE> = $e.into(); }
(<$METRIC_TYPE:ty> pub $METRIC_ID:ident = $e:expr $(;)*) => {
lazy_static! { pub static ref $METRIC_ID: MetricScope<$METRIC_TYPE> = $e.into(); }
};
// typed, public, some metrics
(<$METRIC_TYPE:ty> pub $METRIC_ID:ident = $e:expr => { $($REMAINING:tt)+ }) => {
lazy_static! { pub static ref $METRIC_ID: Metrics<$METRIC_TYPE> = $e.into(); }
lazy_static! { pub static ref $METRIC_ID: MetricScope<$METRIC_TYPE> = $e.into(); }
__metrics_block!($METRIC_ID: $METRIC_TYPE; $($REMAINING)*);
};
// typed, module, no metrics
(<$METRIC_TYPE:ty> $METRIC_ID:ident = $e:expr;) => {
lazy_static! { pub static ref $METRIC_ID: Metrics<$METRIC_TYPE> = $e.into(); }
(<$METRIC_TYPE:ty> $METRIC_ID:ident = $e:expr $(;)*) => {
lazy_static! { static ref $METRIC_ID: MetricScope<$METRIC_TYPE> = $e.into(); }
};
// typed, module, some metrics
(<$METRIC_TYPE:ty> $METRIC_ID:ident = $e:expr => { $($REMAINING:tt)+ }) => {
lazy_static! { pub static ref $METRIC_ID: Metrics<$METRIC_TYPE> = $e.into(); }
lazy_static! { static ref $METRIC_ID: MetricScope<$METRIC_TYPE> = $e.into(); }
__metrics_block!($METRIC_ID: $METRIC_TYPE; $($REMAINING)*);
};
// typed, reuse predeclared
@ -46,48 +45,14 @@ macro_rules! metrics {
};
// typed, unidentified, some metrics
(<$METRIC_TYPE:ty> $e:expr => { $($REMAINING:tt)+ }) => {
lazy_static! { pub static ref UNIDENT_METRIC: Metrics<$METRIC_TYPE> = $e.into(); }
lazy_static! { static ref UNIDENT_METRIC: MetricScope<$METRIC_TYPE> = $e.into(); }
__metrics_block!(UNIDENT_METRIC: $METRIC_TYPE; $($REMAINING)*);
};
// typed, root, some metrics
(<$METRIC_TYPE:ty> { $($REMAINING:tt)+ }) => {
lazy_static! { pub static ref ROOT_METRICS: Metrics<$METRIC_TYPE> = "".into(); }
lazy_static! { static ref ROOT_METRICS: MetricScope<$METRIC_TYPE> = ().into(); }
__metrics_block!(ROOT_METRICS: $METRIC_TYPE; $($REMAINING)*);
};
// DELEGATED
// delegated, public, no metrics
(pub $METRIC_ID:ident = $e:expr;) => {
lazy_static! { pub static ref $METRIC_ID: Metrics<Delegate> = $e.into(); }
};
// delegated, public, some metrics
(pub $METRIC_ID:ident = $e:expr => { $($REMAINING:tt)+ }) => {
lazy_static! { pub static ref $METRIC_ID: Metrics<Delegate> = $e.into(); }
__metrics_block!($METRIC_ID: Delegate; $($REMAINING)*);
};
// delegated, module, no metrics
($METRIC_ID:ident = $e:expr;) => {
lazy_static! { pub static ref $METRIC_ID: Metrics<Delegate> = $e.into(); }
};
// delegated, module, some metrics
($METRIC_ID:ident = $e:expr => { $($REMAINING:tt)+ }) => {
lazy_static! { pub static ref $METRIC_ID: Metrics<Delegate> = $e.into(); }
__metrics_block!($METRIC_ID: Delegate; $($REMAINING)*);
};
// delegated,reuse predeclared
($METRIC_ID:ident => { $($REMAINING:tt)+ }) => {
__metrics_block!($METRIC_ID: Delegate; $($REMAINING)*);
};
// delegated, unidentified, some metrics
($e:expr => { $($REMAINING:tt)+ }) => {
lazy_static! { pub static ref UNIDENT_METRIC: Metrics<Delegate> = $e.into(); }
__metrics_block!(UNIDENT_METRIC: Delegate; $($REMAINING)*);
};
// delegated, root, some metrics
( => { $($REMAINING:tt)+ }) => {
lazy_static! { pub static ref ROOT_METRICS: Metrics<Delegate> = ().into(); }
__metrics_block!(ROOT_METRICS: Delegate; $($REMAINING)*);
};
}
/// Internal macro required to abstract over pub/non-pub versions of the macro
@ -150,13 +115,13 @@ macro_rules! __metrics_block {
#[deprecated(since="0.7.0", note="Use metrics!() instead")]
macro_rules! app_metrics {
($type_param: ty, $metric_id: ident = ($($app_metrics: expr),+ $(,)*)) => {
lazy_static! { pub static ref $metric_id: Metrics<$type_param> = metrics(($($app_metrics),*)); }
lazy_static! { pub static ref $metric_id: MetricScope<$type_param> = metrics(($($app_metrics),*)); }
};
($type_param: ty, $metric_id: ident = [$($app_metrics: expr),+ $(,)*]) => {
lazy_static! { pub static ref $metric_id: Metrics<$type_param> = metrics(&[$($app_metrics),*][..],); }
lazy_static! { pub static ref $metric_id: MetricScope<$type_param> = metrics(&[$($app_metrics),*][..],); }
};
($type_param: ty, $metric_id: ident = $app_metrics: expr) => {
lazy_static! { pub static ref $metric_id: Metrics<$type_param> = $app_metrics.into(); }
lazy_static! { pub static ref $metric_id: MetricScope<$type_param> = $app_metrics.into(); }
};
}
@ -213,13 +178,13 @@ macro_rules! app_timer {
#[deprecated(since="0.7.0", note="Use metrics!() instead")]
macro_rules! mod_metrics {
($type_param: ty, $metric_id: ident = ($($app_metrics: expr),+ $(,)*)) => {
lazy_static! { static ref $metric_id: Metrics<$type_param> = metrics(($($app_metrics),*)); }
lazy_static! { static ref $metric_id: MetricScope<$type_param> = metrics(($($app_metrics),*)); }
};
($type_param: ty, $metric_id: ident = [$($app_metrics: expr),+ $(,)*]) => {
lazy_static! { static ref $metric_id: Metrics<$type_param> = metrics(&[$($app_metrics),*][..],); }
lazy_static! { static ref $metric_id: MetricScope<$type_param> = metrics(&[$($app_metrics),*][..],); }
};
($type_param: ty, $metric_id: ident = $mod_metrics: expr) => {
lazy_static! { static ref $metric_id: Metrics<$type_param> = $mod_metrics.into(); }
lazy_static! { static ref $metric_id: MetricScope<$type_param> = $mod_metrics.into(); }
};
}

View File

@ -11,31 +11,56 @@ use core::*;
use core::Kind::*;
use namespace::*;
use cache::*;
use schedule::*;
use delegate::*;
use schedule::{schedule, CancelHandle};
use context;
use std::sync::Arc;
use std::time::Duration;
// TODO define an 'AsValue' trait + impl for supported number types, then drop 'num' crate
pub use num::ToPrimitive;
lazy_static! {
pub static ref NO_RECV_METRICS: Arc<DefineMetric + Send + Sync> = context::NO_RECV_CONTEXT.open_scope();
}
/// Dynamic counterpart of a `Dispatcher`.
/// Adapter to AppMetrics<_> of unknown type.
pub trait DefineMetric {
/// 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 define_metric(&self, kind: Kind, name: &str, rate: Sampling) -> Box<WriteMetric + Send + Sync>;
/// Flush the receiver's scope.
fn flush(&self);
}
/// Dynamic counterpart of the `DispatcherMetric`.
/// Adapter to a metric of unknown type.
pub trait WriteMetric {
/// Write metric value to a scope.
/// Observers only receive previously registered handles.
fn write(&self, value: Value);
}
/// Wrap the metrics backend to provide an application-friendly interface.
/// Open a metric scope to share across the application.
#[deprecated(since="0.7.0", note="Use metrics() instead")]
pub fn app_metrics<M, AM>(scope: AM) -> Metrics<M>
pub fn app_metrics<M, AM>(scope: AM) -> MetricScope<M>
where
M: Clone + Send + Sync + 'static,
AM: Into<Metrics<M>>,
AM: Into<MetricScope<M>>,
{
scope.into()
}
/// Wrap the metrics backend to provide an application-friendly interface.
/// Open a metric scope to share across the application.
pub fn metrics<M, AM>(scope: AM) -> Metrics<M>
pub fn metrics<M, AM>(scope: AM) -> MetricScope<M>
where
M: Clone + Send + Sync + 'static,
AM: Into<Metrics<M>>,
AM: Into<MetricScope<M>>,
{
scope.into()
}
@ -145,7 +170,7 @@ impl<M> Timer<M> {
/// Help transition to new syntax
#[deprecated(since="0.7.0", note="Use Metrics instead")]
pub type AppMetrics<M> = Metrics<M>;
pub type AppMetrics<M> = MetricScope<M>;
/// Help transition to new syntax
#[deprecated(since="0.7.0", note="Use Marker instead")]
@ -166,31 +191,31 @@ pub type AppTimer<M> = Timer<M>;
/// Variations of this should also provide control of the metric recording scope.
#[derive(Derivative, Clone)]
pub struct Metrics<M> {
pub struct MetricScope<M> {
#[derivative(Debug = "ignore")]
define_metric_fn: DefineMetricFn<M>,
define_fn: DefineMetricFn<M>,
#[derivative(Debug = "ignore")]
single_scope: WriteFn<M>,
write_fn: WriteFn<M>,
}
impl<M> Metrics<M> {
impl<M> MetricScope<M> {
/// Create new application metrics instance.
pub fn new(define_metric_fn: DefineMetricFn<M>, scope: WriteFn<M>) -> Self {
Metrics {
define_metric_fn,
single_scope: scope,
MetricScope {
define_fn: define_metric_fn,
write_fn: scope,
}
}
}
impl<M> Metrics<M>
impl<M> MetricScope<M>
where
M: Clone + Send + Sync + 'static,
{
/// Define a raw metric.
/// Define a metric of specified type.
#[inline]
pub fn define_metric(&self, kind: Kind, name: &str, rate: Rate) -> M {
(self.define_metric_fn)(kind, name, rate)
pub fn define_metric(&self, kind: Kind, name: &str, rate: Sampling) -> M {
(self.define_fn)(kind, name, rate)
}
/// Define an event counter of the provided name.
@ -198,7 +223,7 @@ where
let metric = self.define_metric(Marker, name.as_ref(), 1.0);
Marker {
metric,
scope: self.single_scope.clone(),
scope: self.write_fn.clone(),
}
}
@ -207,7 +232,7 @@ where
let metric = self.define_metric(Counter, name.as_ref(), 1.0);
Counter {
metric,
scope: self.single_scope.clone(),
scope: self.write_fn.clone(),
}
}
@ -216,7 +241,7 @@ where
let metric = self.define_metric(Timer, name.as_ref(), 1.0);
Timer {
metric,
scope: self.single_scope.clone(),
scope: self.write_fn.clone(),
}
}
@ -225,73 +250,73 @@ where
let metric = self.define_metric(Gauge, name.as_ref(), 1.0);
Gauge {
metric,
scope: self.single_scope.clone(),
scope: self.write_fn.clone(),
}
}
/// Flush the backing metrics buffer.
/// The effect, if any, of this method depends on the selected metrics backend.
/// Flushes any recorded metric value.
/// Has no effect on unbuffered metrics.
pub fn flush(&self) {
self.single_scope.flush();
self.write_fn.flush();
}
/// Schedule for the metrics aggregated of buffered by downstream metrics sinks to be
/// sent out at regular intervals.
pub fn flush_every(&self, period: Duration) -> CancelHandle {
let scope = self.single_scope.clone();
let scope = self.write_fn.clone();
schedule(period, move || scope.flush())
}
/// Record a raw metric value.
pub fn write(&self, metric: &M, value: Value) {
self.single_scope.write(metric, value);
self.write_fn.write(metric, value);
}
}
//// Dispatch / Receiver impl
struct RecvMetricImpl<M> {
struct MetricWriter<M> {
metric: M,
scope: WriteFn<M>,
write_fn: WriteFn<M>,
}
impl<M: Send + Sync + Clone + 'static> MetricsRecv for Metrics<M> {
fn define_metric(&self, kind: Kind, name: &str, rate: Rate) -> Box<RecvMetric + Send + Sync> {
let scope: WriteFn<M> = self.single_scope.clone();
let metric: M = self.define_metric(kind, name, rate);
Box::new(RecvMetricImpl { metric, scope })
impl<M: Send + Sync + Clone + 'static> DefineMetric for MetricScope<M> {
fn define_metric(&self, kind: Kind, name: &str, rate: Sampling) -> Box<WriteMetric + Send + Sync> {
Box::new(MetricWriter {
metric: self.define_metric(kind, name, rate),
write_fn: self.write_fn.clone()
})
}
fn flush(&self) {
self.flush()
self.flush();
}
}
impl<M> RecvMetric for RecvMetricImpl<M> {
impl<M> WriteMetric for MetricWriter<M> {
fn write(&self, value: Value) {
self.scope.write(&self.metric, value);
self.write_fn.write(&self.metric, value);
}
}
//// Mutators impl
impl<M: Send + Sync + Clone + 'static> WithNamespace for Metrics<M> {
impl<M: Send + Sync + Clone + 'static> WithNamespace for MetricScope<M> {
fn with_name<IN: Into<Namespace>>(&self, names: IN) -> Self {
let ns = &names.into();
Metrics {
define_metric_fn: add_namespace(ns, self.define_metric_fn.clone()),
single_scope: self.single_scope.clone(),
MetricScope {
define_fn: add_namespace(ns, self.define_fn.clone()),
write_fn: self.write_fn.clone(),
}
}
}
impl<M: Send + Sync + Clone + 'static> WithCache for Metrics<M> {
impl<M: Send + Sync + Clone + 'static> WithCache for MetricScope<M> {
fn with_cache(&self, cache_size: usize) -> Self {
Metrics {
define_metric_fn: add_cache(cache_size, self.define_metric_fn.clone()),
single_scope: self.single_scope.clone(),
MetricScope {
define_fn: add_cache(cache_size, self.define_fn.clone()),
write_fn: self.write_fn.clone(),
}
}
}
@ -304,8 +329,7 @@ mod bench {
#[bench]
fn time_bench_direct_dispatch_event(b: &mut test::Bencher) {
let sink = aggregate(summary, to_void());
let metrics = metrics(sink);
let metrics = metrics(aggregate());
let marker = metrics.marker("aaa");
b.iter(|| test::black_box(marker.mark()));
}

View File

@ -9,19 +9,19 @@ use std::sync::Arc;
/// 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<(MetricContext<M1>, MetricContext<M2>)> for Metrics<(M1, M2)>
impl<M1, M2> From<(MetricContext<M1>, MetricContext<M2>)> for MetricScope<(M1, M2)>
where
M1: 'static + Clone + Send + Sync,
M2: 'static + Clone + Send + Sync,
{
fn from(combo: (MetricContext<M1>, MetricContext<M2>)) -> Metrics<(M1, M2)> {
fn from(combo: (MetricContext<M1>, MetricContext<M2>)) -> MetricScope<(M1, M2)> {
let scope0 = combo.0.open_scope();
let scope1 = combo.1.open_scope();
let scope0a = scope0.clone();
let scope1a = scope1.clone();
Metrics::new(
MetricScope::new(
Arc::new(move |kind, name, rate| (
scope0.define_metric(kind, name, rate),
scope1.define_metric(kind, name, rate),
@ -41,15 +41,15 @@ where
}
}
impl<'a, M> From<&'a [MetricContext<M>]> for Metrics<Vec<M>>
impl<'a, M> From<&'a [MetricContext<M>]> for MetricScope<Vec<M>>
where
M: 'static + Clone + Send + Sync,
{
fn from(chains: &'a [MetricContext<M>]) -> Metrics<Vec<M>> {
let scopes: Vec<Metrics<M>> = chains.iter().map(|x| x.open_scope()).collect();
fn from(chains: &'a [MetricContext<M>]) -> MetricScope<Vec<M>> {
let scopes: Vec<MetricScope<M>> = chains.iter().map(|x| x.open_scope()).collect();
let scopes2 = scopes.clone();
Metrics::new(
MetricScope::new(
Arc::new(move |kind, name, rate| {
scopes.iter().map(|m| m.define_metric(kind, name, rate)).collect()
}),

View File

@ -26,115 +26,3 @@ use scores::{ScoreSnapshot, ScoreType};
use scores::ScoreType::*;
use std::fmt::Debug;
/// A trait to publish metrics.
pub trait Publish: Send + Sync + Debug {
/// Publish the provided metrics data downstream.
fn publish(&self, scores: Vec<ScoreSnapshot>);
}
/// Define and write metrics from aggregated scores to the target channel
/// If this is called repeatedly it can be a good idea to use the metric cache
/// to prevent new metrics from being created every time.
#[derive(Derivative, Clone)]
#[derivative(Debug)]
pub struct Publisher<E, M> {
#[derivative(Debug = "ignore")]
statistics: Box<E>,
output: MetricContext<M>,
}
impl<E, M> Publisher<E, M>
where
E: Fn(Kind, &str, ScoreType) -> Option<(Kind, Vec<&str>, Value)> + Send + Sync + 'static,
M: Clone + Send + Sync + 'static,
{
/// Define a new metrics publishing strategy, from a transformation
/// function and a target metric chain.
pub fn new(stat_fn: E, output: MetricContext<M>) -> Self {
Publisher {
statistics: Box::new(stat_fn),
output,
}
}
}
impl<E, M> Publish for Publisher<E, M>
where
M: Clone + Send + Sync + Debug + 'static,
E: Fn(Kind, &str, ScoreType) -> Option<(Kind, Vec<&str>, Value)> + Send + Sync + 'static,
{
fn publish(&self, snapshot: Vec<ScoreSnapshot>) {
let publish_scope = self.output.open_scope();
if snapshot.is_empty() {
// no data was collected for this period
// TODO repeat previous frame min/max ?
// TODO update some canary metric ?
} else {
for metric in snapshot {
for score in metric.2 {
if let Some(ex) = (self.statistics)(metric.0, metric.1.as_ref(), score) {
let pub_metric = publish_scope.define_metric(ex.0, &ex.1.concat(), 1.0);
publish_scope.write(&pub_metric, ex.2);
}
}
}
}
// TODO parameterize whether to keep ad-hoc metrics after publish
// source.cleanup();
publish_scope.flush()
}
}
/// A predefined export strategy reporting all aggregated stats for all metric types.
/// Resulting stats are named by appending a short suffix to each metric's name.
pub fn all_stats(kind: Kind, name: &str, score: ScoreType) -> Option<(Kind, Vec<&str>, Value)> {
match score {
Count(hit) => Some((Counter, vec![name, ".count"], hit)),
Sum(sum) => Some((kind, vec![name, ".sum"], sum)),
Mean(mean) => Some((kind, vec![name, ".mean"], mean.round() as Value)),
Max(max) => Some((Gauge, vec![name, ".max"], max)),
Min(min) => Some((Gauge, vec![name, ".min"], min)),
Rate(rate) => Some((Gauge, vec![name, ".rate"], rate.round() as Value)),
}
}
/// A predefined export strategy reporting the average value for every non-marker metric.
/// Marker metrics export their hit count instead.
/// Since there is only one stat per metric, there is no risk of collision
/// and so exported stats copy their metric's name.
pub fn average(kind: Kind, name: &str, score: ScoreType) -> Option<(Kind, Vec<&str>, Value)> {
match kind {
Marker => match score {
Count(count) => Some((Counter, vec![name], count)),
_ => None,
},
_ => match score {
Mean(avg) => Some((Gauge, vec![name], avg.round() as Value)),
_ => None,
},
}
}
/// A predefined single-stat-per-metric export strategy:
/// - Timers and Counters each export their sums
/// - Markers each export their hit count
/// - Gauges each export their average
/// Since there is only one stat per metric, there is no risk of collision
/// and so exported stats copy their metric's name.
pub fn summary(kind: Kind, name: &str, score: ScoreType) -> Option<(Kind, Vec<&str>, Value)> {
match kind {
Marker => match score {
Count(count) => Some((Counter, vec![name], count)),
_ => None,
},
Counter | Timer => match score {
Sum(sum) => Some((kind, vec![name], sum)),
_ => None,
},
Gauge => match score {
Mean(mean) => Some((Gauge, vec![name], mean.round() as Value)),
_ => None,
},
}
}

View File

@ -1,41 +1,9 @@
use metrics::Metrics;
use output;
use delegate::{MetricsRecv, MetricsSend};
use delegate::{MetricsRecv, MetricsSend, ContextRecv};
use aggregate::{Aggregator, summary};
use core::*;
use context::MetricContext;
use scores::*;
use std::sync::{Arc, RwLock};
fn no_metrics() -> Arc<MetricsRecv + Send + Sync> {
let void_metrics: Metrics<_> = output::to_void().into();
Arc::new(void_metrics)
}
/// The registry contains a list of every metrics dispatch point in the app.
lazy_static! {
static ref NO_RECV: Arc<MetricsRecv + Sync + Send> = no_metrics();
static ref DEFAULT_RECV: RwLock<Arc<MetricsRecv + Sync + Send>> = RwLock::new(NO_RECV.clone());
static ref DELEGATE_REGISTRY: RwLock<Vec<MetricsSend>> = RwLock::new(vec![]);
}
/// Register a new app send.
pub fn add_metrics_send(send: MetricsSend) {
DELEGATE_REGISTRY.write().unwrap().push(send.clone());
}
/// Get the default app recv.
pub fn get_default_metrics_recv() -> Arc<MetricsRecv + Send + Sync> {
DEFAULT_RECV.read().unwrap().clone()
}
/// Install a new receiver for all dispatched metrics, replacing any previous receiver.
pub fn send_metrics<IS: Into<Metrics<T>>, T: Send + Sync + Clone + 'static>(
into_recv: IS,
) {
let recv = Arc::new(into_recv.into());
for d in DELEGATE_REGISTRY.read().unwrap().iter() {
d.set_receiver(recv.clone());
}
*DEFAULT_RECV.write().unwrap() = recv;
}

View File

@ -13,11 +13,11 @@ where
Self: Sized,
{
/// Perform random sampling of values according to the specified rate.
fn with_sampling_rate(&self, sampling_rate: Rate) -> Self;
fn with_sampling_rate(&self, sampling_rate: Sampling) -> Self;
}
impl<M: Send + Sync + 'static + Clone> WithSamplingRate for MetricContext<M> {
fn with_sampling_rate(&self, sampling_rate: Rate) -> Self {
fn with_sampling_rate(&self, sampling_rate: Sampling) -> Self {
let int_sampling_rate = pcg32::to_int_rate(sampling_rate);
self.mod_both(|metric_fn, scope_fn| {
@ -52,7 +52,7 @@ impl<M: Send + Sync + 'static + Clone> WithSamplingRate for MetricContext<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) -> MetricContext<M>
pub fn sample<M, IC>(sampling_rate: Sampling, chain: IC) -> MetricContext<M>
where
M: Clone + Send + Sync + 'static,
IC: Into<MetricContext<M>>,

View File

@ -1,37 +1,12 @@
//! Internal Dipstick metrics.
//! Collect statistics about various metrics modules at runtime.
//! Stats can can be obtained for publication from `selfstats::SOURCE`.
//! Internal Dipstick runtime metrics.
//! Because the possibly high volume of data, this is pre-set to use aggregation.
//! This is also kept in a separate module because it is not to be exposed outside of the crate.
pub use core::*;
pub use metrics::*;
pub use aggregate::*;
pub use publish::*;
pub use scores::*;
pub use namespace::*;
use output::to_void;
aggregate_metrics!(pub DIPSTICK_METRICS = "dipstick");
lazy_static! {
static ref DIPSTICK_AGGREGATOR: Aggregator = build_aggregator();
}
/// Application metrics are collected to the aggregator
metrics!(<Aggregate> DIPSTICK_METRICS = build_self_metrics(););
fn build_aggregator() -> Aggregator {
// TODO make publishable
aggregate(summary, to_void())
}
/// Capture a snapshot of Dipstick's internal metrics since the last snapshot.
pub fn snapshot() -> Vec<ScoreSnapshot> {
vec![]
}
fn build_self_metrics() -> Metrics<Aggregate> {
let mug: &Aggregator = &DIPSTICK_AGGREGATOR;
let am: Metrics<Aggregate> = mug.clone().into();
am.with_prefix("dipstick")
}