Skip to content

Commit 7e1a811

Browse files
committed
feat: add exit on success and jitter calculation options
1 parent 39d677a commit 7e1a811

File tree

4 files changed

+143
-60
lines changed

4 files changed

+143
-60
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

22
.git/
33
.idea/
4+
.cargo/
45
target/

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tcping"
3-
version = "1.1.0"
3+
version = "1.2.0"
44
authors = ["lvillis<[email protected]>"]
55
edition = "2021"
66
description = "A tool for testing native-to-target port latency, using Rust."

src/main.rs

Lines changed: 140 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ struct Args {
2626
/// Output mode: normal, json, or csv
2727
#[arg(short = 'o', long, value_enum, default_value_t = OutputMode::Normal)]
2828
output_mode: OutputMode,
29+
30+
/// Exit immediately after a successful probe
31+
#[arg(short = 'e', long)]
32+
exit_on_success: bool,
33+
34+
/// Calculate and display jitter
35+
#[arg(short = 'j', long)]
36+
jitter: bool,
2937
}
3038

3139
#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)]
@@ -39,6 +47,7 @@ enum OutputMode {
3947
struct PingResult {
4048
success: bool,
4149
duration_ms: f64,
50+
jitter_ms: Option<f64>,
4251
addr: std::net::SocketAddr,
4352
}
4453

@@ -84,7 +93,7 @@ fn main() {
8493
ctrlc::set_handler(move || {
8594
r.store(false, Ordering::SeqCst);
8695
})
87-
.expect("Error setting Ctrl-C handler");
96+
.expect("Error setting Ctrl-C handler");
8897

8998
if args.continuous && args.output_mode == OutputMode::Normal {
9099
println!();
@@ -113,35 +122,67 @@ fn main() {
113122
total_duration += duration;
114123
successful_pings += 1;
115124

125+
let avg_duration = total_duration / successful_pings as f64;
126+
let jitter = if args.jitter {
127+
Some((duration - avg_duration).abs())
128+
} else {
129+
None
130+
};
131+
116132
let result = PingResult {
117133
success: true,
118134
duration_ms: duration,
135+
jitter_ms: jitter,
119136
addr,
120137
};
121138
results.push(result.clone());
122139

123140
match args.output_mode {
124141
OutputMode::Normal => {
125-
println!(
126-
"Probing {}/tcp - Port is open - time={:.4}ms",
127-
addr, duration
128-
);
142+
if args.jitter {
143+
println!(
144+
"Probing {}/tcp - Port is open - time={:.4}ms jitter={:.4}ms",
145+
addr,
146+
duration,
147+
jitter.unwrap_or(0.0)
148+
);
149+
} else {
150+
println!(
151+
"Probing {}/tcp - Port is open - time={:.4}ms",
152+
addr, duration
153+
);
154+
}
129155
}
130156
OutputMode::Json => {
131157
let json = serde_json::to_string(&result).unwrap();
132158
println!("{}", json);
133159
}
134160
OutputMode::Csv => {
135-
println!("{},{},{:.4}", addr, "open", duration);
161+
if args.jitter {
162+
println!(
163+
"{},{},{:.4},{:.4}",
164+
addr,
165+
"open",
166+
duration,
167+
jitter.unwrap_or(0.0)
168+
);
169+
} else {
170+
println!("{},{},{:.4}", addr, "open", duration);
171+
}
136172
}
137173
}
174+
175+
if args.exit_on_success {
176+
break;
177+
}
138178
}
139179
Err(_) => {
140180
let duration = timeout.as_micros() as f64 / 1000.0;
141181

142182
let result = PingResult {
143183
success: false,
144184
duration_ms: duration,
185+
jitter_ms: None,
145186
addr,
146187
};
147188
results.push(result.clone());
@@ -164,67 +205,92 @@ fn main() {
164205
}
165206
}
166207

208+
if !args.continuous && total_attempts >= args.count {
209+
break;
210+
}
211+
167212
thread::sleep(Duration::from_secs(1));
168213
}
169214

170-
let avg_duration = if successful_pings > 0 {
171-
total_duration / successful_pings as f64
172-
} else {
173-
0.0
174-
};
175-
176-
let packet_loss = 100.0 * (1.0 - (successful_pings as f64 / total_attempts as f64));
177-
178-
let summary = Summary {
179-
addr,
180-
total_attempts,
181-
successful_pings,
182-
packet_loss,
183-
min_duration_ms: if successful_pings > 0 {
184-
min_duration
185-
} else {
186-
0.0
187-
},
188-
avg_duration_ms: avg_duration,
189-
max_duration_ms: if successful_pings > 0 {
190-
max_duration
215+
if args.output_mode == OutputMode::Normal {
216+
let avg_duration = if successful_pings > 0 {
217+
total_duration / successful_pings as f64
191218
} else {
192219
0.0
193-
},
194-
};
220+
};
195221

196-
match args.output_mode {
197-
OutputMode::Normal => {
198-
println!("\n--- {} tcping statistics ---", addr);
199-
println!(
200-
"{} probes sent, {} successful, {:.2}% packet loss",
201-
total_attempts, successful_pings, packet_loss
202-
);
203-
if successful_pings > 0 {
204-
println!(
205-
"Round-trip min/avg/max = {:.4}ms/{:.4}ms/{:.4}ms",
206-
min_duration, avg_duration, max_duration
207-
);
208-
}
209-
println!();
210-
}
211-
OutputMode::Json => {
212-
let json = serde_json::to_string(&summary).unwrap();
213-
println!("{}", json);
214-
}
215-
OutputMode::Csv => {
216-
println!("address,total_probes,successful_probes,packet_loss,min_rtt,avg_rtt,max_rtt");
222+
let packet_loss = 100.0 * (1.0 - (successful_pings as f64 / total_attempts as f64));
223+
224+
let summary = Summary {
225+
addr,
226+
total_attempts,
227+
successful_pings,
228+
packet_loss,
229+
min_duration_ms: if successful_pings > 0 {
230+
min_duration
231+
} else {
232+
0.0
233+
},
234+
avg_duration_ms: avg_duration,
235+
max_duration_ms: if successful_pings > 0 {
236+
max_duration
237+
} else {
238+
0.0
239+
},
240+
};
241+
242+
println!("\n--- {} tcping statistics ---", addr);
243+
println!(
244+
"{} probes sent, {} successful, {:.2}% packet loss",
245+
summary.total_attempts, summary.successful_pings, summary.packet_loss
246+
);
247+
if summary.successful_pings > 0 {
217248
println!(
218-
"{},{},{},{:.2},{:.4},{:.4},{:.4}",
219-
addr,
220-
total_attempts,
221-
successful_pings,
222-
packet_loss,
223-
summary.min_duration_ms,
224-
summary.avg_duration_ms,
225-
summary.max_duration_ms
249+
"Round-trip min/avg/max = {:.4}ms/{:.4}ms/{:.4}ms",
250+
summary.min_duration_ms, summary.avg_duration_ms, summary.max_duration_ms
226251
);
227252
}
253+
println!();
254+
} else if args.output_mode == OutputMode::Json {
255+
let summary = Summary {
256+
addr,
257+
total_attempts,
258+
successful_pings,
259+
packet_loss: 100.0 * (1.0 - (successful_pings as f64 / total_attempts as f64)),
260+
min_duration_ms: if successful_pings > 0 {
261+
min_duration
262+
} else {
263+
0.0
264+
},
265+
avg_duration_ms: if successful_pings > 0 {
266+
total_duration / successful_pings as f64
267+
} else {
268+
0.0
269+
},
270+
max_duration_ms: if successful_pings > 0 {
271+
max_duration
272+
} else {
273+
0.0
274+
},
275+
};
276+
let json = serde_json::to_string(&summary).unwrap();
277+
println!("{}", json);
278+
} else if args.output_mode == OutputMode::Csv {
279+
println!("address,total_probes,successful_probes,packet_loss,min_rtt,avg_rtt,max_rtt");
280+
println!(
281+
"{},{},{},{:.2},{:.4},{:.4},{:.4}",
282+
addr,
283+
total_attempts,
284+
successful_pings,
285+
100.0 * (1.0 - (successful_pings as f64 / total_attempts as f64)),
286+
if successful_pings > 0 { min_duration } else { 0.0 },
287+
if successful_pings > 0 {
288+
total_duration / successful_pings as f64
289+
} else {
290+
0.0
291+
},
292+
if successful_pings > 0 { max_duration } else { 0.0 }
293+
);
228294
}
229295
}
230296

@@ -240,6 +306,8 @@ mod tests {
240306
assert_eq!(args.count, 5);
241307
assert!(!args.continuous);
242308
assert_eq!(args.output_mode, OutputMode::Normal);
309+
assert!(!args.exit_on_success);
310+
assert!(!args.jitter);
243311
}
244312

245313
#[test]
@@ -249,6 +317,8 @@ mod tests {
249317
assert_eq!(args.count, 4);
250318
assert!(args.continuous);
251319
assert_eq!(args.output_mode, OutputMode::Normal);
320+
assert!(!args.exit_on_success);
321+
assert!(!args.jitter);
252322
}
253323

254324
#[test]
@@ -263,4 +333,16 @@ mod tests {
263333
let args = Args::parse_from("tcping 127.0.0.1:80 -o json".split_whitespace());
264334
assert_eq!(args.output_mode, OutputMode::Json);
265335
}
336+
337+
#[test]
338+
fn test_exit_on_success_parsing() {
339+
let args = Args::parse_from("tcping 127.0.0.1:80 -e".split_whitespace());
340+
assert!(args.exit_on_success);
341+
}
342+
343+
#[test]
344+
fn test_jitter_parsing() {
345+
let args = Args::parse_from("tcping 127.0.0.1:80 -j".split_whitespace());
346+
assert!(args.jitter);
347+
}
266348
}

0 commit comments

Comments
 (0)