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}