1use log::{debug, error, info};
2use nagios_range::Error as RangeError;
3use nagios_range::NagiosRange as ThresholdRange;
4use rand::Rng;
5use std::fmt;
6use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, ToSocketAddrs};
7use std::time::{Duration, Instant};
8use thiserror::Error;
9
10#[derive(Debug)]
11pub enum SocketType {
12 Datagram,
13 Raw,
14}
15
16impl fmt::Display for SocketType {
17 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18 match self {
19 SocketType::Datagram => write!(f, "Datagram"),
20 SocketType::Raw => write!(f, "Raw"),
21 }
22 }
23}
24
25#[derive(Copy, Clone, Debug, PartialEq, Eq)]
26pub enum AggregationMethod {
27 Average,
28 Median,
29 Max,
30 Min,
31}
32
33impl std::str::FromStr for AggregationMethod {
34 type Err = String;
35
36 fn from_str(s: &str) -> Result<Self, Self::Err> {
37 match s.to_lowercase().as_str() {
38 "average" => Ok(AggregationMethod::Average),
39 "avg" => Ok(AggregationMethod::Average),
40 "mean" => Ok(AggregationMethod::Average),
41 "median" => Ok(AggregationMethod::Median),
42 "med" => Ok(AggregationMethod::Median),
43 "minimum" => Ok(AggregationMethod::Min),
44 "min" => Ok(AggregationMethod::Min),
45 "maximum" => Ok(AggregationMethod::Max),
46 "max" => Ok(AggregationMethod::Max),
47 _ => Err(format!("'{}' is not a valid aggregation method", s)),
48 }
49 }
50}
51
52impl fmt::Display for AggregationMethod {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 match self {
55 AggregationMethod::Average => write!(f, "Average"),
56 AggregationMethod::Median => write!(f, "Median"),
57 AggregationMethod::Max => write!(f, "Max"),
58 AggregationMethod::Min => write!(f, "Min"),
59 }
60 }
61}
62
63#[derive(Debug)]
64pub struct PingErrorWrapper(ping::Error);
65
66impl PartialEq for PingErrorWrapper {
67 fn eq(&self, other: &Self) -> bool {
68 format!("{:?}", self.0) == format!("{:?}", other.0)
69 }
70}
71
72impl Eq for PingErrorWrapper {}
73
74impl fmt::Display for PingErrorWrapper {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(f, "{:?}", self.0)
77 }
78}
79
80impl std::error::Error for PingErrorWrapper {}
81
82#[non_exhaustive]
83#[derive(Error, Debug, Eq, PartialEq)]
84pub enum CheckJitterError {
85 #[error("DNS Lookup failed for: {0}")]
86 DnsLookupFailed(String),
87
88 #[error("DNS resolution error for '{addr}': {error}")]
89 DnsResolutionError { addr: String, error: String },
90
91 #[error("The delta count is 0. Cannot calculate jitter.")]
92 EmptyDeltas,
93
94 #[error("At least 2 samples are required to calculate jitter, got {0}.")]
95 InsufficientSamples(u8),
96
97 #[error("Invalid IP: {0}")]
98 InvalidIP(String),
99
100 #[error("Ping failed because of insufficient permissions")]
101 PermissionDenied,
102
103 #[error("Ping failed with error: {0}")]
104 PingError(PingErrorWrapper),
105
106 #[error("Ping failed with IO error: {0}")]
107 PingIoError(String),
108
109 #[error("Ping timed out after: {0}ms")]
110 Timeout(String),
111
112 #[error("Unable to parse hostname: {0}")]
113 UrlParseError(url::ParseError),
114}
115
116impl From<std::io::Error> for CheckJitterError {
117 fn from(err: std::io::Error) -> Self {
118 match err.kind() {
119 std::io::ErrorKind::PermissionDenied => CheckJitterError::PermissionDenied,
120 _ => CheckJitterError::PingIoError(err.to_string()),
121 }
122 }
123}
124
125#[derive(Clone, Debug, PartialEq)]
126pub struct Thresholds {
127 pub warning: Option<ThresholdRange>,
128 pub critical: Option<ThresholdRange>,
129}
130
131#[non_exhaustive]
132#[derive(Debug, PartialEq)]
133pub enum UnknownVariant {
134 Error(CheckJitterError),
135 FailedToInitLogger(String),
136 InvalidAddr(String),
137 InvalidMinMaxInterval(u64, u64),
138 ClapError(String),
139 NoThresholds,
140 RangeParseError(String, RangeError),
141 Timeout(Duration),
142}
143
144#[derive(Debug, PartialEq)]
145pub enum Status<'a> {
146 Ok(AggregationMethod, f64, &'a Thresholds),
147 Warning(AggregationMethod, f64, &'a Thresholds),
148 Critical(AggregationMethod, f64, &'a Thresholds),
149 Unknown(UnknownVariant),
150}
151
152fn display_string(label: &str, status: &str, uom: &str, f: f64, t: &Thresholds) -> String {
153 let min: f64 = 0.0;
154 match (t.warning, t.critical) {
155 (Some(w), Some(c)) => {
156 format!("{status} - {label}: {f}{uom}|'{label}'={f}{uom};{w};{c};{min}")
157 }
158 (Some(w), None) => format!("{status} - {label}: {f}{uom}|'{label}'={f}{uom};{w};;{min}"),
159 (None, Some(c)) => format!("{status} - {label}: {f}{uom}|'{label}'={f}{uom};;{c};{min}"),
160 (None, None) => format!("{status} - {label}: {f}{uom}|'{label}'={f}{uom};;;{min}"),
161 }
162}
163
164#[cfg(test)]
165mod display_string_tests {
166 use super::*;
167 use pretty_assertions::assert_eq;
168
169 #[test]
170 fn test_with_both_thresholds() {
171 let thresholds = Thresholds {
172 warning: Some(ThresholdRange::from("0:0.5").unwrap()),
173 critical: Some(ThresholdRange::from("0:1").unwrap()),
174 };
175
176 let expected = "OK - Average Jitter: 0.1ms|'Average Jitter'=0.1ms;0:0.5;0:1;0";
177 let actual = display_string("Average Jitter", "OK", "ms", 0.1, &thresholds);
178
179 assert_eq!(actual, expected);
180 }
181
182 #[test]
183 fn test_with_only_warning() {
184 let thresholds = Thresholds {
185 warning: Some(ThresholdRange::from("0:0.5").unwrap()),
186 critical: None,
187 };
188
189 let expected = "OK - Average Jitter: 0.1ms|'Average Jitter'=0.1ms;0:0.5;;0";
190 let actual = display_string("Average Jitter", "OK", "ms", 0.1, &thresholds);
191
192 assert_eq!(actual, expected);
193 }
194
195 #[test]
196 fn test_with_only_critical() {
197 let thresholds = Thresholds {
198 warning: None,
199 critical: Some(ThresholdRange::from("0:0.5").unwrap()),
200 };
201
202 let expected = "OK - Average Jitter: 0.1ms|'Average Jitter'=0.1ms;;0:0.5;0";
203 let actual = display_string("Average Jitter", "OK", "ms", 0.1, &thresholds);
204
205 assert_eq!(actual, expected);
206 }
207
208 #[test]
209 fn test_with_no_thresholds() {
210 let thresholds = Thresholds {
211 warning: None,
212 critical: None,
213 };
214
215 let expected = "OK - Average Jitter: 0.1ms|'Average Jitter'=0.1ms;;;0";
216 let actual = display_string("Average Jitter", "OK", "ms", 0.1, &thresholds);
217
218 assert_eq!(actual, expected);
219 }
220}
221
222impl fmt::Display for Status<'_> {
223 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224 let uom = "ms";
225 let label = match self {
226 Status::Ok(AggregationMethod::Average, _, _) => "Average Jitter",
227 Status::Ok(AggregationMethod::Median, _, _) => "Median Jitter",
228 Status::Ok(AggregationMethod::Max, _, _) => "Max Jitter",
229 Status::Ok(AggregationMethod::Min, _, _) => "Min Jitter",
230 Status::Warning(AggregationMethod::Average, _, _) => "Average Jitter",
231 Status::Warning(AggregationMethod::Median, _, _) => "Median Jitter",
232 Status::Warning(AggregationMethod::Max, _, _) => "Max Jitter",
233 Status::Warning(AggregationMethod::Min, _, _) => "Min Jitter",
234 Status::Critical(AggregationMethod::Average, _, _) => "Average Jitter",
235 Status::Critical(AggregationMethod::Median, _, _) => "Median Jitter",
236 Status::Critical(AggregationMethod::Max, _, _) => "Max Jitter",
237 Status::Critical(AggregationMethod::Min, _, _) => "Min Jitter",
238 Status::Unknown(_) => "Unknown",
239 };
240
241 match self {
242 Status::Ok(_, n, t) => {
243 write!(f, "{}", display_string(label, "OK", uom, *n, t))
244 }
245 Status::Warning(_, n, t) => {
246 write!(f, "{}", display_string(label, "WARNING", uom, *n, t))
247 }
248 Status::Critical(_, n, t) => {
249 write!(f, "{}", display_string(label, "CRITICAL", uom, *n, t))
250 }
251 Status::Unknown(UnknownVariant::Error(e)) => {
252 write!(f, "UNKNOWN - An error occurred: '{}'", e)
253 }
254 Status::Unknown(UnknownVariant::FailedToInitLogger(s)) => {
255 write!(
256 f,
257 "UNKNOWN - Failed to initialize logger with error: '{}'",
258 s
259 )
260 }
261 Status::Unknown(UnknownVariant::InvalidAddr(s)) => {
262 write!(f, "UNKNOWN - Invalid address or hostname: {}", s)
263 }
264 Status::Unknown(UnknownVariant::InvalidMinMaxInterval(min, max)) => {
265 write!(
266 f,
267 "UNKNOWN - Invalid min/max interval: min: {}, max: {}",
268 min, max
269 )
270 }
271 Status::Unknown(UnknownVariant::ClapError(s)) => {
272 let trimmed = s.trim_end();
273 let without_leading_error = trimmed.trim_start_matches("error: ");
274 write!(
275 f,
276 "UNKNOWN - Command line parsing produced an error: {}",
277 without_leading_error,
278 )
279 }
280 Status::Unknown(UnknownVariant::NoThresholds) => {
281 write!(
282 f,
283 "UNKNOWN - No thresholds provided. Provide at least one threshold."
284 )
285 }
286 Status::Unknown(UnknownVariant::RangeParseError(s, e)) => {
287 write!(
288 f,
289 "UNKNOWN - Unable to parse range '{}' with error: {}",
290 s, e
291 )
292 }
293 Status::Unknown(UnknownVariant::Timeout(d)) => {
294 write!(f, "UNKNOWN - Ping timeout occurred after {:?}", d)
295 }
296 }
297 }
298}
299
300#[cfg(test)]
301mod status_display_tests {
302 use super::*;
303 use pretty_assertions::assert_eq;
304
305 #[test]
306 fn test_with_ok() {
307 let t = Thresholds {
308 warning: Some(ThresholdRange::from("0:0.5").unwrap()),
309 critical: Some(ThresholdRange::from("0:1").unwrap()),
310 };
311 let status = Status::Ok(AggregationMethod::Average, 0.1, &t);
312 let expected = "OK - Average Jitter: 0.1ms|'Average Jitter'=0.1ms;0:0.5;0:1;0";
313 let actual = format!("{}", status);
314
315 assert_eq!(actual, expected);
316 }
317
318 #[test]
319 fn test_with_ok_simple_thresholds() {
322 let t = Thresholds {
323 warning: Some(ThresholdRange::from("0.5").unwrap()),
324 critical: Some(ThresholdRange::from("1").unwrap()),
325 };
326 let status = Status::Ok(AggregationMethod::Median, 0.1, &t);
327 let expected = "OK - Median Jitter: 0.1ms|'Median Jitter'=0.1ms;0:0.5;0:1;0";
328 let actual = format!("{}", status);
329
330 assert_eq!(actual, expected);
331 }
332
333 #[test]
334 fn test_with_warning() {
335 let t = Thresholds {
336 warning: Some(ThresholdRange::from("0:0.5").unwrap()),
337 critical: Some(ThresholdRange::from("0:1").unwrap()),
338 };
339 let status = Status::Warning(AggregationMethod::Average, 0.1, &t);
340 let expected = "WARNING - Average Jitter: 0.1ms|'Average Jitter'=0.1ms;0:0.5;0:1;0";
341 let actual = format!("{}", status);
342
343 assert_eq!(actual, expected);
344 }
345
346 #[test]
347 fn test_with_critical() {
348 let t = Thresholds {
349 warning: Some(ThresholdRange::from("0:0.5").unwrap()),
350 critical: Some(ThresholdRange::from("0:1").unwrap()),
351 };
352 let status = Status::Critical(AggregationMethod::Max, 0.1, &t);
353 let expected = "CRITICAL - Max Jitter: 0.1ms|'Max Jitter'=0.1ms;0:0.5;0:1;0";
354 let actual = format!("{}", status);
355
356 assert_eq!(actual, expected);
357 }
358
359 #[test]
360 fn test_with_error() {
361 let status = Status::Unknown(UnknownVariant::Error(CheckJitterError::DnsLookupFailed(
362 "example.com".to_string(),
363 )));
364
365 let expected = "UNKNOWN - An error occurred: 'DNS Lookup failed for: example.com'";
366 let actual = format!("{}", status);
367
368 assert_eq!(actual, expected);
369 }
370}
371
372impl Status<'_> {
373 pub fn to_int(&self) -> i32 {
374 match self {
375 Status::Ok(_, _, _) => 0,
376 Status::Warning(_, _, _) => 1,
377 Status::Critical(_, _, _) => 2,
378 Status::Unknown(_) => 3,
379 }
380 }
381}
382
383fn abs_diff_duration(a: Duration, b: Duration) -> Duration {
384 if a > b {
385 a - b
386 } else {
387 b - a
388 }
389}
390
391#[cfg(test)]
392mod abs_diff_duration_tests {
393 use super::*;
394 use pretty_assertions::assert_eq;
395
396 #[test]
397 fn test_with_small_a() {
398 let a = Duration::from_nanos(100_000_000);
399 let b = Duration::from_nanos(100_100_000);
400 let expected = Duration::from_nanos(100_000);
401 let actual = abs_diff_duration(a, b);
402
403 assert_eq!(actual, expected);
404 }
405
406 #[test]
407 fn test_with_small_b() {
408 let a = Duration::from_nanos(100_100_000);
409 let b = Duration::from_nanos(100_000_000);
410 let expected = Duration::from_nanos(100_000);
411 let actual = abs_diff_duration(a, b);
412
413 assert_eq!(actual, expected);
414 }
415
416 #[test]
417 fn test_with_equal_values() {
418 let a = Duration::from_nanos(100_000_000);
419 let b = Duration::from_nanos(100_000_000);
420 let expected = Duration::from_nanos(0);
421 let actual = abs_diff_duration(a, b);
422
423 assert_eq!(actual, expected);
424 }
425}
426
427fn generate_intervals(count: u8, min_interval: u64, max_interval: u64) -> Vec<Duration> {
428 if min_interval > max_interval {
429 debug!(
430 "Invalid min and max interval: min: {}, max: {}. No random intervals will be generated.",
431 min_interval, max_interval
432 );
433 return Vec::new();
434 }
435
436 if max_interval == 0 && min_interval == 0 {
437 debug!("Min and max interval are both 0. No random intervals will be generated.");
438 return Vec::new();
439 }
440
441 let mut intervals = Vec::with_capacity(count as usize);
442
443 if max_interval == min_interval {
444 debug!(
445 "Min and max interval are equal: {}ms. Intervals will not be randomized.",
446 min_interval
447 );
448 for _ in 0..count {
449 intervals.push(Duration::from_millis(min_interval));
450 }
451
452 debug!("Random intervals: {:?}", intervals);
453
454 return intervals;
455 }
456
457 debug!(
458 "Generating {} random intervals between {}ms and {}ms...",
459 count, min_interval, max_interval
460 );
461
462 for _ in 0..count {
463 let interval = rand::thread_rng().gen_range(min_interval..=max_interval);
464 intervals.push(Duration::from_millis(interval));
465 }
466
467 debug!("Random intervals: {:?}", intervals);
468
469 intervals
470}
471
472#[cfg(test)]
473mod generate_intervals_tests {
474 use super::*;
475 use pretty_assertions::assert_eq;
476
477 #[test]
478 fn test_with_min_max() {
479 let count = 10;
480 let min_interval = 10;
481 let max_interval = 100;
482 let intervals = generate_intervals(count, min_interval, max_interval);
483
484 assert_eq!(intervals.len(), count as usize);
485 for i in intervals {
486 assert!(i >= Duration::from_millis(min_interval));
487 assert!(i <= Duration::from_millis(max_interval));
488 }
489 }
490
491 #[test]
492 fn test_with_min_max_equal() {
493 let count = 10;
494 let min_interval = 10;
495 let max_interval = 10;
496 let intervals = generate_intervals(count, min_interval, max_interval);
497
498 assert_eq!(intervals.len(), count as usize);
499 for i in intervals {
500 assert_eq!(i, Duration::from_millis(min_interval));
501 }
502 }
503
504 #[test]
505 fn test_with_min_max_zero() {
506 let count = 10;
507 let min_interval = 0;
508 let max_interval = 0;
509 let intervals = generate_intervals(count, min_interval, max_interval);
510
511 assert_eq!(intervals, Vec::<Duration>::new());
512 assert!(intervals.is_empty());
513 }
514
515 #[test]
516 fn test_with_min_max_swapped() {
517 let count = 10;
518 let min_interval = 100;
519 let max_interval = 10;
520 let intervals = generate_intervals(count, min_interval, max_interval);
521
522 assert_eq!(intervals, Vec::<Duration>::new());
523 assert!(intervals.is_empty());
524 }
525 #[test]
526 fn test_with_zero_count() {
527 let count = 0;
528 let min_interval = 10;
529 let max_interval = 100;
530 let intervals = generate_intervals(count, min_interval, max_interval);
531
532 assert_eq!(intervals, Vec::<Duration>::new());
533 assert!(intervals.is_empty());
534 }
535
536 #[test]
537 fn test_with_large_range() {
538 let count = 10;
539 let min_interval = 1;
540 let max_interval = 1_000_000;
541 let intervals = generate_intervals(count, min_interval, max_interval);
542
543 assert_eq!(intervals.len(), count as usize);
544 for i in intervals {
545 assert!(i >= Duration::from_millis(min_interval));
546 assert!(i <= Duration::from_millis(max_interval));
547 }
548 }
549
550 #[test]
551 fn test_with_single_interval() {
552 let count = 1;
553 let min_interval = 10;
554 let max_interval = 100;
555 let intervals = generate_intervals(count, min_interval, max_interval);
556
557 assert_eq!(intervals.len(), 1);
558 assert!(intervals[0] >= Duration::from_millis(min_interval));
559 assert!(intervals[0] <= Duration::from_millis(max_interval));
560 }
561
562 #[test]
563 fn test_with_very_large_intervals() {
564 let count = 10;
565 let min_interval = u64::MAX - 1_000;
566 let max_interval = u64::MAX;
567 let intervals = generate_intervals(count, min_interval, max_interval);
568
569 assert_eq!(intervals.len(), count as usize);
570 for i in intervals {
571 assert!(i >= Duration::from_millis(min_interval));
572 assert!(i <= Duration::from_millis(max_interval));
573 }
574 }
575}
576
577fn default_resolver(addr: &str) -> Result<Vec<IpAddr>, CheckJitterError> {
578 let addr_with_port = format!("{}:0", addr);
579 match addr_with_port.to_socket_addrs() {
580 Ok(addrs_iter) => {
581 let addrs: Vec<IpAddr> = addrs_iter.map(|sockaddr| sockaddr.ip()).collect();
582 if addrs.is_empty() {
583 Err(CheckJitterError::DnsLookupFailed(addr.to_string()))
584 } else {
585 Ok(addrs)
586 }
587 }
588 Err(e) => Err(CheckJitterError::DnsResolutionError {
589 addr: addr.to_string(),
590 error: e.to_string(),
591 }),
592 }
593}
594
595fn parse_addr_with_resolver<F>(addr: &str, resolver: F) -> Result<Vec<IpAddr>, CheckJitterError>
596where
597 F: Fn(&str) -> Result<Vec<IpAddr>, CheckJitterError>,
598{
599 if let Ok(ipv4) = addr.parse::<Ipv4Addr>() {
600 return Ok(vec![IpAddr::V4(ipv4)]);
601 }
602 if let Ok(ipv6) = addr.parse::<Ipv6Addr>() {
603 return Ok(vec![IpAddr::V6(ipv6)]);
604 }
605
606 resolver(addr)
608}
609
610fn parse_addr(addr: &str) -> Result<Vec<IpAddr>, CheckJitterError> {
611 parse_addr_with_resolver(addr, default_resolver)
612}
613
614#[cfg(test)]
615mod parse_addr_tests {
616 use super::*;
617
618 fn mock_resolver(addr: &str) -> Result<Vec<IpAddr>, CheckJitterError> {
619 match addr {
620 "localhost" => Ok(vec![IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))]),
621 "ipv6-localhost" => Ok(vec![IpAddr::V6(Ipv6Addr::LOCALHOST)]),
622 "multi.example.com" => Ok(vec![
623 IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
624 IpAddr::V4(Ipv4Addr::new(192, 0, 2, 2)),
625 IpAddr::V4(Ipv4Addr::new(192, 0, 2, 3)),
626 ]),
627 "unresolved.example.com" => Err(CheckJitterError::DnsLookupFailed(addr.to_string())),
628 "error.example.com" => Err(CheckJitterError::DnsResolutionError {
629 addr: addr.to_string(),
630 error: "mock error".to_string(),
631 }),
632 _ => Err(CheckJitterError::DnsResolutionError {
633 addr: addr.to_string(),
634 error: "unknown host".to_string(),
635 }),
636 }
637 }
638
639 #[test]
640 fn test_valid_ipv4_address() {
641 let addr = "192.168.1.1";
642 let result = parse_addr_with_resolver(addr, mock_resolver);
643 assert_eq!(result, Ok(vec![IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1))]));
644 }
645
646 #[test]
647 fn test_valid_ipv6_address() {
648 let addr = "::1";
649 let result = parse_addr_with_resolver(addr, mock_resolver);
650 assert_eq!(result, Ok(vec![IpAddr::V6(Ipv6Addr::LOCALHOST)]));
651 }
652
653 #[test]
654 fn test_invalid_ip_address() {
655 let addr = "999.999.999.999";
656 let result = parse_addr_with_resolver(addr, mock_resolver);
657 assert_eq!(
658 result,
659 Err(CheckJitterError::DnsResolutionError {
660 addr: addr.to_string(),
661 error: "unknown host".to_string(),
662 })
663 );
664 }
665
666 #[test]
667 fn test_valid_hostname() {
668 let addr = "localhost";
669 let result = parse_addr_with_resolver(addr, mock_resolver);
670 assert_eq!(result, Ok(vec![IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1))]));
671 }
672
673 #[test]
674 fn test_valid_ipv6_hostname() {
675 let addr = "ipv6-localhost";
676 let result = parse_addr_with_resolver(addr, mock_resolver);
677 assert_eq!(result, Ok(vec![IpAddr::V6(Ipv6Addr::LOCALHOST)]));
678 }
679
680 #[test]
681 fn test_unresolved_hostname() {
682 let addr = "unresolved.example.com";
683 let result = parse_addr_with_resolver(addr, mock_resolver);
684 assert_eq!(
685 result,
686 Err(CheckJitterError::DnsLookupFailed(addr.to_string()))
687 );
688 }
689
690 #[test]
691 fn test_dns_resolution_error() {
692 let addr = "error.example.com";
693 let result = parse_addr_with_resolver(addr, mock_resolver);
694 assert_eq!(
695 result,
696 Err(CheckJitterError::DnsResolutionError {
697 addr: addr.to_string(),
698 error: "mock error".to_string(),
699 })
700 );
701 }
702
703 #[test]
704 fn test_unknown_hostname() {
705 let addr = "unknown.example.com";
706 let result = parse_addr_with_resolver(addr, mock_resolver);
707 assert_eq!(
708 result,
709 Err(CheckJitterError::DnsResolutionError {
710 addr: addr.to_string(),
711 error: "unknown host".to_string(),
712 })
713 );
714 }
715
716 #[test]
717 fn test_hostname_with_multiple_ips() {
718 let addr = "multi.example.com";
719 let result = parse_addr_with_resolver(addr, mock_resolver);
720 assert_eq!(
721 result,
722 Ok(vec![
723 IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1)),
724 IpAddr::V4(Ipv4Addr::new(192, 0, 2, 2)),
725 IpAddr::V4(Ipv4Addr::new(192, 0, 2, 3)),
726 ])
727 )
728 }
729}
730
731fn run_samples(
732 ip: IpAddr,
733 socket_type: SocketType,
734 samples: u8,
735 timeout: Duration,
736 intervals: Vec<Duration>,
737) -> Result<Vec<Duration>, CheckJitterError> {
738 let ping_function = match socket_type {
739 SocketType::Datagram => ping::dgramsock::ping,
740 SocketType::Raw => ping::rawsock::ping,
741 };
742
743 let mut durations = Vec::<Duration>::with_capacity(samples as usize);
744 let mut intervals_iter = intervals.into_iter();
745 let mut next_ping_time = Instant::now();
746
747 for i in 0..samples {
748 let now = Instant::now();
749
750 if now < next_ping_time {
751 let sleep_duration = next_ping_time - now;
752 std::thread::sleep(sleep_duration);
753 }
754
755 let start_time = Instant::now();
756
757 match ping_function(ip, Some(timeout), None, None, None, None) {
758 Ok(_) => {
759 let end_time = Instant::now();
760 let duration = end_time - start_time;
761 durations.push(duration);
762 debug!("Ping round {}, duration: {:?}", i + 1, duration);
763
764 if let Some(interval) = intervals_iter.next() {
765 next_ping_time = end_time + interval;
766 } else {
767 next_ping_time = end_time;
768 }
769 }
770 Err(e) => {
771 if let ping::Error::IoError { error } = &e {
772 match error.kind() {
773 std::io::ErrorKind::PermissionDenied => {
774 return Err(CheckJitterError::PermissionDenied);
775 }
776 std::io::ErrorKind::WouldBlock => {
777 return Err(CheckJitterError::Timeout(timeout.as_millis().to_string()));
778 }
779 _ => {
780 return Err(CheckJitterError::PingIoError(error.to_string()));
781 }
782 }
783 }
784 return Err(CheckJitterError::PingError(PingErrorWrapper(e)));
785 }
786 };
787 }
788
789 debug!("Ping durations: {:?}", durations);
790 Ok(durations)
791}
792
793fn get_durations(
794 addr: &str,
795 socket_type: SocketType,
796 samples: u8,
797 timeout: Duration,
798 min_interval: u64,
799 max_interval: u64,
800) -> Result<Vec<Duration>, CheckJitterError> {
801 let ip = match parse_addr(addr)?.first() {
805 Some(ip) => *ip,
806 None => return Err(CheckJitterError::DnsLookupFailed(addr.to_string())),
807 };
808
809 if samples < 2 {
810 return Err(CheckJitterError::InsufficientSamples(samples));
811 }
812
813 let intervals = generate_intervals(samples - 1, min_interval, max_interval);
814 run_samples(ip, socket_type, samples, timeout, intervals)
815}
816
817fn calculate_deltas(durations: &[Duration]) -> Result<Vec<Duration>, CheckJitterError> {
818 if durations.len() < 2 {
819 return Err(CheckJitterError::InsufficientSamples(durations.len() as u8));
820 }
821
822 let deltas = durations
823 .windows(2)
824 .map(|w| abs_diff_duration(w[0], w[1]))
825 .collect();
826
827 debug!("Deltas: {:?}", deltas);
828
829 Ok(deltas)
830}
831
832#[cfg(test)]
833mod calculate_deltas_tests {
834 use super::*;
835 use pretty_assertions::assert_eq;
836
837 #[test]
838 fn test_with_zero_durations() {
839 let durations = &[];
840
841 let result = calculate_deltas(durations);
842
843 assert_eq!(result, Err(CheckJitterError::InsufficientSamples(0)));
844 }
845
846 #[test]
847 fn test_with_one_duration() {
848 let durations = &[Duration::from_nanos(100_000_000)];
849
850 let result = calculate_deltas(durations);
851
852 assert_eq!(result, Err(CheckJitterError::InsufficientSamples(1)));
853 }
854
855 #[test]
856 fn test_with_two_durations() {
857 let durations = &[
858 Duration::from_nanos(100_000_000),
859 Duration::from_nanos(100_100_000),
860 ];
861
862 let expected_deltas = vec![Duration::from_nanos(100_000)];
863
864 let deltas = calculate_deltas(durations).unwrap();
865
866 assert_eq!(deltas, expected_deltas);
867 }
868
869 #[test]
870 fn test_with_simple_durations() {
871 let durations = &[
872 Duration::from_nanos(100_000_000),
873 Duration::from_nanos(100_100_000),
874 Duration::from_nanos(100_200_000),
875 Duration::from_nanos(100_300_000),
876 ];
877
878 let expected_deltas = &[
879 Duration::from_nanos(100_000),
880 Duration::from_nanos(100_000),
881 Duration::from_nanos(100_000),
882 ];
883
884 let deltas = calculate_deltas(durations).unwrap();
885
886 assert_eq!(deltas, expected_deltas);
887 }
888
889 #[test]
890 fn test_with_irregular_durations() {
891 let durations = &[
892 Duration::from_nanos(100_000_000),
893 Duration::from_nanos(100_101_200),
894 Duration::from_nanos(101_200_030),
895 Duration::from_nanos(100_310_900),
896 ];
897
898 let expected_deltas = &[
899 Duration::from_nanos(101_200),
900 Duration::from_nanos(1_098_830),
901 Duration::from_nanos(889_130),
902 ];
903
904 let deltas = calculate_deltas(durations).unwrap();
905 assert_eq!(deltas, expected_deltas);
906 }
907}
908
909fn calculate_avg_jitter(deltas: Vec<Duration>) -> f64 {
910 let total_jitter = deltas.iter().sum::<Duration>();
911 debug!("Sum of deltas: {:?}", total_jitter);
912
913 let avg_jitter = total_jitter / deltas.len() as u32;
914 debug!("Average jitter duration: {:?}", avg_jitter);
915
916 let average_float = avg_jitter.as_secs_f64() * 1_000.0;
917 debug!("Average jitter as f64: {:?}", average_float);
918
919 average_float
920}
921
922fn calculate_median_jitter(deltas: Vec<Duration>) -> f64 {
923 let mut sorted_deltas = deltas.clone();
924 sorted_deltas.sort();
925 debug!("Sorted deltas: {:?}", sorted_deltas);
926
927 let len = sorted_deltas.len();
928 debug!("Number of deltas: {}", len);
929
930 let median_float: f64 = if len % 2 == 0 {
931 let mid = len / 2;
932 let mid_1 = mid - 1;
933 let mid_2 = mid;
934 let dur_1 = sorted_deltas[mid_1].as_secs_f64() * 1_000.0;
935 let dur_2 = sorted_deltas[mid_2].as_secs_f64() * 1_000.0;
936 (dur_1 + dur_2) / 2.0
937 } else {
938 let mid = len / 2;
939 sorted_deltas[mid].as_secs_f64() * 1_000.0
940 };
941 debug!("Median jitter as f64: {:?}", median_float);
942
943 median_float
944}
945
946fn calculate_max_jitter(deltas: Vec<Duration>) -> Result<f64, CheckJitterError> {
947 let max = deltas.iter().max().ok_or(CheckJitterError::EmptyDeltas)?;
948 debug!("Max jitter: {:?}", max);
949 let max_float = max.as_secs_f64() * 1_000.0;
950 debug!("Max jitter as f64: {:?}", max_float);
951
952 Ok(max_float)
953}
954
955fn calculate_min_jitter(deltas: Vec<Duration>) -> Result<f64, CheckJitterError> {
956 let min = deltas.iter().min().ok_or(CheckJitterError::EmptyDeltas)?;
957 debug!("Min jitter: {:?}", min);
958 let min_float = min.as_secs_f64() * 1_000.0;
959 debug!("Min jitter as f64: {:?}", min_float);
960
961 Ok(min_float)
962}
963
964pub fn round_jitter(j: f64, precision: u8) -> f64 {
966 let factor = 10f64.powi(precision as i32);
967 let rounded_jitter = (j * factor).round() / factor;
968 debug!("jitter as rounded f64: {:?}", rounded_jitter);
969
970 rounded_jitter
971}
972
973#[cfg(test)]
974mod calculate_rounded_jitter_tests {
975 use super::*;
976 use pretty_assertions::assert_eq;
977
978 #[test]
979 fn test_with_simple_durations() {
980 let simple_durations = vec![
981 Duration::from_nanos(100_000_000),
982 Duration::from_nanos(100_100_000),
983 Duration::from_nanos(100_200_000),
984 Duration::from_nanos(100_300_000),
985 Duration::from_nanos(100_400_000),
986 Duration::from_nanos(100_500_000),
987 Duration::from_nanos(100_600_000),
988 Duration::from_nanos(100_700_000),
989 Duration::from_nanos(100_800_000),
990 Duration::from_nanos(100_900_000),
991 ];
992
993 let expected_average_jitter = 0.1;
994 let expected_median_jitter = 0.1;
995 let expected_max_jitter = 0.1;
996 let expected_min_jitter = 0.1;
997 let deltas = calculate_deltas(&simple_durations).unwrap();
998 println!("{:#?}", deltas.clone());
999 let average_jitter = calculate_avg_jitter(deltas.clone());
1000 let median_jitter = calculate_median_jitter(deltas.clone());
1001 let max_jitter = calculate_max_jitter(deltas.clone()).unwrap();
1002 let min_jitter = calculate_min_jitter(deltas).unwrap();
1003 let rounded_average_jitter = round_jitter(average_jitter, 3);
1004 let rounded_median_jitter = round_jitter(median_jitter, 3);
1005 let rounded_max_jitter = round_jitter(max_jitter, 3);
1006 let rounded_min_jitter = round_jitter(min_jitter, 3);
1007
1008 assert_eq!(rounded_average_jitter, expected_average_jitter);
1009 assert_eq!(rounded_median_jitter, expected_median_jitter);
1010 assert_eq!(rounded_max_jitter, expected_max_jitter);
1011 assert_eq!(rounded_min_jitter, expected_min_jitter);
1012 }
1013
1014 #[test]
1015 fn test_with_even_number_of_irregular_durations() {
1016 let irregular_durations = vec![
1017 Duration::from_nanos(270_279_792),
1018 Duration::from_nanos(270_400_049),
1019 Duration::from_nanos(270_242_514),
1020 Duration::from_nanos(269_988_869),
1021 Duration::from_nanos(270_157_314),
1022 Duration::from_nanos(270_096_136),
1023 Duration::from_nanos(270_105_637),
1024 Duration::from_nanos(270_003_857),
1025 Duration::from_nanos(270_192_099),
1026 Duration::from_nanos(270_035_557),
1027 ];
1028
1029 let expected_average_jitter = 0.135_236;
1030 let expected_median_jitter = 0.156_542;
1031 let expected_max_jitter = 0.253_645;
1032 let expected_min_jitter = 0.009_501;
1033 let deltas = calculate_deltas(&irregular_durations).unwrap();
1034 println!("{:#?}", deltas.clone());
1035 let average_jitter = calculate_avg_jitter(deltas.clone());
1036 let median_jitter = calculate_median_jitter(deltas.clone());
1037 let max_jitter = calculate_max_jitter(deltas.clone()).unwrap();
1038 let min_jitter = calculate_min_jitter(deltas).unwrap();
1039 let rounded_average_jitter = round_jitter(average_jitter, 6);
1040 let rounded_median_jitter = round_jitter(median_jitter, 6);
1041 let rounded_max_jitter = round_jitter(max_jitter, 6);
1042 let rounded_min_jitter = round_jitter(min_jitter, 6);
1043
1044 assert_eq!(rounded_average_jitter, expected_average_jitter);
1045 assert_eq!(rounded_median_jitter, expected_median_jitter);
1046 assert_eq!(rounded_max_jitter, expected_max_jitter);
1047 assert_eq!(rounded_min_jitter, expected_min_jitter);
1048 }
1049
1050 #[test]
1051 fn test_with_uneven_number_of_irregular_durations() {
1052 let irregular_durations = vec![
1053 Duration::from_nanos(270_279_792),
1054 Duration::from_nanos(270_400_049),
1055 Duration::from_nanos(270_242_514),
1056 Duration::from_nanos(269_988_869),
1057 Duration::from_nanos(270_157_314),
1058 Duration::from_nanos(270_096_136),
1059 Duration::from_nanos(270_105_637),
1060 Duration::from_nanos(270_003_857),
1061 Duration::from_nanos(270_192_099),
1062 ];
1063
1064 let expected_average_jitter = 0.132_572;
1065 let expected_median_jitter = 0.138_896;
1066 let expected_max_jitter = 0.253_645;
1067 let expected_min_jitter = 0.009_501;
1068 let deltas = calculate_deltas(&irregular_durations).unwrap();
1069 println!("{:#?}", deltas.clone());
1070 let average_jitter = calculate_avg_jitter(deltas.clone());
1071 let median_jitter = calculate_median_jitter(deltas.clone());
1072 let max_jitter = calculate_max_jitter(deltas.clone()).unwrap();
1073 let min_jitter = calculate_min_jitter(deltas).unwrap();
1074 let rounded_average_jitter = round_jitter(average_jitter, 6);
1075 let rounded_median_jitter = round_jitter(median_jitter, 6);
1076 let rounded_max_jitter = round_jitter(max_jitter, 6);
1077 let rounded_min_jitter = round_jitter(min_jitter, 6);
1078
1079 assert_eq!(rounded_average_jitter, expected_average_jitter);
1080 assert_eq!(rounded_median_jitter, expected_median_jitter);
1081 assert_eq!(rounded_max_jitter, expected_max_jitter);
1082 assert_eq!(rounded_min_jitter, expected_min_jitter);
1083 }
1084}
1085
1086pub fn get_jitter(
1125 aggr_method: AggregationMethod,
1126 addr: &str,
1127 socket_type: SocketType,
1128 samples: u8,
1129 timeout: Duration,
1130 min_interval: u64,
1131 max_interval: u64,
1132) -> Result<f64, CheckJitterError> {
1133 let durations = get_durations(
1134 addr,
1135 socket_type,
1136 samples,
1137 timeout,
1138 min_interval,
1139 max_interval,
1140 )?;
1141 let deltas = calculate_deltas(&durations)?;
1142 match aggr_method {
1143 AggregationMethod::Average => Ok(calculate_avg_jitter(deltas)),
1144 AggregationMethod::Median => Ok(calculate_median_jitter(deltas)),
1145 AggregationMethod::Max => calculate_max_jitter(deltas),
1146 AggregationMethod::Min => calculate_min_jitter(deltas),
1147 }
1148}
1149
1150pub fn evaluate_thresholds(
1185 aggr_method: AggregationMethod,
1186 value: f64,
1187 thresholds: &Thresholds,
1188) -> Status {
1189 info!("Evaluating jitter: {:?}", value);
1190 if let Some(c) = thresholds.critical {
1191 info!("Checking critical threshold: {:?}", c);
1192 if c.check(value) {
1193 info!("Jitter is critical: {:?}", value);
1194 return Status::Critical(aggr_method, value, thresholds);
1195 } else {
1196 info!("Jitter is not critical: {:?}", value);
1197 }
1198 } else {
1199 info!("No critical threshold provided");
1200 }
1201
1202 if let Some(w) = thresholds.warning {
1203 info!("Checking warning threshold: {:?}", w);
1204 if w.check(value) {
1205 info!("Jitter is warning: {:?}", value);
1206 return Status::Warning(aggr_method, value, thresholds);
1207 } else {
1208 info!("Jitter is not warning: {:?}", value);
1209 }
1210 } else {
1211 info!("No warning threshold provided");
1212 }
1213
1214 Status::Ok(aggr_method, value, thresholds)
1215}