|
|
@@ -1,253 +0,0 @@
|
|
|
-#![allow(clippy::arithmetic_side_effects)]
|
|
|
-extern crate byte_unit;
|
|
|
-
|
|
|
-use {
|
|
|
- byte_unit::Byte,
|
|
|
- clap::{crate_description, crate_name, Arg, ArgMatches, Command},
|
|
|
- serde_derive::{Deserialize, Serialize},
|
|
|
- std::{collections::HashMap, fs, ops::Sub, path::PathBuf},
|
|
|
-};
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, Debug)]
|
|
|
-struct IpAddrMapping {
|
|
|
- private: String,
|
|
|
- public: String,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, Debug)]
|
|
|
-struct LogLine {
|
|
|
- a: String,
|
|
|
- b: String,
|
|
|
- a_to_b: String,
|
|
|
- b_to_a: String,
|
|
|
-}
|
|
|
-
|
|
|
-impl Default for LogLine {
|
|
|
- fn default() -> Self {
|
|
|
- Self {
|
|
|
- a: String::default(),
|
|
|
- b: String::default(),
|
|
|
- a_to_b: "0B".to_string(),
|
|
|
- b_to_a: "0B".to_string(),
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl LogLine {
|
|
|
- fn output(a: &str, b: &str, v1: u128, v2: u128) -> String {
|
|
|
- format!(
|
|
|
- "Lost {}%, {}, ({} - {}), sender {}, receiver {}",
|
|
|
- ((v1 - v2) * 100 / v1),
|
|
|
- Byte::from_bytes(v1 - v2).get_appropriate_unit(true),
|
|
|
- Byte::from_bytes(v1).get_appropriate_unit(true),
|
|
|
- Byte::from_bytes(v2).get_appropriate_unit(true),
|
|
|
- a,
|
|
|
- b
|
|
|
- )
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-impl Sub for &LogLine {
|
|
|
- type Output = String;
|
|
|
-
|
|
|
- #[allow(clippy::comparison_chain)]
|
|
|
- fn sub(self, rhs: Self) -> Self::Output {
|
|
|
- let a_to_b = Byte::from_str(&self.a_to_b)
|
|
|
- .expect("Failed to read a_to_b bytes")
|
|
|
- .get_bytes();
|
|
|
- let b_to_a = Byte::from_str(&self.b_to_a)
|
|
|
- .expect("Failed to read b_to_a bytes")
|
|
|
- .get_bytes();
|
|
|
- let rhs_a_to_b = Byte::from_str(&rhs.a_to_b)
|
|
|
- .expect("Failed to read a_to_b bytes")
|
|
|
- .get_bytes();
|
|
|
- let rhs_b_to_a = Byte::from_str(&rhs.b_to_a)
|
|
|
- .expect("Failed to read b_to_a bytes")
|
|
|
- .get_bytes();
|
|
|
- let mut out1 = if a_to_b > rhs_b_to_a {
|
|
|
- LogLine::output(&self.a, &self.b, a_to_b, rhs_b_to_a)
|
|
|
- } else if a_to_b < rhs_b_to_a {
|
|
|
- LogLine::output(&self.b, &self.a, rhs_b_to_a, a_to_b)
|
|
|
- } else {
|
|
|
- String::default()
|
|
|
- };
|
|
|
- let out2 = if rhs_a_to_b > b_to_a {
|
|
|
- LogLine::output(&self.a, &self.b, rhs_a_to_b, b_to_a)
|
|
|
- } else if rhs_a_to_b < b_to_a {
|
|
|
- LogLine::output(&self.b, &self.a, b_to_a, rhs_a_to_b)
|
|
|
- } else {
|
|
|
- String::default()
|
|
|
- };
|
|
|
- if !out1.is_empty() && !out2.is_empty() {
|
|
|
- out1.push('\n');
|
|
|
- }
|
|
|
- out1.push_str(&out2);
|
|
|
- out1
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-fn map_ip_address(mappings: &[IpAddrMapping], target: String) -> String {
|
|
|
- for mapping in mappings {
|
|
|
- if target.contains(&mapping.private) {
|
|
|
- return target.replace(&mapping.private, mapping.public.as_str());
|
|
|
- }
|
|
|
- }
|
|
|
- target
|
|
|
-}
|
|
|
-
|
|
|
-fn process_iftop_logs(matches: &ArgMatches) {
|
|
|
- let mut map_list: Vec<IpAddrMapping> = vec![];
|
|
|
- if let Some(("map-IP", args_matches)) = matches.subcommand() {
|
|
|
- let mut list = args_matches
|
|
|
- .value_of("list")
|
|
|
- .expect("Missing list of IP address mappings")
|
|
|
- .to_string();
|
|
|
- list.insert(0, '[');
|
|
|
- let terminate_at = list
|
|
|
- .rfind('}')
|
|
|
- .expect("Didn't find a terminating '}' in IP list")
|
|
|
- + 1;
|
|
|
- let _ = list.split_off(terminate_at);
|
|
|
- list.push(']');
|
|
|
- map_list = serde_json::from_str(&list).expect("Failed to parse IP address mapping list");
|
|
|
- };
|
|
|
-
|
|
|
- let log_path = PathBuf::from(matches.value_of_t_or_exit::<String>("file"));
|
|
|
- let mut log = fs::read_to_string(log_path).expect("Unable to read log file");
|
|
|
- log.insert(0, '[');
|
|
|
- let terminate_at = log.rfind('}').expect("Didn't find a terminating '}'") + 1;
|
|
|
- let _ = log.split_off(terminate_at);
|
|
|
- log.push(']');
|
|
|
- let json_log: Vec<LogLine> = serde_json::from_str(&log).expect("Failed to parse log as JSON");
|
|
|
-
|
|
|
- let mut unique_latest_logs = HashMap::new();
|
|
|
-
|
|
|
- json_log.into_iter().rev().for_each(|l| {
|
|
|
- if !l.a.is_empty() && !l.b.is_empty() && !l.a_to_b.is_empty() && !l.b_to_a.is_empty() {
|
|
|
- let key = (l.a.clone(), l.b.clone());
|
|
|
- unique_latest_logs.entry(key).or_insert(l);
|
|
|
- }
|
|
|
- });
|
|
|
- let output: Vec<LogLine> = unique_latest_logs
|
|
|
- .into_values()
|
|
|
- .map(|l| {
|
|
|
- if map_list.is_empty() {
|
|
|
- l
|
|
|
- } else {
|
|
|
- LogLine {
|
|
|
- a: map_ip_address(&map_list, l.a),
|
|
|
- b: map_ip_address(&map_list, l.b),
|
|
|
- a_to_b: l.a_to_b,
|
|
|
- b_to_a: l.b_to_a,
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
- .collect();
|
|
|
-
|
|
|
- println!("{}", serde_json::to_string(&output).unwrap());
|
|
|
-}
|
|
|
-
|
|
|
-fn analyze_logs(matches: &ArgMatches) {
|
|
|
- let dir_path = PathBuf::from(matches.value_of_t_or_exit::<String>("folder"));
|
|
|
- assert!(
|
|
|
- dir_path.is_dir(),
|
|
|
- "Need a folder that contains all log files"
|
|
|
- );
|
|
|
- let list_all_diffs = matches.is_present("all");
|
|
|
- let files = fs::read_dir(dir_path).expect("Failed to read log folder");
|
|
|
- let logs: Vec<_> = files
|
|
|
- .flat_map(|f| {
|
|
|
- if let Ok(f) = f {
|
|
|
- let log_str = fs::read_to_string(f.path()).expect("Unable to read log file");
|
|
|
- let log: Vec<LogLine> =
|
|
|
- serde_json::from_str(log_str.as_str()).expect("Failed to deserialize log");
|
|
|
- log
|
|
|
- } else {
|
|
|
- vec![]
|
|
|
- }
|
|
|
- })
|
|
|
- .collect();
|
|
|
- let mut logs_hash = HashMap::new();
|
|
|
- logs.iter().for_each(|l| {
|
|
|
- let key = (l.a.clone(), l.b.clone());
|
|
|
- logs_hash.entry(key).or_insert(l);
|
|
|
- });
|
|
|
-
|
|
|
- logs.iter().for_each(|l| {
|
|
|
- let diff = logs_hash
|
|
|
- .remove(&(l.a.clone(), l.b.clone()))
|
|
|
- .map(|v1| {
|
|
|
- logs_hash.remove(&(l.b.clone(), l.a.clone())).map_or(
|
|
|
- if list_all_diffs {
|
|
|
- v1 - &LogLine::default()
|
|
|
- } else {
|
|
|
- String::default()
|
|
|
- },
|
|
|
- |v2| v1 - v2,
|
|
|
- )
|
|
|
- })
|
|
|
- .unwrap_or_default();
|
|
|
- if !diff.is_empty() {
|
|
|
- println!("{diff}");
|
|
|
- }
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-fn main() {
|
|
|
- solana_logger::setup();
|
|
|
-
|
|
|
- let matches = Command::new(crate_name!())
|
|
|
- .about(crate_description!())
|
|
|
- .version(solana_version::version!())
|
|
|
- .subcommand(
|
|
|
- Command::new("iftop")
|
|
|
- .about("Process iftop log file")
|
|
|
- .arg(
|
|
|
- Arg::new("file")
|
|
|
- .short('f')
|
|
|
- .long("file")
|
|
|
- .value_name("iftop log file")
|
|
|
- .takes_value(true)
|
|
|
- .help("Location of the log file generated by iftop"),
|
|
|
- )
|
|
|
- .subcommand(
|
|
|
- Command::new("map-IP")
|
|
|
- .about("Map private IP to public IP Address")
|
|
|
- .arg(
|
|
|
- Arg::new("list")
|
|
|
- .short('l')
|
|
|
- .long("list")
|
|
|
- .value_name("JSON string")
|
|
|
- .takes_value(true)
|
|
|
- .required(true)
|
|
|
- .help("JSON string with a list of mapping"),
|
|
|
- ),
|
|
|
- ),
|
|
|
- )
|
|
|
- .subcommand(
|
|
|
- Command::new("analyze")
|
|
|
- .about("Compare processed network log files")
|
|
|
- .arg(
|
|
|
- Arg::new("folder")
|
|
|
- .short('f')
|
|
|
- .long("folder")
|
|
|
- .value_name("DIR")
|
|
|
- .takes_value(true)
|
|
|
- .help("Location of processed log files"),
|
|
|
- )
|
|
|
- .arg(
|
|
|
- Arg::new("all")
|
|
|
- .short('a')
|
|
|
- .long("all")
|
|
|
- .takes_value(false)
|
|
|
- .help("List all differences"),
|
|
|
- ),
|
|
|
- )
|
|
|
- .get_matches();
|
|
|
-
|
|
|
- match matches.subcommand() {
|
|
|
- Some(("iftop", args_matches)) => process_iftop_logs(args_matches),
|
|
|
- Some(("analyze", args_matches)) => analyze_logs(args_matches),
|
|
|
- _ => {}
|
|
|
- };
|
|
|
-}
|