Split Name and Namespace to separate module

This commit is contained in:
Francis Lalonde 2018-09-27 13:29:42 -04:00
parent 306c404191
commit 969f0ed66c
42 changed files with 361 additions and 320 deletions

View File

@ -1,11 +1,8 @@
![a dipstick picture](https://raw.githubusercontent.com/fralalonde/dipstick/master/assets/dipstick_single_ok_horiz_transparent.png)
[![crates.io](https://img.shields.io/crates/v/dipstick.svg)](https://crates.io/crates/dipstick)
[![docs.rs](https://docs.rs/dipstick/badge.svg)](https://docs.rs/dipstick)
[![Build Status](https://travis-ci.org/fralalonde/dipstick.svg?branch=master)](https://travis-ci.org/fralalonde/dipstick)
# dipstick
# dipstick ![a dipstick picture](https://raw.githubusercontent.com/fralalonde/dipstick/master/assets/dipstick_single_ok_horiz_transparent_small.png)
A one-stop shop metrics library for Rust applications with lots of features,
minimal impact on applications and a choice of output to downstream systems.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -20,7 +20,7 @@ fn main() {
app_metrics.counter("just_once").count(4);
// metric names can be prepended with a common prefix
let prefixed_metrics = app_metrics.add_prefix("subsystem");
let prefixed_metrics = app_metrics.namespace("subsystem");
let event = prefixed_metrics.marker("event_c");
let gauge = prefixed_metrics.gauge("gauge_d");

View File

@ -7,11 +7,11 @@ use std::time::Duration;
use dipstick::*;
fn main() {
let bucket = Bucket::new().add_prefix("test");
let bucket = Bucket::new().namespace("test");
// Bucket::set_default_output(to_stdout());
bucket.set_target(Graphite::send_to("localhost:2003").expect("Socket")
.add_prefix("machine1").add_prefix("application"));
.namespace("machine1").namespace("application"));
bucket.flush_every(Duration::from_secs(3));

View File

@ -8,7 +8,7 @@ use std::io;
use dipstick::*;
fn main() {
let metrics = Bucket::new().add_prefix("test");
let metrics = Bucket::new().namespace("test");
// Bucket::set_default_output(to_stdout());
metrics.set_target(Text::write_to(io::stdout()));

View File

@ -8,7 +8,7 @@ use std::io;
use dipstick::*;
fn main() {
let metrics = Text::write_to(io::stdout()).cached(5).input().add_prefix("cache");
let metrics = Text::write_to(io::stdout()).cached(5).input().namespace("cache");
loop {
// report some ad-hoc metric values from our "application" loop

View File

@ -33,7 +33,7 @@ fn main() {
.add_target(one_minute)
.add_target(five_minutes)
.add_target(fifteen_minutes)
.add_prefix("machine_name");
.namespace("machine_name");
// send application metrics to aggregator
Proxy::default_root().set_target(all_buckets);

View File

@ -19,12 +19,12 @@ fn main() {
// prepend and append to metric name
(_, ScoreType::Count(count)) => {
if let Some(last) = name.pop() {
name.push("customized_add_prefix".into());
name.push(format!("{}_and_a_suffix", last));
if let Some(last) = name.pop_back() {
// let name = ;
Some((
Kind::Counter,
name,
name.append("customized_add_prefix")
.append(format!("{}_and_a_suffix", last)),
count,
))
} else {
@ -33,7 +33,7 @@ fn main() {
},
// scaling the score value and appending unit to name
(kind, ScoreType::Sum(sum)) => Some((kind, name.concat("per_thousand"), sum / 1000)),
(kind, ScoreType::Sum(sum)) => Some((kind, name.append("per_thousand"), sum / 1000)),
// using the unmodified metric name
(kind, ScoreType::Mean(avg)) => Some((kind, name, avg.round() as u64)),

View File

@ -10,7 +10,7 @@ fn main() {
let metrics =
Graphite::send_to("localhost:2003")
.expect("Connected")
.add_prefix("my_app")
.namespace("my_app")
.input();
loop {

View File

@ -1,39 +0,0 @@
//! A sample application sending ad-hoc counter values both to statsd _and_ to stdout.
//#[macro_use]
//extern crate dipstick;
//#[macro_use]
//extern crate lazy_static;
//use dipstick::*;
//use std::time::Duration;
//
//#[ignore(deprecated)]
//app_metrics!(
// MultiOutput, DIFFERENT_TYPES = to_multi()
// .with_output(to_statsd("localhost:8125").expect("Statsd"))
// .with_output(to_stdout())
//);
//
//#[ignore(deprecated)]
//app_metrics!(
// MultiOutput, SAME_TYPE = to_multi()
// .with_output(to_stdout().add_prefix("yeah"))
// .with_output(to_stdout().add_prefix("ouch"))
//);
//
//#[ignore(deprecated)]
//app_metrics!(
// MultiOutput, MUTANT_CHILD = SAME_TYPE.add_prefix("super").add_prefix("duper")
//);
fn main() {
// let mmm: &Output = &to_stdout();
//
// loop {
// DIFFERENT_TYPES.counter("counter_a").count(123);
// SAME_TYPE.timer("timer_a").interval_us(2000000);
// MUTANT_CHILD.gauge("gauge_z").value(34534);
// std::thread::sleep(Duration::from_millis(40));
// }
}

View File

@ -3,7 +3,7 @@
#[macro_use]
extern crate dipstick;
#[macro_use]
extern crate lazy_static;
pub extern crate lazy_static;
use dipstick::*;
@ -39,7 +39,7 @@ metrics!(LIB_METRICS => {
});
fn main() {
Proxy::set_default_target(Text::write_to(io::stdout()).input());
dipstick::Proxy::set_default_target(dipstick::Text::write_to(io::stdout()).input());
loop {
ROOT_COUNTER.count(123);

View File

@ -2,22 +2,22 @@
extern crate dipstick;
use dipstick::{MultiInput, Graphite, Text, AddPrefix, Input, InputScope};
use dipstick::{MultiInput, Graphite, Text, Input, InputScope, Naming};
use std::time::Duration;
use std::io;
fn main() {
// will output metrics to graphite and to stdout
let different_type_metrics = MultiInput::inputs()
let different_type_metrics = MultiInput::input()
.add_target(Graphite::send_to("localhost:2003").expect("Connecting"))
.add_target(Text::write_to(io::stdout()))
.input();
// will output metrics twice, once with "cool.yeah" prefix and once with "cool.ouch" prefix.
let same_type_metrics = MultiInput::inputs()
.add_target(Text::write_to(io::stdout()).add_prefix("yeah"))
.add_target(Text::write_to(io::stdout()).add_prefix("ouch"))
.add_prefix("cool")
let same_type_metrics = MultiInput::input()
.add_target(Text::write_to(io::stdout()).namespace("yeah"))
.add_target(Text::write_to(io::stdout()).namespace("ouch"))
.namespace("cool")
.input();
loop {

View File

@ -8,16 +8,16 @@ use std::io;
fn main() {
// will output metrics to graphite and to stdout
let different_type_metrics = MultiOutput::metrics()
let different_type_metrics = MultiOutput::output()
.add_target(Graphite::send_to("localhost:2003").expect("Connecting"))
.add_target(Text::write_to(io::stdout()))
.input();
// will output metrics twice, once with "cool.yeah" prefix and once with "cool.ouch" prefix.
let same_type_metrics = MultiOutput::metrics()
.add_target(Text::write_to(io::stdout()).add_prefix("yeah"))
.add_target(Text::write_to(io::stdout()).add_prefix("ouch"))
.add_prefix("cool").input();
let same_type_metrics = MultiOutput::output()
.add_target(Text::write_to(io::stderr()).namespace("out_1"))
.add_target(Text::write_to(io::stderr()).namespace("out_2"))
.namespace("out_both").input();
loop {
different_type_metrics.new_metric("counter_a".into(), Kind::Counter).write(123);

View File

@ -5,11 +5,12 @@ extern crate dipstick;
use std::thread::sleep;
use std::time::Duration;
use std::io;
use dipstick::{Proxy, Text, InputScope, AddPrefix, Input};
use dipstick::{Proxy, Text, InputScope, Input, Naming};
fn main() {
let root = Proxy::default_root();
let sub = root.add_prefix("sub");
let sub = root.namespace("sub");
let count1 = root.counter("counter_a");
@ -21,12 +22,12 @@ fn main() {
count2.count(2);
// route every metric from the root to stdout with prefix "root"
root.set_target(Text::write_to(io::stdout()).add_prefix("root").input());
root.set_target(Text::write_to(io::stdout()).namespace("root").input());
count1.count(3);
count2.count(4);
// route metrics from "sub" to stdout with prefix "mutant"
sub.set_target(Text::write_to(io::stdout()).add_prefix("mutant").input());
sub.set_target(Text::write_to(io::stdout()).namespace("mutant").input());
count1.count(5);
count2.count(6);

View File

@ -11,7 +11,7 @@ fn main() {
Statsd::send_to("localhost:8125")
.expect("Connected")
// .with_sampling(Sampling::Random(0.2))
.add_prefix("my_app")
.namespace("my_app")
.input();
let counter = metrics.counter("counter_a");

View File

@ -11,7 +11,7 @@ fn main() {
Statsd::send_to("localhost:8125")
.expect("Connected")
.sampled(Sampling::Random(0.2))
.add_prefix("my_app")
.namespace("my_app")
.input();
let counter = metrics.counter("counter_a");

View File

@ -1,6 +1,7 @@
//! Maintain aggregated metrics for deferred reporting,
use core::component::{Attributes, Name, WithAttributes, AddPrefix};
use core::component::{Attributes, WithAttributes, Naming};
use core::name::{Name};
use core::input::{Kind, InputScope, InputMetric};
use core::output::{OutputDyn, OutputScope, OutputMetric, Output, output_none};
use core::clock::TimeHandle;
@ -130,7 +131,7 @@ impl InnerBucket {
impl<S: AsRef<str>> From<S> for Bucket {
fn from(name: S) -> Bucket {
Bucket::new().add_prefix(name.as_ref())
Bucket::new().namespace(name.as_ref())
}
}
@ -210,7 +211,7 @@ impl InputScope for Bucket {
.write()
.expect("Aggregator")
.metrics
.entry(self.qualified_name(name))
.entry(self.qualify(name))
.or_insert_with(|| Arc::new(Scoreboard::new(kind)))
.clone();
InputMetric::new(move |value| scoreb.update(value))
@ -236,12 +237,12 @@ impl WithAttributes for Bucket {
#[allow(dead_code)]
pub fn stats_all(kind: Kind, name: Name, score: ScoreType) -> Option<(Kind, Name, Value)> {
match score {
ScoreType::Count(hit) => Some((Kind::Counter, name.concat("count"), hit)),
ScoreType::Sum(sum) => Some((kind, name.concat("sum"), sum)),
ScoreType::Mean(mean) => Some((kind, name.concat("mean"), mean.round() as Value)),
ScoreType::Max(max) => Some((Kind::Gauge, name.concat("max"), max)),
ScoreType::Min(min) => Some((Kind::Gauge, name.concat("min"), min)),
ScoreType::Rate(rate) => Some((Kind::Gauge, name.concat("rate"), rate.round() as Value)),
ScoreType::Count(hit) => Some((Kind::Counter, name.qualify("count"), hit)),
ScoreType::Sum(sum) => Some((kind, name.qualify("sum"), sum)),
ScoreType::Mean(mean) => Some((kind, name.qualify("mean"), mean.round() as Value)),
ScoreType::Max(max) => Some((Kind::Gauge, name.qualify("max"), max)),
ScoreType::Min(min) => Some((Kind::Gauge, name.qualify("min"), min)),
ScoreType::Rate(rate) => Some((Kind::Gauge, name.qualify("rate"), rate.round() as Value)),
}
}
@ -291,7 +292,6 @@ pub fn stats_summary(kind: Kind, name: Name, score: ScoreType) -> Option<(Kind,
mod bench {
use test;
use core::clock::*;
use super::*;
#[bench]
@ -322,7 +322,7 @@ mod test {
fn make_stats(stats_fn: &StatsFn) -> BTreeMap<String, Value> {
mock_clock_reset();
let metrics = Bucket::new().add_prefix("test");
let metrics = Bucket::new().namespace("test");
let counter = metrics.counter("counter_a");
let timer = metrics.timer("timer_a");

View File

@ -2,7 +2,8 @@
use core::Flush;
use core::input::{Kind, Input, InputScope, InputMetric, InputDyn};
use core::component::{Attributes, WithAttributes, Name, AddPrefix};
use core::component::{Attributes, WithAttributes, Naming};
use core::name::Name;
use cache::lru_cache as lru;
use core::error;
@ -70,7 +71,7 @@ impl WithAttributes for InputScopeCache {
impl InputScope for InputScopeCache {
fn new_metric(&self, name: Name, kind: Kind) -> InputMetric {
let name = self.qualified_name(name);
let name = self.qualify(name);
let lookup = {
let mut cache = self.cache.write().expect("Cache Lock");
cache.get(&name).map(|found| found.clone())

View File

@ -1,7 +1,8 @@
//! Cache metric definitions.
use core::Flush;
use core::component::{Attributes, WithAttributes, Name, AddPrefix};
use core::component::{Attributes, WithAttributes, Naming};
use core::name::Name;
use core::output::{Output, OutputMetric, OutputScope, OutputDyn};
use core::input::Kind;
use cache::lru_cache as lru;
@ -72,7 +73,7 @@ impl WithAttributes for OutputScopeCache {
impl OutputScope for OutputScopeCache {
fn new_metric(&self, name: Name, kind: Kind) -> OutputMetric {
let name = self.qualified_name(name);
let name = self.qualify(name);
let lookup = {
let mut cache = self.cache.write().expect("Cache Lock");
cache.get(&name).map(|found| found.clone())

View File

@ -1,5 +1,10 @@
#[cfg(test)]
use std::ops::Add;
use std::time::{Duration, Instant};
#[cfg(test)]
use std::time::Duration;
use std::time::Instant;
use core::Value;
@ -37,6 +42,7 @@ thread_local! {
/// Enables writing reproducible metrics tests in combination with #mock_clock_advance()
/// Should be called at beginning of test, before the metric scope is created.
/// Not feature-gated so it stays visible to outside crates but may not be used outside of tests.
#[cfg(test)]
pub fn mock_clock_reset() {
if !cfg!(not(test)) {
warn!("Mock clock used outside of cfg[]tests has no effect")
@ -51,6 +57,7 @@ pub fn mock_clock_reset() {
/// Enables writing reproducible metrics tests in combination with #mock_clock_reset()
/// Should be after metrics have been produced but before they are published.
/// Not feature-gated so it stays visible to outside crates but may not be used outside of tests.
#[cfg(test)]
pub fn mock_clock_advance(period: Duration) {
MOCK_CLOCK.with(|now| {
let mut now = now.borrow_mut();

View File

@ -1,13 +1,11 @@
use std::sync::Arc;
use std::ops;
use std::collections::HashMap;
use std::collections::{HashMap};
use core::name::{Namespace, Name};
/// The actual distribution (random, fixed-cycled, etc) depends on selected sampling method.
#[derive(Debug, Clone, Copy)]
pub enum Sampling {
/// Do not sample, use all data.
Full,
/// Floating point sampling rate
/// - 1.0+ records everything
/// - 0.5 records one of two values
@ -15,20 +13,11 @@ pub enum Sampling {
Random(f64)
}
impl Default for Sampling {
fn default() -> Self {
Sampling::Full
}
}
/// A metrics buffering strategy.
/// All strategies other than `Unbuffered` are applied as a best-effort, meaning that the buffer
/// may be flushed at any moment before reaching the limit, for any or no reason in particular.
#[derive(Debug, Clone, Copy)]
pub enum Buffering {
/// No buffering is performed (default).
Unbuffered,
/// A buffer of maximum specified size is used.
BufferSize(usize),
@ -36,24 +25,15 @@ pub enum Buffering {
Unlimited,
}
impl Default for Buffering {
fn default() -> Self {
Buffering::Unbuffered
}
}
/// One struct to rule them all.
/// Possible attributes of metric outputs and scopes.
/// Private trait used by impls of specific With* traits.
/// Not all attributes are used by all structs!
/// This is a design choice to centralize code at the expense of slight waste of memory.
/// Fields have also not been made `pub` to make it easy to change this mechanism.
/// Field access must go through `is_` and `get_` methods declared in sub-traits.
#[derive(Debug, Clone, Default)]
pub struct Attributes {
namespace: Name,
sampling: Sampling,
buffering: Buffering,
namespace: Namespace,
sampling: Option<Sampling>,
buffering: Option<Buffering>,
}
/// The only trait that requires concrete impl by metric components.
@ -79,15 +59,15 @@ pub trait WithAttributes: Clone {
}
/// Name operations support.
pub trait AddPrefix {
/// Return the namespace of the component.
fn get_namespace(&self) -> &Name;
pub trait Naming {
/// Returns namespace of component.
fn get_namespace(&self) -> &Namespace;
/// Join namespace and prepend in newly defined metrics.
fn add_prefix(&self, name: &str) -> Self;
fn namespace<S: Into<String>>(&self, name: S) -> Self;
/// Append the specified name to the local namespace and return the concatenated result.
fn qualified_name(&self, metric_name: Name) -> Name;
fn qualify<S: Into<Name>>(&self, name: S) -> Name;
}
/// Name operations support.
@ -100,20 +80,22 @@ pub trait Label {
}
impl<T: WithAttributes> AddPrefix for T {
fn get_namespace(&self) -> &Name {
impl<T: WithAttributes> Naming for T {
/// Returns namespace of component.
fn get_namespace(&self) -> &Namespace {
&self.get_attributes().namespace
}
/// Join namespace and prepend in newly defined metrics.
fn add_prefix(&self, name: &str) -> Self {
self.with_attributes(|new_attr| new_attr.namespace = new_attr.namespace.concat(name))
fn namespace<S: Into<String>>(&self, name: S) -> Self {
let name = name.into();
self.with_attributes(|new_attr| new_attr.namespace.push_back(name.clone()))
}
/// Append the specified name to the local namespace and return the concatenated result.
fn qualified_name(&self, name: Name) -> Name {
// FIXME (perf) store name in reverse to prepend with an actual push() to the vec
self.get_attributes().namespace.concat(name)
fn qualify<S: Into<Name>>(&self, name: S) -> Name {
name.into().append(self.get_attributes().namespace.clone())
}
}
@ -121,11 +103,11 @@ impl<T: WithAttributes> AddPrefix for T {
pub trait Sampled: WithAttributes {
/// Perform random sampling of values according to the specified rate.
fn sampled(&self, sampling: Sampling) -> Self {
self.with_attributes(|new_attr| new_attr.sampling = sampling)
self.with_attributes(|new_attr| new_attr.sampling = Some(sampling))
}
/// Get the sampling strategy for this component, if any.
fn get_sampling(&self) -> Sampling {
fn get_sampling(&self) -> Option<Sampling> {
self.get_attributes().sampling
}
}
@ -136,85 +118,11 @@ pub trait Sampled: WithAttributes {
pub trait Buffered: WithAttributes {
/// Return a clone with the specified buffering set.
fn buffered(&self, buffering: Buffering) -> Self {
self.with_attributes(|new_attr| new_attr.buffering = buffering)
}
/// Is this component using buffering?
fn is_buffered(&self) -> bool {
match self.get_buffering() {
Buffering::Unbuffered => false,
_ => true
}
self.with_attributes(|new_attr| new_attr.buffering = Some(buffering))
}
/// Return the buffering.
fn get_buffering(&self) -> Buffering {
fn get_buffering(&self) -> Option<Buffering> {
self.get_attributes().buffering
}
}
/// A namespace for metrics.
/// Does _not_ include the metric's "short" name itself.
/// Can be empty.
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Default)]
pub struct Name {
inner: Vec<String>,
}
impl Name {
/// Concatenate with another namespace into a new one.
pub fn concat(&self, name: impl Into<Name>) -> Self {
let mut cloned = self.clone();
cloned.inner.extend_from_slice(&name.into().inner);
cloned
}
/// Returns true if the specified namespace is a subset or is equal to this namespace.
pub fn starts_with(&self, name: &Name) -> bool {
(self.inner.len() >= name.inner.len()) && (name.inner[..] == self.inner[..name.inner.len()])
}
/// Combine name parts into a string.
pub fn join(&self, separator: &str) -> String {
let mut buf = String::with_capacity(64);
let mut i = self.inner.iter();
if let Some(n) = i.next() {
buf.push_str(n.as_ref());
} else {
return "".into()
}
for n in i {
buf.push_str(separator);
buf.push_str(n.as_ref());
}
buf
}
}
impl ops::Deref for Name {
type Target = Vec<String>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl ops::DerefMut for Name {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<S: Into<String>> From<S> for Name {
fn from(name: S) -> Name {
let name: String = name.into();
if name.is_empty() {
Name::default()
} else {
Name { inner: vec![name] }
}
}
}

View File

@ -1,6 +1,6 @@
use core::clock::TimeHandle;
use core::{Value, Flush};
use core::component::Name;
use core::name::Name;
use std::sync::Arc;
use std::fmt;

View File

@ -2,8 +2,8 @@
//! 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.
use core::component::AddPrefix;
use core::input::{Marker, InputScope, Counter};
use core::component::Naming;
use core::proxy::Proxy;
metrics!{

View File

@ -1,4 +1,5 @@
pub mod error;
pub mod name;
pub mod component;
pub mod input;
pub mod output;
@ -38,11 +39,10 @@ pub mod test {
#[cfg(feature = "bench")]
pub mod bench {
use core::{TimeHandle, Marker, Input};
use aggregate::bucket::Bucket;
use super::clock::TimeHandle;
use super::input::*;
use super::clock::*;
use super::super::aggregate::bucket::*;
use test;
use aggregate::Bucket;
#[bench]
fn get_instant(b: &mut test::Bencher) {

155
src/core/name.rs Normal file
View File

@ -0,0 +1,155 @@
use std::ops::{Deref,DerefMut};
use std::collections::{VecDeque};
/// Primitive struct for Namespace and Name
/// A double-ended vec of strings constituting a metric name or a future part thereof.
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Default)]
pub struct Namespace {
/// Nodes are stored in order or their final appearance in the names
/// If there is no namespace, the deque is empty.
nodes: VecDeque<String>,
}
impl Namespace {
/// Build a new StringDeque
/// This is a private shortcut constructor,
/// no one outside this module should need to do that, only use Name or Namespace.
fn new() -> Self {
Namespace { nodes: VecDeque::new() }
}
/// Returns true if this instance is equal to or a subset (more specific) of the target instance.
/// e.g. `a.b.c` is within `a.b`
/// e.g. `a.d.c` is not within `a.b`
pub fn is_within(&self, other: &Namespace) -> bool {
// quick check: if this name has less parts it cannot be equal or more specific
if self.len() < other.nodes.len() {
return false
}
for (i, part) in other.nodes.iter().enumerate() {
if part != &self.nodes[i] {
return false
}
}
true
}
pub fn qualify<S: Into<String>>(&self, leaf: S) -> Name {
let mut nodes = self.clone();
nodes.push_back(leaf.into());
Name { nodes }
}
pub fn leaf(&self) -> Name {
self.back().expect("Short metric name").clone().into()
}
}
/// Turn any string into a StringDeque
impl<S: Into<String>> From<S> for Namespace {
fn from(name_part: S) -> Self {
let name: String = name_part.into();
// can we do better than asserting? empty names should not exist, ever...
debug_assert!(!name.is_empty());
let mut nodes = Namespace::new();
nodes.push_front(name);
nodes
}
}
/// Enable use of VecDeque methods such as len(), push_*, insert()...
impl Deref for Namespace {
type Target = VecDeque<String>;
fn deref(&self) -> &Self::Target {
&self.nodes
}
}
/// Enable use of VecDeque methods such as len(), push_*, insert()...
impl DerefMut for Namespace {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.nodes
}
}
/// The name of a metric, including the concatenated possible namespaces in which it was defined.
#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct Name {
nodes: Namespace,
}
impl Name {
/// Prepend to the existing namespace.
pub fn prepend<S: Into<Namespace>>(mut self, namespace: S) -> Self {
let parts: Namespace = namespace.into();
parts.iter().rev().for_each(|node|
self.nodes.push_front(node.clone())
);
self
}
/// Append to the existing namespace.
pub fn append<S: Into<Namespace>>(mut self, namespace: S) -> Self {
let offset = self.nodes.len() - 1;
let parts: Namespace = namespace.into();
for (i, part) in parts.iter().enumerate() {
self.nodes.insert(i + offset, part.clone())
}
self
}
/// Combine name parts into a string.
pub fn join(&self, separator: &str) -> String {
self.nodes.iter().map(|s| &**s).collect::<Vec<&str>>().join(separator)
}
}
impl<S: Into<String>> From<S> for Name {
fn from(name: S) -> Self {
Name { nodes: Namespace::from(name) }
}
}
impl Deref for Name {
type Target = Namespace;
fn deref(&self) -> &Self::Target {
&self.nodes
}
}
impl DerefMut for Name {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.nodes
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn string_deque_within_same() {
let mut sd1: Namespace = "c".into();
sd1.push_front("b".into());
assert_eq!(true, sd1.is_within(&sd1));
}
#[test]
fn string_deque_within_other() {
let mut sd1: Namespace = "b".into();
sd1.push_front("a".into());
let mut sd2: Namespace = "c".into();
sd2.push_front("b".into());
sd2.push_front("a".into());
assert_eq!(true, sd2.is_within(&sd1));
assert_eq!(false, sd1.is_within(&sd2));
}
}

View File

@ -1,6 +1,7 @@
use core::input::{InputScope, InputMetric, Input, Kind};
use core::output::{Output, OutputScope};
use core::component::{Attributes, WithAttributes, Name, AddPrefix};
use core::component::{Attributes, WithAttributes, Naming};
use core::name::Name;
use core::Flush;
use core::error;
use std::rc::Rc;
@ -23,7 +24,7 @@ impl WithAttributes for LockingScopeBox {
impl InputScope for LockingScopeBox {
fn new_metric(&self, name: Name, kind: Kind) -> InputMetric {
let name = self.qualified_name(name);
let name = self.qualify(name);
let raw_metric = self.inner.lock().expect("RawScope Lock").new_metric(name, kind);
let mutex = self.inner.clone();
InputMetric::new(move |value| {

View File

@ -1,6 +1,6 @@
use core::{Flush, Value};
use core::input::Kind;
use core::component::Name;
use core::name::Name;
use core::void::Void;
use std::rc::Rc;

View File

@ -1,6 +1,7 @@
//! Decouple metric definition from configuration with trait objects.
use core::component::{Attributes, WithAttributes, Name, AddPrefix};
use core::component::{Attributes, WithAttributes, Naming};
use core::name::{Name, Namespace};
use core::Flush;
use core::input::{Kind, InputMetric, InputScope};
use core::void::VOID_INPUT;
@ -24,7 +25,7 @@ lazy_static! {
#[derive(Debug)]
struct ProxyMetric {
// basic info for this metric, needed to recreate new corresponding trait object if target changes
name: Name,
name: Namespace,
kind: Kind,
// the metric trait object to proxy metric values to
@ -62,9 +63,9 @@ impl Proxy {
struct InnerProxy {
// namespaces can target one, many or no metrics
targets: HashMap<Name, Arc<InputScope + Send + Sync>>,
targets: HashMap<Namespace, Arc<InputScope + Send + Sync>>,
// last part of the namespace is the metric's name
metrics: BTreeMap<Name, Weak<ProxyMetric>>,
metrics: BTreeMap<Namespace, Weak<ProxyMetric>>,
}
impl fmt::Debug for InnerProxy {
@ -83,30 +84,31 @@ impl InnerProxy {
}
}
fn set_target(&mut self, target_name: Name, target_scope: Arc<InputScope + Send + Sync>) {
self.targets.insert(target_name.clone(), target_scope.clone());
for (metric_name, metric) in self.metrics.range_mut(target_name.clone()..) {
fn set_target(&mut self, namespace: &Namespace, target_scope: Arc<InputScope + Send + Sync>) {
self.targets.insert(namespace.clone(), target_scope.clone());
for (metric_name, metric) in self.metrics.range_mut(namespace.clone()..) {
if let Some(metric) = metric.upgrade() {
// check for range end
if !metric_name.starts_with(&target_name) { break }
if !metric_name.is_within(namespace) { break }
// check if metric targeted by _lower_ namespace
if metric.target.borrow().1 > target_name.len() { continue }
if metric.target.borrow().1 > namespace.len() { continue }
let target_metric = target_scope.new_metric(metric.name.clone(), metric.kind);
*metric.target.borrow_mut() = (target_metric, target_name.len());
let target_metric = target_scope.new_metric(metric.name.leaf(), metric.kind);
*metric.target.borrow_mut() = (target_metric, namespace.len());
}
}
}
fn get_effective_target(&self, name: &Name) -> Option<(Arc<InputScope + Send + Sync>, usize)> {
if let Some(target) = self.targets.get(name) {
return Some((target.clone(), name.len()));
fn get_effective_target(&self, namespace: &Namespace) -> Option<(Arc<InputScope + Send + Sync>, usize)> {
if let Some(target) = self.targets.get(namespace) {
return Some((target.clone(), namespace.len()));
}
// no 1:1 match, scan upper namespaces
let mut name = name.clone();
while let Some(_popped) = name.pop() {
let mut name = namespace.clone();
while let Some(_popped) = name.pop_back() {
if let Some(target) = self.targets.get(&name) {
return Some((target.clone(), name.len()))
}
@ -114,7 +116,7 @@ impl InnerProxy {
None
}
fn unset_target(&mut self, namespace: &Name) {
fn unset_target(&mut self, namespace: &Namespace) {
if self.targets.remove(namespace).is_none() {
// nothing to do
return
@ -124,27 +126,27 @@ impl InnerProxy {
.unwrap_or_else(|| (VOID_INPUT.input_dyn(), 0));
// update all affected metrics to next upper targeted namespace
for (name, metric) in self.metrics.range_mut(namespace..) {
for (name, metric) in self.metrics.range_mut(namespace.clone()..) {
// check for range end
if !name.starts_with(namespace) { break }
if !name.is_within(namespace) { break }
if let Some(mut metric) = metric.upgrade() {
// check if metric targeted by _lower_ namespace
if metric.target.borrow().1 > namespace.len() { continue }
let new_metric = up_target.new_metric(name.clone(), metric.kind);
let new_metric = up_target.new_metric(name.leaf(), metric.kind);
*metric.target.borrow_mut() = (new_metric, up_nslen);
}
}
}
fn drop_metric(&mut self, name: &Name) {
fn drop_metric(&mut self, name: &Namespace) {
if self.metrics.remove(name).is_none() {
panic!("Could not remove DelegatingMetric weak ref from delegation point")
}
}
fn flush(&self, namespace: &Name) -> error::Result<()> {
fn flush(&self, namespace: &Namespace) -> error::Result<()> {
if let Some((target, _nslen)) = self.get_effective_target(namespace) {
target.flush()
} else {
@ -171,7 +173,7 @@ impl Proxy {
/// Replace target for this proxy and it's children.
pub fn set_target<T: InputScope + Send + Sync + 'static>(&self, target: T) {
let mut inner = self.inner.write().expect("Dispatch Lock");
inner.set_target(self.get_namespace().clone(), Arc::new(target));
inner.set_target(self.get_namespace(), Arc::new(target));
}
/// Replace target for this proxy and it's children.
@ -194,14 +196,14 @@ impl Proxy {
impl<S: AsRef<str>> From<S> for Proxy {
fn from(name: S) -> Proxy {
Proxy::new().add_prefix(name.as_ref())
Proxy::new().namespace(name.as_ref())
}
}
impl InputScope for Proxy {
/// Lookup or create a proxy stub for the requested metric.
fn new_metric(&self, name: Name, kind: Kind) -> InputMetric {
let name = self.qualified_name(name);
let name: Name = self.qualify(name);
let mut inner = self.inner.write().expect("Dispatch Lock");
let proxy = inner
.metrics
@ -209,19 +211,21 @@ impl InputScope for Proxy {
// TODO validate that Kind matches existing
.and_then(|proxy_ref| Weak::upgrade(proxy_ref))
.unwrap_or_else(|| {
let name2 = name.clone();
// not found, define new
let (target, target_namespace_length) = inner.get_effective_target(&name)
.unwrap_or_else(|| (VOID_INPUT.input_dyn(), 0));
let metric_object = target.new_metric(name.clone(), kind);
let proxy = Arc::new(ProxyMetric {
name,
kind,
target: AtomicRefCell::new((metric_object, target_namespace_length)),
proxy: self.inner.clone(),
});
inner.metrics.insert(name2, Arc::downgrade(&proxy));
proxy
let namespace = &*name;
{
// not found, define new
let (target, target_namespace_length) = inner.get_effective_target(namespace)
.unwrap_or_else(|| (VOID_INPUT.input_dyn(), 0));
let metric_object = target.new_metric(namespace.leaf(), kind);
let proxy = Arc::new(ProxyMetric {
name: namespace.clone(),
kind,
target: AtomicRefCell::new((metric_object, target_namespace_length)),
proxy: self.inner.clone(),
});
inner.metrics.insert(namespace.clone(), Arc::downgrade(&proxy));
proxy
}
});
InputMetric::new(move |value| proxy.target.borrow().0.write(value))
}

View File

@ -29,7 +29,7 @@ impl CancelHandle {
/// Schedule a task to run periodically.
/// Starts a new thread for every task.
pub fn set_schedule<F>(every: Duration, operation: F) -> CancelHandle
fn set_schedule<F>(every: Duration, operation: F) -> CancelHandle
where
F: Fn() -> () + Send + 'static,
{

View File

@ -1,6 +1,6 @@
use core::output::{Output, OutputScope, OutputMetric};
use core::component::Name;
use core::input::{Kind, InputDyn};
use core::name::Name;
use core::input::{Kind, InputDyn, InputScope};
use core::Flush;
use std::sync::Arc;
@ -9,8 +9,8 @@ lazy_static! {
/// The reference instance identifying an uninitialized metric config.
pub static ref VOID_INPUT: Arc<InputDyn + Send + Sync> = Arc::new(Void::metrics());
// /// The reference instance identifying an uninitialized metric scope.
// pub static ref NO_METRIC_SCOPE: Arc<InputScope + Send + Sync> = VOID_INPUT.input_dyn();
/// The reference instance identifying an uninitialized metric scope.
pub static ref NO_METRIC_SCOPE: Arc<InputScope + Send + Sync> = VOID_INPUT.input_dyn();
}
/// Discard metrics output.

View File

@ -2,7 +2,7 @@
#![cfg_attr(feature = "bench", feature(test))]
#![warn(missing_docs, trivial_casts, trivial_numeric_casts, unused_extern_crates,
unused_import_braces, unused_qualifications)]
unused_qualifications)]
#![recursion_limit="32"]
#[cfg(feature = "bench")]
@ -27,34 +27,39 @@ mod macros;
mod core;
pub use core::{Flush, Value};
pub use core::component::*;
pub use core::input::*;
pub use core::output::*;
pub use core::scheduler::*;
pub use core::out_lock::*;
pub use core::component::{Naming, Sampling, Sampled, Buffered, Buffering};
pub use core::name::{Name, Namespace};
pub use core::input::{Input, InputDyn, InputScope, InputMetric, Counter, Timer, Marker, Gauge, Kind};
pub use core::output::{Output, OutputDyn, OutputScope, OutputMetric};
pub use core::scheduler::{ScheduleFlush, CancelHandle};
pub use core::out_lock::{LockingScopeBox};
pub use core::error::{Error, Result};
pub use core::clock::{TimeHandle, mock_clock_advance, mock_clock_reset};
pub use core::clock::{TimeHandle};
#[cfg(test)]
pub use core::clock::{mock_clock_advance, mock_clock_reset};
pub use core::proxy::Proxy;
mod output;
pub use output::text::*;
pub use output::graphite::*;
pub use output::statsd::*;
pub use output::map::*;
pub use output::logging::*;
pub use output::text::{Text, TextScope};
pub use output::graphite::{Graphite, GraphiteScope, GraphiteMetric};
pub use output::statsd::{Statsd, StatsdScope, StatsdMetric};
pub use output::map::{StatsMap};
pub use output::logging::{Log, LogScope};
mod aggregate;
pub use aggregate::bucket::*;
pub use aggregate::scores::*;
pub use aggregate::bucket::{Bucket, stats_all, stats_average, stats_summary};
pub use aggregate::scores::{ScoreType, Scoreboard};
mod cache;
pub use cache::cache_in::CachedInput;
pub use cache::cache_out::CachedOutput;
mod multi;
pub use multi::multi_in::*;
pub use multi::multi_out::*;
pub use multi::multi_in::{MultiInput, MultiInputScope};
pub use multi::multi_out::{MultiOutput, MultiOutputScope};
mod queue;
pub use queue::queue_in::*;
pub use queue::queue_out::*;
pub use queue::queue_in::{QueuedInput, InputQueue, InputQueueScope};
pub use queue::queue_out::{QueuedOutput, OutputQueue, OutputQueueScope};

View File

@ -90,27 +90,27 @@ macro_rules! __in_context {
// SUB BRANCH NODE - public identifier
($WITH:expr; $TY:ty; $(#[$attr:meta])* pub $IDENT:ident = $e:expr => { $($BRANCH:tt)*} $($REST:tt)*) => {
lazy_static! { $(#[$attr])* pub static ref $IDENT = $WITH.add_prefix($e); }
lazy_static! { $(#[$attr])* pub static ref $IDENT = $WITH.namespace($e); }
__in_context!($IDENT; $TY; $($BRANCH)*);
__in_context!($WITH; $TY; $($REST)*);
};
// SUB BRANCH NODE - private identifier
($WITH:expr; $TY:ty; $(#[$attr:meta])* $IDENT:ident = $e:expr => { $($BRANCH:tt)*} $($REST:tt)*) => {
lazy_static! { $(#[$attr])* static ref $IDENT = $WITH.add_prefix($e); }
lazy_static! { $(#[$attr])* static ref $IDENT = $WITH.namespace($e); }
__in_context!($IDENT; $TY; $($BRANCH)*);
__in_context!($WITH; $TY; $($REST)*);
};
// SUB BRANCH NODE (not yet)
($WITH:expr; $TY:ty; $(#[$attr:meta])* pub $e:expr => { $($BRANCH:tt)*} $($REST:tt)*) => {
__in_context!($WITH.add_prefix($e); $TY; $($BRANCH)*);
__in_context!($WITH.namespace($e); $TY; $($BRANCH)*);
__in_context!($WITH; $TY; $($REST)*);
};
// SUB BRANCH NODE (not yet)
($WITH:expr; $TY:ty; $(#[$attr:meta])* $e:expr => { $($BRANCH:tt)*} $($REST:tt)*) => {
__in_context!($WITH.add_prefix($e); $TY; $($BRANCH)*);
__in_context!($WITH.namespace($e); $TY; $($BRANCH)*);
__in_context!($WITH; $TY; $($REST)*);
};

View File

@ -2,24 +2,12 @@
use core::Flush;
use core::input::{Kind, Input, InputScope, InputMetric, InputDyn};
use core::component::{Attributes, WithAttributes, Name, AddPrefix};
use core::component::{Attributes, WithAttributes, Naming};
use core::name::Name;
use core::error;
use std::sync::Arc;
/// Wrap this output behind an asynchronous metrics dispatch queue.
/// This is not strictly required for multi threading since the provided scopes
/// are already Send + Sync but might be desired to lower the latency
pub trait WithMultiInputScope: InputScope + Send + Sync + 'static + Sized {
/// Wrap this output with an asynchronous dispatch queue of specified length.
fn add_target<OUT: InputScope + Send + Sync + 'static>(self, target: OUT) -> MultiInputScope {
MultiInputScope::new().add_target(self).add_target(target)
}
}
/// Blanket scope concatenation.
impl<T: InputScope + Send + Sync + 'static + Sized> WithMultiInputScope for T {}
/// Opens multiple scopes at a time from just as many outputs.
#[derive(Clone)]
pub struct MultiInput {
@ -42,7 +30,7 @@ impl Input for MultiInput {
impl MultiInput {
/// Create a new multi-output.
pub fn inputs() -> MultiInput {
pub fn input() -> MultiInput {
MultiInput {
attributes: Attributes::default(),
outputs: vec![],
@ -88,7 +76,7 @@ impl MultiInputScope {
impl InputScope for MultiInputScope {
fn new_metric(&self, name: Name, kind: Kind) -> InputMetric {
let ref name = self.qualified_name(name);
let ref name = self.qualify(name);
let metrics: Vec<InputMetric> = self.scopes.iter()
.map(move |scope| scope.new_metric(name.clone(), kind))
.collect();

View File

@ -1,7 +1,8 @@
//! Dispatch metrics to multiple sinks.
use core::Flush;
use core::component::{Attributes, WithAttributes, Name, AddPrefix};
use core::component::{Attributes, WithAttributes, Naming};
use core::name::Name;
use core::input::Kind;
use core::output::{Output, OutputMetric, OutputScope, OutputDyn};
use core::error;
@ -30,7 +31,7 @@ impl Output for MultiOutput {
impl MultiOutput {
/// Create a new multi-output.
pub fn metrics() -> MultiOutput {
pub fn output() -> MultiOutput {
MultiOutput {
attributes: Attributes::default(),
outputs: vec![],
@ -76,7 +77,7 @@ impl MultiOutputScope {
impl OutputScope for MultiOutputScope {
fn new_metric(&self, name: Name, kind: Kind) -> OutputMetric {
let ref name = self.qualified_name(name);
let ref name = self.qualify(name);
let metrics: Vec<OutputMetric> = self.scopes.iter()
.map(move |scope| scope.new_metric(name.clone(), kind))
.collect();

View File

@ -1,6 +1,7 @@
//! Send metrics to a graphite server.
use core::component::{Buffered, Attributes, WithAttributes, Name, AddPrefix};
use core::component::{Buffered, Attributes, WithAttributes, Naming};
use core::name::Name;
use core::{Flush, Value};
use core::input::Kind;
use core::metrics;
@ -71,7 +72,7 @@ pub struct GraphiteScope {
impl OutputScope for GraphiteScope {
/// Define a metric of the specified type.
fn new_metric(&self, name: Name, kind: Kind) -> OutputMetric {
let mut prefix = self.qualified_name(name).join(".");
let mut prefix = self.qualify(name).join(".");
prefix.push(' ');
let scale = match kind {
@ -125,7 +126,7 @@ impl GraphiteScope {
}
};
if !self.is_buffered() {
if self.get_buffering().is_none() {
if let Err(e) = self.flush_inner(buffer) {
debug!("Could not send to graphite {}", e)
}
@ -186,7 +187,8 @@ impl Drop for GraphiteScope {
#[cfg(feature = "bench")]
mod bench {
use core::*;
use core::component::*;
use core::input::*;
use super::*;
use test;

View File

@ -1,6 +1,7 @@
use core::{Flush, Value};
use core::input::{Kind, Input, InputScope, InputMetric};
use core::component::{Attributes, WithAttributes, Buffered, Name, AddPrefix};
use core::component::{Attributes, WithAttributes, Buffered, Naming};
use core::name::Name;
use core::error;
use cache::cache_in;
use queue::queue_in;
@ -69,13 +70,13 @@ impl cache_in::CachedInput for Log {}
impl InputScope for LogScope {
fn new_metric(&self, name: Name, kind: Kind) -> InputMetric {
let name = self.qualified_name(name);
let name = self.qualify(name);
let template = (self.output.format_fn)(&name, kind);
let print_fn = self.output.print_fn.clone();
let entries = self.entries.clone();
if self.is_buffered() {
if let Some(_buffering) = self.get_buffering() {
InputMetric::new(move |value| {
let mut buffer = Vec::with_capacity(32);
match (print_fn)(&mut buffer, &template, value) {
@ -87,6 +88,7 @@ impl InputScope for LogScope {
}
})
} else {
// unbuffered
InputMetric::new(move |value| {
let mut buffer = Vec::with_capacity(32);
match (print_fn)(&mut buffer, &template, value) {

View File

@ -1,6 +1,6 @@
use core::{Flush, Value};
use core::input::Kind;
use core::component::Name;
use core::name::Name;
use core::output::{OutputMetric, OutputScope};
use std::rc::Rc;

View File

@ -7,7 +7,8 @@
use core::{Flush, Value};
use core::input::{Kind, Input, InputScope, InputMetric};
use core::component::{Attributes, WithAttributes, Buffered, Buffering, Name, AddPrefix};
use core::component::{Attributes, WithAttributes, Buffered, Buffering, Naming};
use core::name::Name;
use core::output::{Output, OutputMetric, OutputScope};
use core::error;

View File

@ -1,6 +1,7 @@
//! Send metrics to a statsd server.
use core::component::{Buffered, Attributes, Sampled, Sampling, WithAttributes, Name, AddPrefix};
use core::component::{Buffered, Attributes, Sampled, Sampling, WithAttributes, Naming};
use core::name::Name;
use core::pcg32;
use core::{Flush, Value};
use core::input::Kind;
@ -78,7 +79,7 @@ impl Sampled for StatsdScope {}
impl OutputScope for StatsdScope {
/// Define a metric of the specified type.
fn new_metric(&self, name: Name, kind: Kind) -> OutputMetric {
let mut prefix = self.qualified_name(name).join(".");
let mut prefix = self.qualify(name).join(".");
prefix.push(':');
let mut suffix = String::with_capacity(16);
@ -97,7 +98,7 @@ impl OutputScope for StatsdScope {
let cloned = self.clone();
if let Sampling::Random(float_rate) = self.get_sampling() {
if let Some(Sampling::Random(float_rate)) = self.get_sampling() {
suffix.push_str(&format!{"|@{}\n", float_rate});
let int_sampling_rate = pcg32::to_int_rate(float_rate);
let metric = StatsdMetric { prefix, suffix, scale };
@ -153,7 +154,7 @@ impl StatsdScope {
buffer.push_str(&metric.suffix);
}
if !self.is_buffered() {
if self.get_buffering().is_none() {
if let Err(e) = self.flush_inner(buffer) {
debug!("Could not send to statsd {}", e)
}
@ -205,7 +206,8 @@ impl Drop for StatsdScope {
#[cfg(feature = "bench")]
mod bench {
use core::*;
use core::component::*;
use core::input::*;
use super::*;
use test;

View File

@ -4,7 +4,8 @@
use core::{Flush, Value};
use core::input::Kind;
use core::component::{Attributes, WithAttributes, Buffered, Name, AddPrefix};
use core::component::{Attributes, WithAttributes, Buffered, Naming};
use core::name::Name;
use core::output::{Output, OutputMetric, OutputScope};
use core::error;
use cache::cache_out;
@ -122,13 +123,13 @@ impl<W: Write + Send + Sync + 'static> Buffered for TextScope<W> {}
impl<W: Write + Send + Sync + 'static> OutputScope for TextScope<W> {
fn new_metric(&self, name: Name, kind: Kind) -> OutputMetric {
let name = self.qualified_name(name);
let name = self.qualify(name);
let template = (self.output.format_fn)(&name, kind);
let print_fn = self.output.print_fn.clone();
let entries = self.entries.clone();
if self.is_buffered() {
if let Some(_buffering) = self.get_buffering() {
OutputMetric::new(move |value| {
let mut buffer = Vec::with_capacity(32);
match (print_fn)(&mut buffer, &template, value) {
@ -140,6 +141,7 @@ impl<W: Write + Send + Sync + 'static> OutputScope for TextScope<W> {
}
})
} else {
// unbuffered
let output = self.output.clone();
OutputMetric::new(move |value| {
let mut buffer = Vec::with_capacity(32);

View File

@ -2,7 +2,8 @@
//! Metrics definitions are still synchronous.
//! If queue size is exceeded, calling code reverts to blocking.
use core::component::{Attributes, Name, WithAttributes, AddPrefix};
use core::component::{Attributes, WithAttributes, Naming};
use core::name::Name;
use core::input::{Kind, Input, InputScope, InputDyn, InputMetric};
use core::{Value, Flush};
use core::metrics;
@ -120,7 +121,7 @@ impl WithAttributes for InputQueueScope {
impl InputScope for InputQueueScope {
fn new_metric(&self, name: Name, kind:Kind) -> InputMetric {
let name = self.qualified_name(name);
let name = self.qualify(name);
let target_metric = self.target.new_metric(name, kind);
let sender = self.sender.clone();
InputMetric::new(move |value| {

View File

@ -2,7 +2,8 @@
//! RawMetrics definitions are still synchronous.
//! If queue size is exceeded, calling code reverts to blocking.
//!
use core::component::{Attributes, Name, WithAttributes, AddPrefix};
use core::component::{Attributes, WithAttributes, Naming};
use core::name::Name;
use core::input::{Kind, Input, InputScope, InputMetric};
use core::output::{OutputDyn, OutputScope, OutputMetric, Output};
use core::{Value, Flush};
@ -114,7 +115,7 @@ impl WithAttributes for OutputQueueScope {
impl InputScope for OutputQueueScope {
fn new_metric(&self, name: Name, kind:Kind) -> InputMetric {
let name = self.qualified_name(name);
let name = self.qualify(name);
let target_metric = Arc::new(self.target.new_metric(name, kind));
let sender = self.sender.clone();
InputMetric::new(move |value| {