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;
|
2017-09-20 17:13:44 +00:00
|
|
|
|
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;
|
2017-12-11 21:39:17 +00:00
|
|
|
|
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;
|
2017-09-20 17:13:44 +00:00
|
|
|
|
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
|
|
|
|
2019-01-26 16:53:38 +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)
|
2017-12-11 21:39:17 +00:00
|
|
|
}
|
|
|
|
})
|
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);
|
|
|
|
}
|
|
|
|
|
2019-01-26 16:53:38 +00:00
|
|
|
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
|
|
|
|
2017-12-06 15:56:20 +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::*;
|
2017-12-06 15:56:20 +00:00
|
|
|
|
|
|
|
#[bench]
|
2018-06-26 16:28:38 +00:00
|
|
|
pub fn immediate_statsd(b: &mut test::Bencher) {
|
2019-01-21 21:07:22 +00:00
|
|
|
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);
|
2017-12-06 15:56:20 +00:00
|
|
|
|
2018-10-12 20:44:39 +00:00
|
|
|
b.iter(|| test::black_box(timer.write(2000, labels![])));
|
2017-12-06 15:56:20 +00:00
|
|
|
}
|
|
|
|
}
|