@@ -3,6 +3,7 @@ use crate::pinger::Pinger;
33use crate :: utils:: { IpVersion , resolve_host} ;
44use anyhow:: Result ;
55use colored:: * ;
6+ use serde:: Serialize ;
67use std:: collections:: HashMap ;
78use std:: time:: Duration ;
89use tokio:: signal;
@@ -66,6 +67,24 @@ mod models {
6667
6768use std:: sync:: Arc ;
6869
70+ #[ derive( Serialize ) ]
71+ pub struct JsonResult {
72+ pub target : String ,
73+ pub protocol : String ,
74+ pub ip : String ,
75+ pub packet_size : usize ,
76+ pub ttl : u32 ,
77+ pub sent : u64 ,
78+ pub received : u64 ,
79+ pub loss : f64 ,
80+ pub time : f64 ,
81+ pub min : f64 ,
82+ pub avg : f64 ,
83+ pub max : f64 ,
84+ pub mdev : f64 ,
85+ pub jitter : f64 ,
86+ }
87+
6988pub struct Session {
7089 cli : Cli ,
7190}
@@ -286,6 +305,100 @@ impl Session {
286305 p. stop ( ) . await . ok ( ) ;
287306 }
288307
308+ // JSON Output Logic
309+ if let Some ( json_arg) = & self . cli . json {
310+ let mut json_results = Vec :: new ( ) ;
311+
312+ for target_host in targets {
313+ if let Some ( stats) = all_stats. get ( target_host) {
314+ let protocol = target_protocols. get ( target_host) . unwrap_or ( & crate :: cli:: Protocol :: Icmp ) ;
315+ let protocol_str = match protocol {
316+ crate :: cli:: Protocol :: Icmp => "ICMP" ,
317+ crate :: cli:: Protocol :: Tcp ( _) => "TCP" ,
318+ crate :: cli:: Protocol :: Http ( _) => "HTTP" ,
319+ } . to_string ( ) ;
320+
321+ // Calculate stats
322+ let loss = if stats. transmitted > 0 {
323+ 100.0 * ( 1.0 - stats. received as f64 / stats. transmitted as f64 )
324+ } else {
325+ 0.0
326+ } ;
327+ let total_time = stats. start_time . elapsed ( ) . as_secs_f64 ( ) * 1000.0 ;
328+
329+ let ( min, max, avg, mdev, jitter) = if stats. received > 0 {
330+ let min = stats. rtts . iter ( ) . min ( ) . unwrap ( ) . as_secs_f64 ( ) * 1000.0 ;
331+ let max = stats. rtts . iter ( ) . max ( ) . unwrap ( ) . as_secs_f64 ( ) * 1000.0 ;
332+ let avg = stats. rtts . iter ( ) . sum :: < Duration > ( ) . as_secs_f64 ( ) * 1000.0 / stats. rtts . len ( ) as f64 ;
333+
334+ let avg_duration = Duration :: from_secs_f64 ( avg / 1000.0 ) ;
335+ let sum_sq_diff: f64 = stats. rtts . iter ( )
336+ . map ( |rtt| ( rtt. as_secs_f64 ( ) - avg_duration. as_secs_f64 ( ) ) . abs ( ) )
337+ . sum ( ) ;
338+ let mdev = sum_sq_diff / stats. rtts . len ( ) as f64 * 1000.0 ;
339+
340+ let jitter = if stats. rtts . len ( ) > 1 {
341+ let sum_diff: f64 = stats. rtts . windows ( 2 )
342+ . map ( |w| ( w[ 1 ] . as_secs_f64 ( ) - w[ 0 ] . as_secs_f64 ( ) ) . abs ( ) )
343+ . sum ( ) ;
344+ sum_diff / ( stats. rtts . len ( ) - 1 ) as f64 * 1000.0
345+ } else {
346+ 0.0
347+ } ;
348+ (
349+ ( min * 1000.0 ) . round ( ) / 1000.0 ,
350+ ( max * 1000.0 ) . round ( ) / 1000.0 ,
351+ ( avg * 1000.0 ) . round ( ) / 1000.0 ,
352+ ( mdev * 1000.0 ) . round ( ) / 1000.0 ,
353+ ( jitter * 1000.0 ) . round ( ) / 1000.0 ,
354+ )
355+ } else {
356+ ( 0.0 , 0.0 , 0.0 , 0.0 , 0.0 )
357+ } ;
358+
359+ json_results. push ( JsonResult {
360+ target : target_host. clone ( ) ,
361+ protocol : protocol_str,
362+ ip : stats. _address . to_string ( ) ,
363+ packet_size : self . cli . size ,
364+ ttl : self . cli . ttl ,
365+ sent : stats. transmitted ,
366+ received : stats. received ,
367+ loss : ( loss * 1000.0 ) . round ( ) / 1000.0 , // Also round loss? Or keep precision? Instructions said "time values".
368+ // Let's stick to time values for strict compliance,
369+ // but usually nice to format loss too.
370+ // Re-reading: "json 中的各项时间保留 3 位小数" -> time values only.
371+ // Loss is percentage. I will round time values.
372+ time : ( total_time * 1000.0 ) . round ( ) / 1000.0 ,
373+ min,
374+ avg,
375+ max,
376+ mdev,
377+ jitter,
378+ } ) ;
379+ }
380+ }
381+
382+ use std:: io:: Write ;
383+ let json_output = if json_results. len ( ) == 1 {
384+ serde_json:: to_string_pretty ( & json_results[ 0 ] ) . unwrap ( )
385+ } else {
386+ serde_json:: to_string_pretty ( & json_results) . unwrap ( )
387+ } ;
388+
389+ if let Some ( path) = json_arg {
390+ if let Ok ( mut file) = std:: fs:: File :: create ( path) {
391+ let _ = file. write_all ( json_output. as_bytes ( ) ) ;
392+ } else {
393+ eprintln ! ( "pingx: Failed to write JSON to {}" , path) ;
394+ }
395+ } else {
396+ println ! ( "{}" , json_output) ;
397+ }
398+
399+ return Ok ( ( ) ) ;
400+ }
401+
289402 // Collect table data and calculate global column widths
290403 let mut tables = Vec :: new ( ) ;
291404 let mut global_key_widths = [ 0usize ; 3 ] ;
0 commit comments