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
464                    .socket_path
465                    .clone()
466                    .expect("checked is_some above");
467
468                // The follow scenarios can occur:
469                // - socket does not exist yet: We will call .bind(...) to create one
470                // - socket exists, but no listener: We will retry attempting to connect
471                //   however, if no listener subscribes to the socket within retries, we will
472                //   fail to initialize
473                // - socket exists, with a listener: Calling .connect(...) will work successfully
474                let mut uds_socket = UnixDatagram::unbound()?;
475                match uds_socket.connect(socket_path.clone()) {
476                    Ok(socket) => socket,
477                    Err(e) => {
478                        println!(
479                            "Couldn't connect to uds socket.. attempting to re-create by binding directly: {e:?}"
480                        );
481                        uds_socket = UnixDatagram::bind(socket_path.clone())?;
482                    }
483                };
484                uds_socket.set_nonblocking(true)?;
485
486                let wrapped_socket = SocketType::Uds(uds_socket);
487                if let Some(batching_options) = options.batching_options {
488                    SocketType::BatchableUds(fn_create_tx_channel(
489                        wrapped_socket,
490                        batching_options,
491                        options.to_addr.clone(),
492                        Some(socket_path),
493                    ))
494                } else {
495                    wrapped_socket
496                }
497            }
498            #[cfg(not(unix))]
499            {
500                return Err(DogstatsdError::from(std::io::Error::new(
501                    std::io::ErrorKind::Unsupported,
502                    "Unix domain sockets are not supported on this platform",
503                )));
504            }
505        } else {
506            let wrapped_socket = SocketType::Udp(UdpSocket::bind(&options.from_addr)?);
507            if let Some(batching_options) = options.batching_options {
508                SocketType::BatchableUdp(fn_create_tx_channel(
509                    wrapped_socket,
510                    batching_options,
511                    options.to_addr.clone(),
512                    None,
513                ))
514            } else {
515                wrapped_socket
516            }
517        };
518
519        let default_tags = Options::merge_with_system_tags(options.default_tags);
520
521        Ok(Client {
522            socket,
523            from_addr: options.from_addr,
524            to_addr: options.to_addr,
525            namespace: options.namespace,
526            default_tags: default_tags.join(",").into_bytes(),
527        })
528    }
529
530    /// Increment a StatsD counter
531    ///
532    /// # Examples
533    ///
534    /// ```
535    ///   use dogstatsd::{Client, Options};
536    ///
537    ///   let client = Client::new(Options::default()).unwrap();
538    ///   client.incr("counter", &["tag:counter"])
539    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
540    /// ```
541    pub fn incr<'a, I, S, T>(&self, stat: S, tags: I) -> DogstatsdResult
542    where
543        I: IntoIterator<Item = T>,
544        S: Into<Cow<'a, str>>,
545        T: AsRef<str>,
546    {
547        self.send(&CountMetric::Incr(stat.into().as_ref(), 1), tags)
548    }
549
550    /// Increment a StatsD counter by the provided amount
551    ///
552    /// # Examples
553    ///
554    /// ```
555    ///   use dogstatsd::{Client, Options};
556    ///
557    ///   let client = Client::new(Options::default()).unwrap();
558    ///   client.incr_by_value("counter", 123, &["tag:counter"])
559    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
560    /// ```
561    pub fn incr_by_value<'a, I, S, T>(&self, stat: S, value: i64, tags: I) -> DogstatsdResult
562    where
563        I: IntoIterator<Item = T>,
564        S: Into<Cow<'a, str>>,
565        T: AsRef<str>,
566    {
567        self.send(&CountMetric::Incr(stat.into().as_ref(), value), tags)
568    }
569
570    /// Decrement a StatsD counter
571    ///
572    /// # Examples
573    ///
574    /// ```
575    ///   use dogstatsd::{Client, Options};
576    ///
577    ///   let client = Client::new(Options::default()).unwrap();
578    ///   client.decr("counter", &["tag:counter"])
579    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
580    /// ```
581    pub fn decr<'a, I, S, T>(&self, stat: S, tags: I) -> DogstatsdResult
582    where
583        I: IntoIterator<Item = T>,
584        S: Into<Cow<'a, str>>,
585        T: AsRef<str>,
586    {
587        self.send(&CountMetric::Decr(stat.into().as_ref(), 1), tags)
588    }
589
590    /// Decrement a StatsD counter by the provided amount
591    ///
592    /// # Examples
593    ///
594    /// ```
595    ///   use dogstatsd::{Client, Options};
596    ///
597    ///   let client = Client::new(Options::default()).unwrap();
598    ///   client.decr_by_value("counter", 23, &["tag:counter"])
599    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
600    /// ```
601    pub fn decr_by_value<'a, I, S, T>(&self, stat: S, value: i64, tags: I) -> DogstatsdResult
602    where
603        I: IntoIterator<Item = T>,
604        S: Into<Cow<'a, str>>,
605        T: AsRef<str>,
606    {
607        self.send(&CountMetric::Decr(stat.into().as_ref(), value), tags)
608    }
609
610    /// Make an arbitrary change to a StatsD counter
611    ///
612    /// # Examples
613    ///
614    /// ```
615    ///   use dogstatsd::{Client, Options};
616    ///
617    ///   let client = Client::new(Options::default()).unwrap();
618    ///   client.count("counter", 42, &["tag:counter"])
619    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
620    /// ```
621    pub fn count<'a, I, S, T>(&self, stat: S, count: i64, tags: I) -> DogstatsdResult
622    where
623        I: IntoIterator<Item = T>,
624        S: Into<Cow<'a, str>>,
625        T: AsRef<str>,
626    {
627        self.send(&CountMetric::Arbitrary(stat.into().as_ref(), count), tags)
628    }
629
630    /// Time how long it takes for a block of code to execute.
631    ///
632    /// # Examples
633    ///
634    /// ```
635    ///   use dogstatsd::{Client, Options};
636    ///   use std::thread;
637    ///   use std::time::Duration;
638    ///
639    ///   let client = Client::new(Options::default()).unwrap();
640    ///   client.time("timer", &["tag:time"], || {
641    ///       thread::sleep(Duration::from_millis(200))
642    ///   }).unwrap_or_else(|(_, e)| println!("Encountered error: {}", e))
643    /// ```
644    pub fn time<'a, F, O, I, S, T>(
645        &self,
646        stat: S,
647        tags: I,
648        block: F,
649    ) -> Result<O, (O, DogstatsdError)>
650    where
651        F: FnOnce() -> O,
652        I: IntoIterator<Item = T>,
653        S: Into<Cow<'a, str>>,
654        T: AsRef<str>,
655    {
656        let start_time = Utc::now();
657        let output = block();
658        let end_time = Utc::now();
659        let stat = stat.into();
660        let metric = TimeMetric::new(stat.as_ref(), &start_time, &end_time);
661        match self.send(&metric, tags) {
662            Ok(()) => Ok(output),
663            Err(error) => Err((output, error)),
664        }
665    }
666
667    /// Time how long it takes for an async block of code to execute.
668    ///
669    /// # Examples
670    ///
671    /// ```
672    ///   use dogstatsd::{Client, Options};
673    ///   use std::thread;
674    ///   use std::time::Duration;
675    ///
676    /// # async fn do_work() {}
677    ///   async fn timer() {
678    ///       let client = Client::new(Options::default()).unwrap();
679    ///       client.async_time("timer", &["tag:time"], do_work)
680    ///       .await
681    ///       .unwrap_or_else(|(_, e)| println!("Encountered error: {}", e))
682    ///   }
683    /// ```
684    pub async fn async_time<'a, Fn, Fut, O, I, S, T>(
685        &self,
686        stat: S,
687        tags: I,
688        block: Fn,
689    ) -> Result<O, (O, DogstatsdError)>
690    where
691        Fn: FnOnce() -> Fut,
692        Fut: Future<Output = O>,
693        I: IntoIterator<Item = T>,
694        S: Into<Cow<'a, str>>,
695        T: AsRef<str>,
696    {
697        let start_time = Utc::now();
698        let output = block().await;
699        let end_time = Utc::now();
700        let stat = stat.into();
701        match self.send(
702            &TimeMetric::new(stat.as_ref(), &start_time, &end_time),
703            tags,
704        ) {
705            Ok(()) => Ok(output),
706            Err(error) => Err((output, error)),
707        }
708    }
709
710    /// Send your own timing metric in milliseconds
711    ///
712    /// # Examples
713    ///
714    /// ```
715    ///   use dogstatsd::{Client, Options};
716    ///
717    ///   let client = Client::new(Options::default()).unwrap();
718    ///   client.timing("timing", 350, &["tag:timing"])
719    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
720    /// ```
721    pub fn timing<'a, I, S, T>(&self, stat: S, ms: i64, tags: I) -> DogstatsdResult
722    where
723        I: IntoIterator<Item = T>,
724        S: Into<Cow<'a, str>>,
725        T: AsRef<str>,
726    {
727        self.send(&TimingMetric::new(stat.into().as_ref(), ms), tags)
728    }
729
730    /// Report an arbitrary value as a gauge
731    ///
732    /// # Examples
733    ///
734    /// ```
735    ///   use dogstatsd::{Client, Options};
736    ///
737    ///   let client = Client::new(Options::default()).unwrap();
738    ///   client.gauge("gauge", "12345", &["tag:gauge"])
739    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
740    /// ```
741    pub fn gauge<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
742    where
743        I: IntoIterator<Item = T>,
744        S: Into<Cow<'a, str>>,
745        SS: Into<Cow<'a, str>>,
746        T: AsRef<str>,
747    {
748        self.send(
749            &GaugeMetric::new(stat.into().as_ref(), val.into().as_ref()),
750            tags,
751        )
752    }
753
754    /// Report a value in a histogram
755    ///
756    /// # Examples
757    ///
758    /// ```
759    ///   use dogstatsd::{Client, Options};
760    ///
761    ///   let client = Client::new(Options::default()).unwrap();
762    ///   client.histogram("histogram", "67890", &["tag:histogram"])
763    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
764    /// ```
765    pub fn histogram<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
766    where
767        I: IntoIterator<Item = T>,
768        S: Into<Cow<'a, str>>,
769        SS: Into<Cow<'a, str>>,
770        T: AsRef<str>,
771    {
772        self.send(
773            &HistogramMetric::new(stat.into().as_ref(), val.into().as_ref()),
774            tags,
775        )
776    }
777
778    /// Report a value in a distribution
779    ///
780    /// # Examples
781    ///
782    /// ```
783    ///   use dogstatsd::{Client, Options};
784    ///
785    ///   let client = Client::new(Options::default()).unwrap();
786    ///   client.distribution("distribution", "67890", &["tag:distribution"])
787    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
788    /// ```
789    pub fn distribution<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
790    where
791        I: IntoIterator<Item = T>,
792        S: Into<Cow<'a, str>>,
793        SS: Into<Cow<'a, str>>,
794        T: AsRef<str>,
795    {
796        self.send(
797            &DistributionMetric::new(stat.into().as_ref(), val.into().as_ref()),
798            tags,
799        )
800    }
801
802    /// Report a value in a set
803    ///
804    /// # Examples
805    ///
806    /// ```
807    ///   use dogstatsd::{Client, Options};
808    ///
809    ///   let client = Client::new(Options::default()).unwrap();
810    ///   client.set("set", "13579", &["tag:set"])
811    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
812    /// ```
813    pub fn set<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
814    where
815        I: IntoIterator<Item = T>,
816        S: Into<Cow<'a, str>>,
817        SS: Into<Cow<'a, str>>,
818        T: AsRef<str>,
819    {
820        self.send(
821            &SetMetric::new(stat.into().as_ref(), val.into().as_ref()),
822            tags,
823        )
824    }
825
826    /// Report the status of a service
827    ///
828    /// # Examples
829    ///
830    /// ```
831    ///   use dogstatsd::{Client, Options, ServiceStatus, ServiceCheckOptions};
832    ///
833    ///   let client = Client::new(Options::default()).unwrap();
834    ///   client.service_check("redis.can_connect", ServiceStatus::OK, &["tag:service"], None)
835    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
836    ///
837    ///   let options = ServiceCheckOptions {
838    ///     hostname: Some("my-host.localhost"),
839    ///     ..Default::default()
840    ///   };
841    ///   client.service_check("redis.can_connect", ServiceStatus::OK, &["tag:service"], Some(options))
842    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
843    ///
844    ///   let all_options = ServiceCheckOptions {
845    ///     hostname: Some("my-host.localhost"),
846    ///     timestamp: Some(1510326433),
847    ///     message: Some("Message about the check or service")
848    ///   };
849    ///   client.service_check("redis.can_connect", ServiceStatus::OK, &["tag:service"], Some(all_options))
850    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
851    /// ```
852    pub fn service_check<'a, I, S, T>(
853        &self,
854        stat: S,
855        val: ServiceStatus,
856        tags: I,
857        options: Option<ServiceCheckOptions>,
858    ) -> DogstatsdResult
859    where
860        I: IntoIterator<Item = T>,
861        S: Into<Cow<'a, str>>,
862        T: AsRef<str>,
863    {
864        let unwrapped_options = options.unwrap_or_default();
865        self.send(
866            &ServiceCheck::new(stat.into().as_ref(), val, unwrapped_options),
867            tags,
868        )
869    }
870
871    /// Send a custom event as a title and a body
872    ///
873    /// # Examples
874    ///
875    /// ```
876    ///   use dogstatsd::{Client, Options};
877    ///
878    ///   let client = Client::new(Options::default()).unwrap();
879    ///   client.event("Event Title", "Event Body", &["tag:event"])
880    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
881    ///
882    /// ```
883    pub fn event<'a, I, S, SS, T>(&self, title: S, text: SS, tags: I) -> DogstatsdResult
884    where
885        I: IntoIterator<Item = T>,
886        S: Into<Cow<'a, str>>,
887        SS: Into<Cow<'a, str>>,
888        T: AsRef<str>,
889    {
890        self.send(
891            &Event::new(title.into().as_ref(), text.into().as_ref()),
892            tags,
893        )
894    }
895
896    /// Send a custom event as a title and a body
897    ///
898    /// # Examples
899    ///
900    /// ```
901    ///   use dogstatsd::{Client, Options, EventOptions, EventAlertType, EventPriority};
902    ///
903    ///   let client = Client::new(Options::default()).unwrap();
904    ///   let event_options = EventOptions::new()
905    ///     .with_timestamp(1638480000)
906    ///     .with_hostname("localhost")
907    ///     .with_priority(EventPriority::Normal)
908    ///     .with_alert_type(EventAlertType::Error);
909    ///   client.event_with_options("My Custom Event Title", "My Custom Event Body", &["tag:event"], Some(event_options))
910    ///       .unwrap_or_else(|e| println!("Encountered error: {}", e));
911    pub fn event_with_options<'a, I, S, SS, T>(
912        &self,
913        title: S,
914        text: SS,
915        tags: I,
916        options: Option<EventOptions<'a>>,
917    ) -> DogstatsdResult
918    where
919        I: IntoIterator<Item = T>,
920        S: Into<Cow<'a, str>>,
921        SS: Into<Cow<'a, str>>,
922        T: AsRef<str>,
923    {
924        let title_owned = title.into();
925        let text_owned = text.into();
926        let mut event = Event::new(title_owned.as_ref(), text_owned.as_ref());
927
928        // Apply additional options if provided
929        if let Some(options) = options {
930            if let Some(timestamp) = options.timestamp {
931                event = event.with_timestamp(timestamp);
932            }
933            if let Some(hostname) = options.hostname {
934                event = event.with_hostname(hostname);
935            }
936            if let Some(aggregation_key) = options.aggregation_key {
937                event = event.with_aggregation_key(aggregation_key);
938            }
939            if let Some(priority) = options.priority {
940                event = event.with_priority(priority);
941            }
942            if let Some(source_type_name) = options.source_type_name {
943                event = event.with_source_type_name(source_type_name);
944            }
945            if let Some(alert_type) = options.alert_type {
946                event = event.with_alert_type(alert_type);
947            }
948        }
949
950        self.send(&event, tags)
951    }
952
953    fn send<I, M, S>(&self, metric: &M, tags: I) -> DogstatsdResult
954    where
955        I: IntoIterator<Item = S>,
956        M: Metric,
957        S: AsRef<str>,
958    {
959        let formatted_metric = format_for_send(metric, &self.namespace, tags, &self.default_tags);
960        match &self.socket {
961            SocketType::Udp(socket) => {
962                socket.send_to(formatted_metric.as_slice(), &self.to_addr)?;
963            }
964            #[cfg(unix)]
965            SocketType::Uds(socket) => {
966                socket.send(formatted_metric.as_slice())?;
967            }
968            SocketType::BatchableUdp(tx_channel) => {
969                tx_channel
970                    .lock()
971                    .expect("Mutex poisoned...")
972                    .send(batch_processor::Message::Data(formatted_metric))
973                    .unwrap_or_else(|error| {
974                        println!("Exception occurred when writing to channel: {:?}", error);
975                    });
976            }
977            #[cfg(unix)]
978            SocketType::BatchableUds(tx_channel) => {
979                tx_channel
980                    .lock()
981                    .expect("Mutex poisoned...")
982                    .send(batch_processor::Message::Data(formatted_metric))
983                    .unwrap_or_else(|error| {
984                        println!("Exception occurred when writing to channel: {:?}", error);
985                    });
986            }
987        }
988        Ok(())
989    }
990}
991
992/// Configuration options for an `Event`.
993///
994/// `EventOptions` provides additional optional metadata that can be attached
995/// to an event, enabling greater flexibility and contextual information
996/// for event handling and monitoring systems.
997///
998/// # Example
999///
1000/// ```rust
1001/// use dogstatsd::{EventOptions, EventAlertType, EventPriority};
1002///
1003/// let options = EventOptions {
1004///     timestamp: Some(1638480000),
1005///     hostname: Some("localhost"),
1006///     aggregation_key: Some("service_down"),
1007///     priority: Some(EventPriority::Normal),
1008///     source_type_name: Some("monitoring"),
1009///     alert_type: Some(EventAlertType::Error),
1010/// };
1011/// ```
1012///
1013#[derive(Default, Clone, Copy, Debug)]
1014pub struct EventOptions<'a> {
1015    /// Optional Unix timestamp representing the event time. The default is the current Unix epoch timestamp.
1016    pub timestamp: Option<u64>,
1017    /// Optional hostname associated with the event.
1018    pub hostname: Option<&'a str>,
1019    /// Optional key for grouping related events.
1020    pub aggregation_key: Option<&'a str>,
1021    /// Optional priority level of the event, e.g., `"low"` or `"normal"`.
1022    pub priority: Option<EventPriority>,
1023    /// Optional source type name of the event, e.g., `"monitoring"`.
1024    pub source_type_name: Option<&'a str>,
1025    /// Optional alert type for the event, e.g., `"error"`, `"warning"`,  `"info"`, `"success"`. Default `"info"`.
1026    pub alert_type: Option<EventAlertType>,
1027}
1028
1029impl<'a> EventOptions<'a> {
1030    /// Creates a new `EventOptions` instance with all fields set to `None`.
1031    pub fn new() -> Self {
1032        EventOptions {
1033            timestamp: None,
1034            hostname: None,
1035            aggregation_key: None,
1036            priority: None,
1037            source_type_name: None,
1038            alert_type: None,
1039        }
1040    }
1041    /// Sets the `hostname` for the event.
1042    pub fn with_timestamp(mut self, timestamp: u64) -> Self {
1043        self.timestamp = Some(timestamp);
1044        self
1045    }
1046
1047    /// Sets the `hostname` for the event.
1048    pub fn with_hostname(mut self, hostname: &'a str) -> Self {
1049        self.hostname = Some(hostname);
1050        self
1051    }
1052
1053    /// Sets the `aggregation_key` for the event.
1054    pub fn with_aggregation_key(mut self, aggregation_key: &'a str) -> Self {
1055        self.aggregation_key = Some(aggregation_key);
1056        self
1057    }
1058
1059    /// Sets the `priority` for the event.
1060    pub fn with_priority(mut self, priority: EventPriority) -> Self {
1061        self.priority = Some(priority);
1062        self
1063    }
1064
1065    /// Sets the `source_type_name` for the event.
1066    pub fn with_source_type_name(mut self, source_type_name: &'a str) -> Self {
1067        self.source_type_name = Some(source_type_name);
1068        self
1069    }
1070
1071    /// Sets the `alert_type` for the event.
1072    pub fn with_alert_type(mut self, alert_type: EventAlertType) -> Self {
1073        self.alert_type = Some(alert_type);
1074        self
1075    }
1076}
1077
1078mod batch_processor {
1079    use std::sync::mpsc::Receiver;
1080    use std::time::SystemTime;
1081
1082    use retry::{delay::jitter, delay::Exponential, retry};
1083
1084    use crate::{BatchingOptions, SocketType};
1085
1086    pub(crate) enum Message {
1087        Data(Vec<u8>),
1088        Shutdown,
1089    }
1090
1091    fn send_to_socket_with_retries(
1092        batching_options: &BatchingOptions,
1093        socket: &SocketType,
1094        data: &Vec<u8>,
1095        to_addr: &String,
1096        _socket_path: &Option<String>,
1097    ) {
1098        retry(
1099            Exponential::from_millis(batching_options.initial_retry_delay)
1100                .map(jitter)
1101                .take(batching_options.max_retry_attempts),
1102            || -> Result<(), std::io::Error> {
1103                match socket {
1104                    SocketType::Udp(socket) => {
1105                        socket.send_to(data.as_slice(), to_addr)?;
1106                    }
1107                    #[cfg(unix)]
1108                    SocketType::Uds(socket) => {
1109                        if let Err(error) = socket.send(data.as_slice()) {
1110                            // Per https://doc.rust-lang.org/stable/std/os/unix/net/struct.UnixDatagram.html#method.send
1111                            // If send fails, it is due to a connection issue, so just attempt
1112                            // to reconnect
1113                            let socket_path_unwrapped = _socket_path
1114                                .as_ref()
1115                                .expect("Only invoked if socket path is defined.");
1116                            socket.connect(socket_path_unwrapped)?;
1117
1118                            return Err(error);
1119                        }
1120                    }
1121                    SocketType::BatchableUdp(_tx_channel) => {
1122                        panic!("Logic Error - socket type should not be batchable.");
1123                    }
1124                    #[cfg(unix)]
1125                    SocketType::BatchableUds(_tx_channel) => {
1126                        panic!("Logic Error - socket type should not be batchable.");
1127                    }
1128                }
1129
1130                Ok(())
1131            },
1132        )
1133        .unwrap_or_else(|error| {
1134            println!(
1135                "Failed to send within retry policy... Dropping metrics: {:?}",
1136                error
1137            )
1138        });
1139    }
1140
1141    pub(crate) fn process_events(
1142        batching_options: BatchingOptions,
1143        to_addr: String,
1144        socket: SocketType,
1145        socket_path: Option<String>,
1146        rx: Receiver<Message>,
1147    ) {
1148        let mut last_updated = SystemTime::now();
1149        let mut buffer: Vec<u8> = vec![];
1150
1151        loop {
1152            match rx.recv() {
1153                Ok(Message::Data(data)) => {
1154                    for ch in data {
1155                        buffer.push(ch);
1156                    }
1157                    buffer.push(b'\n');
1158
1159                    let current_time = SystemTime::now();
1160                    if buffer.len() >= batching_options.max_buffer_size
1161                        || last_updated + batching_options.max_time < current_time
1162                    {
1163                        send_to_socket_with_retries(
1164                            &batching_options,
1165                            &socket,
1166                            &buffer,
1167                            &to_addr,
1168                            &socket_path,
1169                        );
1170                        buffer.clear();
1171                        last_updated = current_time;
1172                    }
1173                }
1174                Ok(Message::Shutdown) => {
1175                    send_to_socket_with_retries(
1176                        &batching_options,
1177                        &socket,
1178                        &buffer,
1179                        &to_addr,
1180                        &socket_path,
1181                    );
1182                    buffer.clear();
1183                }
1184                Err(e) => {
1185                    println!("Exception occurred when reading from channel: {:?}", e);
1186                    break;
1187                }
1188            }
1189        }
1190    }
1191}
1192
1193#[cfg(test)]
1194mod tests {
1195    use metrics::GaugeMetric;
1196    use serial_test::serial;
1197
1198    use super::*;
1199
1200    #[test]
1201    fn test_options_default() {
1202        let options = Options::default();
1203        let expected_options = Options {
1204            from_addr: DEFAULT_FROM_ADDR.into(),
1205            to_addr: DEFAULT_TO_ADDR.into(),
1206            namespace: String::new(),
1207            ..Default::default()
1208        };
1209
1210        assert_eq!(expected_options, options)
1211    }
1212
1213    #[test]
1214    fn test_options_builder_none() {
1215        let options = OptionsBuilder::new().build();
1216        let expected_options = Options {
1217            from_addr: DEFAULT_FROM_ADDR.into(),
1218            to_addr: DEFAULT_TO_ADDR.into(),
1219            namespace: String::new(),
1220            ..Default::default()
1221        };
1222
1223        assert_eq!(expected_options, options);
1224    }
1225
1226    #[test]
1227    fn teset_options_builder_all() {
1228        let options = OptionsBuilder::new()
1229            .from_addr("127.0.0.2:0".into())
1230            .to_addr("127.0.0.2:8125".into())
1231            .namespace("mynamespace".into())
1232            .default_tag(String::from("tag1:tag1val"))
1233            .build();
1234        let expected_options = Options {
1235            from_addr: "127.0.0.2:0".into(),
1236            to_addr: "127.0.0.2:8125".into(),
1237            namespace: "mynamespace".into(),
1238            default_tags: vec!["tag1:tag1val".into()].to_vec(),
1239            socket_path: None,
1240            batching_options: None,
1241        };
1242
1243        assert_eq!(expected_options, options);
1244    }
1245
1246    #[test]
1247    #[serial]
1248    fn test_new() {
1249        let client = Client::new(Options::default()).unwrap();
1250        let expected_client = Client {
1251            socket: SocketType::Udp(UdpSocket::bind(DEFAULT_FROM_ADDR).unwrap()),
1252            from_addr: DEFAULT_FROM_ADDR.into(),
1253            to_addr: DEFAULT_TO_ADDR.into(),
1254            namespace: String::new(),
1255            default_tags: String::new().into_bytes(),
1256        };
1257
1258        assert_eq!(expected_client, client)
1259    }
1260
1261    #[test]
1262    #[serial]
1263    fn test_new_default_tags() {
1264        let options = Options::new(
1265            DEFAULT_FROM_ADDR,
1266            DEFAULT_TO_ADDR,
1267            "",
1268            vec![String::from("tag1:tag1val")],
1269            None,
1270            None,
1271        );
1272        let client = Client::new(options).unwrap();
1273        let expected_client = Client {
1274            socket: SocketType::Udp(UdpSocket::bind(DEFAULT_FROM_ADDR).unwrap()),
1275            from_addr: DEFAULT_FROM_ADDR.into(),
1276            to_addr: DEFAULT_TO_ADDR.into(),
1277            namespace: String::new(),
1278            default_tags: String::from("tag1:tag1val").into_bytes(),
1279        };
1280
1281        assert_eq!(expected_client, client)
1282    }
1283
1284    #[test]
1285    #[serial]
1286    fn test_system_tags() {
1287        let options = Options::new(
1288            DEFAULT_FROM_ADDR,
1289            DEFAULT_TO_ADDR,
1290            "",
1291            vec![String::from("tag1:tag1val"), String::from("version:0.0.2")],
1292            None,
1293            None,
1294        );
1295
1296        let client = with_default_system_tags(|| Client::new(options).unwrap());
1297
1298        dbg!(String::from_utf8_lossy(client.default_tags.as_ref()));
1299
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::from("tag1:tag1val,version:0.0.2,env:production,service:service")
1306                .into_bytes(),
1307        };
1308
1309        assert_eq!(expected_client, client)
1310    }
1311
1312    #[test]
1313    fn test_send() {
1314        let options = Options::new("127.0.0.1:9001", "127.0.0.1:9002", "", vec![], None, None);
1315        let client = Client::new(options).unwrap();
1316        // Shouldn't panic or error
1317        client
1318            .send(
1319                &GaugeMetric::new("gauge".into(), "1234".into()),
1320                &["tag1", "tag2"],
1321            )
1322            .unwrap();
1323    }
1324
1325    fn with_default_system_tags<T, F: FnOnce() -> T>(f: F) -> T {
1326        unsafe {
1327            std::env::set_var("DD_ENV", "production");
1328            std::env::set_var("DD_SERVICE", "service");
1329            std::env::set_var("DD_VERSION", "0.0.1");
1330        }
1331        let t = f();
1332        unsafe {
1333            std::env::remove_var("DD_ENV");
1334            std::env::remove_var("DD_SERVICE");
1335            std::env::remove_var("DD_VERSION");
1336        }
1337        t
1338    }
1339}
1340
1341#[cfg(all(feature = "unstable", test))]
1342mod bench {
1343    extern crate test;
1344
1345    use super::*;
1346
1347    use self::test::Bencher;
1348
1349    #[bench]
1350    fn bench_incr(b: &mut Bencher) {
1351        let options = Options::default();
1352        let client = Client::new(options).unwrap();
1353        let tags = &["name1:value1"];
1354        b.iter(|| {
1355            client.incr("bench.incr", tags).unwrap();
1356        })
1357    }
1358
1359    #[bench]
1360    fn bench_decr(b: &mut Bencher) {
1361        let options = Options::default();
1362        let client = Client::new(options).unwrap();
1363        let tags = &["name1:value1"];
1364        b.iter(|| {
1365            client.decr("bench.decr", tags).unwrap();
1366        })
1367    }
1368
1369    #[bench]
1370    fn bench_count(b: &mut Bencher) {
1371        let options = Options::default();
1372        let client = Client::new(options).unwrap();
1373        let tags = &["name1:value1"];
1374        let mut i = 0;
1375        b.iter(|| {
1376            client.count("bench.count", i, tags).unwrap();
1377            i += 1;
1378        })
1379    }
1380
1381    #[bench]
1382    fn bench_timing(b: &mut Bencher) {
1383        let options = Options::default();
1384        let client = Client::new(options).unwrap();
1385        let tags = &["name1:value1"];
1386        let mut i = 0;
1387        b.iter(|| {
1388            client.timing("bench.timing", i, tags).unwrap();
1389            i += 1;
1390        })
1391    }
1392
1393    #[bench]
1394    fn bench_gauge(b: &mut Bencher) {
1395        let options = Options::default();
1396        let client = Client::new(options).unwrap();
1397        let tags = vec!["name1:value1"];
1398        let mut i = 0;
1399        b.iter(|| {
1400            client.gauge("bench.guage", &i.to_string(), &tags).unwrap();
1401            i += 1;
1402        })
1403    }
1404
1405    #[bench]
1406    fn bench_histogram(b: &mut Bencher) {
1407        let options = Options::default();
1408        let client = Client::new(options).unwrap();
1409        let tags = vec!["name1:value1"];
1410        let mut i = 0;
1411        b.iter(|| {
1412            client
1413                .histogram("bench.histogram", &i.to_string(), &tags)
1414                .unwrap();
1415            i += 1;
1416        })
1417    }
1418
1419    #[bench]
1420    fn bench_distribution(b: &mut Bencher) {
1421        let options = Options::default();
1422        let client = Client::new(options).unwrap();
1423        let tags = vec!["name1:value1"];
1424        let mut i = 0;
1425        b.iter(|| {
1426            client
1427                .distribution("bench.distribution", &i.to_string(), &tags)
1428                .unwrap();
1429            i += 1;
1430        })
1431    }
1432
1433    #[bench]
1434    fn bench_set(b: &mut Bencher) {
1435        let options = Options::default();
1436        let client = Client::new(options).unwrap();
1437        let tags = vec!["name1:value1"];
1438        let mut i = 0;
1439        b.iter(|| {
1440            client.set("bench.set", &i.to_string(), &tags).unwrap();
1441            i += 1;
1442        })
1443    }
1444
1445    #[bench]
1446    fn bench_service_check(b: &mut Bencher) {
1447        let options = Options::default();
1448        let client = Client::new(options).unwrap();
1449        let tags = vec!["name1:value1"];
1450        let all_options = ServiceCheckOptions {
1451            hostname: Some("my-host.localhost"),
1452            timestamp: Some(1510326433),
1453            message: Some("Message about the check or service"),
1454        };
1455        b.iter(|| {
1456            client
1457                .service_check(
1458                    "bench.service_check",
1459                    ServiceStatus::Critical,
1460                    &tags,
1461                    Some(all_options),
1462                )
1463                .unwrap();
1464        })
1465    }
1466
1467    #[bench]
1468    fn bench_event(b: &mut Bencher) {
1469        let options = Options::default();
1470        let client = Client::new(options).unwrap();
1471        let tags = vec!["name1:value1"];
1472        b.iter(|| {
1473            client
1474                .event("Test Event Title", "Test Event Message", &tags, None)
1475                .unwrap();
1476        })
1477    }
1478
1479    fn bench_event_options(b: &mut Bencher) {
1480        let options = Options::default();
1481        let client = Client::new(options).unwrap();
1482        let tags = vec!["name1:value1"];
1483        let event_options = EventOptions::new()
1484            .with_timestamp(1638480000)
1485            .with_hostname("localhost")
1486            .with_priority(EventPriority::Normal)
1487            .with_alert_type(EventAlertType::Error);
1488
1489        b.iter(|| {
1490            client
1491                .event(
1492                    "Test Event Title",
1493                    "Test Event Message",
1494                    &tags,
1495                    Some(event_options),
1496                )
1497                .unwrap();
1498        })
1499    }
1500}