dipstick/src/output/statsd.rs

291 lines
8.2 KiB
Rust
Raw Normal View History

2017-09-01 07:31:25 +00:00
//! Send metrics to a statsd server.
2020-05-15 04:28:40 +00:00
use crate::attributes::{
Attributes, Buffered, MetricId, OnFlush, Prefixed, Sampled, Sampling, WithAttributes,
2019-04-09 11:55:15 +00:00
};
2020-05-15 04:28:40 +00:00
use crate::input::InputKind;
use crate::input::{Input, InputMetric, InputScope};
2020-05-25 13:12:24 +00:00
use crate::metrics;
2020-05-15 04:28:40 +00:00
use crate::name::MetricName;
use crate::pcg32;
2020-05-23 03:47:18 +00:00
use crate::{CachedInput, QueuedInput};
2020-05-15 04:28:40 +00:00
use crate::{Flush, MetricValue};
2022-07-12 18:11:39 +00:00
use std::fmt::Write;
2018-06-26 16:28:38 +00:00
use std::net::ToSocketAddrs;
use std::net::UdpSocket;
2019-04-09 11:55:15 +00:00
use std::sync::Arc;
2020-04-17 11:52:46 +00:00
#[cfg(not(feature = "parking_lot"))]
use std::sync::{RwLock, RwLockWriteGuard};
#[cfg(feature = "parking_lot")]
use parking_lot::{RwLock, RwLockWriteGuard};
2020-05-23 03:47:18 +00:00
use std::io;
2020-04-17 11:52:46 +00:00
2018-06-26 16:28:38 +00:00
/// Use a safe maximum size for UDP to prevent fragmentation.
// TODO make configurable?
const MAX_UDP_PAYLOAD: usize = 576;
2020-05-15 04:28:40 +00:00
/// Statsd Input holds a datagram (UDP) socket to a statsd server.
/// The socket is shared between scopes opened from the Input.
2018-06-26 16:28:38 +00:00
#[derive(Clone, Debug)]
2018-07-03 15:44:29 +00:00
pub struct Statsd {
2018-06-20 19:04:04 +00:00
attributes: Attributes,
socket: Arc<UdpSocket>,
}
2018-07-03 15:44:29 +00:00
impl Statsd {
/// Send metrics to a statsd server at the address and port provided.
2020-05-23 03:47:18 +00:00
pub fn send_to<ADDR: ToSocketAddrs>(address: ADDR) -> io::Result<Statsd> {
2018-07-03 15:44:29 +00:00
let socket = Arc::new(UdpSocket::bind("0.0.0.0:0")?);
socket.set_nonblocking(true)?;
socket.connect(address)?;
2018-06-26 16:28:38 +00:00
2018-07-03 15:44:29 +00:00
Ok(Statsd {
attributes: Attributes::default(),
socket,
})
}
}
2018-09-11 12:27:10 +00:00
impl Buffered for Statsd {}
2022-07-12 18:11:39 +00:00
2018-09-11 12:27:10 +00:00
impl Sampled for Statsd {}
2018-07-03 15:44:29 +00:00
2020-05-15 04:28:40 +00:00
impl QueuedInput for Statsd {}
2022-07-12 18:11:39 +00:00
2020-05-15 04:28:40 +00:00
impl CachedInput for Statsd {}
2018-06-26 16:28:38 +00:00
2020-05-15 04:28:40 +00:00
impl Input for Statsd {
2018-07-03 15:44:29 +00:00
type SCOPE = StatsdScope;
2020-05-15 04:28:40 +00:00
fn metrics(&self) -> Self::SCOPE {
2018-07-03 15:44:29 +00:00
StatsdScope {
2018-06-14 16:37:21 +00:00
attributes: self.attributes.clone(),
2020-04-17 11:52:46 +00:00
buffer: Arc::new(RwLock::new(String::with_capacity(MAX_UDP_PAYLOAD))),
2018-06-26 15:46:55 +00:00
socket: self.socket.clone(),
2018-06-14 16:37:21 +00:00
}
}
}
2018-07-03 15:44:29 +00:00
impl WithAttributes for Statsd {
2019-04-09 11:55:15 +00:00
fn get_attributes(&self) -> &Attributes {
&self.attributes
}
fn mut_attributes(&mut self) -> &mut Attributes {
&mut self.attributes
}
2018-06-14 16:37:21 +00:00
}
2018-06-26 16:28:38 +00:00
/// Statsd Input
#[derive(Debug, Clone)]
2018-07-03 15:44:29 +00:00
pub struct StatsdScope {
2018-06-14 16:37:21 +00:00
attributes: Attributes,
2020-04-17 11:52:46 +00:00
buffer: Arc<RwLock<String>>,
2018-06-26 15:46:55 +00:00
socket: Arc<UdpSocket>,
2018-06-14 16:37:21 +00:00
}
2018-09-11 12:27:10 +00:00
impl Sampled for StatsdScope {}
2018-06-26 15:46:55 +00:00
2020-05-15 04:28:40 +00:00
impl InputScope for StatsdScope {
2018-06-26 16:28:38 +00:00
/// Define a metric of the specified type.
2020-05-15 04:28:40 +00:00
fn new_metric(&self, name: MetricName, kind: InputKind) -> InputMetric {
let mut prefix = self.prefix_prepend(name.clone()).join(".");
2018-06-14 16:37:21 +00:00
prefix.push(':');
let mut suffix = String::with_capacity(16);
suffix.push('|');
suffix.push_str(match kind {
2018-10-26 01:20:47 +00:00
InputKind::Marker | InputKind::Counter => "c",
2018-12-20 21:19:03 +00:00
InputKind::Gauge | InputKind::Level => "g",
2018-10-26 01:20:47 +00:00
InputKind::Timer => "ms",
2018-06-14 16:37:21 +00:00
});
let scale = match kind {
// timers are in µs, statsd wants ms
2018-10-26 01:20:47 +00:00
InputKind::Timer => 1000,
2018-06-14 16:37:21 +00:00
_ => 1,
};
2018-06-26 16:28:38 +00:00
let cloned = self.clone();
2020-05-15 04:28:40 +00:00
let metric_id = MetricId::forge("statsd", name);
2018-06-26 15:46:55 +00:00
if let Sampling::Random(float_rate) = self.get_sampling() {
2023-01-16 16:00:13 +00:00
let _ = writeln!(suffix, "|@{}", float_rate);
2018-06-14 16:37:21 +00:00
let int_sampling_rate = pcg32::to_int_rate(float_rate);
2019-04-09 11:55:15 +00:00
let metric = StatsdMetric {
prefix,
suffix,
scale,
};
2018-06-14 16:37:21 +00:00
2020-05-15 04:28:40 +00:00
InputMetric::new(metric_id, move |value, _labels| {
2018-06-14 16:37:21 +00:00
if pcg32::accept_sample(int_sampling_rate) {
2018-06-26 16:28:38 +00:00
cloned.print(&metric, value)
}
})
2018-06-14 16:37:21 +00:00
} else {
2022-07-12 18:11:39 +00:00
suffix.push('\n');
2019-04-09 11:55:15 +00:00
let metric = StatsdMetric {
prefix,
suffix,
scale,
};
2020-05-15 04:28:40 +00:00
InputMetric::new(metric_id, move |value, _labels| {
cloned.print(&metric, value)
})
2018-06-14 16:37:21 +00:00
}
}
2018-06-24 04:31:34 +00:00
}
2018-07-03 15:44:29 +00:00
impl Flush for StatsdScope {
2020-05-23 03:47:18 +00:00
fn flush(&self) -> io::Result<()> {
2019-03-08 21:59:51 +00:00
self.notify_flush_listeners();
2020-04-17 11:52:46 +00:00
let buf = write_lock!(self.buffer);
2018-06-26 16:28:38 +00:00
self.flush_inner(buf)
}
}
2018-07-03 15:44:29 +00:00
impl StatsdScope {
2019-04-09 11:55:15 +00:00
fn print(&self, metric: &StatsdMetric, value: MetricValue) {
2018-06-26 16:28:38 +00:00
let scaled_value = value / metric.scale;
let value_str = scaled_value.to_string();
let entry_len = metric.prefix.len() + value_str.len() + metric.suffix.len();
2020-04-17 11:52:46 +00:00
let mut buffer = write_lock!(self.buffer);
2018-06-26 16:28:38 +00:00
if entry_len > buffer.capacity() {
// TODO report entry too big to fit in buffer (!?)
return;
}
2020-05-15 04:28:40 +00:00
let available = buffer.capacity() - buffer.len();
if entry_len + 1 > available {
2018-06-26 16:28:38 +00:00
// buffer is nearly full, make room
let _ = self.flush_inner(buffer);
2020-04-17 11:52:46 +00:00
buffer = write_lock!(self.buffer);
2018-06-26 16:28:38 +00:00
} else {
if !buffer.is_empty() {
// separate from previous entry
buffer.push('\n')
}
buffer.push_str(&metric.prefix);
buffer.push_str(&value_str);
buffer.push_str(&metric.suffix);
}
if !self.is_buffered() {
2018-06-26 16:28:38 +00:00
if let Err(e) = self.flush_inner(buffer) {
debug!("Could not send to statsd {}", e)
}
}
}
2020-05-23 03:47:18 +00:00
fn flush_inner(&self, mut buffer: RwLockWriteGuard<String>) -> io::Result<()> {
2018-06-26 16:28:38 +00:00
if !buffer.is_empty() {
2018-06-26 15:46:55 +00:00
match self.socket.send(buffer.as_bytes()) {
Ok(size) => {
metrics::STATSD_SENT_BYTES.count(size);
trace!("Sent {} bytes to statsd", buffer.len());
}
Err(e) => {
metrics::STATSD_SEND_ERR.mark();
2020-05-25 13:12:24 +00:00
return Err(e);
2018-06-26 15:46:55 +00:00
}
};
buffer.clear();
}
Ok(())
2018-06-14 16:37:21 +00:00
}
}
2018-07-03 15:44:29 +00:00
impl WithAttributes for StatsdScope {
2019-04-09 11:55:15 +00:00
fn get_attributes(&self) -> &Attributes {
&self.attributes
}
fn mut_attributes(&mut self) -> &mut Attributes {
&mut self.attributes
}
2017-06-27 10:04:02 +00:00
}
2018-09-11 12:27:10 +00:00
impl Buffered for StatsdScope {}
2017-07-19 20:46:04 +00:00
2018-06-26 16:28:38 +00:00
/// Key of a statsd metric.
#[derive(Debug, Clone)]
pub struct StatsdMetric {
prefix: String,
suffix: String,
2018-12-18 21:06:03 +00:00
scale: isize,
2018-06-26 16:28:38 +00:00
}
2017-07-19 20:46:04 +00:00
2017-09-28 22:17:16 +00:00
/// Any remaining buffered data is flushed on Drop.
2018-07-03 15:44:29 +00:00
impl Drop for StatsdScope {
2017-09-22 21:39:57 +00:00
fn drop(&mut self) {
2018-06-14 16:37:21 +00:00
if let Err(err) = self.flush() {
2018-06-26 16:28:38 +00:00
warn!("Could not flush statsd metrics upon Drop: {}", err)
2018-06-14 16:37:21 +00:00
}
2017-07-22 02:51:49 +00:00
}
}
2020-05-23 03:47:18 +00:00
// use crate::output::format::LineOp::{ScaledValueAsText, ValueAsText};
//
// impl LineFormat for StatsdScope {
// fn template(&self, name: &MetricName, kind: InputKind) -> LineTemplate {
// let mut prefix = name.join(".");
// prefix.push(':');
//
// let mut suffix = String::with_capacity(16);
// suffix.push('|');
// suffix.push_str(match kind {
// InputKind::Marker | InputKind::Counter => "c",
// InputKind::Gauge | InputKind::Level => "g",
// InputKind::Timer => "ms",
// });
//
// // specify sampling rate if any
// if let Sampling::Random(float_rate) = self.get_sampling() {
// suffix.push_str(&format! {"|@{}\n", float_rate});
// }
//
// // scale timer values
// let op_value_text = match kind {
// // timers are in µs, statsd wants ms
// InputKind::Timer => ScaledValueAsText(1000.0),
// _ => ValueAsText,
// };
//
// LineTemplate::new(vec![
// LineOp::Literal(prefix.into_bytes()),
// op_value_text,
// LineOp::Literal(suffix.into_bytes()),
// LineOp::NewLine,
// ])
// }
// }
2018-10-10 18:29:30 +00:00
#[cfg(feature = "bench")]
mod bench {
2019-04-09 11:55:15 +00:00
use super::*;
2020-05-15 04:28:40 +00:00
use crate::attributes::*;
use crate::input::*;
#[bench]
2018-06-26 16:28:38 +00:00
pub fn immediate_statsd(b: &mut test::Bencher) {
let sd = Statsd::send_to("localhost:2003").unwrap().metrics();
2018-10-26 01:20:47 +00:00
let timer = sd.new_metric("timer".into(), InputKind::Timer);
2018-06-26 16:28:38 +00:00
2018-10-12 20:44:39 +00:00
b.iter(|| test::black_box(timer.write(2000, labels![])));
2018-06-26 16:28:38 +00:00
}
#[bench]
pub fn buffering_statsd(b: &mut test::Bencher) {
2019-04-09 11:55:15 +00:00
let sd = Statsd::send_to("localhost:2003")
.unwrap()
.buffered(Buffering::BufferSize(65465))
.metrics();
2018-10-26 01:20:47 +00:00
let timer = sd.new_metric("timer".into(), InputKind::Timer);
2018-10-12 20:44:39 +00:00
b.iter(|| test::black_box(timer.write(2000, labels![])));
}
}