mirror of https://github.com/fralalonde/dipstick
Raw Bridge
This commit is contained in:
parent
537fc102ad
commit
252eba5d48
|
@ -0,0 +1,6 @@
|
||||||
|
[Dolphin]
|
||||||
|
Timestamp=2018,6,21,9,51,11
|
||||||
|
Version=4
|
||||||
|
|
||||||
|
[Settings]
|
||||||
|
HiddenFilesShown=true
|
|
@ -20,7 +20,6 @@ travis-ci = { repository = "fralalonde/dipstick", branch = "master" }
|
||||||
[dependencies]
|
[dependencies]
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
#derivative = "1.0"
|
|
||||||
atomic_refcell = "0.1"
|
atomic_refcell = "0.1"
|
||||||
skeptic = { version = "0.13", optional = true }
|
skeptic = { version = "0.13", optional = true }
|
||||||
num = { version = "0.1", default-features = false }
|
num = { version = "0.1", default-features = false }
|
||||||
|
|
|
@ -135,7 +135,7 @@ timer.interval_us(123_456);
|
||||||
Related metrics can share a namespace:
|
Related metrics can share a namespace:
|
||||||
```rust,skt-run
|
```rust,skt-run
|
||||||
let app_metrics = metric_scope(to_stdout());
|
let app_metrics = metric_scope(to_stdout());
|
||||||
let db_metrics = app_metrics.add_name("database");
|
let db_metrics = app_metrics.add_prefix("database");
|
||||||
let _db_timer = db_metrics.timer("db_timer");
|
let _db_timer = db_metrics.timer("db_timer");
|
||||||
let _db_counter = db_metrics.counter("db_counter");
|
let _db_counter = db_metrics.counter("db_counter");
|
||||||
```
|
```
|
||||||
|
|
|
@ -13,7 +13,7 @@ use dipstick::*;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
metrics!{
|
metrics!{
|
||||||
<QueueRawInput> ZUG = to_stdout().async(10).new_input() => {
|
<QueueRawInput> ZUG = output_stdout().async(10).new_input() => {
|
||||||
Counter COUNTER: "counter_a";
|
Counter COUNTER: "counter_a";
|
||||||
Marker EVENT: "event_c";
|
Marker EVENT: "event_c";
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ use dipstick::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// for this demo, print metric values to the console
|
// for this demo, print metric values to the console
|
||||||
let app_metrics = to_stdout().new_input();
|
let app_metrics = output_stdout().new_input();
|
||||||
|
|
||||||
// metrics can be predefined by type and name
|
// metrics can be predefined by type and name
|
||||||
let counter = app_metrics.counter("counter_a");
|
let counter = app_metrics.counter("counter_a");
|
||||||
|
@ -19,7 +19,7 @@ fn main() {
|
||||||
app_metrics.counter("just_once").count(4);
|
app_metrics.counter("just_once").count(4);
|
||||||
|
|
||||||
// metric names can be prepended with a common prefix
|
// metric names can be prepended with a common prefix
|
||||||
let prefixed_metrics = app_metrics.add_name("subsystem");
|
let prefixed_metrics = app_metrics.add_prefix("subsystem");
|
||||||
let event = prefixed_metrics.marker("event_c");
|
let event = prefixed_metrics.marker("event_c");
|
||||||
let gauge = prefixed_metrics.gauge("gauge_d");
|
let gauge = prefixed_metrics.gauge("gauge_d");
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ use std::time::Duration;
|
||||||
use dipstick::*;
|
use dipstick::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let metrics = Bucket::new().add_name("test");
|
let metrics = Bucket::new().add_prefix("test");
|
||||||
|
|
||||||
// Bucket::set_default_output(to_stdout());
|
// Bucket::set_default_output(to_stdout());
|
||||||
metrics.set_output(to_graphite("localhost:2003").expect("Graphite host name and port")
|
metrics.set_output(output_graphite("localhost:2003").expect("Graphite host name and port")
|
||||||
.add_name("machine1").add_name("application"));
|
.add_prefix("machine1").add_prefix("application"));
|
||||||
|
|
||||||
metrics.flush_every(Duration::from_secs(3));
|
metrics.flush_every(Duration::from_secs(3));
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,10 @@ use std::time::Duration;
|
||||||
use dipstick::*;
|
use dipstick::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let metrics = to_bucket().add_name("test");
|
let metrics = input_bucket().add_prefix("test");
|
||||||
|
|
||||||
// Bucket::set_default_output(to_stdout());
|
// Bucket::set_default_output(to_stdout());
|
||||||
metrics.set_output(to_stdout());
|
metrics.set_output(output_stdout());
|
||||||
|
|
||||||
metrics.flush_every(Duration::from_secs(3));
|
metrics.flush_every(Duration::from_secs(3));
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
//! A sample application continuously aggregating metrics,
|
||||||
|
//! printing the summary stats every three seconds
|
||||||
|
|
||||||
|
extern crate dipstick;
|
||||||
|
|
||||||
|
use dipstick::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let metrics = input_bucket();
|
||||||
|
|
||||||
|
let counter = metrics.counter("counter_a");
|
||||||
|
let timer = metrics.timer("timer_a");
|
||||||
|
let gauge = metrics.gauge("gauge_a");
|
||||||
|
let marker = metrics.marker("marker_a");
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ use std::thread::sleep;
|
||||||
use dipstick::*;
|
use dipstick::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let output = to_stdout().with_buffering(Buffering::Unlimited);
|
let output = output_stdout().with_buffering(Buffering::Unlimited);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// add counts forever, non-stop
|
// add counts forever, non-stop
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::time::Duration;
|
||||||
use dipstick::*;
|
use dipstick::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let metrics = to_stdout().cache(5).new_input().add_name("cache");
|
let metrics = output_stdout().cache(5).new_input().add_prefix("cache");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// report some ad-hoc metric values from our "application" loop
|
// report some ad-hoc metric values from our "application" loop
|
||||||
|
|
|
@ -19,7 +19,7 @@ fn main() {
|
||||||
// prepend and append to metric name
|
// prepend and append to metric name
|
||||||
(_, ScoreType::Count(count)) => {
|
(_, ScoreType::Count(count)) => {
|
||||||
if let Some(last) = name.pop() {
|
if let Some(last) = name.pop() {
|
||||||
name.push("customized_add_name".into());
|
name.push("customized_add_prefix".into());
|
||||||
name.push(format!("{}_and_a_suffix", last));
|
name.push(format!("{}_and_a_suffix", last));
|
||||||
Some((
|
Some((
|
||||||
Kind::Counter,
|
Kind::Counter,
|
||||||
|
@ -32,7 +32,7 @@ fn main() {
|
||||||
},
|
},
|
||||||
|
|
||||||
// scaling the score value and appending unit to name
|
// scaling the score value and appending unit to name
|
||||||
(kind, ScoreType::Sum(sum)) => Some((kind, name.add_name("per_thousand"), sum / 1000)),
|
(kind, ScoreType::Sum(sum)) => Some((kind, name.concat("per_thousand"), sum / 1000)),
|
||||||
|
|
||||||
// using the unmodified metric name
|
// using the unmodified metric name
|
||||||
(kind, ScoreType::Mean(avg)) => Some((kind, name, avg.round() as u64)),
|
(kind, ScoreType::Mean(avg)) => Some((kind, name, avg.round() as u64)),
|
||||||
|
@ -43,7 +43,7 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// send application metrics to aggregator
|
// send application metrics to aggregator
|
||||||
Bucket::set_default_output(to_stdout());
|
Bucket::set_default_output(output_stdout());
|
||||||
Bucket::set_default_stats(custom_statistics);
|
Bucket::set_default_stats(custom_statistics);
|
||||||
|
|
||||||
let app_metrics = Bucket::new();
|
let app_metrics = Bucket::new();
|
||||||
|
|
|
@ -8,9 +8,9 @@ use std::time::Duration;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let metrics =
|
let metrics =
|
||||||
to_graphite("localhost:2003")
|
output_graphite("localhost:2003")
|
||||||
.expect("Connecting")
|
.expect("Connected")
|
||||||
.add_name("my_app")
|
.add_prefix("my_app")
|
||||||
.new_input();
|
.new_input();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use dipstick::*;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
// undeclared root (un-prefixed) metrics
|
// undeclared root (un-prefixed) metrics
|
||||||
metrics!(<Bucket> pub AGGREGATE = to_bucket() => {
|
metrics!(<Bucket> pub AGGREGATE = input_bucket() => {
|
||||||
// create counter "some_counter"
|
// create counter "some_counter"
|
||||||
pub Counter ROOT_COUNTER: "root_counter";
|
pub Counter ROOT_COUNTER: "root_counter";
|
||||||
// create gauge "root_gauge"
|
// create gauge "root_gauge"
|
||||||
|
@ -19,14 +19,14 @@ metrics!(<Bucket> pub AGGREGATE = to_bucket() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
metrics!( <Bucket> AGGREGATE.add_name("module_prefix") => {
|
metrics!( <Bucket> AGGREGATE.add_prefix("module_prefix") => {
|
||||||
// create counter "module_prefix.module_counter"
|
// create counter "module_prefix.module_counter"
|
||||||
Counter MOD_COUNTER: "module_counter";
|
Counter MOD_COUNTER: "module_counter";
|
||||||
});
|
});
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// print aggregated metrics to the console
|
// print aggregated metrics to the console
|
||||||
Bucket::set_default_output(to_stdout());
|
Bucket::set_default_output(output_stdout());
|
||||||
|
|
||||||
// enable autoflush...
|
// enable autoflush...
|
||||||
AGGREGATE.flush_every(Duration::from_millis(4000));
|
AGGREGATE.flush_every(Duration::from_millis(4000));
|
||||||
|
|
|
@ -37,7 +37,7 @@ metrics!(LIB_METRICS => {
|
||||||
});
|
});
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
ROOT_PROXY.set_target(to_stdout().new_input());
|
ROOT_PROXY.set_target(output_stdout().new_input());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
ROOT_COUNTER.count(123);
|
ROOT_COUNTER.count(123);
|
||||||
|
|
|
@ -18,13 +18,13 @@
|
||||||
//#[ignore(deprecated)]
|
//#[ignore(deprecated)]
|
||||||
//app_metrics!(
|
//app_metrics!(
|
||||||
// MultiOutput, SAME_TYPE = to_multi()
|
// MultiOutput, SAME_TYPE = to_multi()
|
||||||
// .with_output(to_stdout().add_name("yeah"))
|
// .with_output(to_stdout().add_prefix("yeah"))
|
||||||
// .with_output(to_stdout().add_name("ouch"))
|
// .with_output(to_stdout().add_prefix("ouch"))
|
||||||
//);
|
//);
|
||||||
//
|
//
|
||||||
//#[ignore(deprecated)]
|
//#[ignore(deprecated)]
|
||||||
//app_metrics!(
|
//app_metrics!(
|
||||||
// MultiOutput, MUTANT_CHILD = SAME_TYPE.add_name("super").add_name("duper")
|
// MultiOutput, MUTANT_CHILD = SAME_TYPE.add_prefix("super").add_prefix("duper")
|
||||||
//);
|
//);
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -8,14 +8,14 @@ use std::time::Duration;
|
||||||
fn main() {
|
fn main() {
|
||||||
// will output metrics to graphite and to stdout
|
// will output metrics to graphite and to stdout
|
||||||
let different_type_metrics = MultiOutput::new()
|
let different_type_metrics = MultiOutput::new()
|
||||||
.with_output(to_graphite("localhost:2003").expect("Connecting"))
|
.with_output(output_graphite("localhost:2003").expect("Connecting"))
|
||||||
.with_output(to_stdout()).new_input();
|
.with_output(output_stdout()).new_input();
|
||||||
|
|
||||||
// will output metrics twice, once with "cool.yeah" prefix and once with "cool.ouch" prefix.
|
// will output metrics twice, once with "cool.yeah" prefix and once with "cool.ouch" prefix.
|
||||||
let same_type_metrics = MultiOutput::new()
|
let same_type_metrics = MultiOutput::new()
|
||||||
.with_output(to_stdout().add_name("yeah"))
|
.with_output(output_stdout().add_prefix("yeah"))
|
||||||
.with_output(to_stdout().add_name("ouch"))
|
.with_output(output_stdout().add_prefix("ouch"))
|
||||||
.add_name("cool").new_input();
|
.add_prefix("cool").new_input();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
different_type_metrics.counter("counter_a").count(123);
|
different_type_metrics.counter("counter_a").count(123);
|
||||||
|
|
|
@ -7,25 +7,25 @@ use std::time::Duration;
|
||||||
use dipstick::*;
|
use dipstick::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let root = to_proxy();
|
let root = input_proxy();
|
||||||
let sub = root.add_name("sub");
|
let sub = root.add_prefix("sub");
|
||||||
|
|
||||||
let count1 = root.counter("counter_a");
|
let count1 = root.counter("counter_a");
|
||||||
|
|
||||||
let count2 = sub.counter("counter_b");
|
let count2 = sub.counter("counter_b");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
root.set_target(to_stdout().new_input());
|
root.set_target(output_stdout().new_input());
|
||||||
count1.count(1);
|
count1.count(1);
|
||||||
count2.count(2);
|
count2.count(2);
|
||||||
|
|
||||||
// route every metric from the root to stdout with prefix "root"
|
// route every metric from the root to stdout with prefix "root"
|
||||||
root.set_target(to_stdout().add_name("root").new_input());
|
root.set_target(output_stdout().add_prefix("root").new_input());
|
||||||
count1.count(3);
|
count1.count(3);
|
||||||
count2.count(4);
|
count2.count(4);
|
||||||
|
|
||||||
// route metrics from "sub" to stdout with prefix "mutant"
|
// route metrics from "sub" to stdout with prefix "mutant"
|
||||||
sub.set_target(to_stdout().add_name("mutant").new_input());
|
sub.set_target(output_stdout().add_prefix("mutant").new_input());
|
||||||
count1.count(5);
|
count1.count(5);
|
||||||
count2.count(6);
|
count2.count(6);
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ fn main() {
|
||||||
count2.count(10);
|
count2.count(10);
|
||||||
|
|
||||||
// go back to initial single unprefixed route
|
// go back to initial single unprefixed route
|
||||||
root.set_target(to_stdout().new_input());
|
root.set_target(output_stdout().new_input());
|
||||||
count1.count(11);
|
count1.count(11);
|
||||||
count2.count(12);
|
count2.count(12);
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ fn main() {
|
||||||
|
|
||||||
pub fn raw_write() {
|
pub fn raw_write() {
|
||||||
// setup dual metric channels
|
// setup dual metric channels
|
||||||
let metrics_log = dipstick::to_log().new_input();
|
let metrics_log = dipstick::output_log().new_input();
|
||||||
|
|
||||||
// define and send metrics using raw channel API
|
// define and send metrics using raw channel API
|
||||||
let counter = metrics_log.new_metric(
|
let counter = metrics_log.new_metric(
|
||||||
|
|
|
@ -7,7 +7,7 @@ use dipstick::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// print only 1 out of every 10000 metrics recorded
|
// print only 1 out of every 10000 metrics recorded
|
||||||
let app_metrics = to_statsd("statsd:8125").expect("Statsd")
|
let app_metrics = output_statsd("statsd:8125").expect("Statsd")
|
||||||
.with_sampling_rate(Sampling::SampleRate(0.0001)).new_input_dyn();
|
.with_sampling_rate(Sampling::SampleRate(0.0001)).new_input_dyn();
|
||||||
|
|
||||||
let marker = app_metrics.marker("marker_a");
|
let marker = app_metrics.marker("marker_a");
|
||||||
|
|
|
@ -9,7 +9,7 @@ use dipstick::*;
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
||||||
let app_metrics = Bucket::new();
|
let app_metrics = Bucket::new();
|
||||||
app_metrics.set_output(to_stdout());
|
app_metrics.set_output(output_stdout());
|
||||||
|
|
||||||
app_metrics.flush_every(Duration::from_secs(3));
|
app_metrics.flush_every(Duration::from_secs(3));
|
||||||
|
|
||||||
|
|
129
src/bucket.rs
129
src/bucket.rs
|
@ -1,6 +1,7 @@
|
||||||
//! Maintain aggregated metrics for deferred reporting,
|
//! Maintain aggregated metrics for deferred reporting,
|
||||||
//!
|
//!
|
||||||
use core::{Kind, Value, Name, WithName, NO_METRIC_OUTPUT, Input, OutputDyn, Metric, WithAttributes, Attributes};
|
use core::{Kind, Value, Name, WithName, output_none, Input, Metric, WithAttributes, Attributes,
|
||||||
|
RawInput, RawMetric, RawOutputDyn};
|
||||||
use clock::TimeHandle;
|
use clock::TimeHandle;
|
||||||
use core::Kind::*;
|
use core::Kind::*;
|
||||||
use error;
|
use error;
|
||||||
|
@ -10,26 +11,28 @@ use scores::ScoreType::*;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::fmt;
|
||||||
|
use std::borrow::Borrow;
|
||||||
|
|
||||||
/// A function type to transform aggregated scores into publishable statistics.
|
/// A function type to transform aggregated scores into publishable statistics.
|
||||||
pub type StatsFn = Fn(Kind, Name, ScoreType) -> Option<(Kind, Name, Value)> + Send + Sync + 'static;
|
pub type StatsFn = Fn(Kind, Name, ScoreType) -> Option<(Kind, Name, Value)> + Send + Sync + 'static;
|
||||||
|
|
||||||
fn initial_stats() -> &'static StatsFn {
|
fn initial_stats() -> &'static StatsFn {
|
||||||
&summary
|
&stats_summary
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initial_output() -> Arc<OutputDyn + Send + Sync> {
|
fn initial_output() -> Arc<RawOutputDyn + Send + Sync> {
|
||||||
NO_METRIC_OUTPUT.clone()
|
Arc::new(output_none())
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref DEFAULT_AGGREGATE_STATS: RwLock<Arc<StatsFn>> = RwLock::new(Arc::new(initial_stats()));
|
static ref DEFAULT_AGGREGATE_STATS: RwLock<Arc<StatsFn>> = RwLock::new(Arc::new(initial_stats()));
|
||||||
|
|
||||||
static ref DEFAULT_AGGREGATE_OUTPUT: RwLock<Arc<OutputDyn + Send + Sync>> = RwLock::new(initial_output());
|
static ref DEFAULT_AGGREGATE_OUTPUT: RwLock<Arc<RawOutputDyn + Send + Sync>> = RwLock::new(initial_output());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new metric aggregating bucket.
|
/// Create a new metric aggregating bucket.
|
||||||
pub fn to_bucket() -> Bucket {
|
pub fn input_bucket() -> Bucket {
|
||||||
Bucket::new()
|
Bucket::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,28 +44,59 @@ pub struct Bucket {
|
||||||
inner: Arc<RwLock<InnerBucket>>,
|
inner: Arc<RwLock<InnerBucket>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Derivative)]
|
|
||||||
#[derivative(Debug)]
|
|
||||||
struct InnerBucket {
|
struct InnerBucket {
|
||||||
metrics: BTreeMap<Name, Arc<Scoreboard>>,
|
metrics: BTreeMap<Name, Arc<Scoreboard>>,
|
||||||
period_start: TimeHandle,
|
period_start: TimeHandle,
|
||||||
#[derivative(Debug = "ignore")]
|
|
||||||
stats: Option<Arc<Fn(Kind, Name, ScoreType)
|
stats: Option<Arc<Fn(Kind, Name, ScoreType)
|
||||||
-> Option<(Kind, Name, Value)> + Send + Sync + 'static>>,
|
-> Option<(Kind, Name, Value)> + Send + Sync + 'static>>,
|
||||||
#[derivative(Debug = "ignore")]
|
output: Option<Arc<RawOutputDyn + Send + Sync + 'static>>,
|
||||||
output: Option<Arc<OutputDyn + Send + Sync + 'static>>,
|
|
||||||
publish_metadata: bool,
|
publish_metadata: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for InnerBucket {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "metrics: {:?}", self.metrics)?;
|
||||||
|
write!(f, "period_start: {:?}", self.period_start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref PERIOD_LENGTH: Name = "_period_length".into();
|
static ref PERIOD_LENGTH: Name = "_period_length".into();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InnerBucket {
|
impl InnerBucket {
|
||||||
|
|
||||||
|
pub fn flush(&mut self) -> error::Result<()> {
|
||||||
|
let stats_fn = match self.stats {
|
||||||
|
Some(ref stats_fn) => stats_fn.clone(),
|
||||||
|
None => DEFAULT_AGGREGATE_STATS.read().unwrap().clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let pub_scope = match self.output {
|
||||||
|
Some(ref out) => out.new_raw_input_dyn(),
|
||||||
|
None => output_none().new_raw_input_dyn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.flush_to(pub_scope.borrow(), stats_fn.as_ref());
|
||||||
|
|
||||||
|
// all metrics published!
|
||||||
|
// purge: if bucket is the last owner of the metric, remove it
|
||||||
|
// TODO parameterize whether to keep ad-hoc metrics after publish
|
||||||
|
let mut purged = self.metrics.clone();
|
||||||
|
self.metrics.iter()
|
||||||
|
.filter(|&(_k, v)| Arc::strong_count(v) == 1)
|
||||||
|
.map(|(k, _v)| k)
|
||||||
|
.for_each(|k| {purged.remove(k);});
|
||||||
|
self.metrics = purged;
|
||||||
|
|
||||||
|
pub_scope.flush_raw()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// Take a snapshot of aggregated values and reset them.
|
/// Take a snapshot of aggregated values and reset them.
|
||||||
/// Compute stats on captured values using assigned or default stats function.
|
/// Compute stats on captured values using assigned or default stats function.
|
||||||
/// Write stats to assigned or default output.
|
/// Write stats to assigned or default output.
|
||||||
pub fn flush_to(&mut self, publish_scope: &Input, stats_fn: &StatsFn) {
|
pub fn flush_to(&mut self, publish_scope: &RawInput, stats_fn: &StatsFn) {
|
||||||
|
|
||||||
let now = TimeHandle::now();
|
let now = TimeHandle::now();
|
||||||
let duration_seconds = self.period_start.elapsed_us() as f64 / 1_000_000.0;
|
let duration_seconds = self.period_start.elapsed_us() as f64 / 1_000_000.0;
|
||||||
|
@ -89,7 +123,7 @@ impl InnerBucket {
|
||||||
for score in metric.2 {
|
for score in metric.2 {
|
||||||
let filtered = (stats_fn)(metric.1, metric.0.clone(), score);
|
let filtered = (stats_fn)(metric.1, metric.0.clone(), score);
|
||||||
if let Some((kind, name, value)) = filtered {
|
if let Some((kind, name, value)) = filtered {
|
||||||
let metric: Metric = publish_scope.new_metric(name, kind);
|
let metric: RawMetric = publish_scope.new_metric_raw(name, kind);
|
||||||
metric.write(value)
|
metric.write(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,7 +135,7 @@ impl InnerBucket {
|
||||||
|
|
||||||
impl<S: AsRef<str>> From<S> for Bucket {
|
impl<S: AsRef<str>> From<S> for Bucket {
|
||||||
fn from(name: S) -> Bucket {
|
fn from(name: S) -> Bucket {
|
||||||
Bucket::new().add_name(name.as_ref())
|
Bucket::new().add_prefix(name.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +168,7 @@ impl Bucket {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Install a new receiver for all aggregateed metrics, replacing any previous receiver.
|
/// Install a new receiver for all aggregateed metrics, replacing any previous receiver.
|
||||||
pub fn set_default_output(default_config: impl OutputDyn + Send + Sync + 'static) {
|
pub fn set_default_output(default_config: impl RawOutputDyn + Send + Sync + 'static) {
|
||||||
*DEFAULT_AGGREGATE_OUTPUT.write().unwrap() = Arc::new(default_config);
|
*DEFAULT_AGGREGATE_OUTPUT.write().unwrap() = Arc::new(default_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +191,7 @@ impl Bucket {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Install a new receiver for all aggregated metrics, replacing any previous receiver.
|
/// Install a new receiver for all aggregated metrics, replacing any previous receiver.
|
||||||
pub fn set_output(&self, new_config: impl OutputDyn + Send + Sync + 'static) {
|
pub fn set_output(&self, new_config: impl RawOutputDyn + Send + Sync + 'static) {
|
||||||
self.inner.write().expect("Aggregator").output = Some(Arc::new(new_config))
|
self.inner.write().expect("Aggregator").output = Some(Arc::new(new_config))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,27 +201,11 @@ impl Bucket {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Flush the aggregator scores using the specified scope and stats.
|
/// Flush the aggregator scores using the specified scope and stats.
|
||||||
pub fn flush_to(&self, publish_scope: &Input, stats_fn: &StatsFn) {
|
pub fn flush_to(&self, publish_scope: &RawInput, stats_fn: &StatsFn) {
|
||||||
let mut inner = self.inner.write().expect("Aggregator");
|
let mut inner = self.inner.write().expect("Aggregator");
|
||||||
inner.flush_to(publish_scope, stats_fn);
|
inner.flush_to(publish_scope, stats_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// Discard scores for ad-hoc metrics.
|
|
||||||
// pub fn cleanup(&self) {
|
|
||||||
// let orphans: Vec<Name> = self.inner.read().expect("Aggregator").metrics.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();
|
|
||||||
// if !orphans.is_empty() {
|
|
||||||
// let remover = &mut self.inner.write().unwrap().metrics;
|
|
||||||
// orphans.iter().for_each(|k| {
|
|
||||||
// remover.remove(k);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input for Bucket {
|
impl Input for Bucket {
|
||||||
|
@ -207,22 +225,7 @@ impl Input for Bucket {
|
||||||
/// Publish statistics
|
/// Publish statistics
|
||||||
fn flush(&self) -> error::Result<()> {
|
fn flush(&self) -> error::Result<()> {
|
||||||
let mut inner = self.inner.write().expect("Aggregator");
|
let mut inner = self.inner.write().expect("Aggregator");
|
||||||
|
inner.flush()
|
||||||
let stats_fn = match &inner.stats {
|
|
||||||
&Some(ref stats_fn) => stats_fn.clone(),
|
|
||||||
&None => DEFAULT_AGGREGATE_STATS.read().unwrap().clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let pub_scope = match &inner.output {
|
|
||||||
&Some(ref out) => out.new_input_dyn(),
|
|
||||||
&None => DEFAULT_AGGREGATE_OUTPUT.read().unwrap().new_input_dyn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
inner.flush_to(pub_scope.as_ref(), stats_fn.as_ref());
|
|
||||||
|
|
||||||
// TODO parameterize whether to keep ad-hoc metrics after publish
|
|
||||||
// source.cleanup();
|
|
||||||
pub_scope.flush()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,14 +237,14 @@ impl WithAttributes for Bucket {
|
||||||
/// A predefined export strategy reporting all aggregated stats for all metric types.
|
/// 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.
|
/// Resulting stats are named by appending a short suffix to each metric's name.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn all_stats(kind: Kind, name: Name, score: ScoreType) -> Option<(Kind, Name, Value)> {
|
pub fn stats_all(kind: Kind, name: Name, score: ScoreType) -> Option<(Kind, Name, Value)> {
|
||||||
match score {
|
match score {
|
||||||
Count(hit) => Some((Counter, name.add_name("count"), hit)),
|
Count(hit) => Some((Counter, name.concat("count"), hit)),
|
||||||
Sum(sum) => Some((kind, name.add_name("sum"), sum)),
|
Sum(sum) => Some((kind, name.concat("sum"), sum)),
|
||||||
Mean(mean) => Some((kind, name.add_name("mean"), mean.round() as Value)),
|
Mean(mean) => Some((kind, name.concat("mean"), mean.round() as Value)),
|
||||||
Max(max) => Some((Gauge, name.add_name("max"), max)),
|
Max(max) => Some((Gauge, name.concat("max"), max)),
|
||||||
Min(min) => Some((Gauge, name.add_name("min"), min)),
|
Min(min) => Some((Gauge, name.concat("min"), min)),
|
||||||
Rate(rate) => Some((Gauge, name.add_name("rate"), rate.round() as Value)),
|
Rate(rate) => Some((Gauge, name.concat("rate"), rate.round() as Value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +253,7 @@ pub fn all_stats(kind: Kind, name: Name, score: ScoreType) -> Option<(Kind, Name
|
||||||
/// Since there is only one stat per metric, there is no risk of collision
|
/// Since there is only one stat per metric, there is no risk of collision
|
||||||
/// and so exported stats copy their metric's name.
|
/// and so exported stats copy their metric's name.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn average(kind: Kind, name: Name, score: ScoreType) -> Option<(Kind, Name, Value)> {
|
pub fn stats_average(kind: Kind, name: Name, score: ScoreType) -> Option<(Kind, Name, Value)> {
|
||||||
match kind {
|
match kind {
|
||||||
Marker => match score {
|
Marker => match score {
|
||||||
Count(count) => Some((Counter, name, count)),
|
Count(count) => Some((Counter, name, count)),
|
||||||
|
@ -270,7 +273,7 @@ pub fn average(kind: Kind, name: Name, score: ScoreType) -> Option<(Kind, Name,
|
||||||
/// Since there is only one stat per metric, there is no risk of collision
|
/// Since there is only one stat per metric, there is no risk of collision
|
||||||
/// and so exported stats copy their metric's name.
|
/// and so exported stats copy their metric's name.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn summary(kind: Kind, name: Name, score: ScoreType) -> Option<(Kind, Name, Value)> {
|
pub fn stats_summary(kind: Kind, name: Name, score: ScoreType) -> Option<(Kind, Name, Value)> {
|
||||||
match kind {
|
match kind {
|
||||||
Marker => match score {
|
Marker => match score {
|
||||||
Count(count) => Some((Counter, name, count)),
|
Count(count) => Some((Counter, name, count)),
|
||||||
|
@ -313,7 +316,7 @@ mod bench {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use core::*;
|
use core::*;
|
||||||
use bucket::{Bucket, all_stats, summary, average, StatsFn};
|
use bucket::{Bucket, stats_all, stats_summary, stats_average, StatsFn};
|
||||||
use clock::{mock_clock_advance, mock_clock_reset};
|
use clock::{mock_clock_advance, mock_clock_reset};
|
||||||
use map::StatsMap;
|
use map::StatsMap;
|
||||||
|
|
||||||
|
@ -323,7 +326,7 @@ mod test {
|
||||||
fn make_stats(stats_fn: &StatsFn) -> BTreeMap<String, Value> {
|
fn make_stats(stats_fn: &StatsFn) -> BTreeMap<String, Value> {
|
||||||
mock_clock_reset();
|
mock_clock_reset();
|
||||||
|
|
||||||
let metrics = Bucket::new().add_name("test");
|
let metrics = Bucket::new().add_prefix("test");
|
||||||
|
|
||||||
let counter = metrics.counter("counter_a");
|
let counter = metrics.counter("counter_a");
|
||||||
let timer = metrics.timer("timer_a");
|
let timer = metrics.timer("timer_a");
|
||||||
|
@ -353,7 +356,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn external_aggregate_all_stats() {
|
fn external_aggregate_all_stats() {
|
||||||
let map = make_stats(&all_stats);
|
let map = make_stats(&stats_all);
|
||||||
|
|
||||||
assert_eq!(map["test.counter_a.count"], 2);
|
assert_eq!(map["test.counter_a.count"], 2);
|
||||||
assert_eq!(map["test.counter_a.sum"], 30);
|
assert_eq!(map["test.counter_a.sum"], 30);
|
||||||
|
@ -377,7 +380,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn external_aggregate_summary() {
|
fn external_aggregate_summary() {
|
||||||
let map = make_stats(&summary);
|
let map = make_stats(&stats_summary);
|
||||||
|
|
||||||
assert_eq!(map["test.counter_a"], 30);
|
assert_eq!(map["test.counter_a"], 30);
|
||||||
assert_eq!(map["test.timer_a"], 30_000_000);
|
assert_eq!(map["test.timer_a"], 30_000_000);
|
||||||
|
@ -387,7 +390,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn external_aggregate_average() {
|
fn external_aggregate_average() {
|
||||||
let map = make_stats(&average);
|
let map = make_stats(&stats_average);
|
||||||
|
|
||||||
assert_eq!(map["test.counter_a"], 15);
|
assert_eq!(map["test.counter_a"], 15);
|
||||||
assert_eq!(map["test.timer_a"], 15_000_000);
|
assert_eq!(map["test.timer_a"], 15_000_000);
|
||||||
|
|
13
src/cache.rs
13
src/cache.rs
|
@ -76,19 +76,6 @@ impl Input for CacheInput {
|
||||||
|
|
||||||
impl Async for CacheOutput {}
|
impl Async for CacheOutput {}
|
||||||
|
|
||||||
impl CacheInput {
|
|
||||||
/// Create and increment an ad-hoc counter.
|
|
||||||
pub fn count(&self, name: &str, value: Value) {
|
|
||||||
self.counter(name).count(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create and increment an ad-hoc marker.
|
|
||||||
pub fn mark(&self, name: &str) {
|
|
||||||
self.marker(name).mark()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
mod lru {
|
mod lru {
|
||||||
//! A fixed-size cache with LRU expiration criteria.
|
//! A fixed-size cache with LRU expiration criteria.
|
||||||
//! Stored values will be held onto as long as there is space.
|
//! Stored values will be held onto as long as there is space.
|
||||||
|
|
137
src/core.rs
137
src/core.rs
|
@ -6,12 +6,12 @@ use clock::TimeHandle;
|
||||||
use queue;
|
use queue;
|
||||||
use raw_queue;
|
use raw_queue;
|
||||||
use cache;
|
use cache;
|
||||||
|
use error;
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::ops;
|
use std::ops;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use text;
|
use std::fmt;
|
||||||
use error;
|
|
||||||
|
|
||||||
// TODO define an 'AsValue' trait + impl for supported number types, then drop 'num' crate
|
// TODO define an 'AsValue' trait + impl for supported number types, then drop 'num' crate
|
||||||
pub use num::ToPrimitive;
|
pub use num::ToPrimitive;
|
||||||
|
@ -115,7 +115,7 @@ pub trait WithName {
|
||||||
fn get_namespace(&self) -> &Name;
|
fn get_namespace(&self) -> &Name;
|
||||||
|
|
||||||
/// Join namespace and prepend in newly defined metrics.
|
/// Join namespace and prepend in newly defined metrics.
|
||||||
fn add_name(&self, name: &str) -> Self;
|
fn add_prefix(&self, name: &str) -> Self;
|
||||||
|
|
||||||
/// Append the specified name to the local namespace and return the concatenated result.
|
/// Append the specified name to the local namespace and return the concatenated result.
|
||||||
fn qualified_name(&self, metric_name: Name) -> Name;
|
fn qualified_name(&self, metric_name: Name) -> Name;
|
||||||
|
@ -127,14 +127,14 @@ impl<T: WithAttributes> WithName for T {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Join namespace and prepend in newly defined metrics.
|
/// Join namespace and prepend in newly defined metrics.
|
||||||
fn add_name(&self, name: &str) -> Self {
|
fn add_prefix(&self, name: &str) -> Self {
|
||||||
self.with_attributes(|new_attr| new_attr.namespace = new_attr.namespace.add_name(name))
|
self.with_attributes(|new_attr| new_attr.namespace = new_attr.namespace.concat(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append the specified name to the local namespace and return the concatenated result.
|
/// Append the specified name to the local namespace and return the concatenated result.
|
||||||
fn qualified_name(&self, name: Name) -> Name {
|
fn qualified_name(&self, name: Name) -> Name {
|
||||||
// FIXME (perf) store name in reverse to prepend with an actual push() to the vec
|
// FIXME (perf) store name in reverse to prepend with an actual push() to the vec
|
||||||
self.get_attributes().namespace.add_name(name)
|
self.get_attributes().namespace.concat(name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ pub struct Name {
|
||||||
impl Name {
|
impl Name {
|
||||||
|
|
||||||
/// Concatenate with another namespace into a new one.
|
/// Concatenate with another namespace into a new one.
|
||||||
pub fn add_name(&self, name: impl Into<Name>) -> Self {
|
pub fn concat(&self, name: impl Into<Name>) -> Self {
|
||||||
let mut cloned = self.clone();
|
let mut cloned = self.clone();
|
||||||
cloned.inner.extend_from_slice(&name.into().inner);
|
cloned.inner.extend_from_slice(&name.into().inner);
|
||||||
cloned
|
cloned
|
||||||
|
@ -242,7 +242,7 @@ impl<S: Into<String>> From<S> for Name {
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
/// The reference instance identifying an uninitialized metric config.
|
/// The reference instance identifying an uninitialized metric config.
|
||||||
pub static ref NO_METRIC_OUTPUT: Arc<OutputDyn + Send + Sync> = Arc::new(text::to_void());
|
pub static ref NO_METRIC_OUTPUT: Arc<OutputDyn + Send + Sync> = Arc::new(output_none());
|
||||||
|
|
||||||
/// The reference instance identifying an uninitialized metric scope.
|
/// The reference instance identifying an uninitialized metric scope.
|
||||||
pub static ref NO_METRIC_SCOPE: Arc<Input + Send + Sync> = NO_METRIC_OUTPUT.new_input_dyn();
|
pub static ref NO_METRIC_SCOPE: Arc<Input + Send + Sync> = NO_METRIC_OUTPUT.new_input_dyn();
|
||||||
|
@ -280,7 +280,7 @@ pub trait Cache: OutputDyn + Send + Sync + 'static + Sized {
|
||||||
|
|
||||||
/// Dynamic variant of the Output trait
|
/// Dynamic variant of the Output trait
|
||||||
pub trait OutputDyn {
|
pub trait OutputDyn {
|
||||||
/// Open a new metric input with dynamic typing.
|
/// Open a new metric input with dynamic trait typing.
|
||||||
fn new_input_dyn(&self) -> Arc<Input + Send + Sync + 'static>;
|
fn new_input_dyn(&self) -> Arc<Input + Send + Sync + 'static>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ impl<T: Output + Send + Sync + 'static> OutputDyn for T {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Define metrics, write values and flush them.
|
/// Define metrics, write values and flush them.
|
||||||
pub trait Input: Send + Sync {
|
pub trait Input {
|
||||||
/// Define a metric of the specified type.
|
/// Define a metric of the specified type.
|
||||||
fn new_metric(&self, name: Name, kind: Kind) -> Metric;
|
fn new_metric(&self, name: Name, kind: Kind) -> Metric;
|
||||||
|
|
||||||
|
@ -321,6 +321,16 @@ pub trait Input: Send + Sync {
|
||||||
self.new_metric(name.into(), Kind::Gauge).into()
|
self.new_metric(name.into(), Kind::Gauge).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create and increment an ad-hoc counter.
|
||||||
|
fn count(&self, name: &str, value: Value) {
|
||||||
|
self.counter(name).count(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create and increment an ad-hoc marker.
|
||||||
|
fn mark(&self, name: &str) {
|
||||||
|
self.marker(name).mark()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A metric is actually a function that knows to write a metric value to a metric output.
|
/// A metric is actually a function that knows to write a metric value to a metric output.
|
||||||
|
@ -329,6 +339,12 @@ pub struct Metric {
|
||||||
inner: Arc<Fn(Value) + Send + Sync>
|
inner: Arc<Fn(Value) + Send + Sync>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Metric {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Arc<Fn(Value) + Send + Sync>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Metric {
|
impl Metric {
|
||||||
/// Utility constructor
|
/// Utility constructor
|
||||||
pub fn new<F: Fn(Value) + Send + Sync + 'static>(wfn: F) -> Metric {
|
pub fn new<F: Fn(Value) + Send + Sync + 'static>(wfn: F) -> Metric {
|
||||||
|
@ -363,13 +379,13 @@ pub trait RawAsync: RawOutput + Sized {
|
||||||
/// Dynamic variant of the RawOutput trait
|
/// Dynamic variant of the RawOutput trait
|
||||||
pub trait RawOutputDyn {
|
pub trait RawOutputDyn {
|
||||||
/// Open a new metric input with dynamic typing.
|
/// Open a new metric input with dynamic typing.
|
||||||
fn new_raw_input_dyn(&self) -> RawInputBox;
|
fn new_raw_input_dyn(&self) -> Rc<RawInput + 'static>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blanket impl that provides RawOutputs their dynamic flavor.
|
/// Blanket impl that provides RawOutputs their dynamic flavor.
|
||||||
impl<T: RawOutput + Send + Sync + 'static> RawOutputDyn for T {
|
impl<T: RawOutput + Send + Sync + 'static> RawOutputDyn for T {
|
||||||
fn new_raw_input_dyn(&self) -> RawInputBox {
|
fn new_raw_input_dyn(&self) -> Rc<RawInput + 'static> {
|
||||||
RawInputBox (Rc::new(self.new_raw_input()))
|
Rc::new(self.new_raw_input())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,7 +393,7 @@ impl<T: RawOutput + Send + Sync + 'static> RawOutputDyn for T {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct LockingInputBox {
|
pub struct LockingInputBox {
|
||||||
attributes: Attributes,
|
attributes: Attributes,
|
||||||
inner: Arc<Mutex<RawInputBox>>
|
inner: Arc<Mutex<UnsafeInput>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WithAttributes for LockingInputBox {
|
impl WithAttributes for LockingInputBox {
|
||||||
|
@ -389,7 +405,7 @@ impl Input for LockingInputBox {
|
||||||
|
|
||||||
fn new_metric(&self, name: Name, kind: Kind) -> Metric {
|
fn new_metric(&self, name: Name, kind: Kind) -> Metric {
|
||||||
let name = self.qualified_name(name);
|
let name = self.qualified_name(name);
|
||||||
let raw_metric = self.inner.lock().expect("RawInput Lock").new_metric(name, kind);
|
let raw_metric = self.inner.lock().expect("RawInput Lock").new_metric_raw(name, kind);
|
||||||
let mutex = self.inner.clone();
|
let mutex = self.inner.clone();
|
||||||
Metric::new(move |value| {
|
Metric::new(move |value| {
|
||||||
let _guard = mutex.lock().expect("RawMetric Lock");
|
let _guard = mutex.lock().expect("RawMetric Lock");
|
||||||
|
@ -398,7 +414,7 @@ impl Input for LockingInputBox {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&self) -> error::Result<()> {
|
fn flush(&self) -> error::Result<()> {
|
||||||
self.inner.lock().expect("RawInput Lock").flush()
|
self.inner.lock().expect("RawInput Lock").flush_raw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,32 +425,46 @@ impl<T: RawOutput + Send + Sync + 'static> Output for T {
|
||||||
fn new_input(&self) -> Self::INPUT {
|
fn new_input(&self) -> Self::INPUT {
|
||||||
LockingInputBox {
|
LockingInputBox {
|
||||||
attributes: Attributes::default(),
|
attributes: Attributes::default(),
|
||||||
inner: Arc::new(Mutex::new(RawInputBox (Rc::new(self.new_raw_input()))))
|
inner: Arc::new(Mutex::new(UnsafeInput(self.new_raw_input_dyn())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Define metrics, write values and flush them.
|
/// Define metrics, write values and flush them.
|
||||||
pub trait RawInput {
|
pub trait RawInput {
|
||||||
/// Define a metric of the specified type.
|
/// Define a metric of the specified type.
|
||||||
fn new_metric(&self, name: Name, kind: Kind) -> RawMetric;
|
fn new_metric_raw(&self, name: Name, kind: Kind) -> RawMetric;
|
||||||
|
|
||||||
/// Flush does nothing by default.
|
/// Flush does nothing by default.
|
||||||
fn flush(&self) -> error::Result<()> {
|
fn flush_raw(&self) -> error::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Blanket impl that provides RawOutputs their dynamic flavor.
|
||||||
|
impl<T: Input + Send + Sync + 'static> RawInput for T {
|
||||||
|
fn new_metric_raw(&self, name: Name, kind: Kind) -> RawMetric {
|
||||||
|
let raw = self.new_metric(name, kind);
|
||||||
|
RawMetric::new(move |value| raw.write(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Wrap a RawInput to make it Send + Sync, allowing it to travel the world of threads.
|
/// Wrap a RawInput to make it Send + Sync, allowing it to travel the world of threads.
|
||||||
/// Obviously, it should only still be used from a single thread, which RawAsync does.
|
/// Obviously, it should only still be used from a single thread or dragons may occur.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RawInputBox ( Rc<RawInput + 'static> );
|
pub struct UnsafeInput(Rc<RawInput + 'static> );
|
||||||
|
|
||||||
unsafe impl Send for RawInputBox {}
|
unsafe impl Send for UnsafeInput {}
|
||||||
unsafe impl Sync for RawInputBox {}
|
unsafe impl Sync for UnsafeInput {}
|
||||||
|
|
||||||
impl ops::Deref for RawInputBox {
|
impl UnsafeInput {
|
||||||
|
/// Wrap a dynamic RawInput to make it Send + Sync.
|
||||||
|
pub fn new(input: Rc<RawInput + 'static>) -> Self {
|
||||||
|
UnsafeInput(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Deref for UnsafeInput {
|
||||||
type Target = RawInput + 'static;
|
type Target = RawInput + 'static;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
Rc::as_ref(&self.0)
|
Rc::as_ref(&self.0)
|
||||||
|
@ -447,6 +477,12 @@ pub struct RawMetric {
|
||||||
inner: Box<Fn(Value)>
|
inner: Box<Fn(Value)>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for RawMetric {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "Box<Fn(Value)>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RawMetric {
|
impl RawMetric {
|
||||||
/// Utility constructor
|
/// Utility constructor
|
||||||
pub fn new<F: Fn(Value) + 'static>(wfn: F) -> RawMetric {
|
pub fn new<F: Fn(Value) + 'static>(wfn: F) -> RawMetric {
|
||||||
|
@ -467,10 +503,8 @@ unsafe impl Sync for RawMetric {}
|
||||||
/// A monotonic counter metric.
|
/// A monotonic counter metric.
|
||||||
/// Since value is only ever increased by one, no value parameter is provided,
|
/// Since value is only ever increased by one, no value parameter is provided,
|
||||||
/// preventing programming errors.
|
/// preventing programming errors.
|
||||||
#[derive(Derivative)]
|
#[derive(Debug)]
|
||||||
#[derivative(Debug)]
|
|
||||||
pub struct Marker {
|
pub struct Marker {
|
||||||
#[derivative(Debug = "ignore")]
|
|
||||||
inner: Metric,
|
inner: Metric,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,10 +516,8 @@ impl Marker {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A counter that sends values to the metrics backend
|
/// A counter that sends values to the metrics backend
|
||||||
#[derive(Derivative)]
|
#[derive(Debug)]
|
||||||
#[derivative(Debug)]
|
|
||||||
pub struct Counter {
|
pub struct Counter {
|
||||||
#[derivative(Debug = "ignore")]
|
|
||||||
inner: Metric,
|
inner: Metric,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -497,10 +529,8 @@ impl Counter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A gauge that sends values to the metrics backend
|
/// A gauge that sends values to the metrics backend
|
||||||
#[derive(Derivative)]
|
#[derive(Debug)]
|
||||||
#[derivative(Debug)]
|
|
||||||
pub struct Gauge {
|
pub struct Gauge {
|
||||||
#[derivative(Debug = "ignore")]
|
|
||||||
inner: Metric,
|
inner: Metric,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,10 +547,8 @@ impl Gauge {
|
||||||
/// - with the time(Fn) methodhich wraps a closure with start() and stop() calls.
|
/// - with the time(Fn) methodhich wraps a closure with start() and stop() calls.
|
||||||
/// - with start() and stop() methodsrapping around the operation to time
|
/// - with start() and stop() methodsrapping around the operation to time
|
||||||
/// - with the interval_us() method, providing an externally determined microsecond interval
|
/// - with the interval_us() method, providing an externally determined microsecond interval
|
||||||
#[derive(Derivative)]
|
#[derive(Debug)]
|
||||||
#[derivative(Debug)]
|
|
||||||
pub struct Timer {
|
pub struct Timer {
|
||||||
#[derivative(Debug = "ignore")]
|
|
||||||
inner: Metric,
|
inner: Metric,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -587,6 +615,40 @@ impl From<Metric> for Marker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Discard metrics output.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct VoidOutput {}
|
||||||
|
|
||||||
|
impl RawOutput for VoidOutput {
|
||||||
|
type INPUT = VoidOutput;
|
||||||
|
fn new_raw_input(&self) -> VoidOutput {
|
||||||
|
VoidOutput {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawInput for VoidOutput {
|
||||||
|
fn new_metric_raw(&self, _name: Name, _kind: Kind) -> RawMetric {
|
||||||
|
RawMetric::new(|_value| {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Discard all metric values sent to it.
|
||||||
|
pub fn output_none() -> VoidOutput {
|
||||||
|
VoidOutput {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use core::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_void() {
|
||||||
|
let c = output_none().new_input();
|
||||||
|
let m = c.new_metric("test".into(), Kind::Marker);
|
||||||
|
m.write(33);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bench")]
|
#[cfg(feature = "bench")]
|
||||||
mod bench {
|
mod bench {
|
||||||
|
@ -608,4 +670,3 @@ mod bench {
|
||||||
b.iter(|| test::black_box(marker.mark()));
|
b.iter(|| test::black_box(marker.mark()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,7 +203,7 @@ mod legacy_test {
|
||||||
use self_metrics::*;
|
use self_metrics::*;
|
||||||
use deprecated::*;
|
use deprecated::*;
|
||||||
|
|
||||||
metrics!(<Aggregate> TEST_METRICS = DIPSTICK_METRICS.add_name("test_prefix"));
|
metrics!(<Aggregate> TEST_METRICS = DIPSTICK_METRICS.add_prefix("test_prefix"));
|
||||||
|
|
||||||
app_marker!(<Aggregate> TEST_METRICS => {
|
app_marker!(<Aggregate> TEST_METRICS => {
|
||||||
M1: "failed",
|
M1: "failed",
|
||||||
|
|
|
@ -18,7 +18,7 @@ use std::rc::Rc;
|
||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
|
|
||||||
metrics!{
|
metrics!{
|
||||||
<Bucket> DIPSTICK_METRICS.add_name("graphite") => {
|
<Bucket> DIPSTICK_METRICS.add_prefix("graphite") => {
|
||||||
Marker SEND_ERR: "send_failed";
|
Marker SEND_ERR: "send_failed";
|
||||||
Marker TRESHOLD_EXCEEDED: "bufsize_exceeded";
|
Marker TRESHOLD_EXCEEDED: "bufsize_exceeded";
|
||||||
Counter SENT_BYTES: "sent_bytes";
|
Counter SENT_BYTES: "sent_bytes";
|
||||||
|
@ -26,7 +26,7 @@ metrics!{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send metrics to a graphite server at the address and port provided.
|
/// Send metrics to a graphite server at the address and port provided.
|
||||||
pub fn to_graphite<A: ToSocketAddrs + Debug + Clone>(address: A) -> error::Result<GraphiteOutput> {
|
pub fn output_graphite<A: ToSocketAddrs + Debug + Clone>(address: A) -> error::Result<GraphiteOutput> {
|
||||||
debug!("Connecting to graphite {:?}", address);
|
debug!("Connecting to graphite {:?}", address);
|
||||||
let socket = Arc::new(RwLock::new(RetrySocket::new(address.clone())?));
|
let socket = Arc::new(RwLock::new(RetrySocket::new(address.clone())?));
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ pub struct GraphiteInput {
|
||||||
|
|
||||||
impl RawInput for GraphiteInput {
|
impl RawInput for GraphiteInput {
|
||||||
/// Define a metric of the specified type.
|
/// Define a metric of the specified type.
|
||||||
fn new_metric(&self, name: Name, kind: Kind) -> RawMetric {
|
fn new_metric_raw(&self, name: Name, kind: Kind) -> RawMetric {
|
||||||
let mut prefix = self.qualified_name(name).join(".");
|
let mut prefix = self.qualified_name(name).join(".");
|
||||||
prefix.push(' ');
|
prefix.push(' ');
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ impl RawInput for GraphiteInput {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
RawMetric::new(move |value| {
|
RawMetric::new(move |value| {
|
||||||
if let Err(err) = cloned.buf_write(&metric, value).and_then(|_| cloned.flush()) {
|
if let Err(err) = cloned.buf_write(&metric, value).and_then(|_| cloned.flush_raw()) {
|
||||||
debug!("Graphite buffer write failed: {}", err);
|
debug!("Graphite buffer write failed: {}", err);
|
||||||
SEND_ERR.mark();
|
SEND_ERR.mark();
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ impl RawInput for GraphiteInput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&self) -> error::Result<()> {
|
fn flush_raw(&self) -> error::Result<()> {
|
||||||
let buf = self.buffer.borrow_mut();
|
let buf = self.buffer.borrow_mut();
|
||||||
self.flush_inner(buf)
|
self.flush_inner(buf)
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ pub struct GraphiteMetric {
|
||||||
/// Any remaining buffered data is flushed on Drop.
|
/// Any remaining buffered data is flushed on Drop.
|
||||||
impl Drop for GraphiteInput {
|
impl Drop for GraphiteInput {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Err(err) = self.flush() {
|
if let Err(err) = self.flush_raw() {
|
||||||
warn!("Could not flush graphite metrics upon Drop: {}", err)
|
warn!("Could not flush graphite metrics upon Drop: {}", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,16 +201,16 @@ mod bench {
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
pub fn unbuffered_graphite(b: &mut test::Bencher) {
|
pub fn unbuffered_graphite(b: &mut test::Bencher) {
|
||||||
let sd = to_graphite("localhost:2003").unwrap().new_raw_input();
|
let sd = output_graphite("localhost:2003").unwrap().new_raw_input();
|
||||||
let timer = sd.new_metric("timer".into(), Kind::Timer);
|
let timer = sd.new_metric_raw("timer".into(), Kind::Timer);
|
||||||
|
|
||||||
b.iter(|| test::black_box(timer.write(2000)));
|
b.iter(|| test::black_box(timer.write(2000)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
pub fn buffered_graphite(b: &mut test::Bencher) {
|
pub fn buffered_graphite(b: &mut test::Bencher) {
|
||||||
let sd = to_graphite("localhost:2003").unwrap().with_buffering(Buffering::BufferSize(65465)).new_raw_input();
|
let sd = output_graphite("localhost:2003").unwrap().with_buffering(Buffering::BufferSize(65465)).new_raw_input();
|
||||||
let timer = sd.new_metric("timer".into(), Kind::Timer);
|
let timer = sd.new_metric_raw("timer".into(), Kind::Timer);
|
||||||
|
|
||||||
b.iter(|| test::black_box(timer.write(2000)));
|
b.iter(|| test::black_box(timer.write(2000)));
|
||||||
}
|
}
|
||||||
|
|
29
src/lib.rs
29
src/lib.rs
|
@ -10,9 +10,6 @@ extern crate test;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
//#[macro_use]
|
|
||||||
//extern crate derivative;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
extern crate atomic_refcell;
|
extern crate atomic_refcell;
|
||||||
|
@ -25,26 +22,26 @@ pub mod error;
|
||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
|
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub use core::{Value, Kind, Marker, Timer, Counter, Gauge, Input,
|
pub use core::{Value, Kind, Marker, Timer, Counter, Gauge,
|
||||||
Output, NO_METRIC_OUTPUT, OutputDyn,
|
Input, Output, OutputDyn,
|
||||||
Name, WithName, WithSamplingRate, Sampling, Buffering, WithBuffering,
|
Name, WithName, WithSamplingRate, Sampling, Buffering, WithBuffering,
|
||||||
Cache, Async, RawAsync, RawInput, RawOutput, RawMetric, RawInputBox, RawOutputDyn};
|
Cache, Async, RawAsync, RawInput, RawOutput, RawMetric, UnsafeInput, RawOutputDyn,
|
||||||
|
output_none, VoidOutput};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
|
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
pub use proxy::{InputProxy, ROOT_PROXY, to_proxy};
|
pub use proxy::{InputProxy, ROOT_PROXY, input_proxy};
|
||||||
|
|
||||||
mod bucket;
|
mod bucket;
|
||||||
pub use bucket::{Bucket, to_bucket, summary, all_stats, average};
|
pub use bucket::{Bucket, input_bucket, stats_summary, stats_all, stats_average};
|
||||||
|
|
||||||
mod text;
|
mod text;
|
||||||
pub use text::{to_stdout, TextOutput, TextInput};
|
pub use text::{output_stdout, TextOutput, TextInput};
|
||||||
pub use text::{to_void, Void};
|
|
||||||
|
|
||||||
mod logging;
|
mod logging;
|
||||||
pub use logging::{LogOutput, LogInput, to_log};
|
pub use logging::{LogOutput, LogInput, output_log};
|
||||||
|
|
||||||
mod pcg32;
|
mod pcg32;
|
||||||
|
|
||||||
|
@ -52,16 +49,16 @@ mod scores;
|
||||||
pub use scores::ScoreType;
|
pub use scores::ScoreType;
|
||||||
|
|
||||||
mod statsd;
|
mod statsd;
|
||||||
pub use statsd::{StatsdOutput, StatsdInput, to_statsd};
|
pub use statsd::{StatsdOutput, StatsdInput, output_statsd};
|
||||||
|
|
||||||
mod graphite;
|
mod graphite;
|
||||||
pub use graphite::{GraphiteOutput, GraphiteInput, to_graphite};
|
pub use graphite::{GraphiteOutput, GraphiteInput, output_graphite};
|
||||||
|
|
||||||
//mod prometheus;
|
//mod prometheus;
|
||||||
//pub use prometheus::{Prometheus, to_prometheus};
|
//pub use prometheus::{Prometheus, to_prometheus};
|
||||||
|
|
||||||
mod map;
|
mod map;
|
||||||
pub use map::StatsMap;
|
pub use map::{StatsMap, output_map};
|
||||||
|
|
||||||
mod socket;
|
mod socket;
|
||||||
pub use socket::RetrySocket;
|
pub use socket::RetrySocket;
|
||||||
|
@ -70,7 +67,7 @@ mod cache;
|
||||||
pub use cache::{CacheInput, CacheOutput};
|
pub use cache::{CacheInput, CacheOutput};
|
||||||
|
|
||||||
mod multi;
|
mod multi;
|
||||||
pub use multi::{MultiOutput, MultiInput, to_multi};
|
pub use multi::{MultiOutput, MultiInput, output_multi, input_multi};
|
||||||
|
|
||||||
mod queue;
|
mod queue;
|
||||||
pub use queue::{QueueInput, QueueOutput};
|
pub use queue::{QueueInput, QueueOutput};
|
||||||
|
|
|
@ -8,7 +8,7 @@ use log;
|
||||||
|
|
||||||
/// Write metric values to the standard log using `info!`.
|
/// Write metric values to the standard log using `info!`.
|
||||||
// TODO parameterize log level
|
// TODO parameterize log level
|
||||||
pub fn to_log() -> LogOutput {
|
pub fn output_log() -> LogOutput {
|
||||||
LogOutput {
|
LogOutput {
|
||||||
attributes: Attributes::default(),
|
attributes: Attributes::default(),
|
||||||
format_fn: Arc::new(text::format_name),
|
format_fn: Arc::new(text::format_name),
|
||||||
|
@ -115,7 +115,7 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_to_log() {
|
fn test_to_log() {
|
||||||
let c = super::to_log().new_input_dyn();
|
let c = super::output_log().new_input_dyn();
|
||||||
let m = c.new_metric("test".into(), Kind::Marker);
|
let m = c.new_metric("test".into(), Kind::Marker);
|
||||||
m.write(33);
|
m.write(33);
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,7 +135,7 @@ mod test {
|
||||||
use bucket::Bucket;
|
use bucket::Bucket;
|
||||||
use self_metrics::*;
|
use self_metrics::*;
|
||||||
|
|
||||||
metrics!(<Bucket> DIPSTICK_METRICS.add_name("test_prefix") => {
|
metrics!(<Bucket> DIPSTICK_METRICS.add_prefix("test_prefix") => {
|
||||||
Marker M1: "failed";
|
Marker M1: "failed";
|
||||||
Marker M2: "success";
|
Marker M2: "success";
|
||||||
Counter C1: "failed";
|
Counter C1: "failed";
|
||||||
|
|
|
@ -1,34 +1,41 @@
|
||||||
use core::{Value, Metric, Kind, Name, Input};
|
use core::{Value, RawMetric, Kind, Name, RawInput};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
/// Create a new StatsMap input to capture metrics to a map
|
||||||
|
pub fn output_map() -> StatsMap {
|
||||||
|
StatsMap::new()
|
||||||
|
}
|
||||||
|
|
||||||
/// A HashMap wrapper to receive metrics or stats values.
|
/// A HashMap wrapper to receive metrics or stats values.
|
||||||
/// Every received value for a metric replaces the previous one (if any).
|
/// Every received value for a metric replaces the previous one (if any).
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct StatsMap {
|
pub struct StatsMap {
|
||||||
inner: Arc<RwLock<BTreeMap<String, Value>>>,
|
inner: Rc<RefCell<BTreeMap<String, Value>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatsMap {
|
impl StatsMap {
|
||||||
/// Create a new StatsMap.
|
/// Create a new StatsMap.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
StatsMap { inner: Arc::new(RwLock::new(BTreeMap::new())) }
|
StatsMap { inner: Rc::new(RefCell::new(BTreeMap::new())) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Input for StatsMap {
|
impl RawInput for StatsMap {
|
||||||
fn new_metric(&self, name: Name, _kind: Kind) -> Metric {
|
fn new_metric_raw(&self, name: Name, _kind: Kind) -> RawMetric {
|
||||||
let write_to = self.inner.clone();
|
let write_to = self.inner.clone();
|
||||||
let name: String = name.join(".");
|
let name: String = name.join(".");
|
||||||
Metric::new(move |value| {
|
RawMetric::new(move |value| {
|
||||||
let _previous = write_to.write().expect("StatsMap").insert(name.clone(), value);
|
let _previous = write_to.borrow_mut().insert(name.clone(), value);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<StatsMap> for BTreeMap<String, Value> {
|
impl From<StatsMap> for BTreeMap<String, Value> {
|
||||||
fn from(map: StatsMap) -> Self {
|
fn from(map: StatsMap) -> Self {
|
||||||
let z = Arc::try_unwrap(map.inner).expect("StatsMap");
|
// FIXME this is is possibly a full map copy, for nothing.
|
||||||
z.into_inner().expect("StatsMap")
|
// into_inner() is what we'd really want here but would require some `unsafe`
|
||||||
|
map.inner.borrow().clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
26
src/multi.rs
26
src/multi.rs
|
@ -12,7 +12,7 @@ pub struct MultiOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new multi-output.
|
/// Create a new multi-output.
|
||||||
pub fn to_multi() -> MultiOutput {
|
pub fn output_multi() -> MultiOutput {
|
||||||
MultiOutput::new()
|
MultiOutput::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ impl MultiOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a clone of the dispatch with the new output added to the list.
|
/// Returns a clone of the dispatch with the new output added to the list.
|
||||||
pub fn with_output<O: OutputDyn + Send + Sync + 'static>(&self, out: O) -> Self {
|
pub fn with_output<OUT: OutputDyn + Send + Sync + 'static>(&self, out: OUT) -> Self {
|
||||||
let mut cloned = self.clone();
|
let mut cloned = self.clone();
|
||||||
cloned.outputs.push(Arc::new(out));
|
cloned.outputs.push(Arc::new(out));
|
||||||
cloned
|
cloned
|
||||||
|
@ -50,6 +50,11 @@ impl WithAttributes for MultiOutput {
|
||||||
fn mut_attributes(&mut self) -> &mut Attributes { &mut self.attributes }
|
fn mut_attributes(&mut self) -> &mut Attributes { &mut self.attributes }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new multi-output.
|
||||||
|
pub fn input_multi() -> MultiInput {
|
||||||
|
MultiInput::new()
|
||||||
|
}
|
||||||
|
|
||||||
/// Dispatch metric values to a list of inputs.
|
/// Dispatch metric values to a list of inputs.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MultiInput {
|
pub struct MultiInput {
|
||||||
|
@ -57,6 +62,23 @@ pub struct MultiInput {
|
||||||
inputs: Vec<Arc<Input + Send + Sync>>,
|
inputs: Vec<Arc<Input + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MultiInput {
|
||||||
|
/// Create a new multi input dispatcher with no inputs configured.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
MultiInput {
|
||||||
|
attributes: Attributes::default(),
|
||||||
|
inputs: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a clone of the dispatch with the new output added to the list.
|
||||||
|
pub fn with_input<IN: Input + Send + Sync + 'static>(&self, input: IN) -> Self {
|
||||||
|
let mut cloned = self.clone();
|
||||||
|
cloned.inputs.push(Arc::new(input));
|
||||||
|
cloned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Input for MultiInput {
|
impl Input for MultiInput {
|
||||||
fn new_metric(&self, name: Name, kind: Kind) -> Metric {
|
fn new_metric(&self, name: Name, kind: Kind) -> Metric {
|
||||||
let ref name = self.qualified_name(name);
|
let ref name = self.qualified_name(name);
|
||||||
|
|
|
@ -16,7 +16,7 @@ use std::fmt::Debug;
|
||||||
use socket::RetrySocket;
|
use socket::RetrySocket;
|
||||||
|
|
||||||
metrics!{
|
metrics!{
|
||||||
<Aggregate> DIPSTICK_METRICS.add_name("prometheus") => {
|
<Aggregate> DIPSTICK_METRICS.add_prefix("prometheus") => {
|
||||||
Marker SEND_ERR: "send_failed";
|
Marker SEND_ERR: "send_failed";
|
||||||
Marker TRESHOLD_EXCEEDED: "bufsize_exceeded";
|
Marker TRESHOLD_EXCEEDED: "bufsize_exceeded";
|
||||||
Counter SENT_BYTES: "sent_bytes";
|
Counter SENT_BYTES: "sent_bytes";
|
||||||
|
|
19
src/proxy.rs
19
src/proxy.rs
|
@ -5,6 +5,7 @@ use error;
|
||||||
|
|
||||||
use std::collections::{HashMap, BTreeMap};
|
use std::collections::{HashMap, BTreeMap};
|
||||||
use std::sync::{Arc, RwLock, Weak};
|
use std::sync::{Arc, RwLock, Weak};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use atomic_refcell::*;
|
use atomic_refcell::*;
|
||||||
|
|
||||||
|
@ -17,13 +18,12 @@ lazy_static! {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the root metric proxy.
|
/// Return the root metric proxy.
|
||||||
pub fn to_proxy() -> InputProxy {
|
pub fn input_proxy() -> InputProxy {
|
||||||
ROOT_PROXY.clone()
|
ROOT_PROXY.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A dynamically proxyed metric.
|
/// A dynamically proxyed metric.
|
||||||
#[derive(Derivative)]
|
#[derive(Debug)]
|
||||||
#[derivative(Debug)]
|
|
||||||
struct ProxiedMetric {
|
struct ProxiedMetric {
|
||||||
// basic info for this metric, needed to recreate new corresponding trait object if target changes
|
// basic info for this metric, needed to recreate new corresponding trait object if target changes
|
||||||
name: Name,
|
name: Name,
|
||||||
|
@ -32,11 +32,9 @@ struct ProxiedMetric {
|
||||||
// the metric trait object to proxy metric values to
|
// the metric trait object to proxy metric values to
|
||||||
// the second part can be up to namespace.len() + 1 if this metric was individually targeted
|
// the second part can be up to namespace.len() + 1 if this metric was individually targeted
|
||||||
// 0 if no target assigned
|
// 0 if no target assigned
|
||||||
#[derivative(Debug = "ignore")]
|
|
||||||
target: (AtomicRefCell<(Metric, usize)>),
|
target: (AtomicRefCell<(Metric, usize)>),
|
||||||
|
|
||||||
// a reference to the the parent proxyer to remove the metric from when it is dropped
|
// a reference to the the parent proxyer to remove the metric from when it is dropped
|
||||||
#[derivative(Debug = "ignore")]
|
|
||||||
proxy: Arc<RwLock<InnerProxy>>,
|
proxy: Arc<RwLock<InnerProxy>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +49,7 @@ impl Drop for ProxiedMetric {
|
||||||
/// Decouples metrics definition from backend configuration.
|
/// Decouples metrics definition from backend configuration.
|
||||||
/// Allows defining metrics before a concrete type has been selected.
|
/// Allows defining metrics before a concrete type has been selected.
|
||||||
/// Allows replacing metrics backend on the fly at runtime.
|
/// Allows replacing metrics backend on the fly at runtime.
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct InputProxy {
|
pub struct InputProxy {
|
||||||
attributes: Attributes,
|
attributes: Attributes,
|
||||||
inner: Arc<RwLock<InnerProxy>>,
|
inner: Arc<RwLock<InnerProxy>>,
|
||||||
|
@ -64,6 +62,13 @@ struct InnerProxy {
|
||||||
metrics: BTreeMap<Name, Weak<ProxiedMetric>>,
|
metrics: BTreeMap<Name, Weak<ProxiedMetric>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for InnerProxy {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "metrics: {:?}", self.metrics.keys())?;
|
||||||
|
write!(f, "targets: {:?}", self.targets.keys())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl InnerProxy {
|
impl InnerProxy {
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
|
@ -174,7 +179,7 @@ impl InputProxy {
|
||||||
|
|
||||||
impl<S: AsRef<str>> From<S> for InputProxy {
|
impl<S: AsRef<str>> From<S> for InputProxy {
|
||||||
fn from(name: S) -> InputProxy {
|
fn from(name: S) -> InputProxy {
|
||||||
InputProxy::new().add_name(name.as_ref())
|
InputProxy::new().add_prefix(name.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
metrics!{
|
metrics!{
|
||||||
<Bucket> DIPSTICK_METRICS.add_name("async_queue") => {
|
<Bucket> DIPSTICK_METRICS.add_prefix("async_queue") => {
|
||||||
/// Maybe queue was full?
|
/// Maybe queue was full?
|
||||||
Marker SEND_FAILED: "send_failed";
|
Marker SEND_FAILED: "send_failed";
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
//! RawMetrics definitions are still synchronous.
|
//! RawMetrics definitions are still synchronous.
|
||||||
//! If queue size is exceeded, calling code reverts to blocking.
|
//! If queue size is exceeded, calling code reverts to blocking.
|
||||||
use core::{Value, RawMetric, Name, Kind, Marker, WithName, RawOutputDyn,
|
use core::{Value, RawMetric, Name, Kind, Marker, WithName, RawOutputDyn,
|
||||||
WithAttributes, Attributes, Input, Output, Metric, RawInputBox};
|
WithAttributes, Attributes, Input, Output, Metric, UnsafeInput};
|
||||||
|
|
||||||
use bucket::Bucket;
|
use bucket::Bucket;
|
||||||
use error;
|
use error;
|
||||||
|
@ -13,7 +13,7 @@ use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
metrics!{
|
metrics!{
|
||||||
<Bucket> DIPSTICK_METRICS.add_name("raw_async_queue") => {
|
<Bucket> DIPSTICK_METRICS.add_prefix("raw_async_queue") => {
|
||||||
/// Maybe queue was full?
|
/// Maybe queue was full?
|
||||||
Marker SEND_FAILED: "send_failed";
|
Marker SEND_FAILED: "send_failed";
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ fn new_async_channel(length: usize) -> Arc<mpsc::SyncSender<RawQueueCmd>> {
|
||||||
while !done {
|
while !done {
|
||||||
match receiver.recv() {
|
match receiver.recv() {
|
||||||
Ok(RawQueueCmd::Write(wfn, value)) => wfn.write(value),
|
Ok(RawQueueCmd::Write(wfn, value)) => wfn.write(value),
|
||||||
Ok(RawQueueCmd::Flush(input)) => if let Err(e) = input.flush() {
|
Ok(RawQueueCmd::Flush(input)) => if let Err(e) = input.flush_raw() {
|
||||||
debug!("Could not asynchronously flush metrics: {}", e);
|
debug!("Could not asynchronously flush metrics: {}", e);
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -40,6 +40,7 @@ fn new_async_channel(length: usize) -> Arc<mpsc::SyncSender<RawQueueCmd>> {
|
||||||
Arc::new(sender)
|
Arc::new(sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Wrap new inputs with an asynchronous metric write & flush dispatcher.
|
/// Wrap new inputs with an asynchronous metric write & flush dispatcher.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct QueueRawOutput {
|
pub struct QueueRawOutput {
|
||||||
|
@ -69,7 +70,7 @@ impl Output for QueueRawOutput {
|
||||||
|
|
||||||
/// Wrap new inputs with an asynchronous metric write & flush dispatcher.
|
/// Wrap new inputs with an asynchronous metric write & flush dispatcher.
|
||||||
fn new_input(&self) -> Self::INPUT {
|
fn new_input(&self) -> Self::INPUT {
|
||||||
let target_input = self.target.new_raw_input_dyn();
|
let target_input = UnsafeInput::new(self.target.new_raw_input_dyn());
|
||||||
QueueRawInput {
|
QueueRawInput {
|
||||||
attributes: self.attributes.clone(),
|
attributes: self.attributes.clone(),
|
||||||
sender: self.sender.clone(),
|
sender: self.sender.clone(),
|
||||||
|
@ -83,7 +84,7 @@ impl Output for QueueRawOutput {
|
||||||
/// Async commands should be of no concerns to applications.
|
/// Async commands should be of no concerns to applications.
|
||||||
pub enum RawQueueCmd {
|
pub enum RawQueueCmd {
|
||||||
Write(Arc<RawMetric>, Value),
|
Write(Arc<RawMetric>, Value),
|
||||||
Flush(Arc<RawInputBox>),
|
Flush(Arc<UnsafeInput>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A metric input wrapper that sends writes & flushes over a Rust sync channel.
|
/// A metric input wrapper that sends writes & flushes over a Rust sync channel.
|
||||||
|
@ -92,7 +93,7 @@ pub enum RawQueueCmd {
|
||||||
pub struct QueueRawInput {
|
pub struct QueueRawInput {
|
||||||
attributes: Attributes,
|
attributes: Attributes,
|
||||||
sender: Arc<mpsc::SyncSender<RawQueueCmd>>,
|
sender: Arc<mpsc::SyncSender<RawQueueCmd>>,
|
||||||
target: Arc<RawInputBox>,
|
target: Arc<UnsafeInput>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WithAttributes for QueueRawInput {
|
impl WithAttributes for QueueRawInput {
|
||||||
|
@ -103,7 +104,7 @@ impl WithAttributes for QueueRawInput {
|
||||||
impl Input for QueueRawInput {
|
impl Input for QueueRawInput {
|
||||||
fn new_metric(&self, name: Name, kind:Kind) -> Metric {
|
fn new_metric(&self, name: Name, kind:Kind) -> Metric {
|
||||||
let name = self.qualified_name(name);
|
let name = self.qualified_name(name);
|
||||||
let target_metric = Arc::new(self.target.new_metric(name, kind));
|
let target_metric = Arc::new(self.target.new_metric_raw(name, kind));
|
||||||
let sender = self.sender.clone();
|
let sender = self.sender.clone();
|
||||||
Metric::new(move |value| {
|
Metric::new(move |value| {
|
||||||
if let Err(e) = sender.send(RawQueueCmd::Write(target_metric.clone(), value)) {
|
if let Err(e) = sender.send(RawQueueCmd::Write(target_metric.clone(), value)) {
|
||||||
|
|
|
@ -14,14 +14,14 @@ use std::sync::{Arc, RwLock};
|
||||||
pub use std::net::ToSocketAddrs;
|
pub use std::net::ToSocketAddrs;
|
||||||
|
|
||||||
metrics! {
|
metrics! {
|
||||||
<Bucket> DIPSTICK_METRICS.add_name("statsd") => {
|
<Bucket> DIPSTICK_METRICS.add_prefix("statsd") => {
|
||||||
Marker SEND_ERR: "send_failed";
|
Marker SEND_ERR: "send_failed";
|
||||||
Counter SENT_BYTES: "sent_bytes";
|
Counter SENT_BYTES: "sent_bytes";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send metrics to a statsd server at the address and port provided.
|
/// Send metrics to a statsd server at the address and port provided.
|
||||||
pub fn to_statsd<ADDR: ToSocketAddrs>(address: ADDR) -> error::Result<StatsdOutput> {
|
pub fn output_statsd<ADDR: ToSocketAddrs>(address: ADDR) -> error::Result<StatsdOutput> {
|
||||||
let socket = Arc::new(UdpSocket::bind("0.0.0.0:0")?);
|
let socket = Arc::new(UdpSocket::bind("0.0.0.0:0")?);
|
||||||
socket.set_nonblocking(true)?;
|
socket.set_nonblocking(true)?;
|
||||||
socket.connect(address)?;
|
socket.connect(address)?;
|
||||||
|
@ -204,7 +204,7 @@ mod bench {
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
pub fn timer_statsd(b: &mut test::Bencher) {
|
pub fn timer_statsd(b: &mut test::Bencher) {
|
||||||
let sd = to_statsd("localhost:8125").unwrap().new_input_dyn();
|
let sd = output_statsd("localhost:8125").unwrap().new_input_dyn();
|
||||||
let timer = sd.new_metric("timer".into(), Kind::Timer);
|
let timer = sd.new_metric("timer".into(), Kind::Timer);
|
||||||
|
|
||||||
b.iter(|| test::black_box(timer.write(2000)));
|
b.iter(|| test::black_box(timer.write(2000)));
|
||||||
|
|
40
src/text.rs
40
src/text.rs
|
@ -9,7 +9,7 @@ use std::rc::Rc;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
/// Write metric values to stdout using `println!`.
|
/// Write metric values to stdout using `println!`.
|
||||||
pub fn to_stdout() -> TextOutput<io::Stdout> {
|
pub fn output_stdout() -> TextOutput<io::Stdout> {
|
||||||
TextOutput {
|
TextOutput {
|
||||||
attributes: Attributes::default(),
|
attributes: Attributes::default(),
|
||||||
inner: Arc::new(RwLock::new(io::stdout())),
|
inner: Arc::new(RwLock::new(io::stdout())),
|
||||||
|
@ -100,7 +100,7 @@ impl<W: Write + Send + Sync + 'static> WithAttributes for TextInput<W> {
|
||||||
impl<W: Write + Send + Sync + 'static> WithBuffering for TextInput<W> {}
|
impl<W: Write + Send + Sync + 'static> WithBuffering for TextInput<W> {}
|
||||||
|
|
||||||
impl<W: Write + Send + Sync + 'static> RawInput for TextInput<W> {
|
impl<W: Write + Send + Sync + 'static> RawInput for TextInput<W> {
|
||||||
fn new_metric(&self, name: Name, kind: Kind) -> RawMetric {
|
fn new_metric_raw(&self, name: Name, kind: Kind) -> RawMetric {
|
||||||
let name = self.qualified_name(name);
|
let name = self.qualified_name(name);
|
||||||
let template = (self.output.format_fn)(&name, kind);
|
let template = (self.output.format_fn)(&name, kind);
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ impl<W: Write + Send + Sync + 'static> RawInput for TextInput<W> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&self) -> error::Result<()> {
|
fn flush_raw(&self) -> error::Result<()> {
|
||||||
let mut entries = self.entries.borrow_mut();
|
let mut entries = self.entries.borrow_mut();
|
||||||
if !entries.is_empty() {
|
if !entries.is_empty() {
|
||||||
let mut output = self.output.inner.write().expect("TextOutput");
|
let mut output = self.output.inner.write().expect("TextOutput");
|
||||||
|
@ -150,49 +150,19 @@ impl<W: Write + Send + Sync + 'static> RawInput for TextInput<W> {
|
||||||
|
|
||||||
impl<W: Write + Send + Sync + 'static> Drop for TextInput<W> {
|
impl<W: Write + Send + Sync + 'static> Drop for TextInput<W> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if let Err(e) = self.flush() {
|
if let Err(e) = self.flush_raw() {
|
||||||
warn!("Could not flush text metrics on Drop. {}", e)
|
warn!("Could not flush text metrics on Drop. {}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Discard metrics output.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Void {}
|
|
||||||
|
|
||||||
impl RawOutput for Void {
|
|
||||||
type INPUT = Void;
|
|
||||||
|
|
||||||
fn new_raw_input(&self) -> Void {
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawInput for Void {
|
|
||||||
fn new_metric(&self, _name: Name, _kind: Kind) -> RawMetric {
|
|
||||||
RawMetric::new(|_value| {})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Discard all metric values sent to it.
|
|
||||||
pub fn to_void() -> Void {
|
|
||||||
Void {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use core::*;
|
use core::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sink_print() {
|
fn sink_print() {
|
||||||
let c = super::to_stdout().new_input_dyn();
|
let c = super::output_stdout().new_input_dyn();
|
||||||
let m = c.new_metric("test".into(), Kind::Marker);
|
|
||||||
m.write(33);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_to_void() {
|
|
||||||
let c = super::to_void().new_input_dyn();
|
|
||||||
let m = c.new_metric("test".into(), Kind::Marker);
|
let m = c.new_metric("test".into(), Kind::Marker);
|
||||||
m.write(33);
|
m.write(33);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue