Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for distribution metric type #125

Merged
merged 4 commits into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cadence-macros/examples/production-setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.

use cadence::{BufferedUdpMetricSink, QueuingMetricSink, StatsdClient, DEFAULT_PORT};
use cadence_macros::{statsd_count, statsd_gauge, statsd_histogram, statsd_meter, statsd_set, statsd_time};
use cadence_macros::{statsd_count, statsd_distribution, statsd_gauge, statsd_histogram, statsd_meter, statsd_set, statsd_time};
use std::net::UdpSocket;

fn main() {
Expand All @@ -23,5 +23,6 @@ fn main() {
statsd_time!("some.timer", 1, "tag" => "val");
statsd_meter!("some.meter", 1, "tag" => "val");
statsd_histogram!("some.histogram", 1, "tag" => "val");
statsd_distribution!("some.distribution", 1, "tag" => "val");
statsd_set!("some.set", 1, "tag" => "val");
}
3 changes: 2 additions & 1 deletion cadence-macros/examples/simple-setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.

use cadence::{StatsdClient, UdpMetricSink, DEFAULT_PORT};
use cadence_macros::{statsd_count, statsd_gauge, statsd_histogram, statsd_meter, statsd_set, statsd_time};
use cadence_macros::{statsd_count, statsd_distribution, statsd_gauge, statsd_histogram, statsd_meter, statsd_set, statsd_time};
use std::net::UdpSocket;

fn main() {
Expand All @@ -22,5 +22,6 @@ fn main() {
statsd_time!("some.timer", 1, "tag" => "val");
statsd_meter!("some.meter", 1, "tag" => "val");
statsd_histogram!("some.histogram", 1, "tag" => "val");
statsd_distribution!("some.distribution", 1, "tag" => "val");
statsd_set!("some.set", 1, "tag" => "val");
}
50 changes: 50 additions & 0 deletions cadence-macros/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,56 @@ macro_rules! statsd_histogram {
}
}

/// Emit a distribution using the default global client, optionally with tags
///
/// The distribution will use the prefix from the default global client combined
/// with the provided key.
///
/// Any errors encountered sending metrics will be handled by the error handler
/// registered with the default global client. This error handler is a no-op
/// unless explicitly set. Callers should set the error handler for the default
/// client if you wish to handle these errors (by logging them or something similar).
///
/// # Panics
///
/// This macro will panic if the default global client has not been set when
/// it is invoked (via `cadence_macros::set_global_default`).
///
/// # Examples
///
/// ```
/// use cadence::{StatsdClient, NopMetricSink};
/// use cadence_macros::statsd_distribution;
///
/// let client = StatsdClient::builder("my.prefix", NopMetricSink)
/// .with_error_handler(|e| { eprintln!("metric error: {}", e) })
/// .build();
///
/// cadence_macros::set_global_default(client);
///
/// // "my.prefix.some.distribution:123|d"
/// statsd_distribution!("some.distribution", 123);
/// // "my.prefix.some.distribution:123|d|#tag:val"
/// statsd_distribution!("some.distribution", 123, "tag" => "val");
/// // "my.prefix.some.distribution:123|d|#tag:val,another:thing"
/// statsd_distribution!("some.distribution", 123, "tag" => "val", "another" => "thing");
/// ```
///
/// # Limitations
///
/// Only key-value style tags are supported. Value style tags are not
/// supported, e.g. `builder.with_tag_value("val")`.
#[macro_export]
macro_rules! statsd_distribution {
($key:expr, $val:expr) => {
$crate::statsd_distribution!($key, $val,)
};

($key:expr, $val:expr, $($tag_key:expr => $tag_val:expr),*) => {
$crate::_generate_impl!(distribution_with_tags, $key, $val, $($tag_key => $tag_val),*)
}
}

/// Emit a set using the default global client, optionally with tags
///
/// The set will use the prefix from the default global client combined
Expand Down
13 changes: 12 additions & 1 deletion cadence-macros/tests/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use cadence::{SpyMetricSink, StatsdClient};
use cadence_macros::{statsd_count, statsd_gauge, statsd_histogram, statsd_meter, statsd_set, statsd_time};
use cadence_macros::{statsd_count, statsd_distribution, statsd_gauge, statsd_histogram, statsd_meter, statsd_set, statsd_time};
use std::io;
use std::sync::{Arc, Mutex, Once};

Expand Down Expand Up @@ -127,6 +127,17 @@ fn test_statsd_histogram() {
assert!(storage.contains(&"my.prefix.some.histogram:223|h|#method:auth,result:error".to_owned()));
}

#[test]
fn test_statsd_distribution() {
init_default_client();
statsd_distribution!("some.distribution", 223);
statsd_distribution!("some.distribution", 223, "method" => "auth", "result" => "error");

let storage = get_default_storage();
assert!(storage.contains(&"my.prefix.some.distribution:223|d".to_owned()));
assert!(storage.contains(&"my.prefix.some.distribution:223|d|#method:auth,result:error".to_owned()));
}

#[test]
fn test_statsd_set() {
init_default_client();
Expand Down
11 changes: 9 additions & 2 deletions cadence/examples/tag-view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@

use cadence::prelude::*;
use cadence::{
Counted, Counter, Gauge, Gauged, Histogram, Histogrammed, Meter, Metered, Metric, MetricBuilder, MetricSink, Set,
Setted, StatsdClient, Timed, Timer,
Counted, Counter, Distribution, Distributed, Gauge, Gauged, Histogram, Histogrammed, Meter, Metered, Metric, MetricBuilder,
MetricSink, Set, Setted, StatsdClient, Timed, Timer,
};
use std::fmt;
use std::io;
Expand Down Expand Up @@ -140,6 +140,13 @@ impl Histogrammed for MetricTagDecorator {
}
}

impl Distributed for MetricTagDecorator {
fn distribution_with_tags<'a>(&'a self, key: &'a str, value: u64) -> MetricBuilder<'_, '_, Distribution> {
let builder = self.client.distribution_with_tags(key, value);
self.copy_tags_to_builder(builder)
}
}

