1#![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
111pub 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#[derive(Debug, PartialEq, Clone, Copy)]
119pub struct BatchingOptions {
120 pub max_buffer_size: usize,
122 pub max_time: Duration,
124 pub max_retry_attempts: usize,
126 pub initial_retry_delay: u64,
128}
129
130#[derive(Debug, PartialEq)]
132pub struct Options {
133 pub from_addr: String,
135 pub to_addr: String,
137 pub namespace: String,
139 pub default_tags: Vec<String>,
141 pub socket_path: Option<String>,
143 pub batching_options: Option<BatchingOptions>,
145}
146
147impl Default for Options {
148 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 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#[derive(Default, Debug)]
234pub struct OptionsBuilder {
235 from_addr: Option<String>,
237 to_addr: Option<String>,
239 namespace: Option<String>,
241 default_tags: Vec<String>,
243 socket_path: Option<String>,
245 batching_options: Option<BatchingOptions>,
247}
248
249impl OptionsBuilder {
250 pub fn new() -> Self {
260 Self::default()
261 }
262
263 pub fn from_addr(&mut self, from_addr: String) -> &mut OptionsBuilder {
273 self.from_addr = Some(from_addr);
274 self
275 }
276
277 pub fn to_addr(&mut self, to_addr: String) -> &mut OptionsBuilder {
287 self.to_addr = Some(to_addr);
288 self
289 }
290
291 pub fn namespace(&mut self, namespace: String) -> &mut OptionsBuilder {
301 self.namespace = Some(namespace);
302 self
303 }
304
305 pub fn default_tag(&mut self, default_tag: String) -> &mut OptionsBuilder {
315 self.default_tags.push(default_tag);
316 self
317 }
318
319 pub fn socket_path(&mut self, socket_path: Option<String>) -> &mut OptionsBuilder {
329 self.socket_path = socket_path;
330 self
331 }
332
333 pub fn batching_options(&mut self, batching_options: BatchingOptions) -> &mut OptionsBuilder {
344 self.batching_options = Some(batching_options);
345 self
346 }
347
348 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#[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 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 pub fn new(options: Options) -> Result<Self, DogstatsdError> {
448 let fn_create_tx_channel = |socket: SocketType,
449 batching_options: BatchingOptions,
450 to_addr: String,
451 socket_path: Option<String>|
452 -> Mutex<Sender<batch_processor::Message>> {
453 let (tx, rx) = mpsc::channel();
454 thread::spawn(move || {
455 batch_processor::process_events(batching_options, to_addr, socket, socket_path, rx);
456 });
457 Mutex::from(tx)
458 };
459
460 let socket = if options.socket_path.is_some() {
461 #[cfg(unix)]
462 {
463 let socket_path = options.socket_path.clone().expect("checked is_some above");
464
465 let mut uds_socket = UnixDatagram::unbound()?;
472 match uds_socket.connect(socket_path.clone()) {
473 Ok(socket) => socket,
474 Err(e) => {
475 println!(
476 "Couldn't connect to uds socket.. attempting to re-create by binding directly: {e:?}"
477 );
478 uds_socket = UnixDatagram::bind(socket_path.clone())?;
479 }
480 };
481 uds_socket.set_nonblocking(true)?;
482
483 let wrapped_socket = SocketType::Uds(uds_socket);
484 if let Some(batching_options) = options.batching_options {
485 SocketType::BatchableUds(fn_create_tx_channel(
486 wrapped_socket,
487 batching_options,
488 options.to_addr.clone(),
489 Some(socket_path),
490 ))
491 } else {
492 wrapped_socket
493 }
494 }
495 #[cfg(not(unix))]
496 {
497 return Err(DogstatsdError::from(std::io::Error::new(
498 std::io::ErrorKind::Unsupported,
499 "Unix domain sockets are not supported on this platform",
500 )));
501 }
502 } else {
503 let wrapped_socket = SocketType::Udp(UdpSocket::bind(&options.from_addr)?);
504 if let Some(batching_options) = options.batching_options {
505 SocketType::BatchableUdp(fn_create_tx_channel(
506 wrapped_socket,
507 batching_options,
508 options.to_addr.clone(),
509 None,
510 ))
511 } else {
512 wrapped_socket
513 }
514 };
515
516 let default_tags = Options::merge_with_system_tags(options.default_tags);
517
518 Ok(Client {
519 socket,
520 from_addr: options.from_addr,
521 to_addr: options.to_addr,
522 namespace: options.namespace,
523 default_tags: default_tags.join(",").into_bytes(),
524 })
525 }
526
527 pub fn incr<'a, I, S, T>(&self, stat: S, tags: I) -> DogstatsdResult
539 where
540 I: IntoIterator<Item = T>,
541 S: Into<Cow<'a, str>>,
542 T: AsRef<str>,
543 {
544 self.send(&CountMetric::Incr(stat.into().as_ref(), 1), tags)
545 }
546
547 pub fn incr_by_value<'a, I, S, T>(&self, stat: S, value: i64, tags: I) -> DogstatsdResult
559 where
560 I: IntoIterator<Item = T>,
561 S: Into<Cow<'a, str>>,
562 T: AsRef<str>,
563 {
564 self.send(&CountMetric::Incr(stat.into().as_ref(), value), tags)
565 }
566
567 pub fn decr<'a, I, S, T>(&self, stat: S, tags: I) -> DogstatsdResult
579 where
580 I: IntoIterator<Item = T>,
581 S: Into<Cow<'a, str>>,
582 T: AsRef<str>,
583 {
584 self.send(&CountMetric::Decr(stat.into().as_ref(), 1), tags)
585 }
586
587 pub fn decr_by_value<'a, I, S, T>(&self, stat: S, value: i64, tags: I) -> DogstatsdResult
599 where
600 I: IntoIterator<Item = T>,
601 S: Into<Cow<'a, str>>,
602 T: AsRef<str>,
603 {
604 self.send(&CountMetric::Decr(stat.into().as_ref(), value), tags)
605 }
606
607 pub fn count<'a, I, S, T>(&self, stat: S, count: i64, tags: I) -> DogstatsdResult
619 where
620 I: IntoIterator<Item = T>,
621 S: Into<Cow<'a, str>>,
622 T: AsRef<str>,
623 {
624 self.send(&CountMetric::Arbitrary(stat.into().as_ref(), count), tags)
625 }
626
627 pub fn time<'a, F, O, I, S, T>(
642 &self,
643 stat: S,
644 tags: I,
645 block: F,
646 ) -> Result<O, (O, DogstatsdError)>
647 where
648 F: FnOnce() -> O,
649 I: IntoIterator<Item = T>,
650 S: Into<Cow<'a, str>>,
651 T: AsRef<str>,
652 {
653 let start_time = Utc::now();
654 let output = block();
655 let end_time = Utc::now();
656 let stat = stat.into();
657 let metric = TimeMetric::new(stat.as_ref(), &start_time, &end_time);
658 match self.send(&metric, tags) {
659 Ok(()) => Ok(output),
660 Err(error) => Err((output, error)),
661 }
662 }
663
664 pub async fn async_time<'a, Fn, Fut, O, I, S, T>(
682 &self,
683 stat: S,
684 tags: I,
685 block: Fn,
686 ) -> Result<O, (O, DogstatsdError)>
687 where
688 Fn: FnOnce() -> Fut,
689 Fut: Future<Output = O>,
690 I: IntoIterator<Item = T>,
691 S: Into<Cow<'a, str>>,
692 T: AsRef<str>,
693 {
694 let start_time = Utc::now();
695 let output = block().await;
696 let end_time = Utc::now();
697 let stat = stat.into();
698 match self.send(
699 &TimeMetric::new(stat.as_ref(), &start_time, &end_time),
700 tags,
701 ) {
702 Ok(()) => Ok(output),
703 Err(error) => Err((output, error)),
704 }
705 }
706
707 pub fn timing<'a, I, S, T>(&self, stat: S, ms: i64, tags: I) -> DogstatsdResult
719 where
720 I: IntoIterator<Item = T>,
721 S: Into<Cow<'a, str>>,
722 T: AsRef<str>,
723 {
724 self.send(&TimingMetric::new(stat.into().as_ref(), ms), tags)
725 }
726
727 pub fn gauge<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
739 where
740 I: IntoIterator<Item = T>,
741 S: Into<Cow<'a, str>>,
742 SS: Into<Cow<'a, str>>,
743 T: AsRef<str>,
744 {
745 self.send(
746 &GaugeMetric::new(stat.into().as_ref(), val.into().as_ref()),
747 tags,
748 )
749 }
750
751 pub fn histogram<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
763 where
764 I: IntoIterator<Item = T>,
765 S: Into<Cow<'a, str>>,
766 SS: Into<Cow<'a, str>>,
767 T: AsRef<str>,
768 {
769 self.send(
770 &HistogramMetric::new(stat.into().as_ref(), val.into().as_ref()),
771 tags,
772 )
773 }
774
775 pub fn distribution<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
787 where
788 I: IntoIterator<Item = T>,
789 S: Into<Cow<'a, str>>,
790 SS: Into<Cow<'a, str>>,
791 T: AsRef<str>,
792 {
793 self.send(
794 &DistributionMetric::new(stat.into().as_ref(), val.into().as_ref()),
795 tags,
796 )
797 }
798
799 pub fn set<'a, I, S, SS, T>(&self, stat: S, val: SS, tags: I) -> DogstatsdResult
811 where
812 I: IntoIterator<Item = T>,
813 S: Into<Cow<'a, str>>,
814 SS: Into<Cow<'a, str>>,
815 T: AsRef<str>,
816 {
817 self.send(
818 &SetMetric::new(stat.into().as_ref(), val.into().as_ref()),
819 tags,
820 )
821 }
822
823 pub fn service_check<'a, I, S, T>(
850 &self,
851 stat: S,
852 val: ServiceStatus,
853 tags: I,
854 options: Option<ServiceCheckOptions>,
855 ) -> DogstatsdResult
856 where
857 I: IntoIterator<Item = T>,
858 S: Into<Cow<'a, str>>,
859 T: AsRef<str>,
860 {
861 let unwrapped_options = options.unwrap_or_default();
862 self.send(
863 &ServiceCheck::new(stat.into().as_ref(), val, unwrapped_options),
864 tags,
865 )
866 }
867
868 pub fn event<'a, I, S, SS, T>(&self, title: S, text: SS, tags: I) -> DogstatsdResult
881 where
882 I: IntoIterator<Item = T>,
883 S: Into<Cow<'a, str>>,
884 SS: Into<Cow<'a, str>>,
885 T: AsRef<str>,
886 {
887 self.send(
888 &Event::new(title.into().as_ref(), text.into().as_ref()),
889 tags,
890 )
891 }
892
893 pub fn event_with_options<'a, I, S, SS, T>(
909 &self,
910 title: S,
911 text: SS,
912 tags: I,
913 options: Option<EventOptions<'a>>,
914 ) -> DogstatsdResult
915 where
916 I: IntoIterator<Item = T>,
917 S: Into<Cow<'a, str>>,
918 SS: Into<Cow<'a, str>>,
919 T: AsRef<str>,
920 {
921 let title_owned = title.into();
922 let text_owned = text.into();
923 let mut event = Event::new(title_owned.as_ref(), text_owned.as_ref());
924
925 if let Some(options) = options {
927 if let Some(timestamp) = options.timestamp {
928 event = event.with_timestamp(timestamp);
929 }
930 if let Some(hostname) = options.hostname {
931 event = event.with_hostname(hostname);
932 }
933 if let Some(aggregation_key) = options.aggregation_key {
934 event = event.with_aggregation_key(aggregation_key);
935 }
936 if let Some(priority) = options.priority {
937 event = event.with_priority(priority);
938 }
939 if let Some(source_type_name) = options.source_type_name {
940 event = event.with_source_type_name(source_type_name);
941 }
942 if let Some(alert_type) = options.alert_type {
943 event = event.with_alert_type(alert_type);
944 }
945 }
946
947 self.send(&event, tags)
948 }
949
950 fn send<I, M, S>(&self, metric: &M, tags: I) -> DogstatsdResult
951 where
952 I: IntoIterator<Item = S>,
953 M: Metric,
954 S: AsRef<str>,
955 {
956 let formatted_metric = format_for_send(metric, &self.namespace, tags, &self.default_tags);
957 match &self.socket {
958 SocketType::Udp(socket) => {
959 socket.send_to(formatted_metric.as_slice(), &self.to_addr)?;
960 }
961 #[cfg(unix)]
962 SocketType::Uds(socket) => {
963 socket.send(formatted_metric.as_slice())?;
964 }
965 SocketType::BatchableUdp(tx_channel) => {
966 tx_channel
967 .lock()
968 .expect("Mutex poisoned...")
969 .send(batch_processor::Message::Data(formatted_metric))
970 .unwrap_or_else(|error| {
971 println!("Exception occurred when writing to channel: {:?}", error);
972 });
973 }
974 #[cfg(unix)]
975 SocketType::BatchableUds(tx_channel) => {
976 tx_channel
977 .lock()
978 .expect("Mutex poisoned...")
979 .send(batch_processor::Message::Data(formatted_metric))
980 .unwrap_or_else(|error| {
981 println!("Exception occurred when writing to channel: {:?}", error);
982 });
983 }
984 }
985 Ok(())
986 }
987}
988
989#[derive(Default, Clone, Copy, Debug)]
1011pub struct EventOptions<'a> {
1012 pub timestamp: Option<u64>,
1014 pub hostname: Option<&'a str>,
1016 pub aggregation_key: Option<&'a str>,
1018 pub priority: Option<EventPriority>,
1020 pub source_type_name: Option<&'a str>,
1022 pub alert_type: Option<EventAlertType>,
1024}
1025
1026impl<'a> EventOptions<'a> {
1027 pub fn new() -> Self {
1029 EventOptions {
1030 timestamp: None,
1031 hostname: None,
1032 aggregation_key: None,
1033 priority: None,
1034 source_type_name: None,
1035 alert_type: None,
1036 }
1037 }
1038 pub fn with_timestamp(mut self, timestamp: u64) -> Self {
1040 self.timestamp = Some(timestamp);
1041 self
1042 }
1043
1044 pub fn with_hostname(mut self, hostname: &'a str) -> Self {
1046 self.hostname = Some(hostname);
1047 self
1048 }
1049
1050 pub fn with_aggregation_key(mut self, aggregation_key: &'a str) -> Self {
1052 self.aggregation_key = Some(aggregation_key);
1053 self
1054 }
1055
1056 pub fn with_priority(mut self, priority: EventPriority) -> Self {
1058 self.priority = Some(priority);
1059 self
1060 }
1061
1062 pub fn with_source_type_name(mut self, source_type_name: &'a str) -> Self {
1064 self.source_type_name = Some(source_type_name);
1065 self
1066 }
1067
1068 pub fn with_alert_type(mut self, alert_type: EventAlertType) -> Self {
1070 self.alert_type = Some(alert_type);
1071 self
1072 }
1073}
1074
1075mod batch_processor {
1076 use std::sync::mpsc::{Receiver, RecvTimeoutError};
1077 use std::time::{Duration, Instant};
1078
1079 use retry::{delay::jitter, delay::Exponential, retry};
1080
1081 use crate::{BatchingOptions, SocketType};
1082
1083 pub(crate) enum Message {
1084 Data(Vec<u8>),
1085 Shutdown,
1086 }
1087
1088 fn send_to_socket_with_retries(
1089 batching_options: &BatchingOptions,
1090 socket: &SocketType,
1091 data: &Vec<u8>,
1092 to_addr: &String,
1093 _socket_path: &Option<String>,
1094 ) {
1095 retry(
1096 Exponential::from_millis(batching_options.initial_retry_delay)
1097 .map(jitter)
1098 .take(batching_options.max_retry_attempts),
1099 || -> Result<(), std::io::Error> {
1100 match socket {
1101 SocketType::Udp(socket) => {
1102 socket.send_to(data.as_slice(), to_addr)?;
1103 }
1104 #[cfg(unix)]
1105 SocketType::Uds(socket) => {
1106 if let Err(error) = socket.send(data.as_slice()) {
1107 let socket_path_unwrapped = _socket_path
1111 .as_ref()
1112 .expect("Only invoked if socket path is defined.");
1113 socket.connect(socket_path_unwrapped)?;
1114
1115 return Err(error);
1116 }
1117 }
1118 SocketType::BatchableUdp(_tx_channel) => {
1119 panic!("Logic Error - socket type should not be batchable.");
1120 }
1121 #[cfg(unix)]
1122 SocketType::BatchableUds(_tx_channel) => {
1123 panic!("Logic Error - socket type should not be batchable.");
1124 }
1125 }
1126
1127 Ok(())
1128 },
1129 )
1130 .unwrap_or_else(|error| {
1131 println!(
1132 "Failed to send within retry policy... Dropping metrics: {:?}",
1133 error
1134 )
1135 });
1136 }
1137
1138 fn flush_buffer(
1139 batching_options: &BatchingOptions,
1140 socket: &SocketType,
1141 buffer: &mut Vec<u8>,
1142 to_addr: &String,
1143 socket_path: &Option<String>,
1144 ) {
1145 if buffer.is_empty() {
1146 return;
1147 }
1148
1149 send_to_socket_with_retries(batching_options, socket, buffer, to_addr, socket_path);
1150 buffer.clear();
1151 }
1152
1153 pub(crate) fn process_events(
1154 batching_options: BatchingOptions,
1155 to_addr: String,
1156 socket: SocketType,
1157 socket_path: Option<String>,
1158 rx: Receiver<Message>,
1159 ) {
1160 let mut buffer: Vec<u8> = vec![];
1161 let mut batch_started_at: Option<Instant> = None;
1162
1163 loop {
1164 let message = match batch_started_at {
1165 Some(started_at) => match rx
1166 .recv_timeout(remaining_batch_time(started_at, batching_options.max_time))
1167 {
1168 Ok(message) => Ok(message),
1169 Err(RecvTimeoutError::Timeout) => {
1170 flush_buffer(
1171 &batching_options,
1172 &socket,
1173 &mut buffer,
1174 &to_addr,
1175 &socket_path,
1176 );
1177 batch_started_at = None;
1178 continue;
1179 }
1180 Err(RecvTimeoutError::Disconnected) => Err(RecvTimeoutError::Disconnected),
1181 },
1182 None => rx.recv().map_err(|_| RecvTimeoutError::Disconnected),
1183 };
1184
1185 match message {
1186 Ok(Message::Data(data)) => {
1187 let next_buffer_size = buffer.len() + data.len() + 1;
1188
1189 if !buffer.is_empty() && next_buffer_size > batching_options.max_buffer_size {
1190 flush_buffer(
1191 &batching_options,
1192 &socket,
1193 &mut buffer,
1194 &to_addr,
1195 &socket_path,
1196 );
1197 batch_started_at = None;
1198 }
1199
1200 if buffer.is_empty() {
1201 batch_started_at = Some(Instant::now());
1202 }
1203
1204 buffer.extend(data);
1205 buffer.push(b'\n');
1206
1207 if buffer.len() >= batching_options.max_buffer_size {
1208 flush_buffer(
1209 &batching_options,
1210 &socket,
1211 &mut buffer,
1212 &to_addr,
1213 &socket_path,
1214 );
1215 batch_started_at = None;
1216 }
1217 }
1218 Ok(Message::Shutdown) => {
1219 flush_buffer(
1220 &batching_options,
1221 &socket,
1222 &mut buffer,
1223 &to_addr,
1224 &socket_path,
1225 );
1226 break;
1227 }
1228 Err(e) => {
1229 println!("Exception occurred when reading from channel: {:?}", e);
1230 break;
1231 }
1232 }
1233 }
1234 }
1235
1236 fn remaining_batch_time(started_at: Instant, max_time: Duration) -> Duration {
1237 max_time
1238 .checked_sub(started_at.elapsed())
1239 .unwrap_or(Duration::ZERO)
1240 }
1241}
1242
1243#[cfg(test)]
1244mod tests {
1245 use metrics::GaugeMetric;
1246 use serial_test::serial;
1247
1248 use super::*;
1249
1250 #[test]
1251 fn test_options_default() {
1252 let options = Options::default();
1253 let expected_options = Options {
1254 from_addr: DEFAULT_FROM_ADDR.into(),
1255 to_addr: DEFAULT_TO_ADDR.into(),
1256 namespace: String::new(),
1257 ..Default::default()
1258 };
1259
1260 assert_eq!(expected_options, options)
1261 }
1262
1263 #[test]
1264 fn test_options_builder_none() {
1265 let options = OptionsBuilder::new().build();
1266 let expected_options = Options {
1267 from_addr: DEFAULT_FROM_ADDR.into(),
1268 to_addr: DEFAULT_TO_ADDR.into(),
1269 namespace: String::new(),
1270 ..Default::default()
1271 };
1272
1273 assert_eq!(expected_options, options);
1274 }
1275
1276 #[test]
1277 fn teset_options_builder_all() {
1278 let options = OptionsBuilder::new()
1279 .from_addr("127.0.0.2:0".into())
1280 .to_addr("127.0.0.2:8125".into())
1281 .namespace("mynamespace".into())
1282 .default_tag(String::from("tag1:tag1val"))
1283 .build();
1284 let expected_options = Options {
1285 from_addr: "127.0.0.2:0".into(),
1286 to_addr: "127.0.0.2:8125".into(),
1287 namespace: "mynamespace".into(),
1288 default_tags: ["tag1:tag1val".into()].to_vec(),
1289 socket_path: None,
1290 batching_options: None,
1291 };
1292
1293 assert_eq!(expected_options, options);
1294 }
1295
1296 #[test]
1297 #[serial]
1298 fn test_new() {
1299 let client = Client::new(Options::default()).unwrap();
1300 let expected_client = Client {
1301 socket: SocketType::Udp(UdpSocket::bind(DEFAULT_FROM_ADDR).unwrap()),
1302 from_addr: DEFAULT_FROM_ADDR.into(),
1303 to_addr: DEFAULT_TO_ADDR.into(),
1304 namespace: String::new(),
1305 default_tags: String::new().into_bytes(),
1306 };
1307
1308 assert_eq!(expected_client, client)
1309 }
1310
1311 #[test]
1312 #[serial]
1313 fn test_new_default_tags() {
1314 let options = Options::new(
1315 DEFAULT_FROM_ADDR,
1316 DEFAULT_TO_ADDR,
1317 "",
1318 vec![String::from("tag1:tag1val")],
1319 None,
1320 None,
1321 );
1322 let client = Client::new(options).unwrap();
1323 let expected_client = Client {
1324 socket: SocketType::Udp(UdpSocket::bind(DEFAULT_FROM_ADDR).unwrap()),
1325 from_addr: DEFAULT_FROM_ADDR.into(),
1326 to_addr: DEFAULT_TO_ADDR.into(),
1327 namespace: String::new(),
1328 default_tags: String::from("tag1:tag1val").into_bytes(),
1329 };
1330
1331 assert_eq!(expected_client, client)
1332 }
1333
1334 #[test]
1335 #[serial]
1336 fn test_system_tags() {
1337 let options = Options::new(
1338 DEFAULT_FROM_ADDR,
1339 DEFAULT_TO_ADDR,
1340 "",
1341 vec![String::from("tag1:tag1val"), String::from("version:0.0.2")],
1342 None,
1343 None,
1344 );
1345
1346 let client = with_default_system_tags(|| Client::new(options).unwrap());
1347
1348 dbg!(String::from_utf8_lossy(client.default_tags.as_ref()));
1349
1350 let expected_client = Client {
1351 socket: SocketType::Udp(UdpSocket::bind(DEFAULT_FROM_ADDR).unwrap()),
1352 from_addr: DEFAULT_FROM_ADDR.into(),
1353 to_addr: DEFAULT_TO_ADDR.into(),
1354 namespace: String::new(),
1355 default_tags: String::from("tag1:tag1val,version:0.0.2,env:production,service:service")
1356 .into_bytes(),
1357 };
1358
1359 assert_eq!(expected_client, client)
1360 }
1361
1362 #[test]
1363 fn test_send() {
1364 let options = Options::new("127.0.0.1:9001", "127.0.0.1:9002", "", vec![], None, None);
1365 let client = Client::new(options).unwrap();
1366 client
1368 .send(&GaugeMetric::new("gauge", "1234"), ["tag1", "tag2"])
1369 .unwrap();
1370 }
1371
1372 fn with_default_system_tags<T, F: FnOnce() -> T>(f: F) -> T {
1373 unsafe {
1374 std::env::set_var("DD_ENV", "production");
1375 std::env::set_var("DD_SERVICE", "service");
1376 std::env::set_var("DD_VERSION", "0.0.1");
1377 }
1378 let t = f();
1379 unsafe {
1380 std::env::remove_var("DD_ENV");
1381 std::env::remove_var("DD_SERVICE");
1382 std::env::remove_var("DD_VERSION");
1383 }
1384 t
1385 }
1386}
1387
1388#[cfg(all(feature = "unstable", test))]
1389mod bench {
1390 extern crate test;
1391
1392 use super::*;
1393
1394 use self::test::Bencher;
1395
1396 #[bench]
1397 fn bench_incr(b: &mut Bencher) {
1398 let options = Options::default();
1399 let client = Client::new(options).unwrap();
1400 let tags = &["name1:value1"];
1401 b.iter(|| {
1402 client.incr("bench.incr", tags).unwrap();
1403 })
1404 }
1405
1406 #[bench]
1407 fn bench_decr(b: &mut Bencher) {
1408 let options = Options::default();
1409 let client = Client::new(options).unwrap();
1410 let tags = &["name1:value1"];
1411 b.iter(|| {
1412 client.decr("bench.decr", tags).unwrap();
1413 })
1414 }
1415
1416 #[bench]
1417 fn bench_count(b: &mut Bencher) {
1418 let options = Options::default();
1419 let client = Client::new(options).unwrap();
1420 let tags = &["name1:value1"];
1421 let mut i = 0;
1422 b.iter(|| {
1423 client.count("bench.count", i, tags).unwrap();
1424 i += 1;
1425 })
1426 }
1427
1428 #[bench]
1429 fn bench_timing(b: &mut Bencher) {
1430 let options = Options::default();
1431 let client = Client::new(options).unwrap();
1432 let tags = &["name1:value1"];
1433 let mut i = 0;
1434 b.iter(|| {
1435 client.timing("bench.timing", i, tags).unwrap();
1436 i += 1;
1437 })
1438 }
1439
1440 #[bench]
1441 fn bench_gauge(b: &mut Bencher) {
1442 let options = Options::default();
1443 let client = Client::new(options).unwrap();
1444 let tags = vec!["name1:value1"];
1445 let mut i = 0;
1446 b.iter(|| {
1447 client.gauge("bench.guage", &i.to_string(), &tags).unwrap();
1448 i += 1;
1449 })
1450 }
1451
1452 #[bench]
1453 fn bench_histogram(b: &mut Bencher) {
1454 let options = Options::default();
1455 let client = Client::new(options).unwrap();
1456 let tags = vec!["name1:value1"];
1457 let mut i = 0;
1458 b.iter(|| {
1459 client
1460 .histogram("bench.histogram", &i.to_string(), &tags)
1461 .unwrap();
1462 i += 1;
1463 })
1464 }
1465
1466 #[bench]
1467 fn bench_distribution(b: &mut Bencher) {
1468 let options = Options::default();
1469 let client = Client::new(options).unwrap();
1470 let tags = vec!["name1:value1"];
1471 let mut i = 0;
1472 b.iter(|| {
1473 client
1474 .distribution("bench.distribution", &i.to_string(), &tags)
1475 .unwrap();
1476 i += 1;
1477 })
1478 }
1479
1480 #[bench]
1481 fn bench_set(b: &mut Bencher) {
1482 let options = Options::default();
1483 let client = Client::new(options).unwrap();
1484 let tags = vec!["name1:value1"];
1485 let mut i = 0;
1486 b.iter(|| {
1487 client.set("bench.set", &i.to_string(), &tags).unwrap();
1488 i += 1;
1489 })
1490 }
1491
1492 #[bench]
1493 fn bench_service_check(b: &mut Bencher) {
1494 let options = Options::default();
1495 let client = Client::new(options).unwrap();
1496 let tags = vec!["name1:value1"];
1497 let all_options = ServiceCheckOptions {
1498 hostname: Some("my-host.localhost"),
1499 timestamp: Some(1510326433),
1500 message: Some("Message about the check or service"),
1501 };
1502 b.iter(|| {
1503 client
1504 .service_check(
1505 "bench.service_check",
1506 ServiceStatus::Critical,
1507 &tags,
1508 Some(all_options),
1509 )
1510 .unwrap();
1511 })
1512 }
1513
1514 #[bench]
1515 fn bench_event(b: &mut Bencher) {
1516 let options = Options::default();
1517 let client = Client::new(options).unwrap();
1518 let tags = vec!["name1:value1"];
1519 b.iter(|| {
1520 client
1521 .event("Test Event Title", "Test Event Message", &tags, None)
1522 .unwrap();
1523 })
1524 }
1525
1526 fn bench_event_options(b: &mut Bencher) {
1527 let options = Options::default();
1528 let client = Client::new(options).unwrap();
1529 let tags = vec!["name1:value1"];
1530 let event_options = EventOptions::new()
1531 .with_timestamp(1638480000)
1532 .with_hostname("localhost")
1533 .with_priority(EventPriority::Normal)
1534 .with_alert_type(EventAlertType::Error);
1535
1536 b.iter(|| {
1537 client
1538 .event(
1539 "Test Event Title",
1540 "Test Event Message",
1541 &tags,
1542 Some(event_options),
1543 )
1544 .unwrap();
1545 })
1546 }
1547}