From 89575cb55514b4cd59fe0e6bdf6788d1595115d8 Mon Sep 17 00:00:00 2001 From: Janakarajan Natarajan Date: Tue, 10 Dec 2024 16:28:48 +0000 Subject: [PATCH 1/3] Aperf: Add infrastructure for Analytics This is in preparation for adding rules for SystemInfo and CPU Utilization. --- Cargo.lock | 10 +++++ Cargo.toml | 1 + src/data.rs | 11 ++---- src/data/aperf_runlog.rs | 8 +++- src/data/aperf_stats.rs | 8 +++- src/data/cpu_utilization.rs | 22 +++++++++-- src/data/diskstats.rs | 16 +++++++- src/data/flamegraphs.rs | 10 ++++- src/data/interrupts.rs | 27 ++++++++++++-- src/data/java_profile.rs | 8 +++- src/data/kernel_config.rs | 15 +++++++- src/data/meminfodata.rs | 16 +++++++- src/data/netstat.rs | 16 +++++++- src/data/perf_profile.rs | 8 +++- src/data/perf_stat.rs | 15 +++++++- src/data/processes.rs | 8 +++- src/data/sysctldata.rs | 15 +++++++- src/data/systeminfo.rs | 15 +++++++- src/data/vmstat.rs | 16 +++++++- src/lib.rs | 23 +++++++++++- src/report.rs | 10 ++++- src/utils.rs | 74 +++++++++++++++++++++++++++++++++++++ src/visualizer.rs | 19 ++++++++-- 23 files changed, 328 insertions(+), 43 deletions(-) create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index ceb075fa..5f04c5e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,6 +132,7 @@ dependencies = [ "sysctl", "sysinfo", "tar", + "tdigest", "tempfile", "thiserror", "timerfd", @@ -2186,6 +2187,15 @@ dependencies = [ "xattr", ] +[[package]] +name = "tdigest" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c45d225a08ebccf0e0c7d46db4066ea8ab05b29d3750ecc1a04f1675978bf3c8" +dependencies = [ + "ordered-float", +] + [[package]] name = "tempfile" version = "3.9.0" diff --git a/Cargo.toml b/Cargo.toml index 872142aa..f3b10e3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,3 +53,4 @@ cfg-if = "1.0" tempfile = "3" serial_test = "3.1.1" log4rs = "1.3.0" +tdigest = "0.2" diff --git a/src/data.rs b/src/data.rs index 55126d35..f8c2c3a5 100644 --- a/src/data.rs +++ b/src/data.rs @@ -29,8 +29,9 @@ pub mod systeminfo; pub mod utils; pub mod vmstat; +use crate::utils::DataMetrics; use crate::visualizer::{GetData, ReportParams}; -use crate::{InitParams, APERF_FILE_FORMAT}; +use crate::{noop, InitParams, APERF_FILE_FORMAT}; use anyhow::Result; use aperf_runlog::AperfRunlog; use aperf_stats::AperfStat; @@ -285,10 +286,10 @@ macro_rules! processed_data { )* } } - pub fn get_data(&mut self, values: Vec, query: String) -> Result { + pub fn get_data(&mut self, values: Vec, query: String, metrics: &mut DataMetrics) -> Result { match self { $( - ProcessedData::$x(ref mut value) => Ok(value.get_data(values, query)?), + ProcessedData::$x(ref mut value) => Ok(value.get_data(values, query, metrics)?), )* } } @@ -339,10 +340,6 @@ processed_data!( JavaProfile ); -macro_rules! noop { - () => {}; -} - pub trait CollectData { fn prepare_data_collector(&mut self, _params: &CollectorParams) -> Result<()> { noop!(); diff --git a/src/data/aperf_runlog.rs b/src/data/aperf_runlog.rs index 8910ac07..a255c964 100644 --- a/src/data/aperf_runlog.rs +++ b/src/data/aperf_runlog.rs @@ -1,5 +1,6 @@ extern crate ctor; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData, ReportParams}; use crate::{data::ProcessedData, APERF_RUNLOG, VISUALIZATION_DATA}; use anyhow::Result; @@ -47,7 +48,12 @@ impl GetData for AperfRunlog { Ok(vec!["values".to_string()]) } - fn get_data(&mut self, buffer: Vec, _query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + _query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { diff --git a/src/data/aperf_stats.rs b/src/data/aperf_stats.rs index 71d944ed..7f636afb 100644 --- a/src/data/aperf_stats.rs +++ b/src/data/aperf_stats.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData, GraphLimitType, GraphMetadata, ReportParams}; use crate::VISUALIZATION_DATA; use anyhow::Result; @@ -103,7 +104,12 @@ impl GetData for AperfStat { Ok(vec!["keys".to_string(), "values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { diff --git a/src/data/cpu_utilization.rs b/src/data/cpu_utilization.rs index b3c159cd..13d5d34c 100644 --- a/src/data/cpu_utilization.rs +++ b/src/data/cpu_utilization.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -331,7 +332,12 @@ impl GetData for CpuUtilization { Ok(vec!["keys".to_string(), "values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { @@ -407,6 +413,7 @@ fn init_cpu_utilization() { mod cpu_tests { use super::{CpuData, CpuUtilization, CpuUtilizationRaw, UtilData}; use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; + use crate::utils::DataMetrics; use crate::visualizer::GetData; #[test] @@ -438,6 +445,7 @@ mod cpu_tests { .get_data( processed_buffer, "run=test&get=values&=aggregate".to_string(), + &mut DataMetrics::new(String::new()), ) .unwrap(); let values: Vec = serde_json::from_str(&json).unwrap(); @@ -447,7 +455,11 @@ mod cpu_tests { #[test] fn test_get_util_types() { let types = CpuUtilization::new() - .get_data(Vec::new(), "run=test&get=keys".to_string()) + .get_data( + Vec::new(), + "run=test&get=keys".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec<&str> = serde_json::from_str(&types).unwrap(); for type_str in values { @@ -476,7 +488,11 @@ mod cpu_tests { processed_buffer.push(CpuUtilization::new().process_raw_data(buf).unwrap()); } let json = CpuUtilization::new() - .get_data(processed_buffer, "run=test&get=values&key=user".to_string()) + .get_data( + processed_buffer, + "run=test&get=values&key=user".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec = serde_json::from_str(&json).unwrap(); assert!(!values.is_empty()); diff --git a/src/data/diskstats.rs b/src/data/diskstats.rs index b02fa550..62f7ebb7 100644 --- a/src/data/diskstats.rs +++ b/src/data/diskstats.rs @@ -2,6 +2,7 @@ extern crate ctor; use crate::data::constants::*; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData, GraphLimitType, GraphMetadata}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -308,7 +309,12 @@ impl GetData for Diskstats { Ok(vec!["keys".to_string(), "values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { @@ -364,6 +370,7 @@ fn init_diskstats() { mod tests { use super::{DiskstatKeys, Diskstats, DiskstatsRaw, EndDiskValues}; use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; + use crate::utils::DataMetrics; use crate::visualizer::GetData; use std::collections::HashMap; use strum::IntoEnumIterator; @@ -420,7 +427,11 @@ mod tests { .unwrap(), ); let json = Diskstats::new() - .get_data(processed_buffer, "run=test&get=keys".to_string()) + .get_data( + processed_buffer, + "run=test&get=keys".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec = serde_json::from_str(&json).unwrap(); assert!(!values.is_empty()); @@ -445,6 +456,7 @@ mod tests { .get_data( processed_buffer, "run=test&get=values&key=Reads".to_string(), + &mut DataMetrics::new(String::new()), ) .unwrap(); let data: EndDiskValues = serde_json::from_str(&json).unwrap(); diff --git a/src/data/flamegraphs.rs b/src/data/flamegraphs.rs index 92442e0e..f5d048f4 100644 --- a/src/data/flamegraphs.rs +++ b/src/data/flamegraphs.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData, ReportParams}; use crate::{get_file_name, PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -63,7 +64,7 @@ impl CollectData for FlamegraphRaw { "inject", "-j", "-i", - &file_pathbuf.to_str().unwrap(), + file_pathbuf.to_str().unwrap(), "-o", perf_jit_loc.to_str().unwrap(), ]) @@ -149,7 +150,12 @@ impl GetData for Flamegraph { Ok(vec!["values".to_string()]) } - fn get_data(&mut self, _buffer: Vec, _query: String) -> Result { + fn get_data( + &mut self, + _buffer: Vec, + _query: String, + _metrics: &mut DataMetrics, + ) -> Result { let values: Vec<&str> = Vec::new(); Ok(serde_json::to_string(&values)?) } diff --git a/src/data/interrupts.rs b/src/data/interrupts.rs index 2defc056..5dbd6c1a 100644 --- a/src/data/interrupts.rs +++ b/src/data/interrupts.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -289,7 +290,12 @@ impl GetData for InterruptData { Ok(vec!["keys".to_string(), "values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { @@ -345,6 +351,7 @@ mod tests { use super::{InterruptData, InterruptDataRaw, InterruptLine, InterruptLineData}; use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; use crate::get_file; + use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData}; #[test] @@ -412,7 +419,11 @@ mod tests { processed_buffer.push(InterruptData::new().process_raw_data(buf).unwrap()); } let json = InterruptData::new() - .get_data(processed_buffer, "run=test&get=keys".to_string()) + .get_data( + processed_buffer, + "run=test&get=keys".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec = serde_json::from_str(&json).unwrap(); assert!(!values.is_empty()); @@ -432,12 +443,20 @@ mod tests { processed_buffer.push(InterruptData::new().process_raw_data(buf).unwrap()); } let json = InterruptData::new() - .get_data(processed_buffer.clone(), "run=test&get=keys".to_string()) + .get_data( + processed_buffer.clone(), + "run=test&get=keys".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec = serde_json::from_str(&json).unwrap(); let key_query = format!("run=test&get=values&key={}", values[0]); let ld_json = InterruptData::new() - .get_data(processed_buffer, key_query) + .get_data( + processed_buffer, + key_query, + &mut DataMetrics::new(String::new()), + ) .unwrap(); let line_data: Vec = serde_json::from_str(&ld_json).unwrap(); assert!(!line_data.is_empty()); diff --git a/src/data/java_profile.rs b/src/data/java_profile.rs index bcfab241..e5560021 100644 --- a/src/data/java_profile.rs +++ b/src/data/java_profile.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -308,7 +309,12 @@ impl GetData for JavaProfile { Ok(vec!["values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { diff --git a/src/data/kernel_config.rs b/src/data/kernel_config.rs index 89caac03..89b54a70 100644 --- a/src/data/kernel_config.rs +++ b/src/data/kernel_config.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -204,7 +205,12 @@ impl GetData for KernelConfig { Ok(vec!["values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { @@ -258,6 +264,7 @@ fn init_kernel_config() { mod tests { use super::{KernelConfig, KernelConfigEntryGroup}; use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; + use crate::utils::DataMetrics; use crate::visualizer::GetData; #[test] @@ -284,7 +291,11 @@ mod tests { .unwrap(), ); let json = KernelConfig::new() - .get_data(processed_buffer, "run=test&get=values".to_string()) + .get_data( + processed_buffer, + "run=test&get=values".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec = serde_json::from_str(&json).unwrap(); assert!(!values.is_empty()); diff --git a/src/data/meminfodata.rs b/src/data/meminfodata.rs index 5202cddb..b92f3fc5 100644 --- a/src/data/meminfodata.rs +++ b/src/data/meminfodata.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData, GraphLimitType, GraphMetadata}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -416,7 +417,12 @@ impl GetData for MeminfoData { Ok(vec!["keys".to_string(), "values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { @@ -472,6 +478,7 @@ fn init_meminfo() { mod tests { use super::{EndMemValues, MeminfoData, MeminfoDataRaw, MeminfoKeys}; use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; + use crate::utils::DataMetrics; use crate::visualizer::GetData; use std::collections::HashMap; use strum::IntoEnumIterator; @@ -527,7 +534,11 @@ mod tests { .unwrap(), ); let json = MeminfoData::new() - .get_data(processed_buffer, "run=test&get=keys".to_string()) + .get_data( + processed_buffer, + "run=test&get=keys".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec = serde_json::from_str(&json).unwrap(); assert!(!values.is_empty()); @@ -552,6 +563,7 @@ mod tests { .get_data( processed_buffer, "run=test&get=values&key=Mem Total".to_string(), + &mut DataMetrics::new(String::new()), ) .unwrap(); let memdata: EndMemValues = serde_json::from_str(&json).unwrap(); diff --git a/src/data/netstat.rs b/src/data/netstat.rs index c555500a..c8f34780 100644 --- a/src/data/netstat.rs +++ b/src/data/netstat.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData, GraphLimitType, GraphMetadata}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -156,7 +157,12 @@ impl GetData for Netstat { Ok(vec!["keys".to_string(), "values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { @@ -212,6 +218,7 @@ fn init_netstat() { mod tests { use super::{EndNetData, Netstat, NetstatRaw}; use crate::data::{CollectData, CollectorParams, Data, ProcessedData, TimeEnum}; + use crate::utils::DataMetrics; use crate::visualizer::GetData; #[test] @@ -234,7 +241,11 @@ mod tests { buffer.push(Data::NetstatRaw(netstat)); processed_buffer.push(Netstat::new().process_raw_data(buffer[0].clone()).unwrap()); let json = Netstat::new() - .get_data(processed_buffer, "run=test&get=keys".to_string()) + .get_data( + processed_buffer, + "run=test&get=keys".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec<&str> = serde_json::from_str(&json).unwrap(); assert!(!values.is_empty()); @@ -254,6 +265,7 @@ mod tests { .get_data( processed_buffer, "run=test&get=values&key=TcpExt: TCPDSACKRecv".to_string(), + &mut DataMetrics::new(String::new()), ) .unwrap(); let data: EndNetData = serde_json::from_str(&json).unwrap(); diff --git a/src/data/perf_profile.rs b/src/data/perf_profile.rs index 962860e1..81cd7ddb 100644 --- a/src/data/perf_profile.rs +++ b/src/data/perf_profile.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData, ReportParams}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -159,7 +160,12 @@ impl GetData for PerfProfile { Ok(vec!["values".to_string()]) } - fn get_data(&mut self, buffer: Vec, _query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + _query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { diff --git a/src/data/perf_stat.rs b/src/data/perf_stat.rs index 65f7526d..0daaad8d 100644 --- a/src/data/perf_stat.rs +++ b/src/data/perf_stat.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData, GraphLimitType, GraphMetadata}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -499,7 +500,12 @@ impl GetData for PerfStat { Ok(vec!["keys".to_string(), "values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { @@ -558,6 +564,7 @@ fn init_perf_stat_raw() { mod tests { use super::{PerfStat, PerfStatRaw}; use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; + use crate::utils::DataMetrics; use crate::visualizer::GetData; use std::collections::HashMap; use std::io::ErrorKind; @@ -612,7 +619,11 @@ mod tests { processed_buffer.push(PerfStat::new().process_raw_data(buf).unwrap()); } let events = PerfStat::new() - .get_data(processed_buffer, "run=test&get=keys".to_string()) + .get_data( + processed_buffer, + "run=test&get=keys".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec = serde_json::from_str(&events).unwrap(); diff --git a/src/data/processes.rs b/src/data/processes.rs index 55f3bd9b..8bc7374a 100644 --- a/src/data/processes.rs +++ b/src/data/processes.rs @@ -2,6 +2,7 @@ extern crate ctor; extern crate lazy_static; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -284,7 +285,12 @@ impl GetData for Processes { Ok(vec!["values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { diff --git a/src/data/sysctldata.rs b/src/data/sysctldata.rs index f4ca43b3..e18c3f4b 100644 --- a/src/data/sysctldata.rs +++ b/src/data/sysctldata.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -86,7 +87,12 @@ impl GetData for SysctlData { Ok(vec!["values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { @@ -140,6 +146,7 @@ fn init_sysctl() { mod tests { use super::{SysctlData, DONT_COLLECT}; use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; + use crate::utils::DataMetrics; use crate::visualizer::GetData; use std::collections::BTreeMap; @@ -183,7 +190,11 @@ mod tests { .unwrap(), ); let json = SysctlData::new() - .get_data(processed_buffer, "run=test&get=values".to_string()) + .get_data( + processed_buffer, + "run=test&get=values".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: BTreeMap = serde_json::from_str(&json).unwrap(); assert!(!values.is_empty()); diff --git a/src/data/systeminfo.rs b/src/data/systeminfo.rs index 247c1f53..7a84a6b2 100644 --- a/src/data/systeminfo.rs +++ b/src/data/systeminfo.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -210,7 +211,12 @@ impl GetData for SystemInfo { Ok(vec!["values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { @@ -260,6 +266,7 @@ fn init_systeminfo() { mod tests { use super::{SUTConfigEntry, SystemInfo}; use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; + use crate::utils::DataMetrics; use crate::visualizer::GetData; #[test] @@ -290,7 +297,11 @@ mod tests { .unwrap(), ); let json = SystemInfo::new() - .get_data(processed_buffer, "run=test&get=values".to_string()) + .get_data( + processed_buffer, + "run=test&get=values".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec = serde_json::from_str(&json).unwrap(); assert!(!values.is_empty()); diff --git a/src/data/vmstat.rs b/src/data/vmstat.rs index 29cb4d74..a59899f5 100644 --- a/src/data/vmstat.rs +++ b/src/data/vmstat.rs @@ -1,6 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; +use crate::utils::DataMetrics; use crate::visualizer::{DataVisualizer, GetData, GraphLimitType, GraphMetadata}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -142,7 +143,12 @@ impl GetData for Vmstat { Ok(vec!["keys".to_string(), "values".to_string()]) } - fn get_data(&mut self, buffer: Vec, query: String) -> Result { + fn get_data( + &mut self, + buffer: Vec, + query: String, + _metrics: &mut DataMetrics, + ) -> Result { let mut values = Vec::new(); for data in buffer { match data { @@ -198,6 +204,7 @@ fn init_vmstat() { mod tests { use super::{EndVmstatData, Vmstat, VmstatRaw}; use crate::data::{CollectData, CollectorParams, Data, ProcessedData, TimeEnum}; + use crate::utils::DataMetrics; use crate::visualizer::GetData; #[test] @@ -220,7 +227,11 @@ mod tests { buffer.push(Data::VmstatRaw(vmstat)); processed_buffer.push(Vmstat::new().process_raw_data(buffer[0].clone()).unwrap()); let json = Vmstat::new() - .get_data(processed_buffer, "run=test&get=keys".to_string()) + .get_data( + processed_buffer, + "run=test&get=keys".to_string(), + &mut DataMetrics::new(String::new()), + ) .unwrap(); let values: Vec<&str> = serde_json::from_str(&json).unwrap(); assert!(!values.is_empty()); @@ -240,6 +251,7 @@ mod tests { .get_data( processed_buffer, "run=test&get=values&key=nr_dirty".to_string(), + &mut DataMetrics::new(String::new()), ) .unwrap(); let data: EndVmstatData = serde_json::from_str(&json).unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 566b9efd..857bc3e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ extern crate lazy_static; pub mod data; pub mod record; pub mod report; +pub mod utils; pub mod visualizer; use anyhow::Result; use chrono::prelude::*; @@ -24,6 +25,7 @@ use std::sync::Mutex; use std::{fs, process, time}; use thiserror::Error; use timerfd::{SetTimeFlags, TimerFd, TimerState}; +use utils::DataMetrics; pub static APERF_FILE_FORMAT: &str = "bin"; pub static APERF_TMP: &str = "/tmp"; @@ -104,6 +106,11 @@ pub enum PDError { DependencyError(String), } +#[macro_export] +macro_rules! noop { + () => {}; +} + lazy_static! { pub static ref PERFORMANCE_DATA: Mutex = Mutex::new(PerformanceData::new()); } @@ -412,6 +419,7 @@ pub struct VisualizationData { pub visualizers: HashMap, pub js_files: HashMap, pub run_names: Vec, + pub analytics_data: HashMap, } impl VisualizationData { @@ -420,6 +428,7 @@ impl VisualizationData { visualizers: HashMap::new(), js_files: HashMap::new(), run_names: Vec::new(), + analytics_data: HashMap::new(), } } @@ -444,6 +453,8 @@ impl VisualizationData { error_count += 1; } } + self.analytics_data + .insert(dir_name.clone(), DataMetrics::new(dir_name.clone())); /* Works if a new type of visualizer is introduced but data not present */ if error_count == visualizers_len { @@ -454,7 +465,7 @@ impl VisualizationData { pub fn add_visualizer(&mut self, name: String, dv: visualizer::DataVisualizer) { self.js_files.insert(dv.js_file_name.clone(), dv.js.clone()); - self.visualizers.insert(name, dv); + self.visualizers.insert(name.clone(), dv); } pub fn get_all_js_files(&mut self) -> Result> { @@ -514,13 +525,21 @@ impl VisualizationData { let visualizer = self.visualizers.get_mut(visualizer_name).ok_or( PDError::VisualizerHashMapEntryError(visualizer_name.to_string()), )?; - visualizer.get_data(run_name.to_string(), query.clone()) + visualizer.get_data( + run_name.to_string(), + query.clone(), + self.analytics_data.get_mut(run_name).unwrap(), + ) } pub fn get_calls(&mut self, name: String) -> Result> { let visualizer = self.visualizers.get_mut(&name).unwrap(); visualizer.get_calls() } + + pub fn get_analytics(&mut self) -> Result { + Ok(serde_json::to_string(&self.analytics_data)?) + } } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/src/report.rs b/src/report.rs index 50fa1f98..7817d61e 100644 --- a/src/report.rs +++ b/src/report.rs @@ -199,6 +199,7 @@ pub fn report(report: &Report, tmp_dir: &PathBuf) -> Result<()> { let index_css = include_str!("html_files/index.css"); let index_js = include_str!(concat!(env!("JS_DIR"), "/index.js")); let utils_js = include_str!(concat!(env!("JS_DIR"), "/utils.js")); + let analytics_js = include_str!(concat!(env!("JS_DIR"), "/analytics.js")); let plotly_js = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), "/node_modules/plotly.js/dist/plotly.min.js" @@ -221,6 +222,7 @@ pub fn report(report: &Report, tmp_dir: &PathBuf) -> Result<()> { let mut index_html_file = File::create(report_name.join("index.html"))?; let mut index_css_file = File::create(report_name.join("index.css"))?; let mut index_js_file = File::create(report_name.join("index.js"))?; + let mut analytics_js_file = File::create(report_name.join("js/analytics.js"))?; let mut utils_js_file = File::create(report_name.join("js/utils.js"))?; let mut plotly_js_file = File::create(report_name.join("js/plotly.js"))?; let mut configure_js_file = File::create(report_name.join("js/configure.js"))?; @@ -229,6 +231,7 @@ pub fn report(report: &Report, tmp_dir: &PathBuf) -> Result<()> { write!(index_html_file, "{}", index_html)?; write!(index_css_file, "{}", index_css)?; write!(index_js_file, "{}", index_js)?; + write!(analytics_js_file, "{}", analytics_js)?; write!(utils_js_file, "{}", utils_js)?; write!(plotly_js_file, "{}", plotly_js)?; write!(configure_js_file, "{}", configure_js)?; @@ -281,7 +284,7 @@ pub fn report(report: &Report, tmp_dir: &PathBuf) -> Result<()> { if keys { for key in &temp_keys { let query = format!("run={}&get=values&key={}", run_name, key); - data = visualizer.get_data(run_name, &api_name, query)?; + data = visualizer.get_data(run_name, &api_name, query.clone())?; run.key_values.insert(key.clone(), data.clone()); } } else { @@ -299,6 +302,11 @@ pub fn report(report: &Report, tmp_dir: &PathBuf) -> Result<()> { let str_out_data = format!("{}_raw_data = {}", api.name.clone(), out_data.clone()); write!(out_file, "{}", str_out_data)?; } + let out_analytics = report_name.join("data/js/analytics.js"); + let mut out_file = File::create(out_analytics)?; + let stats = visualizer.get_analytics()?; + let str_out_stats = format!("raw_analytics = {}", stats); + write!(out_file, "{}", str_out_stats)?; /* Generate aperf_report.tar.gz */ info!("Generating {}", report_name_tgz.display()); let tar_gz = File::create(&report_name_tgz)?; diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..78f15ae5 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,74 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use tdigest::TDigest; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub enum ValueType { + UInt64(u64), + F64(f64), + String(String), + Stats(Stats), +} + +#[derive(Deserialize, Serialize, Default, Debug, Clone, PartialEq)] +pub struct Stats { + pub p99: f64, + pub p90: f64, + pub mean: f64, +} + +impl Stats { + fn new() -> Self { + Stats { + p99: 0.0, + p90: 0.0, + mean: 0.0, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DataMetrics { + pub run: String, + pub values: HashMap>, +} + +impl DataMetrics { + pub fn new(run: String) -> Self { + DataMetrics { + run, + values: HashMap::new(), + } + } +} + +/// Used to generate ValueType::Stats in form_stats(). +#[derive(Default, Debug, Clone)] +pub struct Metric { + pub name: String, + pub values: Vec, + pub stats: Stats, +} + +impl Metric { + pub fn new(name: String) -> Self { + Metric { + name, + values: Vec::new(), + stats: Stats::new(), + } + } + + pub fn insert_value(&mut self, value: f64) { + self.values.push(value) + } + + pub fn form_stats(&mut self) -> ValueType { + let t = TDigest::new_with_size(100); + let t = t.merge_unsorted(self.values.clone()); + self.stats.p99 = t.estimate_quantile(0.99); + self.stats.p90 = t.estimate_quantile(0.90); + self.stats.mean = t.mean(); + ValueType::Stats(self.stats.clone()) + } +} diff --git a/src/visualizer.rs b/src/visualizer.rs index b4fb633b..1f463fa5 100644 --- a/src/visualizer.rs +++ b/src/visualizer.rs @@ -1,3 +1,4 @@ +use crate::utils::DataMetrics; use crate::{data::Data, data::ProcessedData, get_file, PDError}; use anyhow::Result; use log::debug; @@ -128,7 +129,12 @@ impl DataVisualizer { Ok(()) } - pub fn get_data(&mut self, name: String, query: String) -> Result { + pub fn get_data( + &mut self, + name: String, + query: String, + metrics: &mut DataMetrics, + ) -> Result { if !self.data_available.get(&name).unwrap() { debug!("No data available for: {} query: {}", self.api_name, query); return Ok("No data collected".to_string()); @@ -144,7 +150,7 @@ impl DataVisualizer { if values.is_empty() { return Ok("No data collected".to_string()); } - self.data.get_data(values.clone(), query) + self.data.get_data(values.clone(), query, metrics) } pub fn get_calls(&mut self) -> Result> { @@ -234,7 +240,12 @@ pub trait GetData { fn get_calls(&mut self) -> Result> { unimplemented!(); } - fn get_data(&mut self, _values: Vec, _query: String) -> Result { + fn get_data( + &mut self, + _values: Vec, + _query: String, + _metrics: &mut DataMetrics, + ) -> Result { unimplemented!(); } fn process_raw_data(&mut self, _buffer: Data) -> Result { @@ -250,6 +261,7 @@ mod tests { use super::DataVisualizer; use crate::data::cpu_utilization::{CpuData, CpuUtilization}; use crate::data::{ProcessedData, TimeEnum}; + use crate::utils::DataMetrics; use std::path::PathBuf; #[test] @@ -273,6 +285,7 @@ mod tests { .get_data( "test".to_string(), "run=test&get=values&key=aggregate".to_string(), + &mut DataMetrics::new(String::new()), ) .unwrap(); let values: Vec = serde_json::from_str(&ret).unwrap(); From dd767ed6625ced0c2b9918a411e0a33d0c061603 Mon Sep 17 00:00:00 2001 From: Janakarajan Natarajan Date: Tue, 10 Dec 2024 16:33:37 +0000 Subject: [PATCH 2/3] Analytics: Form data for 2 types Form the metrics which will be used by the front-end for analytics work. Generate it for SystemInfo and CPU Utilization. --- src/data/cpu_utilization.rs | 37 +++++++++++++++++-- src/data/systeminfo.rs | 73 +++++++++++++++++++++++++++++++------ 2 files changed, 94 insertions(+), 16 deletions(-) diff --git a/src/data/cpu_utilization.rs b/src/data/cpu_utilization.rs index 13d5d34c..a6b616e1 100644 --- a/src/data/cpu_utilization.rs +++ b/src/data/cpu_utilization.rs @@ -1,7 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; -use crate::utils::DataMetrics; +use crate::utils::{DataMetrics, Metric}; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -10,6 +10,7 @@ use ctor::ctor; use log::trace; use procfs::{CpuTime, KernelStats}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::ops::Sub; pub static CPU_UTILIZATION_FILE_NAME: &str = "cpu_utilization"; @@ -240,10 +241,19 @@ fn set_as_percent(value: UtilValues) -> UtilValues { new_values } -pub fn get_aggregate_data(values: Vec) -> Result { +pub fn get_aggregate_data(values: Vec, metrics: &mut DataMetrics) -> Result { let mut end_values = Vec::new(); let mut prev_cpu_data = values[0].values; let time_zero = values[0].time; + let mut metric_map = HashMap::new(); + let mut user = Metric::new("User".to_string()); + let mut nice = Metric::new("Nice".to_string()); + let mut system = Metric::new("System".to_string()); + let mut irq = Metric::new("Irq".to_string()); + let mut softirq = Metric::new("SoftIrq".to_string()); + let mut idle = Metric::new("Idle".to_string()); + let mut iowait = Metric::new("Iowait".to_string()); + let mut steal = Metric::new("Steal".to_string()); for value in values { let mut end_value = CpuData::new(); let current_cpu_data = value.values; @@ -255,10 +265,29 @@ pub fn get_aggregate_data(values: Vec) -> Result { } end_value.cpu = value.cpu; end_value.values = set_as_percent(current_cpu_data - prev_cpu_data); + user.insert_value(end_value.values.user as f64); + nice.insert_value(end_value.values.nice as f64); + system.insert_value(end_value.values.system as f64); + irq.insert_value(end_value.values.irq as f64); + softirq.insert_value(end_value.values.softirq as f64); + idle.insert_value(end_value.values.idle as f64); + iowait.insert_value(end_value.values.iowait as f64); + steal.insert_value(end_value.values.steal as f64); end_value.set_time(current_time - time_zero); end_values.push(end_value); prev_cpu_data = current_cpu_data; } + metric_map.insert("User".to_string(), user.form_stats()); + metric_map.insert("Nice".to_string(), nice.form_stats()); + metric_map.insert("System".to_string(), system.form_stats()); + metric_map.insert("Irq".to_string(), irq.form_stats()); + metric_map.insert("SoftIrq".to_string(), softirq.form_stats()); + metric_map.insert("Idle".to_string(), idle.form_stats()); + metric_map.insert("Iowait".to_string(), iowait.form_stats()); + metric_map.insert("Steal".to_string(), steal.form_stats()); + metrics + .values + .insert(CPU_UTILIZATION_FILE_NAME.to_string(), metric_map); Ok(serde_json::to_string(&end_values)?) } @@ -336,7 +365,7 @@ impl GetData for CpuUtilization { &mut self, buffer: Vec, query: String, - _metrics: &mut DataMetrics, + metrics: &mut DataMetrics, ) -> Result { let mut values = Vec::new(); for data in buffer { @@ -370,7 +399,7 @@ impl GetData for CpuUtilization { for value in values { temp_values.push(value.total); } - get_aggregate_data(temp_values) + get_aggregate_data(temp_values, metrics) } else { get_type(values[0].per_cpu.len() as u64, values, key) } diff --git a/src/data/systeminfo.rs b/src/data/systeminfo.rs index 7a84a6b2..66395073 100644 --- a/src/data/systeminfo.rs +++ b/src/data/systeminfo.rs @@ -1,7 +1,7 @@ extern crate ctor; use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; -use crate::utils::DataMetrics; +use crate::utils::{DataMetrics, ValueType}; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -9,6 +9,7 @@ use chrono::prelude::*; use ctor::ctor; use log::{trace, warn}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use sysinfo::{System, SystemExt}; pub static SYSTEMINFO_FILE_NAME: &str = "system_info"; @@ -147,53 +148,101 @@ struct SUTConfigEntry { pub value: String, } -fn get_values(buffer: SystemInfo) -> Result { +fn get_values(buffer: SystemInfo, metrics: &mut DataMetrics) -> Result { let mut end_values = Vec::new(); + let mut my_metrics = HashMap::new(); + let system_name = SUTConfigEntry { name: "System Name".to_string(), - value: buffer.system_name, + value: buffer.system_name.clone(), }; + my_metrics.insert( + "System Name".to_string(), + ValueType::String(buffer.system_name), + ); end_values.push(system_name); + let os_version = SUTConfigEntry { name: "OS Version".to_string(), - value: buffer.os_version, + value: buffer.os_version.clone(), }; + my_metrics.insert( + "OS Version".to_string(), + ValueType::String(buffer.os_version), + ); end_values.push(os_version); + let kernel_version = SUTConfigEntry { name: "Kernel Version".to_string(), - value: buffer.kernel_version, + value: buffer.kernel_version.clone(), }; + my_metrics.insert( + "Kernel Version".to_string(), + ValueType::String(buffer.kernel_version), + ); end_values.push(kernel_version); + let region = SUTConfigEntry { name: "Region".to_string(), - value: buffer.instance_metadata.region, + value: buffer.instance_metadata.region.clone(), }; + my_metrics.insert( + "Region".to_string(), + ValueType::String(buffer.instance_metadata.region), + ); end_values.push(region); + let instance_type = SUTConfigEntry { name: "Instance Type".to_string(), - value: buffer.instance_metadata.instance_type, + value: buffer.instance_metadata.instance_type.clone(), }; + my_metrics.insert( + "Instance Type".to_string(), + ValueType::String(buffer.instance_metadata.instance_type), + ); end_values.push(instance_type); + let total_cpus = SUTConfigEntry { name: "Total CPUs".to_string(), value: buffer.total_cpus.to_string(), }; + my_metrics.insert( + "Total CPUs".to_string(), + ValueType::UInt64(buffer.total_cpus as u64), + ); end_values.push(total_cpus); + let instance_id = SUTConfigEntry { name: "Instance ID".to_string(), - value: buffer.instance_metadata.instance_id, + value: buffer.instance_metadata.instance_id.clone(), }; + my_metrics.insert( + "Instance ID".to_string(), + ValueType::String(buffer.instance_metadata.instance_id), + ); end_values.push(instance_id); + let ami_id = SUTConfigEntry { name: "AMI ID".to_string(), - value: buffer.instance_metadata.ami_id, + value: buffer.instance_metadata.ami_id.clone(), }; + my_metrics.insert( + "AMI ID".to_string(), + ValueType::String(buffer.instance_metadata.ami_id), + ); end_values.push(ami_id); + let host_name = SUTConfigEntry { name: "Host Name".to_string(), - value: buffer.host_name, + value: buffer.host_name.clone(), }; + my_metrics.insert("Host Name".to_string(), ValueType::String(buffer.host_name)); end_values.push(host_name); + + metrics + .values + .insert(SYSTEMINFO_FILE_NAME.to_string(), my_metrics); + Ok(serde_json::to_string(&end_values)?) } @@ -215,7 +264,7 @@ impl GetData for SystemInfo { &mut self, buffer: Vec, query: String, - _metrics: &mut DataMetrics, + metrics: &mut DataMetrics, ) -> Result { let mut values = Vec::new(); for data in buffer { @@ -228,7 +277,7 @@ impl GetData for SystemInfo { let (_, req_str) = ¶m[1]; match req_str.as_str() { - "values" => get_values(values[0].clone()), + "values" => get_values(values[0].clone(), metrics), _ => panic!("Unsupported API"), } } From a1e31638a49c7078c9f0992c677b8ff465a83b35 Mon Sep 17 00:00:00 2001 From: Janakarajan Natarajan Date: Tue, 10 Dec 2024 16:35:16 +0000 Subject: [PATCH 3/3] Analytics: Add the front-end for analytics --- src/html_files/analytics.ts | 62 ++++++++++++ src/html_files/cpu_utilization.ts | 53 ++++++++++ src/html_files/flamegraphs.ts | 4 +- src/html_files/index.html | 10 +- src/html_files/index.ts | 8 +- src/html_files/system_info.ts | 157 ++++++++++++++++++++++++++++-- src/html_files/utils.ts | 36 +++++++ 7 files changed, 318 insertions(+), 12 deletions(-) create mode 100644 src/html_files/analytics.ts diff --git a/src/html_files/analytics.ts b/src/html_files/analytics.ts new file mode 100644 index 00000000..83ea9893 --- /dev/null +++ b/src/html_files/analytics.ts @@ -0,0 +1,62 @@ +class Rule { + name: string; + func: Function; +} + +class Rules { + data_type: string; + pretty_name: string; + single_run_rules: Array; + all_run_rules: Array; + per_run_rules: Array; +} + +class RuleOpts { + data_type: string; + runs: Array; + key: string; + all_data: any; + base_run: string; + base_run_data: any; + other_run_data: Map; + per_run_data: Array; +} + +enum Status { + Good = '✅', + NotGood = '❌', +} + +class Analytics { + name: string; + analysis: Array; +} + +class Finding { + text: string; + status: Status; + recommendation: string; + + constructor(text: string = '', status: Status = Status.NotGood, recommendation: string = '') { + this.text = text; + this.status = status; + this.recommendation = recommendation; + } + + is_good() { + this.status = Status.Good; + } + + is_not_good() { + this.status = Status.NotGood; + } +} + +function is_unique(values_array) { + return new Set(values_array).size == 1; +} + +let all_rules: Rules[] = [ + system_info_rules, + cpu_utilization_rules, +]; \ No newline at end of file diff --git a/src/html_files/cpu_utilization.ts b/src/html_files/cpu_utilization.ts index 4e7f1c11..963ae438 100644 --- a/src/html_files/cpu_utilization.ts +++ b/src/html_files/cpu_utilization.ts @@ -1,6 +1,59 @@ let got_cpu_util_data = false; let util_cpu_list: Map = new Map(); +let cpu_utilization_rules = { + data_type: "cpu_utilization", + pretty_name: "CPU Utilization", + single_run_rules: [ + { + name: "User", + func: function (opts) { + let system_util = get_data_key(opts.data_type, "System"); + let total_util: number = opts.base_run_data + system_util.get(opts.base_run); + if (total_util < 50) { + return new Finding( + `Average CPU Utilization is less than 50%.`, + Status.NotGood, + ); + } + }, + }, + { + name: "Idle", + func: function (opts) { + if (opts.base_run_data > 50) { + return new Finding(`'Idle time' is greater than 50%.`, Status.NotGood); + } + }, + } + ], + per_run_rules: [ + { + name: "User", + func: function (key, run_data, base_run_key, base_run_data, opts) { + let system_util = get_data_key(opts.data_type, "System"); + let init_total_util: number = base_run_data + system_util.get(base_run_key); + let run_total_util: number = run_data + system_util.get(key); + let cpu_diff = Math.ceil(Math.abs(run_total_util - init_total_util)); + return new Finding( + `Average CPU Utilization difference between ${base_run_key} and ${key} is ${cpu_diff}%.`, + cpu_diff > 10 ? Status.NotGood : Status.Good, + ); + }, + }, + { + name: "Idle", + func: function (key, run_data, base_run_key, base_run_data, opts) { + let idle_diff = Math.ceil(Math.abs(run_data - base_run_data)); + return new Finding( + `Average Idle time difference between ${base_run_key} and ${key} is ${idle_diff}%.`, + idle_diff > 10 ? Status.NotGood : Status.Good, + ) + }, + } + ], + all_run_rules: [], +} function getUtilizationType(run, elem, type, run_data) { var cpu_type_datas = []; var type_data; diff --git a/src/html_files/flamegraphs.ts b/src/html_files/flamegraphs.ts index f2e69585..38ff5252 100644 --- a/src/html_files/flamegraphs.ts +++ b/src/html_files/flamegraphs.ts @@ -34,11 +34,9 @@ function getFlamegraphInfo(run, container_id) { addElemToNode(container_id, div); } -function flamegraphs(set: boolean|string) { +function flamegraphs(set) { if (set == got_flamegraphs_data) { return; - } else if (typeof(set) == "boolean") { - set = "flamegraphs"; } got_flamegraphs_data = set; clear_and_create('flamegraphs'); diff --git a/src/html_files/index.html b/src/html_files/index.html index 90b464a4..3ee83d66 100644 --- a/src/html_files/index.html +++ b/src/html_files/index.html @@ -11,7 +11,7 @@
APerf
- + @@ -30,6 +30,12 @@
+
+ Findings + System Info +

+
+
@@ -131,6 +137,7 @@

Hide N/A and all-zero graphs:

+ @@ -149,6 +156,7 @@

Hide N/A and all-zero graphs:

+ diff --git a/src/html_files/index.ts b/src/html_files/index.ts index cfb99670..cda3c86d 100644 --- a/src/html_files/index.ts +++ b/src/html_files/index.ts @@ -14,7 +14,7 @@ DataTypes.set('meminfo', {name: 'meminfo', hideClass: 'meminfoHide', trueId: 'me DataTypes.set('netstat', {name: 'netstat', hideClass: 'netstatHide', trueId: 'netstat_hide_yes', callback: netStat}); DataTypes.set('interrupts', {name: 'interrupts', hideClass: '', trueId: '', callback: interrupts}); DataTypes.set('cpu_utilization', {name: 'cpuutilization', hideClass: '', trueId: '', callback: cpuUtilization}); -DataTypes.set('system_info', {name: 'systeminfo', hideClass: '', trueId: '', callback: systemInfo}); +DataTypes.set('system_info', {name: 'systeminfo', hideClass: 'landingChoice', trueId: '', callback: systemInfo}); DataTypes.set('flamegraphs', {name: 'flamegraphs', hideClass: 'flamegraphsSelection', trueId: '', callback: flamegraphs}); DataTypes.set('top_functions', {name: 'topfunctions', hideClass: '', trueId: '', callback: topFunctions}); DataTypes.set('processes', {name: 'processes', hideClass: '', trueId: '', callback: processes}); @@ -44,7 +44,11 @@ function display_tab(name) { if (datatype.hideClass != "") { let queryInput = `input[name="${datatype.hideClass}"]:checked`; let checkedId = document.querySelector(queryInput).id; - datatype.callback(checkedId == datatype.trueId); + if (datatype.trueId != "") { + datatype.callback(checkedId == datatype.trueId); + } else { + datatype.callback(checkedId); + } } else { datatype.callback(); } diff --git a/src/html_files/system_info.ts b/src/html_files/system_info.ts index b7f7efd9..0266606c 100644 --- a/src/html_files/system_info.ts +++ b/src/html_files/system_info.ts @@ -1,4 +1,51 @@ -let got_system_info_data = false; +let got_system_info_data = "none"; +let system_info_rules = { + data_type: "system_info", + pretty_name: "System Info", + single_run_rules: [], + per_run_rules: [], + all_run_rules: [ + { + name: "System Name", + func: function (ruleOpts: RuleOpts) { + let os_version = get_data_key(ruleOpts.data_type, "OS Version").values(); + if (is_unique(ruleOpts.per_run_data) && is_unique(os_version)) { + return new Finding("Same OS across runs.", Status.Good); + } else { + return new Finding("Different OS and/or version across runs."); + } + }, + }, + { + name: "Total CPUs", + func: function (ruleOpts: RuleOpts) { + if (is_unique(ruleOpts.per_run_data)) { + return new Finding("Total CPUs are the same across runs.", Status.Good); + } else { + return new Finding("Total CPUs are not the same across runs."); + } + }, + }, + { + name: "Kernel Version", + func: function (ruleOpts: RuleOpts) { + let versions = ruleOpts.per_run_data; + for (let i = 0; i < versions.length; i++) { + if (versions[i].split(".").length > 2) { + versions[i] = versions[i].split(".").slice(0, 2).join("."); + } else if (versions[i].split("-").length > 1) { + versions[i] = versions[i].split("-")[0]; + } + } + if (is_unique(versions)) { + return new Finding("Kernel versions (major, minor) are the same across all runs.", Status.Good); + } else { + return new Finding("Kernel versions (major, minor) are not the same across all runs."); + } + }, + }, + ], +} function getSystemInfo(run, container_id, run_data) { var data = JSON.parse(run_data); @@ -19,11 +66,89 @@ function getSystemInfo(run, container_id, run_data) { }) } -function systemInfo() { - if (got_system_info_data) { - return; +function formRuleOpts(rules_group, data_type, rule) { + let per_run_data = get_data_key(data_type, rule.name); + let base_run_data = per_run_data.get(runs_raw[0]); + let other_run_data = new Map(per_run_data); + other_run_data.delete(runs_raw[0]); + let ruleOpts: RuleOpts = { + data_type: data_type, + base_run: runs_raw[0], + runs: runs_raw, + key: rule.name, + all_data: raw_analytics, + base_run_data: base_run_data, + other_run_data: other_run_data, + per_run_data: [...per_run_data.values()], } - clear_and_create('systeminfo'); + return ruleOpts; +} +function analytics() { + let all_analytics = []; + for (var i = 0; i < all_rules.length; i++) { + let rules_group = all_rules[i]; + let data_type = rules_group.data_type; + let analytics = { + name: rules_group.pretty_name, + analysis: [], + } + if (runs_raw.length > 1) { + for (var j = 0; j < rules_group.per_run_rules.length; j++) { + let rule = rules_group.per_run_rules[j]; + let opts = formRuleOpts(rules_group, data_type, rule); + let findings = []; + for (const [key, value] of opts.other_run_data) { + let out = rule.func(key, value, runs_raw[0], opts.base_run_data, opts); + if (out) { + findings.push(out); + } + } + analytics.analysis = analytics.analysis.concat(findings); + } + for (var j = 0; j < rules_group.all_run_rules.length; j++) { + let rule = rules_group.all_run_rules[j]; + let opts = formRuleOpts(rules_group, data_type, rule); + let out = rule.func(opts); + if (out) { + analytics.analysis = analytics.analysis.concat(out); + } + } + } else { + for (var j = 0; j < rules_group.single_run_rules.length; j++) { + let rule = rules_group.single_run_rules[j]; + let opts = formRuleOpts(rules_group, data_type, rule); + let out = rule.func(opts); + if (out) { + analytics.analysis = analytics.analysis.concat(rule.func(opts)); + } + } + } + all_analytics.push(analytics); + } + var table = document.createElement('table'); + table.style.border = 'none'; + table.id = 'analytics-table'; + addElemToNode("findings-data", table); + for (let j = 0; j < all_analytics.length; j++) { + let analytics = all_analytics[j]; + for (let k = 0; k < analytics.analysis.length; k++) { + const row = table.insertRow(); + if (k == 0) { + const key = row.insertCell(); + key.textContent = `${analytics.name}`; + } else { + const key = row.insertCell(); + key.textContent = ''; + } + const tick = row.insertCell(); + tick.textContent = `${analytics.analysis[k].status}`; + const data = row.insertCell(); + data.textContent = `${analytics.analysis[k].text}`; + } + } +} + +function sutconfig() { for (let i = 0; i < system_info_raw_data['runs'].length; i++) { let run_name = system_info_raw_data['runs'][i]['name']; let elem_id = `${run_name}-systeminfo-per-data`; @@ -32,5 +157,25 @@ function systemInfo() { getSystemInfo(run_name, elem_id, this_run_data['key_values']['values']); }, 0); } - got_system_info_data = true; +} + +function systemInfo(set) { + if (set == got_system_info_data) { + return; + } + clear_and_create('systeminfo'); + clearElements("findings-data"); + got_system_info_data = set; + switch (set) { + case 'findings': + document.getElementById('landing-text').innerHTML = 'Findings'; + analytics(); + break; + case 'sutconfig': + document.getElementById('landing-text').innerHTML = 'System Info'; + sutconfig(); + break; + default: + return; + } } diff --git a/src/html_files/utils.ts b/src/html_files/utils.ts index efc7fe74..fe42ddf6 100644 --- a/src/html_files/utils.ts +++ b/src/html_files/utils.ts @@ -15,7 +15,9 @@ declare let flamegraph_raw_data; declare let aperf_run_stats_raw_data; declare let java_profile_raw_data; declare let aperf_runlog_raw_data; +declare let raw_analytics; +let comparator = 'mean'; let all_run_keys: Array = new Array(); let key_limits: Map = new Map(); @@ -171,3 +173,37 @@ function allRunCPUListUnchanged(cpu_list) { } return true; } + +function get_inside_value(data) { + if ('F64' in data) { + return data['F64']; + } else if ('UInt64' in data) { + return data['UInt64']; + } else if ('String' in data) { + return data['String']; + } else if ('Stats' in data) { + if (comparator == 'mean') { + return data['Stats']['mean']; + } else if (comparator == 'p99') { + return data['Stats']['p99']; + } else if (comparator == 'p90') { + return data['Stats']['p90']; + } else { + return undefined; + } + } else { + return undefined; + } +} + +function get_data_key(data_type, key) { + let key_value_map = new Map(); + for (let run in raw_analytics) { + let run_data = raw_analytics[run].values; + if (key in run_data[data_type]) { + let v = run_data[data_type][key]; + key_value_map.set(run, get_inside_value(v)); + } + } + return key_value_map; +}