impl Setted for MetricTagDecorator {
fn set_with_tags<'a>(&'a self, key: &'a str, value: i64) -> MetricBuilder<'_, '_, Set> {
let builder = self.client.set_with_tags(key, value);
Expand Down
6 changes: 6 additions & 0 deletions cadence/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ enum MetricType {
Meter,
Histogram,
Set,
Distribution,
}

impl fmt::Display for MetricType {
Expand All @@ -52,6 +53,7 @@ impl fmt::Display for MetricType {
MetricType::Meter => "m".fmt(f),
MetricType::Histogram => "h".fmt(f),
MetricType::Set => "s".fmt(f),
MetricType::Distribution => "d".fmt(f),
}
}
}
Expand Down Expand Up @@ -99,6 +101,10 @@ where
Self::from_u64(prefix, key, val, MetricType::Histogram)
}

pub(crate) fn distribution(prefix: &'a str, key: &'a str, val: u64) -> Self {
Self::from_u64(prefix, key, val, MetricType::Distribution)
}

pub(crate) fn set(prefix: &'a str, key: &'a str, val: i64) -> Self {
Self::from_i64(prefix, key, val, MetricType::Set)
}
Expand Down
51 changes: 48 additions & 3 deletions cadence/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

use crate::builder::{MetricBuilder, MetricFormatter};
use crate::sinks::{MetricSink, UdpMetricSink};
use crate::types::{Counter, ErrorKind, Gauge, Histogram, Meter, Metric, MetricError, MetricResult, Set, Timer};
use crate::types::{Counter, Distribution, ErrorKind, Gauge, Histogram, Meter, Metric, MetricError, MetricResult, Set, Timer};
use std::fmt;
use std::net::{ToSocketAddrs, UdpSocket};
use std::panic::RefUnwindSafe;
Expand Down Expand Up @@ -213,6 +213,29 @@ pub trait Histogrammed {
-> MetricBuilder<'_, '_, Histogram>;
}

/// Trait for recording distribution values.
///
/// Similar to histrograms, but applies globally. A distrubtion can be used to
/// instrument logical objects, like services, independently from the underlying
/// hosts.
///
/// See the [Datadog docs](https://docs.datadoghq.com/developers/metrics/types/?tab=distribution#definition)
/// for more information.
///
/// Note that tags and distributions are a
/// [Datadog](https://docs.datadoghq.com/developers/dogstatsd/) extension to
/// Statsd and may not be supported by your server.
pub trait Distributed {
/// Record a single distribution value with the given key
fn distribution(&self, key: &str, value: u64) -> MetricResult<Distribution> {
self.distribution_with_tags(key, value).try_send()
}

/// Record a single distribution value with the given key and return a
/// `MetricBuilder` that can be used to add tags to the metric.
fn distribution_with_tags<'a>(&'a self, key: &'a str, value: u64) -> MetricBuilder<'_, '_, Distribution>;
}

/// Trait for recording set values.
///
/// Sets count the number of unique elements in a group. You can use them to,
Expand Down Expand Up @@ -251,7 +274,7 @@ pub trait Setted {
/// client.histogram("some.histogram", 4).unwrap();
/// client.set("some.set", 5).unwrap();
/// ```
pub trait MetricClient: Counted + Timed + Gauged + Metered + Histogrammed + Setted {}
pub trait MetricClient: Counted + Timed + Gauged + Metered + Histogrammed + Setted + Distributed {}

/// Typically internal methods for sending metrics and handling errors.
///
Expand Down Expand Up @@ -784,6 +807,13 @@ impl Histogrammed for StatsdClient {
}
}

