Skip to main content

dogstatsd/
lib.rs

1//! A Rust client for interacting with Dogstatsd
2//!
3//! Dogstatsd is a custom `StatsD` implementation by `DataDog` for sending metrics and events to their
4//! system. Through this client you can report any type of metric you want, tag it, and enjoy your
5//! custom metrics.
6//!
7//! ## Usage
8//!
9//! Build an options struct and create a client:
10//!
11//! ```
12//! use dogstatsd::{Client, Options, OptionsBuilder};
13//! use std::time::Duration;
14//!
15//! // Binds to a udp socket on an available ephemeral port on 127.0.0.1 for
16//! // transmitting, and sends to  127.0.0.1:8125, the default dogstatsd
17//! // address.
18//! let default_options = Options::default();
19//! let default_client = Client::new(default_options).unwrap();
20//!
21//! // Binds to 127.0.0.1:9000 for transmitting and sends to 10.1.2.3:8125, with a
22//! // namespace of "analytics".
23//! let custom_options = Options::new("127.0.0.1:9000", "10.1.2.3:8125", "analytics", vec!(String::new()), None, None);
24//! let custom_client = Client::new(custom_options).unwrap();
25//!
26//! // You can also use the OptionsBuilder API to avoid needing to specify every option.
27//! let built_options = OptionsBuilder::new().from_addr(String::from("127.0.0.1:9001")).build();
28//! let built_client = Client::new(built_options).unwrap();
29//! ```
30//!
31//! Start sending metrics:
32//!
33//! ```
34//! use dogstatsd::{Client, Options, ServiceCheckOptions, ServiceStatus};
35//!
36//! let client = Client::new(Options::default()).unwrap();
37//! let tags = &["env:production"];
38//!
39//! // Increment a counter
40//! client.incr("my_counter", tags).unwrap();
41//!
42//! // Decrement a counter
43//! client.decr("my_counter", tags).unwrap();
44//!
45//! // Time a block of code (reports in ms)
46//! client.time("my_time", tags, || {
47//!     // Some time consuming code
48//! }).unwrap();
49//!
50//! // Report your own timing in ms
51//! client.timing("my_timing", 500, tags).unwrap();
52//!
53//! // Report an arbitrary value (a gauge)
54//! client.gauge("my_gauge", "12345", tags).unwrap();
55//!
56//! // Report a sample of a histogram
57//! client.histogram("my_histogram", "67890", tags).unwrap();
58//!
59//! // Report a sample of a distribution
60//! client.distribution("distribution", "67890", tags).unwrap();
61//!
62//! // Report a member of a set
63//! client.set("my_set", "13579", tags).unwrap();
64//!
65//! // Report a service check
66//! let service_check_options = ServiceCheckOptions {
67//!   hostname: Some("my-host.localhost"),
68//!   ..Default::default()
69//! };
70//! client.service_check("redis.can_connect", ServiceStatus::OK, tags, Some(service_check_options)).unwrap();
71//!
72//! // Send a custom event
73//! client.event("My Custom Event Title", "My Custom Event Body", tags).unwrap();
74//!
75//! use dogstatsd::{EventOptions, EventPriority, EventAlertType};
76//! let event_options = EventOptions::new()
77//!     .with_timestamp(1638480000)
78//!     .with_hostname("localhost")
79//!     .with_priority(EventPriority::Normal)
80//!     .with_alert_type(EventAlertType::Error);
81//! client.event_with_options("My Custom Event Title", "My Custom Event Body", tags, Some(event_options)).unwrap();
82//! ```
83
84#![cfg_attr(feature = "unstable", feature(test))]
85#![deny(
86    warnings,
87    missing_debug_implementations,
88    missing_copy_implementations,
89    missing_docs
90)]
91extern crate chrono;
92
93use chrono::Utc;
94use std::borrow::Cow;
95use std::future::Future;
96use std::net::UdpSocket;
97#[cfg(unix)]
98use std::os::unix::net::UnixDatagram;
99use std::sync::mpsc::Sender;
100use std::sync::{mpsc, Mutex};
101use std::thread;
102use std::time::Duration;
103
104pub use self::error::DogstatsdError;
105use self::metrics::*;
106pub use self::metrics::{EventAlertType, EventPriority, ServiceCheckOptions, ServiceStatus};
107
108mod error;
109mod metrics;
110
111/// A type alias for returning a unit type or an error
112pub type DogstatsdResult = Result<(), DogstatsdError>;
113
114const DEFAULT_FROM_ADDR: &str = "0.0.0.0:0";
115const DEFAULT_TO_ADDR: &str = "127.0.0.1:8125";
116
117/// The struct that represents the options available for the Dogstatsd client.
118#[derive(Debug, PartialEq, Clone, Copy)]
119pub struct BatchingOptions {
120    /// The maximum buffer size in bytes of a batch of events.
121    pub max_buffer_size: usize,
122    /// The maximum time before sending a batch of events.
123    pub max_time: Duration,
124    /// The maximum retry attempts if we fail to flush our buffer
125    pub max_retry_attempts: usize,
126    /// Upon retry, there is an exponential backoff, this value sets the starting value
127    pub initial_retry_delay: u64,
128}
129
130/// The struct that represents the options available for the Dogstatsd client.
131#[derive(Debug, PartialEq)]
132pub struct Options {
133    /// The address of the udp socket we'll bind to for sending.
134    pub from_addr: String,
135    /// The address of the udp socket we'll send metrics and events to.
136    pub to_addr: String,
137    /// A namespace to prefix all metrics with, joined with a '.'.
138    pub namespace: String,
139    /// Default tags to include with every request.
140    pub default_tags: Vec<String>,
141    /// OPTIONAL, if defined, will use UDS instead of UDP and will ignore UDP options
142    pub socket_path: Option<String>,
143    /// OPTIONAL, if defined, will utilize batching for sending metrics
144    pub batching_options: Option<BatchingOptions>,
145}
146
147impl Default for Options {
148    /// Create a new options struct with all the default settings.
149    ///
150    /// # Examples
151    ///
152    /// ```
153    ///   use dogstatsd::Options;
154    ///
155    ///   let options = Options::default();
156    ///
157    ///   assert_eq!(
158    ///       Options {
159    ///           from_addr: "0.0.0.0:0".into(),
160    ///           to_addr: "127.0.0.1:8125".into(),
161    ///           namespace: String::new(),
162    ///           default_tags: vec!(),
163    ///           socket_path: None,
164    ///           batching_options: None,
165    ///       },
166    ///       options
167    ///   )
168    /// ```
169    fn default() -> Self {
170        Options {
171            from_addr: DEFAULT_FROM_ADDR.into(),
172            to_addr: DEFAULT_TO_ADDR.into(),
173            namespace: String::new(),
174            default_tags: vec![],
175            socket_path: None,
176            batching_options: None,
177        }
178    }
179}
180
181impl Options {
182    /// Create a new options struct by supplying values for all fields.
183    ///
184    /// # Examples
185    ///
186    /// ```
187    ///   use dogstatsd::Options;
188    ///
189    ///   let options = Options::new("127.0.0.1:9000", "127.0.0.1:9001", "", vec!(String::new()), None, None);
190    /// ```
191    pub fn new(
192        from_addr: &str,
193        to_addr: &str,
194        namespace: &str,
195        default_tags: Vec<String>,
196        socket_path: Option<String>,
197        batching_options: Option<BatchingOptions>,
198    ) -> Self {
199        Options {
200            from_addr: from_addr.into(),
201            to_addr: to_addr.into(),
202            namespace: namespace.into(),
203            default_tags,
204            socket_path,
205            batching_options,
206        }
207    }
208
209    fn merge_with_system_tags(default_tags: Vec<String>) -> Vec<String> {
210        let mut merged_tags = default_tags;
211
212        if !merged_tags.iter().any(|tag| tag.starts_with("env:")) {
213            if let Ok(env) = std::env::var("DD_ENV") {
214                merged_tags.push(format!("env:{}", env));
215            }
216        }
217        if !merged_tags.iter().any(|tag| tag.starts_with("service:")) {
218            if let Ok(service) = std::env::var("DD_SERVICE") {
219                merged_tags.push(format!("service:{}", service));
220            }
221        }
222        if !merged_tags.iter().any(|tag| tag.starts_with("version:")) {
223            if let Ok(version) = std::env::var("DD_VERSION") {
224                merged_tags.push(format!("version:{}", version));
225            }
226        }
227
228        merged_tags
229    }
230}
231
232/// Struct that allows build an `Options` for available for the Dogstatsd client.
233#[derive(Default, Debug)]
234pub struct OptionsBuilder {
235    /// The address of the udp socket we'll bind to for sending.
236    from_addr: Option<String>,
237    /// The address of the udp socket we'll send metrics and events to.
238    to_addr: Option<String>,
239    /// A namespace to prefix all metrics with, joined with a '.'.
240    namespace: Option<String>,
241    /// Default tags to include with every request.
242    default_tags: Vec<String>,
243    /// OPTIONAL, if defined, will use UDS instead of UDP and will ignore UDP options
244    socket_path: Option<String>,
245    /// OPTIONAL, if defined, will utilize batching for sending metrics
246    batching_options: Option<BatchingOptions>,
247}
248
249impl OptionsBuilder {
250    /// Create a new `OptionsBuilder` struct.
251    ///
252    /// # Examples
253    ///
254    /// ```
255    ///   use dogstatsd::OptionsBuilder;
256    ///
257    ///   let options_builder = OptionsBuilder::new();
258    /// ```
259    pub fn new() -> Self {
260        Self::default()
261    }
262
263    /// Will allow the builder to generate an `Options` struct with the provided value.
264    ///
265    /// # Examples
266    ///
267    /// ```
268    ///   use dogstatsd::OptionsBuilder;
269    ///
270    ///   let options_builder = OptionsBuilder::new().from_addr(String::from("127.0.0.1:9000"));
271    /// ```
272    pub fn from_addr(&mut self, from_addr: String) -> &mut OptionsBuilder {
273        self.from_addr = Some(from_addr);
274        self
275    }
276
277    /// Will allow the builder to generate an `Options` struct with the provided value.
278    ///
279    /// # Examples
280    ///
281    /// ```
282    ///   use dogstatsd::OptionsBuilder;
283    ///
284    ///   let options_builder = OptionsBuilder::new().to_addr(String::from("127.0.0.1:9001"));
285    /// ```
286    pub fn to_addr(&mut self, to_addr: String) -> &mut OptionsBuilder {
287        self.to_addr = Some(to_addr);
288        self
289    }
290
291    /// Will allow the builder to generate an `Options` struct with the provided value.
292    ///
293    /// # Examples
294    ///
295    /// ```
296    ///   use dogstatsd::OptionsBuilder;
297    ///
298    ///   let options_builder = OptionsBuilder::new().namespace(String::from("mynamespace"));
299    /// ```
300    pub fn namespace(&mut self, namespace: String) -> &mut OptionsBuilder {
301        self.namespace = Some(namespace);
302        self
303    }
304
305    /// Will allow the builder to generate an `Options` struct with the provided value. Can be called multiple times to add multiple `default_tags` to the `Options`.
306    ///
307    /// # Examples
308    ///
309    /// ```
310    ///   use dogstatsd::OptionsBuilder;
311    ///
312    ///   let options_builder = OptionsBuilder::new().default_tag(String::from("tag1:tav1val")).default_tag(String::from("tag2:tag2val"));
313    /// ```
314    pub fn default_tag(&mut self, default_tag: String) -> &mut OptionsBuilder {
315        self.default_tags.push(default_tag);
316        self
317    }
318
319    /// Will allow the builder to generate an `Options` struct with the provided value.
320    ///
321    /// # Examples
322    ///
323    /// ```
324    ///   use dogstatsd::OptionsBuilder;
325    ///
326    ///   let options_builder = OptionsBuilder::new().default_tag(String::from("tag1:tav1val")).default_tag(String::from("tag2:tav2val"));
327    /// ```
328    pub fn socket_path(&mut self, socket_path: Option<String>) -> &mut OptionsBuilder {
329        self.socket_path = socket_path;
330        self
331    }
332
333    /// Will allow the builder to generate an `Options` struct with the provided value.
334    ///
335    /// # Examples
336    ///
337    /// ```
338    ///   use dogstatsd::{ OptionsBuilder, BatchingOptions };
339    ///   use std::time::Duration;
340    ///
341    ///   let options_builder = OptionsBuilder::new().batching_options(BatchingOptions { max_buffer_size: 8000, max_time: Duration::from_millis(3000), max_retry_attempts: 3, initial_retry_delay: 10 });
342    /// ```
343    pub fn batching_options(&mut self, batching_options: BatchingOptions) -> &mut OptionsBuilder {
344        self.batching_options = Some(batching_options);
345        self
346    }
347
348    /// Will construct an `Options` with all of the provided values and fall back to the default values if they aren't provided.
349    ///
350    /// # Examples
351    ///
352    /// ```
353    ///   use dogstatsd::OptionsBuilder;
354    ///   use dogstatsd::Options;
355    ///
356    ///   let options = OptionsBuilder::new().namespace(String::from("mynamespace")).default_tag(String::from("tag1:tav1val")).build();
357    ///
358    ///   assert_eq!(
359    ///       Options {
360    ///           from_addr: "0.0.0.0:0".into(),
361    ///           to_addr: "127.0.0.1:8125".into(),
362    ///           namespace: String::from("mynamespace"),
363    ///           default_tags: vec!(String::from("tag1:tav1val")),
364    ///           socket_path: None,
365    ///           batching_options: None,
366    ///       },
367    ///       options
368    ///   )
369    /// ```
370    pub fn build(&self) -> Options {
371        Options::new(
372            self.from_addr
373                .as_ref()
374                .unwrap_or(&String::from(DEFAULT_FROM_ADDR)),
375            self.to_addr
376                .as_ref()
377                .unwrap_or(&String::from(DEFAULT_TO_ADDR)),
378            self.namespace.as_ref().unwrap_or(&String::default()),
379            self.default_tags.to_vec(),
380            self.socket_path.clone(),
381            self.batching_options,
382        )
383    }
384}
385
386#[derive(Debug)]
387enum SocketType {
388    Udp(UdpSocket),
389    #[cfg(unix)]
390    Uds(UnixDatagram),
391    BatchableUdp(Mutex<Sender<batch_processor::Message>>),
392    #[cfg(unix)]
393    BatchableUds(Mutex<Sender<batch_processor::Message>>),
394}
395
396/// The client struct that handles sending metrics to the Dogstatsd server.
397#[derive(Debug)]
398pub struct Client {
399    socket: SocketType,
400    from_addr: String,
401    to_addr: String,
402    namespace: String,
403    default_tags: Vec<u8>,
404}
405
406impl PartialEq for Client {
407    fn eq(&self, other: &Self) -> bool {
408        // Ignore `socket`, which will never be the same
409        self.from_addr == other.from_addr
410            && self.to_addr == other.to_addr
411            && self.namespace == other.namespace
412            && self.default_tags == other.default_tags
413    }
414}
415
416impl Drop for Client {
417    fn drop(&mut self) {
418        match &self.socket {
419            SocketType::BatchableUdp(tx_channel) => {
420                let _ = tx_channel
421                    .lock()
422                    .unwrap()
423                    .send(batch_processor::Message::Shutdown);
424            }
425            #[cfg(unix)]
426            SocketType::BatchableUds(tx_channel) => {
427                let _ = tx_channel
428                    .lock()
429                    .unwrap()
430                    .send(batch_processor::Message::Shutdown);
431            }
432            _ => {}
433        }
434    }
435}
436
437impl Client {
438    /// Create a new client from an options struct.
439    ///
440    /// # Examples
441    ///
442    /// ```
443    ///   use dogstatsd::{Client, Options};
444    ///
445    ///   let client = Client::new(Options::default()).unwrap();
446    /// ```
447    pub fn new(options: Options) -> Result<Self, DogstatsdError> {
448        let fn_create_tx_channel = |socket: SocketType,
449                                    batching_options: BatchingOptions,
450                                    to_addr: String,
451                                    socket_path: Option<String>|
452         -> Mutex<Sender<batch_processor::Message>> {
453            let (tx, rx) = mpsc::channel();
454            thread::spawn(move || {
455                batch_processor::process_events(batching_options, to_addr, socket, socket_path, rx);
456            });
457            Mutex::from(tx)
458        };
459
460        let socket = if options.socket_path.is_some() {
461            #[cfg(unix)]
462            {
463                let socket_path = options.socket_path.clone().expect("checked is_some above");
464
465                // The follow scenarios can occur:
466                // - socket does not exist yet: We will call .bind(...) to create one
467                // - socket exists, but no listener: We will retry attempting to connect
468                //   however, if no listener subscribes to the socket within retries, we will
469                //   fail to initialize
470                // - socket exists, with a listener: Calling .connect(...) will work successfully
471                let mut uds_socket = UnixDatagram::unbound()?;
472                match uds_socket.connect(socket_path.clone()) {
473                    Ok(socket) => socket,
474                    Err(e) => {
475                        println!(
476                            "Couldn't connect to uds socket.. attempting to re-create by binding directly: {e:?}"
477                        );
478                        uds_socket = UnixDatagram::bind(socket_path.clone())?;
479                    }
480                };
481                uds_socket.set_nonblocking(true)?;
482
483                let wrapped_socket = SocketType::Uds(uds_socket);
484                if let Some(batching_options) = options.batching_options {
485                    SocketType::BatchableUds(fn_create_tx_channel(
486                        wrapped_socket,
487                        batching_options,
488                        options.to_addr.clone(),
489                        Some(socket_path),
490                    ))
491                } else {
492                    wrapped_socket
493                }
494            }
495            #[cfg(not(unix))]
496            {
497                return Err(DogstatsdError::from(std::io::Error::new(
498                    std::io::ErrorKind::Unsupported,
499                    "Unix domain sockets are not supported on this platform",
500                )));
501            }
502        } else {
503            let wrapped_socket = SocketType::Udp(UdpSocket::bind(&options.from_addr)?);
504            if let Some(batching_options) = options.batching_options {
505                SocketType::BatchableUdp(fn_create_tx_channel(
506                    wrapped_socket,
507                    batching_options,
508                    options.to_addr.clone(),
509                    None,
510                ))
511            } else {
512                wrapped_socket
513            }
514        };
515
516        let default_tags = Options::merge_with_system_tags(options.default_tags);
517
518        Ok(Client {
519            socket,
520            from_addr: options.from_addr,
521            to_addr: options.to_addr,
522            namespace: options.namespace,
523            default_tags: default_tags.join(",").into_bytes(),
524        })
525    }
526
527    /// Increment a StatsD counter
528    ///
529    /// # Examples
530    ///
531    /// ```
532    ///   use dogstatsd::{Client, Options};
533    ///
534    ///   let client = Client::new(Options::default()).unwrap();
535    ///   client.incr("counter", &["tag:counter"])
536    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
537    /// ```
538    pub fn incr<'a, I, S, T>(&self, stat: S, tags: I) -> DogstatsdResult
539    where
540        I: IntoIterator<Item = T>,
541        S: Into<Cow<'a, str>>,
542        T: AsRef<str>,
543    {
544        self.send(&CountMetric::Incr(stat.into().as_ref(), 1), tags)
545    }
546
547    /// Increment a StatsD counter by the provided amount
548    ///
549    /// # Examples
550    ///
551    /// ```
552    ///   use dogstatsd::{Client, Options};
553    ///
554    ///   let client = Client::new(Options::default()).unwrap();
555    ///   client.incr_by_value("counter", 123, &["tag:counter"])
556    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
557    /// ```
558    pub fn incr_by_value<'a, I, S, T>(&self, stat: S, value: i64, tags: I) -> DogstatsdResult
559    where
560        I: IntoIterator<Item = T>,
561        S: Into<Cow<'a, str>>,
562        T: AsRef<str>,
563    {
564        self.send(&CountMetric::Incr(stat.into().as_ref(), value), tags)
565    }
566
567    /// Decrement a StatsD counter
568    ///
569    /// # Examples
570    ///
571    /// ```
572    ///   use dogstatsd::{Client, Options};
573    ///
574    ///   let client = Client::new(Options::default()).unwrap();
575    ///   client.decr("counter", &["tag:counter"])
576    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
577    /// ```
578    pub fn decr<'a, I, S, T>(&self, stat: S, tags: I) -> DogstatsdResult
579    where
580        I: IntoIterator<Item = T>,
581        S: Into<Cow<'a, str>>,
582        T: AsRef<str>,
583    {
584        self.send(&CountMetric::Decr(stat.into().as_ref(), 1), tags)
585    }
586
587    /// Decrement a StatsD counter by the provided amount
588    ///
589    /// # Examples
590    ///
591    /// ```
592    ///   use dogstatsd::{Client, Options};
593    ///
594    ///   let client = Client::new(Options::default()).unwrap();
595    ///   client.decr_by_value("counter", 23, &["tag:counter"])
596    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
597    /// ```
598    pub fn decr_by_value<'a, I, S, T>(&self, stat: S, value: i64, tags: I) -> DogstatsdResult
599    where
600        I: IntoIterator<Item = T>,
601        S: Into<Cow<'a, str>>,
602        T: AsRef<str>,
603    {
604        self.send(&CountMetric::Decr(stat.into().as_ref(), value), tags)
605    }
606
607    /// Make an arbitrary change to a StatsD counter
608    ///
609    /// # Examples
610    ///
611    /// ```
612    ///   use dogstatsd::{Client, Options};
613    ///
614    ///   let client = Client::new(Options::default()).unwrap();
615    ///   client.count("counter", 42, &["tag:counter"])
616    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
617    /// ```
618    pub fn count<'a, I, S, T>(&self, stat: S, count: i64, tags: I) -> DogstatsdResult
619    where
620        I: IntoIterator<Item = T>,
621        S: Into<Cow<'a, str>>,
622        T: AsRef<str>,
623    {
624        self.send(&CountMetric::Arbitrary(stat.into().as_ref(), count), tags)
625    }
626
627    /// Time how long it takes for a block of code to execute.
628    ///
629    /// # Examples
630    ///
631    /// ```
632    ///   use dogstatsd::{Client, Options};
633    ///   use std::thread;
634    ///   use std::time::Duration;
635    ///
636    ///   let client = Client::new(Options::default()).unwrap();
637    ///   client.time("timer", &["tag:time"], || {
638    ///       thread::sleep(Duration::from_millis(200))
639    ///   }).unwrap_or_else(|(_, e)| println!("Encountered error: {}", e))
640    /// ```
641    pub fn time<'a, F, O, I, S, T>(
642        &self,
643        stat: S,
644        tags: I,
645        block: F,
646    ) -> Result<O, (O, DogstatsdError)>
647    where
648        F: FnOnce() -> O,
649        I: IntoIterator<Item = T>,
650        S: Into<Cow<'a, str>>,
651        T: AsRef<str>,
652    {
653        let start_time = Utc::now();
654        let output = block();
655        let end_time = Utc::now();
656        let stat = stat.into();
657        let metric = TimeMetric::new(stat.as_ref(), &start_time, &end_time);
658        match self.send(&metric, tags) {
659            Ok(()) => Ok(output),
660            Err(error) => Err((output, error)),
661        }
662    }
663
664    /// Time how long it takes for an async block of code to execute.
665    ///
666    /// # Examples
667    ///
668    /// ```
669    ///   use dogstatsd::{Client, Options};
670    ///   use std::thread;
671    ///   use std::time::Duration;
672    ///
673    /// # async fn do_work() {}
674    ///   async fn timer() {
675    ///       let client = Client::new(Options::default()).unwrap();
676    ///       client.async_time("timer", &["tag:time"], do_work)
677    ///       .await
678    ///       .unwrap_or_else(|(_, e)| println!("Encountered error: {}", e))
679    ///   }
680    /// ```
681    pub async fn async_time<'a, Fn, Fut, O, I, S, T>(
682        &self,
683        stat: S,
684        tags: I,
685        block: Fn,
686    ) -> Result<O, (O, DogstatsdError)>
687    where
688        Fn: FnOnce() -> Fut,
689        Fut: Future<Output = O>,
690        I: IntoIterator<Item = T>,
691        S: Into<Cow<'a, str>>,
692        T: AsRef<str>,
693    {
694        let start_time = Utc::now();
695        let output = block().await;
696        let end_time = Utc::now();
697        let stat = stat.into();
698        match self.send(
699            &TimeMetric::new(stat.as_ref(), &start_time, &end_time),
700            tags,
701        ) {
702            Ok(()) => Ok(output),
703            Err(error) => Err((output, error)),
704        }
705    }
706
707    /// Send your own timing metric in milliseconds
708    ///
709    /// # Examples
710    ///
711    /// ```
712    ///   use dogstatsd::{Client, Options};
713    ///
714    ///   let client = Client::new(Options::default()).unwrap();
715    ///   client.timing("timing", 350, &["tag:timing"])
716    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
717    /// ```
718    pub fn timing<'a, I, S, T>(&self, stat: S, ms: i64, tags: I) -> DogstatsdResult
719    where
720        I: IntoIterator<Item = T>,
721        S: Into<Cow<'a, str>>,
722        T: AsRef<str>,
723    {
724        self.send(&TimingMetric::new(stat.into().as_ref(), ms), tags)
725    }
726
727    /// Report an arbitrary value as a gauge
728    ///
729    /// # Examples
730    ///
731    /// ```
732    ///   use dogstatsd::{Client, Options};
733    ///
734    ///   let client = Client::new(Options::default()).unwrap();
735    ///   client.gauge("gauge", "12345", &["tag:gauge"])
736    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
737    /// ```
738    pub fn gauge<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
739    where
740        I: IntoIterator<Item = T>,
741        S: Into<Cow<'a, str>>,
742        SS: Into<Cow<'a, str>>,
743        T: AsRef<str>,
744    {
745        self.send(
746            &GaugeMetric::new(stat.into().as_ref(), val.into().as_ref()),
747            tags,
748        )
749    }
750
751    /// Report a value in a histogram
752    ///
753    /// # Examples
754    ///
755    /// ```
756    ///   use dogstatsd::{Client, Options};
757    ///
758    ///   let client = Client::new(Options::default()).unwrap();
759    ///   client.histogram("histogram", "67890", &["tag:histogram"])
760    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
761    /// ```
762    pub fn histogram<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
763    where
764        I: IntoIterator<Item = T>,
765        S: Into<Cow<'a, str>>,
766        SS: Into<Cow<'a, str>>,
767        T: AsRef<str>,
768    {
769        self.send(
770            &HistogramMetric::new(stat.into().as_ref(), val.into().as_ref()),
771            tags,
772        )
773    }
774
775    /// Report a value in a distribution
776    ///
777    /// # Examples
778    ///
779    /// ```
780    ///   use dogstatsd::{Client, Options};
781    ///
782    ///   let client = Client::new(Options::default()).unwrap();
783    ///   client.distribution("distribution", "67890", &["tag:distribution"])
784    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
785    /// ```
786    pub fn distribution<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
787    where
788        I: IntoIterator<Item = T>,
789        S: Into<Cow<'a, str>>,
790        SS: Into<Cow<'a, str>>,
791        T: AsRef<str>,
792    {
793        self.send(
794            &DistributionMetric::new(stat.into().as_ref(), val.into().as_ref()),
795            tags,
796        )
797    }
798
799    /// Report a value in a set
800    ///
801    /// # Examples
802    ///
803    /// ```
804    ///   use dogstatsd::{Client, Options};
805    ///
806    ///   let client = Client::new(Options::default()).unwrap();
807    ///   client.set("set", "13579", &["tag:set"])
808    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
809    /// ```
810    pub fn set<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
811    where
812        I: IntoIterator<Item = T>,
813        S: Into<Cow<'a, str>>,
814        SS: Into<Cow<'a, str>>,
815        T: AsRef<str>,
816    {
817        self.send(
818            &SetMetric::new(stat.into().as_ref(), val.into().as_ref()),
819            tags,
820        )
821    }
822
823    /// Report the status of a service
824    ///
825    /// # Examples
826    ///
827    /// ```
828    ///   use dogstatsd::{Client, Options, ServiceStatus, ServiceCheckOptions};
829    ///
830    ///   let client = Client::new(Options::default()).unwrap();
831    ///   client.service_check("redis.can_connect", ServiceStatus::OK, &["tag:service"], None)
832    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
833    ///
834    ///   let options = ServiceCheckOptions {
835    ///     hostname: Some("my-host.localhost"),
836    ///     ..Default::default()
837    ///   };
838    ///   client.service_check("redis.can_connect", ServiceStatus::OK, &["tag:service"], Some(options))
839    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
840    ///
841    ///   let all_options = ServiceCheckOptions {
842    ///     hostname: Some("my-host.localhost"),
843    ///     timestamp: Some(1510326433),
844    ///     message: Some("Message about the check or service")
845    ///   };
846    ///   client.service_check("redis.can_connect", ServiceStatus::OK, &["tag:service"], Some(all_options))
847    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
848    /// ```
849    pub fn service_check<'a, I, S, T>(
850        &self,
851        stat: S,
852        val: ServiceStatus,
853        tags: I,
854        options: Option<ServiceCheckOptions>,
855    ) -> DogstatsdResult
856    where
857        I: IntoIterator<Item = T>,
858        S: Into<Cow<'a, str>>,
859        T: AsRef<str>,
860    {
861        let unwrapped_options = options.unwrap_or_default();
862        self.send(
863            &ServiceCheck::new(stat.into().as_ref(), val, unwrapped_options),
864            tags,
865        )
866    }
867
868    /// Send a custom event as a title and a body
869    ///
870    /// # Examples
871    ///
872    /// ```
873    ///   use dogstatsd::{Client, Options};
874    ///
875    ///   let client = Client::new(Options::default()).unwrap();
876    ///   client.event("Event Title", "Event Body", &["tag:event"])
877    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
878    ///
879    /// ```
880    pub fn event<'a, I, S, SS, T>(&self, title: S, text: SS, tags: I) -> DogstatsdResult
881    where
882        I: IntoIterator<Item = T>,
883        S: Into<Cow<'a, str>>,
884        SS: Into<Cow<'a, str>>,
885        T: AsRef<str>,
886    {
887        self.send(
888            &Event::new(title.into().as_ref(), text.into().as_ref()),
889            tags,
890        )
891    }
892
893    /// Send a custom event as a title and a body
894    ///
895    /// # Examples
896    ///
897    /// ```
898    ///   use dogstatsd::{Client, Options, EventOptions, EventAlertType, EventPriority};
899    ///
900    ///   let client = Client::new(Options::default()).unwrap();
901    ///   let event_options = EventOptions::new()
902    ///     .with_timestamp(1638480000)
903    ///     .with_hostname("localhost")
904    ///     .with_priority(EventPriority::Normal)
905    ///     .with_alert_type(EventAlertType::Error);
906    ///   client.event_with_options("My Custom Event Title", "My Custom Event Body", &["tag:event"], Some(event_options))
907    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
908    pub fn event_with_options<'a, I, S, SS, T>(
909        &self,
910        title: S,
911        text: SS,
912        tags: I,
913        options: Option<EventOptions<'a>>,
914    ) -> DogstatsdResult
915    where
916        I: IntoIterator<Item = T>,
917        S: Into<Cow<'a, str>>,
918        SS: Into<Cow<'a, str>>,
919        T: AsRef<str>,
920    {
921        let title_owned = title.into();
922        let text_owned = text.into();
923        let mut event = Event::new(title_owned.as_ref(), text_owned.as_ref());
924
925        // Apply additional options if provided
926        if let Some(options) = options {
927            if let Some(timestamp) = options.timestamp {
928                event = event.with_timestamp(timestamp);
929            }
930            if let Some(hostname) = options.hostname {
931                event = event.with_hostname(hostname);
932            }
933            if let Some(aggregation_key) = options.aggregation_key {
934                event = event.with_aggregation_key(aggregation_key);
935            }
936            if let Some(priority) = options.priority {
937                event = event.with_priority(priority);
938            }
939            if let Some(source_type_name) = options.source_type_name {
940                event = event.with_source_type_name(source_type_name);
941            }
942            if let Some(alert_type) = options.alert_type {
943                event = event.with_alert_type(alert_type);
944            }
945        }
946
947        self.send(&event, tags)
948    }
949
950    fn send<I, M, S>(&self, metric: &M, tags: I) -> DogstatsdResult
951    where
952        I: IntoIterator<Item = S>,
953        M: Metric,
954        S: AsRef<str>,
955    {
956        let formatted_metric = format_for_send(metric, &self.namespace, tags, &self.default_tags);
957        match &self.socket {
958            SocketType::Udp(socket) => {
959                socket.send_to(formatted_metric.as_slice(), &self.to_addr)?;
960            }
961            #[cfg(unix)]
962            SocketType::Uds(socket) => {
963                socket.send(formatted_metric.as_slice())?;
964            }
965            SocketType::BatchableUdp(tx_channel) => {
966                tx_channel
967                    .lock()
968                    .expect("Mutex poisoned...")
969                    .send(batch_processor::Message::Data(formatted_metric))
970                    .unwrap_or_else(|error| {
971                        println!("Exception occurred when writing to channel: {:?}", error);
972                    });
973            }
974            #[cfg(unix)]
975            SocketType::BatchableUds(tx_channel) => {
976                tx_channel
977                    .lock()
978                    .expect("Mutex poisoned...")
979                    .send(batch_processor::Message::Data(formatted_metric))
980                    .unwrap_or_else(|error| {
981                        println!("Exception occurred when writing to channel: {:?}", error);
982                    });
983            }
984        }
985        Ok(())
986    }
987}
988
989/// Configuration options for an `Event`.
990///
991/// `EventOptions` provides additional optional metadata that can be attached
992/// to an event, enabling greater flexibility and contextual information
993/// for event handling and monitoring systems.
994///
995/// # Example
996///
997/// ```rust
998/// use dogstatsd::{EventOptions, EventAlertType, EventPriority};
999///
1000/// let options = EventOptions {
1001///     timestamp: Some(1638480000),
1002///     hostname: Some("localhost"),
1003///     aggregation_key: Some("service_down"),
1004///     priority: Some(EventPriority::Normal),
1005///     source_type_name: Some("monitoring"),
1006///     alert_type: Some(EventAlertType::Error),
1007/// };
1008/// ```
1009///
1010#[derive(Default, Clone, Copy, Debug)]
1011pub struct EventOptions<'a> {
1012    /// Optional Unix timestamp representing the event time. The default is the current Unix epoch timestamp.
1013    pub timestamp: Option<u64>,
1014    /// Optional hostname associated with the event.
1015    pub hostname: Option<&'a str>,
1016    /// Optional key for grouping related events.
1017    pub aggregation_key: Option<&'a str>,
1018    /// Optional priority level of the event, e.g., `"low"` or `"normal"`.
1019    pub priority: Option<EventPriority>,
1020    /// Optional source type name of the event, e.g., `"monitoring"`.
1021    pub source_type_name: Option<&'a str>,
1022    /// Optional alert type for the event, e.g., `"error"`, `"warning"`,  `"info"`, `"success"`. Default `"info"`.
1023    pub alert_type: Option<EventAlertType>,
1024}
1025
1026impl<'a> EventOptions<'a> {
1027    /// Creates a new `EventOptions` instance with all fields set to `None`.
1028    pub fn new() -> Self {
1029        EventOptions {
1030            timestamp: None,
1031            hostname: None,
1032            aggregation_key: None,
1033            priority: None,
1034            source_type_name: None,
1035            alert_type: None,
1036        }
1037    }
1038    /// Sets the `hostname` for the event.
1039    pub fn with_timestamp(mut self, timestamp: u64) -> Self {
1040        self.timestamp = Some(timestamp);
1041        self
1042    }
1043
1044    /// Sets the `hostname` for the event.
1045    pub fn with_hostname(mut self, hostname: &'a str) -> Self {
1046        self.hostname = Some(hostname);
1047        self
1048    }
1049
1050    /// Sets the `aggregation_key` for the event.
1051    pub fn with_aggregation_key(mut self, aggregation_key: &'a str) -> Self {
1052        self.aggregation_key = Some(aggregation_key);
1053        self
1054    }
1055
1056    /// Sets the `priority` for the event.
1057    pub fn with_priority(mut self, priority: EventPriority) -> Self {
1058        self.priority = Some(priority);
1059        self
1060    }
1061
1062    /// Sets the `source_type_name` for the event.
1063    pub fn with_source_type_name(mut self, source_type_name: &'a str) -> Self {
1064        self.source_type_name = Some(source_type_name);
1065        self
1066    }
1067
1068    /// Sets the `alert_type` for the event.
1069    pub fn with_alert_type(mut self, alert_type: EventAlertType) -> Self {
1070        self.alert_type = Some(alert_type);
1071        self
1072    }
1073}
1074
1075mod batch_processor {
1076    use std::sync::mpsc::{Receiver, RecvTimeoutError};
1077    use std::time::{Duration, Instant};
1078
1079    use retry::{delay::jitter, delay::Exponential, retry};
1080
1081    use crate::{BatchingOptions, SocketType};
1082
1083    pub(crate) enum Message {
1084        Data(Vec<u8>),
1085        Shutdown,
1086    }
1087
1088    fn send_to_socket_with_retries(
1089        batching_options: &BatchingOptions,
1090        socket: &SocketType,
1091        data: &Vec<u8>,
1092        to_addr: &String,
1093        _socket_path: &Option<String>,
1094    ) {
1095        retry(
1096            Exponential::from_millis(batching_options.initial_retry_delay)
1097                .map(jitter)
1098                .take(batching_options.max_retry_attempts),
1099            || -> Result<(), std::io::Error> {
1100                match socket {
1101                    SocketType::Udp(socket) => {
1102                        socket.send_to(data.as_slice(), to_addr)?;
1103                    }
1104                    #[cfg(unix)]
1105                    SocketType::Uds(socket) => {
1106                        if let Err(error) = socket.send(data.as_slice()) {
1107                            // Per https://doc.rust-lang.org/stable/std/os/unix/net/struct.UnixDatagram.html#method.send
1108                            // If send fails, it is due to a connection issue, so just attempt
1109                            // to reconnect
1110                            let socket_path_unwrapped = _socket_path
1111                                .as_ref()
1112                                .expect("Only invoked if socket path is defined.");
1113                            socket.connect(socket_path_unwrapped)?;
1114
1115                            return Err(error);
1116                        }
1117                    }
1118                    SocketType::BatchableUdp(_tx_channel) => {
1119                        panic!("Logic Error - socket type should not be batchable.");
1120                    }
1121                    #[cfg(unix)]
1122                    SocketType::BatchableUds(_tx_channel) => {
1123                        panic!("Logic Error - socket type should not be batchable.");
1124                    }
1125                }
1126
1127                Ok(())
1128            },
1129        )
1130        .unwrap_or_else(|error| {
1131            println!(
1132                "Failed to send within retry policy... Dropping metrics: {:?}",
1133                error
1134            )
1135        });
1136    }
1137
1138    fn flush_buffer(
1139        batching_options: &BatchingOptions,
1140        socket: &SocketType,
1141        buffer: &mut Vec<u8>,
1142        to_addr: &String,
1143        socket_path: &Option<String>,
1144    ) {
1145        if buffer.is_empty() {
1146            return;
1147        }
1148
1149        send_to_socket_with_retries(batching_options, socket, buffer, to_addr, socket_path);
1150        buffer.clear();
1151    }
1152
1153    pub(crate) fn process_events(
1154        batching_options: BatchingOptions,
1155        to_addr: String,
1156        socket: SocketType,
1157        socket_path: Option<String>,
1158        rx: Receiver<Message>,
1159    ) {
1160        let mut buffer: Vec<u8> = vec![];
1161        let mut batch_started_at: Option<Instant> = None;
1162
1163        loop {
1164            let message = match batch_started_at {
1165                Some(started_at) => match rx
1166                    .recv_timeout(remaining_batch_time(started_at, batching_options.max_time))
1167                {
1168                    Ok(message) => Ok(message),
1169                    Err(RecvTimeoutError::Timeout) => {
1170                        flush_buffer(
1171                            &batching_options,
1172                            &socket,
1173                            &mut buffer,
1174                            &to_addr,
1175                            &socket_path,
1176                        );
1177                        batch_started_at = None;
1178                        continue;
1179                    }
1180                    Err(RecvTimeoutError::Disconnected) => Err(RecvTimeoutError::Disconnected),
1181                },
1182                None => rx.recv().map_err(|_| RecvTimeoutError::Disconnected),
1183            };
1184
1185            match message {
1186                Ok(Message::Data(data)) => {
1187                    let next_buffer_size = buffer.len() + data.len() + 1;
1188
1189                    if !buffer.is_empty() && next_buffer_size > batching_options.max_buffer_size {
1190                        flush_buffer(
1191                            &batching_options,
1192                            &socket,
1193                            &mut buffer,
1194                            &to_addr,
1195                            &socket_path,
1196                        );
1197                        batch_started_at = None;
1198                    }
1199
1200                    if buffer.is_empty() {
1201                        batch_started_at = Some(Instant::now());
1202                    }
1203
1204                    buffer.extend(data);
1205                    buffer.push(b'\n');
1206
1207                    if buffer.len() >= batching_options.max_buffer_size {
1208                        flush_buffer(
1209                            &batching_options,
1210                            &socket,
1211                            &mut buffer,
1212                            &to_addr,
1213                            &socket_path,
1214                        );
1215                        batch_started_at = None;
1216                    }
1217                }
1218                Ok(Message::Shutdown) => {
1219                    flush_buffer(
1220                        &batching_options,
1221                        &socket,
1222                        &mut buffer,
1223                        &to_addr,
1224                        &socket_path,
1225                    );
1226                    break;
1227                }
1228                Err(e) => {
1229                    println!("Exception occurred when reading from channel: {:?}", e);
1230                    break;
1231                }
1232            }
1233        }
1234    }
1235
1236    fn remaining_batch_time(started_at: Instant, max_time: Duration) -> Duration {
1237        max_time
1238            .checked_sub(started_at.elapsed())
1239            .unwrap_or(Duration::ZERO)
1240    }
1241}
1242
1243#[cfg(test)]
1244mod tests {
1245    use metrics::GaugeMetric;
1246    use serial_test::serial;
1247
1248    use super::*;
1249
1250    #[test]
1251    fn test_options_default() {
1252        let options = Options::default();
1253        let expected_options = Options {
1254            from_addr: DEFAULT_FROM_ADDR.into(),
1255            to_addr: DEFAULT_TO_ADDR.into(),
1256            namespace: String::new(),
1257            ..Default::default()
1258        };
1259
1260        assert_eq!(expected_options, options)
1261    }
1262
1263    #[test]
1264    fn test_options_builder_none() {
1265        let options = OptionsBuilder::new().build();
1266        let expected_options = Options {
1267            from_addr: DEFAULT_FROM_ADDR.into(),
1268            to_addr: DEFAULT_TO_ADDR.into(),
1269            namespace: String::new(),
1270            ..Default::default()
1271        };
1272
1273        assert_eq!(expected_options, options);
1274    }
1275
1276    #[test]
1277    fn teset_options_builder_all() {
1278        let options = OptionsBuilder::new()
1279            .from_addr("127.0.0.2:0".into())
1280            .to_addr("127.0.0.2:8125".into())
1281            .namespace("mynamespace".into())
1282            .default_tag(String::from("tag1:tag1val"))
1283            .build();
1284        let expected_options = Options {
1285            from_addr: "127.0.0.2:0".into(),
1286            to_addr: "127.0.0.2:8125".into(),
1287            namespace: "mynamespace".into(),
1288            default_tags: ["tag1:tag1val".into()].to_vec(),
1289            socket_path: None,
1290            batching_options: None,
1291        };
1292
1293        assert_eq!(expected_options, options);
1294    }
1295
1296    #[test]
1297    #[serial]
1298    fn test_new() {
1299        let client = Client::new(Options::default()).unwrap();
1300        let expected_client = Client {
1301            socket: SocketType::Udp(UdpSocket::bind(DEFAULT_FROM_ADDR).unwrap()),
1302            from_addr: DEFAULT_FROM_ADDR.into(),
1303            to_addr: DEFAULT_TO_ADDR.into(),
1304            namespace: String::new(),
1305            default_tags: String::new().into_bytes(),
1306        };
1307
1308        assert_eq!(expected_client, client)
1309    }
1310
1311    #[test]
1312    #[serial]
1313    fn test_new_default_tags() {
1314        let options = Options::new(
1315            DEFAULT_FROM_ADDR,
1316            DEFAULT_TO_ADDR,
1317            "",
1318            vec![String::from("tag1:tag1val")],
1319            None,
1320            None,
1321        );
1322        let client = Client::new(options).unwrap();
1323        let expected_client = Client {
1324            socket: SocketType::Udp(UdpSocket::bind(DEFAULT_FROM_ADDR).unwrap()),
1325            from_addr: DEFAULT_FROM_ADDR.into(),
1326            to_addr: DEFAULT_TO_ADDR.into(),
1327            namespace: String::new(),
1328            default_tags: String::from("tag1:tag1val").into_bytes(),
1329        };
1330
1331        assert_eq!(expected_client, client)
1332    }
1333
1334    #[test]
1335    #[serial]
1336    fn test_system_tags() {
1337        let options = Options::new(
1338            DEFAULT_FROM_ADDR,
1339            DEFAULT_TO_ADDR,
1340            "",
1341            vec![String::from("tag1:tag1val"), String::from("version:0.0.2")],
1342            None,
1343            None,
1344        );
1345
1346        let client = with_default_system_tags(|| Client::new(options).unwrap());
1347
1348        dbg!(String::from_utf8_lossy(client.default_tags.as_ref()));
1349
1350        let expected_client = Client {
1351            socket: SocketType::Udp(UdpSocket::bind(DEFAULT_FROM_ADDR).unwrap()),
1352            from_addr: DEFAULT_FROM_ADDR.into(),
1353            to_addr: DEFAULT_TO_ADDR.into(),
1354            namespace: String::new(),
1355            default_tags: String::from("tag1:tag1val,version:0.0.2,env:production,service:service")
1356                .into_bytes(),
1357        };
1358
1359        assert_eq!(expected_client, client)
1360    }
1361
1362    #[test]
1363    fn test_send() {
1364        let options = Options::new("127.0.0.1:9001", "127.0.0.1:9002", "", vec![], None, None);
1365        let client = Client::new(options).unwrap();
1366        // Shouldn't panic or error
1367        client
1368            .send(&GaugeMetric::new("gauge", "1234"), ["tag1", "tag2"])
1369            .unwrap();
1370    }
1371
1372    fn with_default_system_tags<T, F: FnOnce() -> T>(f: F) -> T {
1373        unsafe {
1374            std::env::set_var("DD_ENV", "production");
1375            std::env::set_var("DD_SERVICE", "service");
1376            std::env::set_var("DD_VERSION", "0.0.1");
1377        }
1378        let t = f();
1379        unsafe {
1380            std::env::remove_var("DD_ENV");
1381            std::env::remove_var("DD_SERVICE");
1382            std::env::remove_var("DD_VERSION");
1383        }
1384        t
1385    }
1386}
1387
1388#[cfg(all(feature = "unstable", test))]
1389mod bench {
1390    extern crate test;
1391
1392    use super::*;
1393
1394    use self::test::Bencher;
1395
1396    #[bench]
1397    fn bench_incr(b: &mut Bencher) {
1398        let options = Options::default();
1399        let client = Client::new(options).unwrap();
1400        let tags = &["name1:value1"];
1401        b.iter(|| {
1402            client.incr("bench.incr", tags).unwrap();
1403        })
1404    }
1405
1406    #[bench]
1407    fn bench_decr(b: &mut Bencher) {
1408        let options = Options::default();
1409        let client = Client::new(options).unwrap();
1410        let tags = &["name1:value1"];
1411        b.iter(|| {
1412            client.decr("bench.decr", tags).unwrap();
1413        })
1414    }
1415
1416    #[bench]
1417    fn bench_count(b: &mut Bencher) {
1418        let options = Options::default();
1419        let client = Client::new(options).unwrap();
1420        let tags = &["name1:value1"];
1421        let mut i = 0;
1422        b.iter(|| {
1423            client.count("bench.count", i, tags).unwrap();
1424            i += 1;
1425        })
1426    }
1427
1428    #[bench]
1429    fn bench_timing(b: &mut Bencher) {
1430        let options = Options::default();
1431        let client = Client::new(options).unwrap();
1432        let tags = &["name1:value1"];
1433        let mut i = 0;
1434        b.iter(|| {
1435            client.timing("bench.timing", i, tags).unwrap();
1436            i += 1;
1437        })
1438    }
1439
1440    #[bench]
1441    fn bench_gauge(b: &mut Bencher) {
1442        let options = Options::default();
1443        let client = Client::new(options).unwrap();
1444        let tags = vec!["name1:value1"];
1445        let mut i = 0;
1446        b.iter(|| {
1447            client.gauge("bench.guage", &i.to_string(), &tags).unwrap();
1448            i += 1;
1449        })
1450    }
1451
1452    #[bench]
1453    fn bench_histogram(b: &mut Bencher) {
1454        let options = Options::default();
1455        let client = Client::new(options).unwrap();
1456        let tags = vec!["name1:value1"];
1457        let mut i = 0;
1458        b.iter(|| {
1459            client
1460                .histogram("bench.histogram", &i.to_string(), &tags)
1461                .unwrap();
1462            i += 1;
1463        })
1464    }
1465
1466    #[bench]
1467    fn bench_distribution(b: &mut Bencher) {
1468        let options = Options::default();
1469        let client = Client::new(options).unwrap();
1470        let tags = vec!["name1:value1"];
1471        let mut i = 0;
1472        b.iter(|| {
1473            client
1474                .distribution("bench.distribution", &i.to_string(), &tags)
1475                .unwrap();
1476            i += 1;
1477        })
1478    }
1479
1480    #[bench]
1481    fn bench_set(b: &mut Bencher) {
1482        let options = Options::default();
1483        let client = Client::new(options).unwrap();
1484        let tags = vec!["name1:value1"];
1485        let mut i = 0;
1486        b.iter(|| {
1487            client.set("bench.set", &i.to_string(), &tags).unwrap();
1488            i += 1;
1489        })
1490    }
1491
1492    #[bench]
1493    fn bench_service_check(b: &mut Bencher) {
1494        let options = Options::default();
1495        let client = Client::new(options).unwrap();
1496        let tags = vec!["name1:value1"];
1497        let all_options = ServiceCheckOptions {
1498            hostname: Some("my-host.localhost"),
1499            timestamp: Some(1510326433),
1500            message: Some("Message about the check or service"),
1501        };
1502        b.iter(|| {
1503            client
1504                .service_check(
1505                    "bench.service_check",
1506                    ServiceStatus::Critical,
1507                    &tags,
1508                    Some(all_options),
1509                )
1510                .unwrap();
1511        })
1512    }
1513
1514    #[bench]
1515    fn bench_event(b: &mut Bencher) {
1516        let options = Options::default();
1517        let client = Client::new(options).unwrap();
1518        let tags = vec!["name1:value1"];
1519        b.iter(|| {
1520            client
1521                .event("Test Event Title", "Test Event Message", &tags, None)
1522                .unwrap();
1523        })
1524    }
1525
1526    fn bench_event_options(b: &mut Bencher) {
1527        let options = Options::default();
1528        let client = Client::new(options).unwrap();
1529        let tags = vec!["name1:value1"];
1530        let event_options = EventOptions::new()
1531            .with_timestamp(1638480000)
1532            .with_hostname("localhost")
1533            .with_priority(EventPriority::Normal)
1534            .with_alert_type(EventAlertType::Error);
1535
1536        b.iter(|| {
1537            client
1538                .event(
1539                    "Test Event Title",
1540                    "Test Event Message",
1541                    &tags,
1542                    Some(event_options),
1543                )
1544                .unwrap();
1545        })
1546    }
1547}