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