Skip to main content

fping/
output.rs

1use std::time::{Duration, SystemTime, UNIX_EPOCH};
2
3use crate::types::HostEntry;
4
5pub fn sprint_tm(d: Duration) -> String {
6  let ms = d.as_secs_f64() * 1000.0;
7  if ms < 0.0 {
8    format!("{:.2}", ms)
9  } else if ms < 1.0 {
10    format!("{:.3}", ms)
11  } else if ms < 10.0 {
12    format!("{:.2}", ms)
13  } else if ms < 100.0 {
14    format!("{:.1}", ms)
15  } else if ms < 1_000_000.0 {
16    format!("{:.0}", ms)
17  } else {
18    format!("{:.3e}", ms)
19  }
20}
21
22pub fn now_ts() -> String {
23  let secs = SystemTime::now()
24    .duration_since(UNIX_EPOCH)
25    .unwrap_or_default()
26    .as_secs_f64();
27  format!("[{:.5}]", secs)
28}
29
30pub fn max_host_len(hosts: &[HostEntry]) -> usize {
31  hosts.iter().map(|h| h.display.len()).max().unwrap_or(0)
32}
33
34pub fn print_alive(host: &HostEntry, timestamp: bool, json: bool) {
35  let prefix = if timestamp { format!("{} ", now_ts()) } else { String::new() };
36  if json {
37    println!("{{\"alive\": {{\"host\": \"{}\"}}}}", host.display);
38  } else {
39    println!("{}{} is alive", prefix, host.display);
40  }
41}
42
43pub fn print_unreachable(host: &HostEntry, timestamp: bool, json: bool) {
44  let prefix = if timestamp { format!("{} ", now_ts()) } else { String::new() };
45  if json {
46    println!("{{\"unreachable\": {{\"host\": \"{}\"}}}}", host.display);
47  } else {
48    println!("{}{} is unreachable", prefix, host.display);
49  }
50}
51
52pub struct RecvLineOpts<'a> {
53  pub host: &'a HostEntry,
54  pub ping_index: u32,
55  pub rtt: Duration,
56  pub raw_len: usize,
57  pub max_len: usize,
58  pub timestamp: bool,
59  pub json: bool,
60  pub verbose_count: bool,
61}
62
63pub fn print_recv(opts: RecvLineOpts) {
64  let prefix = if opts.timestamp {
65    format!("{} ", now_ts())
66  } else {
67    String::new()
68  };
69
70  let h = opts.host;
71  let rtt_str = sprint_tm(opts.rtt);
72
73  if opts.json {
74    println!(
75      "{{\"resp\": {{\"host\": \"{}\", \"seq\": {}, \"rtt\": {}}}}}",
76      h.display, opts.ping_index, rtt_str
77    );
78    return;
79  }
80
81  if opts.verbose_count {
82    println!(
83      "{}{:<width$} : [{}], {} ms",
84      prefix, h.display, opts.ping_index, rtt_str,
85      width = opts.max_len
86    );
87    return;
88  }
89
90  let avg_str = h.avg_reply().map(sprint_tm).unwrap_or_default();
91  println!(
92    "{}{:<width$} : [{}], {} bytes, {} ms ({} avg, {}% loss)",
93    prefix, h.display, opts.ping_index,
94    opts.raw_len.saturating_sub(28),
95    rtt_str, avg_str, h.loss_pct(),
96    width = opts.max_len
97  );
98}
99
100pub struct TimeoutLineOpts<'a> {
101  pub host: &'a HostEntry,
102  pub ping_index: u32,
103  pub max_len: usize,
104  pub timestamp: bool,
105  pub json: bool,
106}
107
108pub fn print_timeout(opts: TimeoutLineOpts) {
109  let prefix = if opts.timestamp {
110    format!("{} ", now_ts())
111  } else {
112    String::new()
113  };
114
115  let h = opts.host;
116
117  if opts.json {
118    println!(
119      "{{\"timeout\": {{\"host\": \"{}\", \"seq\": {}}}}}",
120      h.display, opts.ping_index
121    );
122    return;
123  }
124
125  print!(
126    "{}{:<width$} : [{}], timed out",
127    prefix, h.display, opts.ping_index,
128    width = opts.max_len
129  );
130
131  match h.avg_reply() {
132    Some(avg) => print!(" ({} avg, ", sprint_tm(avg)),
133    None => print!(" (NaN avg, "),
134  }
135
136  if h.num_recv <= h.num_sent {
137    println!("{}% loss)", h.loss_pct());
138  } else {
139    println!(
140      "{}% return)",
141      (h.num_recv * 100) / h.num_sent.max(1)
142    );
143  }
144}
145
146pub fn print_per_host_stats(host: &HostEntry, max_len: usize, json: bool, verbose: bool) {
147  if verbose {
148    let rtts: Vec<String> = host.resp_times
149      .iter()
150      .map(|r| r.as_ref().map(|d| sprint_tm(*d)).unwrap_or_else(|| "-".into()))
151      .collect();
152
153    if json {
154      let vals: Vec<String> = host.resp_times
155        .iter()
156        .map(|r| r.as_ref().map(|d| sprint_tm(*d)).unwrap_or_else(|| "null".into()))
157        .collect();
158      eprintln!(
159        "{{\"vSum\": {{\"host\": \"{}\", \"values\": [{}]}}}}",
160        host.display,
161        vals.join(", ")
162      );
163    } else {
164      eprint!("{:<width$} :", host.display, width = max_len);
165      for rtt in &rtts {
166        eprint!(" {}", rtt);
167      }
168      eprintln!();
169    }
170    return;
171  }
172
173  if json {
174    eprint!(
175      "{{\"summary\": {{\"host\": \"{}\", \"xmt\": {}, \"rcv\": {}, \"loss\": {}",
176      host.display, host.num_sent, host.num_recv, host.loss_pct()
177    );
178    if let (Some(mn), Some(av), Some(mx)) =
179      (host.min_reply, host.avg_reply(), host.max_reply)
180    {
181      eprint!(
182        ", \"rttMin\": {}, \"rttAvg\": {}, \"rttMax\": {}",
183        sprint_tm(mn), sprint_tm(av), sprint_tm(mx)
184      );
185    }
186    eprintln!("}}}}");
187  } else {
188    eprint!(
189      "{:<width$} : xmt/rcv/%loss = {}/{}/{}%",
190      host.display, host.num_sent, host.num_recv, host.loss_pct(),
191      width = max_len
192    );
193    if let (Some(mn), Some(av), Some(mx)) =
194      (host.min_reply, host.avg_reply(), host.max_reply)
195    {
196      eprint!(
197        ", min/avg/max = {}/{}/{}",
198        sprint_tm(mn), sprint_tm(av), sprint_tm(mx)
199      );
200    }
201    eprintln!();
202  }
203}
204
205pub struct GlobalStatsSummary {
206  pub num_hosts: usize,
207  pub num_alive: usize,
208  pub num_unreachable: usize,
209  pub total_sent: u32,
210  pub total_recv: u32,
211  pub min_rtt: Option<Duration>,
212  pub avg_rtt: Option<Duration>,
213  pub max_rtt: Option<Duration>,
214  pub elapsed: Duration,
215}
216
217pub fn print_global_stats(s: &GlobalStatsSummary, json: bool) {
218  if json {
219    println!(
220      "{{\"stats\": {{\"targets\": {}, \"alive\": {}, \"unreachable\": {}, \
221        \"icmpEchosSent\": {}, \"icmpEchoRepliesReceived\": {}, \"elapsed\": {:.3}}}}}",
222      s.num_hosts, s.num_alive, s.num_unreachable,
223      s.total_sent, s.total_recv,
224      s.elapsed.as_secs_f64()
225    );
226    return;
227  }
228
229  eprintln!();
230  eprintln!(" {:>7} targets",       s.num_hosts);
231  eprintln!(" {:>7} alive",         s.num_alive);
232  eprintln!(" {:>7} unreachable",   s.num_unreachable);
233  eprintln!();
234  eprintln!(" {:>7} ICMP Echos sent",            s.total_sent);
235  eprintln!(" {:>7} ICMP Echo Replies received", s.total_recv);
236  eprintln!();
237
238  if let (Some(mn), Some(av), Some(mx)) = (s.min_rtt, s.avg_rtt, s.max_rtt) {
239    eprintln!(" {} ms (min round trip time)", sprint_tm(mn));
240    eprintln!(" {} ms (avg round trip time)", sprint_tm(av));
241    eprintln!(" {} ms (max round trip time)", sprint_tm(mx));
242  }
243
244  eprintln!(" {:>12.3} sec (elapsed real time)", s.elapsed.as_secs_f64());
245  eprintln!();
246}