impl Distributed for StatsdClient {
fn distribution_with_tags<'a>(&'a self, key: &'a str, value: u64) -> MetricBuilder<'_, '_, Distribution> {
let fmt = MetricFormatter::distribution(&self.prefix, key, value);
MetricBuilder::new(fmt, self)
}
}

impl Setted for StatsdClient {
fn set_with_tags<'a>(&'a self, key: &'a str, value: i64) -> MetricBuilder<'_, '_, Set> {
let fmt = MetricFormatter::set(&self.prefix, key, value);
Expand All @@ -800,7 +830,7 @@ fn nop_error_handler(_err: MetricError) {

#[cfg(test)]
mod tests {
use super::{Counted, Gauged, Histogrammed, Metered, MetricClient, Setted, StatsdClient, Timed};
use super::{Counted, Distributed, Gauged, Histogrammed, Metered, MetricClient, Setted, StatsdClient, Timed};
use crate::sinks::{MetricSink, NopMetricSink, QueuingMetricSink};
use crate::types::{ErrorKind, Metric, MetricError};
use std::cell::RefCell;
Expand Down Expand Up @@ -927,6 +957,21 @@ mod tests {
assert_eq!(ErrorKind::InvalidInput, res.unwrap_err().kind());
}

#[test]
fn test_statsd_client_distribution_with_tags() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
let res = client
.distribution_with_tags("some.distr", 27)
.with_tag("host", "www03.example.com")
.with_tag_value("rc1")
.try_send();

assert_eq!(
"prefix.some.distr:27|d|#host:www03.example.com,rc1",
res.unwrap().as_metric_str()
);
}

#[test]
fn test_statsd_client_time_duration() {
let client = StatsdClient::from_sink("prefix", NopMetricSink);
Expand Down
6 changes: 3 additions & 3 deletions cadence/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
//!
//! ## Features
//!
//! * Support for emitting counters, timers, histograms, gauges, meters, and sets to
//! * Support for emitting counters, timers, histograms, distributions, gauges, meters, and sets to
//! Statsd over UDP (or optionally Unix sockets).
//! * Support for alternate backends via the `MetricSink` trait.
//! * Support for [Datadog](https://docs.datadoghq.com/developers/dogstatsd/) style metrics tags.
Expand Down Expand Up @@ -415,15 +415,15 @@ pub const DEFAULT_PORT: u16 = 8125;
pub use self::builder::MetricBuilder;

pub use self::client::{
Counted, Gauged, Histogrammed, Metered, MetricClient, Setted, StatsdClient, StatsdClientBuilder, Timed,
Counted, Distributed, Gauged, Histogrammed, Metered, MetricClient, Setted, StatsdClient, StatsdClientBuilder, Timed,
};

pub use self::sinks::{
BufferedSpyMetricSink, BufferedUdpMetricSink, MetricSink, NopMetricSink, QueuingMetricSink, SpyMetricSink,
UdpMetricSink,
};

pub use self::types::{Counter, ErrorKind, Gauge, Histogram, Meter, Metric, MetricError, MetricResult, Set, Timer};
pub use self::types::{Counter, Distribution, ErrorKind, Gauge, Histogram, Meter, Metric, MetricError, MetricResult, Set, Timer};

mod builder;
mod client;
Expand Down
2 changes: 1 addition & 1 deletion cadence/src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@
//! client.set("some.set", 123).unwrap();
//! ```

pub use crate::client::{Counted, Gauged, Histogrammed, Metered, MetricClient, Setted, Timed};
pub use crate::client::{Counted, Distributed, Gauged, Histogrammed, Metered, MetricClient, Setted, Timed};
26 changes: 26 additions & 0 deletions cadence/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,32 @@ impl Metric for Histogram {
}
}

/// Distributions represent a global statistical distribution of a set of values.
///
/// See the `Distributed` trait for more information.
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
pub struct Distribution {
repr: String,
}

impl Distribution {
pub fn new(prefix: &str, key: &str, value: u64) -> Distribution {
MetricFormatter::distribution(prefix, key, value).build()
}
}

impl From<String> for Distribution {
fn from(s: String) -> Self {
Distribution { repr: s }
}
}

impl Metric for Distribution {
fn as_metric_str(&self) -> &str {
&self.repr
}
}

/// Sets count the number of unique elements in a group.
///
/// See the `Setted` trait for more information.
Expand Down