sshping 0.2.4

SSH-based ping that measures interactive character echo latency and file transfer throughput. Pronounced "shipping".
use std::time::Duration;

use serde::{Deserialize, Serialize};
use tabled::Tabled;

use crate::util::Formatter;

#[derive(Deserialize, Serialize)]
pub struct EchoTestSummary {
    pub char_sent: usize,
    pub avg_latency: String,
    pub std_latency: String,
    pub med_latency: String,
    pub min_latency: String,
    pub max_latency: String,
}

impl EchoTestSummary {
    pub fn from_latencies(latencies: &[u128], formatter: &Formatter) -> Self {
        let char_sent = latencies.len();
        let avg_latency = latencies.iter().sum::<u128>() / (char_sent as u128);
        let std_latency = formatter.format_duration(Duration::from_nanos(
            ((latencies
                .iter()
                .map(|&latency| ((latency as i128) - (avg_latency as i128)).pow(2))
                .sum::<i128>() as f64)
                / (char_sent as f64))
                .sqrt() as u64,
        ));
        let avg_latency = formatter.format_duration(Duration::from_nanos(avg_latency as u64));
        let med_latency = formatter.format_duration(Duration::from_nanos(
            (match char_sent % 2 {
                0 => (latencies[char_sent / 2 - 1] + latencies[char_sent / 2]) / 2,
                _ => latencies[char_sent / 2],
            }) as u64,
        ));
        let min_latency = formatter.format_duration(Duration::from_nanos(
            latencies.first().unwrap().to_owned() as u64,
        ));
        let max_latency = formatter.format_duration(Duration::from_nanos(
            latencies.last().unwrap().to_owned() as u64,
        ));
        Self {
            char_sent,
            avg_latency,
            std_latency,
            med_latency,
            min_latency,
            max_latency,
        }
    }
    pub fn to_formatted_frame(&self) -> Vec<Record> {
        vec![
            Record::new("Latency", "Average", self.avg_latency.clone()),
            Record::new("Latency", "Std deviation", self.std_latency.clone()),
            Record::new("Latency", "Median", self.med_latency.clone()),
            Record::new("Latency", "Minimum", self.min_latency.clone()),
            Record::new("Latency", "Maximum", self.max_latency.clone()),
        ]
    }
}

#[derive(Deserialize, Serialize)]
pub struct SpeedTestResult {
    pub size: String,
    pub time: String,
    pub speed: String,
}

impl SpeedTestResult {
    pub fn new(size: u64, time: Duration, formatter: &Formatter) -> Self {
        Self {
            size: formatter.format_size(size),
            time: formatter.format_duration(time),
            speed: formatter.format_size(((size as f64) / time.as_secs_f64()) as u64) + "/s",
        }
    }
}

#[derive(Deserialize, Serialize)]
pub struct SpeedTestSummary {
    pub upload: SpeedTestResult,
    pub download: SpeedTestResult,
}

impl SpeedTestSummary {
    pub fn to_formatted_frame(&self) -> Vec<Record> {
        vec![
            Record::new("Speed", "Upload", self.upload.speed.clone()),
            Record::new("Speed", "Download", self.download.speed.clone()),
        ]
    }
}

#[derive(Tabled)]
pub struct Record {
    #[tabled(rename = "Test")]
    pub test: &'static str,
    #[tabled(rename = "Metric")]
    pub metric: &'static str,
    #[tabled(rename = "Result")]
    pub result: String,
}

impl Record {
    pub fn new(test: &'static str, metric: &'static str, result: String) -> Self {
        Self {
            test,
            metric,
            result,
        }
    }
}