diff --git a/EXAMPLE.md b/EXAMPLE.md index f43fb915..8b03b8ba 100644 --- a/EXAMPLE.md +++ b/EXAMPLE.md @@ -62,3 +62,5 @@ Here are some example screenshots showing the comparison of two different perfor ![Compare Kernel Configs](images/kernel_config_compare.png "Comparing Kernel Connfigs") ### Comparing PMU Data ![Compare PMU Data](images/pmu_stat_compare.png "Comparing PMU Data") +### Comparing Flamegraphs +![Compare Flamegraphs](images/flamegraph_compare.png "Comparing Flamegraphs") \ No newline at end of file diff --git a/README.md b/README.md index 6eace43e..0e8834c2 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ APerf collects the following performance data: - Network stats - Meminfo - Profile data (if enabled with `--profile` and `perf` binary present) +- JVM profile data with [async-profiler](https://github.com/async-profiler/async-profiler/tree/master) binary ## Requirements * [Rust toolchain (v1.61.0+)](https://www.rust-lang.org/tools/install) @@ -96,8 +97,9 @@ To see a step-by-step example, please see our example [here](./EXAMPLE.md) `--profile` gather profiling data using the 'perf' binary +`--profile-java` profile JVMs by PID or name using async-profiler (default profiles all JVMs) -`./aperf report -h` +`./aperf record -h` **Reporter Flags:** @@ -111,6 +113,8 @@ To see a step-by-step example, please see our example [here](./EXAMPLE.md) `-vv, --verbose --verbose` more verbose messages +`./aperf report -h` + ## APerf Issues? Below are some prerequisites for profiling with APerf: 1. Select the [appropriate instance size](https://github.com/aws/aws-graviton-getting-started/blob/main/perfrunbook/debug_hw_perf.md) if you need PMU stats. diff --git a/images/flamegraph_compare.png b/images/flamegraph_compare.png new file mode 100644 index 00000000..77ec7ca9 Binary files /dev/null and b/images/flamegraph_compare.png differ diff --git a/src/data.rs b/src/data.rs index f388ceef..3444dfdf 100644 --- a/src/data.rs +++ b/src/data.rs @@ -13,6 +13,7 @@ cfg_if::cfg_if! { } } pub mod interrupts; +pub mod java_profile; pub mod kernel_config; pub mod meminfodata; pub mod netstat; @@ -33,6 +34,7 @@ use cpu_utilization::{CpuUtilization, CpuUtilizationRaw}; use diskstats::{Diskstats, DiskstatsRaw}; use flamegraphs::{Flamegraph, FlamegraphRaw}; use interrupts::{InterruptData, InterruptDataRaw}; +use java_profile::{JavaProfile, JavaProfileRaw}; use kernel_config::KernelConfig; use log::trace; use meminfodata::{MeminfoData, MeminfoDataRaw}; @@ -41,6 +43,7 @@ use perf_profile::{PerfProfile, PerfProfileRaw}; use perf_stat::{PerfStat, PerfStatRaw}; use processes::{Processes, ProcessesRaw}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use std::fs::{File, OpenOptions}; use std::ops::Sub; use sysctldata::SysctlData; @@ -50,18 +53,22 @@ use vmstat::{Vmstat, VmstatRaw}; #[derive(Clone, Debug)] pub struct CollectorParams { pub collection_time: u64, + pub elapsed_time: u64, pub data_file_path: String, pub data_dir: String, pub run_name: String, + pub profile: HashMap, } impl CollectorParams { fn new() -> Self { CollectorParams { collection_time: 0, + elapsed_time: 0, data_file_path: String::new(), data_dir: String::new(), run_name: String::new(), + profile: HashMap::new(), } } } @@ -73,6 +80,7 @@ pub struct DataType { pub full_path: String, pub dir_name: String, pub is_static: bool, + pub is_profile_option: bool, pub collector_params: CollectorParams, } @@ -85,6 +93,7 @@ impl DataType { full_path: String::new(), dir_name: String::new(), is_static, + is_profile_option: false, collector_params: CollectorParams::new(), } } @@ -93,6 +102,10 @@ impl DataType { self.file_handle = handle; } + pub fn is_profile_option(&mut self) { + self.is_profile_option = true; + } + pub fn init_data_type(&mut self, param: InitParams) -> Result<()> { trace!("Initializing data type..."); let name = format!( @@ -106,8 +119,10 @@ impl DataType { self.dir_name = param.dir_name.clone(); self.collector_params.run_name = param.dir_name.clone(); self.collector_params.collection_time = param.period; + self.collector_params.elapsed_time = 0; self.collector_params.data_file_path = self.full_path.clone(); self.collector_params.data_dir = param.dir_name.clone(); + self.collector_params.profile = param.profile.clone(); self.file_handle = Some( OpenOptions::new() @@ -130,7 +145,7 @@ impl DataType { pub fn collect_data(&mut self) -> Result<()> { trace!("Collecting Data..."); - self.data.collect_data()?; + self.data.collect_data(&self.collector_params)?; Ok(()) } @@ -193,10 +208,10 @@ macro_rules! data { } impl Data { - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, params: &CollectorParams) -> Result<()> { match self { $( - Data::$x(ref mut value) => value.collect_data()?, + Data::$x(ref mut value) => value.collect_data(¶ms)?, )* } Ok(()) @@ -286,7 +301,8 @@ data!( MeminfoDataRaw, NetstatRaw, PerfProfileRaw, - FlamegraphRaw + FlamegraphRaw, + JavaProfileRaw ); processed_data!( @@ -303,7 +319,8 @@ processed_data!( Netstat, PerfProfile, Flamegraph, - AperfStat + AperfStat, + JavaProfile ); macro_rules! noop { @@ -315,7 +332,7 @@ pub trait CollectData { noop!(); Ok(()) } - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { noop!(); Ok(()) } @@ -349,6 +366,7 @@ mod tests { full_path: String::new(), dir_name: String::new(), is_static: false, + is_profile_option: false, collector_params: CollectorParams::new(), }; @@ -376,6 +394,7 @@ mod tests { full_path: String::new(), dir_name: String::new(), is_static: false, + is_profile_option: false, collector_params: CollectorParams::new(), }; diff --git a/src/data/cpu_utilization.rs b/src/data/cpu_utilization.rs index 364535a5..b3c159cd 100644 --- a/src/data/cpu_utilization.rs +++ b/src/data/cpu_utilization.rs @@ -1,6 +1,6 @@ extern crate ctor; -use crate::data::{CollectData, Data, DataType, ProcessedData, TimeEnum}; +use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -36,7 +36,7 @@ impl Default for CpuUtilizationRaw { } impl CollectData for CpuUtilizationRaw { - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { self.time = TimeEnum::DateTime(Utc::now()); self.data = String::new(); self.data = std::fs::read_to_string("/proc/stat")?; @@ -406,14 +406,15 @@ fn init_cpu_utilization() { #[cfg(test)] mod cpu_tests { use super::{CpuData, CpuUtilization, CpuUtilizationRaw, UtilData}; - use crate::data::{CollectData, Data, ProcessedData}; + use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; use crate::visualizer::GetData; #[test] fn test_collect_data() { let mut cpu_utilization = CpuUtilizationRaw::new(); + let params = CollectorParams::new(); - cpu_utilization.collect_data().unwrap(); + cpu_utilization.collect_data(¶ms).unwrap(); assert!(!cpu_utilization.data.is_empty()); } @@ -423,9 +424,10 @@ mod cpu_tests { let mut cpu_util_zero = CpuUtilizationRaw::new(); let mut cpu_util_one = CpuUtilizationRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - cpu_util_zero.collect_data().unwrap(); - cpu_util_one.collect_data().unwrap(); + cpu_util_zero.collect_data(¶ms).unwrap(); + cpu_util_one.collect_data(¶ms).unwrap(); buffer.push(Data::CpuUtilizationRaw(cpu_util_zero)); buffer.push(Data::CpuUtilizationRaw(cpu_util_one)); @@ -463,9 +465,10 @@ mod cpu_tests { let mut cpu_util_zero = CpuUtilizationRaw::new(); let mut cpu_util_one = CpuUtilizationRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - cpu_util_zero.collect_data().unwrap(); - cpu_util_one.collect_data().unwrap(); + cpu_util_zero.collect_data(¶ms).unwrap(); + cpu_util_one.collect_data(¶ms).unwrap(); buffer.push(Data::CpuUtilizationRaw(cpu_util_zero)); buffer.push(Data::CpuUtilizationRaw(cpu_util_one)); diff --git a/src/data/diskstats.rs b/src/data/diskstats.rs index 209411cd..b02fa550 100644 --- a/src/data/diskstats.rs +++ b/src/data/diskstats.rs @@ -1,7 +1,7 @@ extern crate ctor; use crate::data::constants::*; -use crate::data::{CollectData, Data, DataType, ProcessedData, TimeEnum}; +use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; use crate::visualizer::{DataVisualizer, GetData, GraphLimitType, GraphMetadata}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -32,7 +32,7 @@ impl DiskstatsRaw { } impl CollectData for DiskstatsRaw { - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { self.time = TimeEnum::DateTime(Utc::now()); self.data = String::new(); self.data = std::fs::read_to_string("/proc/diskstats")?; @@ -363,7 +363,7 @@ fn init_diskstats() { #[cfg(test)] mod tests { use super::{DiskstatKeys, Diskstats, DiskstatsRaw, EndDiskValues}; - use crate::data::{CollectData, Data, ProcessedData}; + use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; use crate::visualizer::GetData; use std::collections::HashMap; use strum::IntoEnumIterator; @@ -371,8 +371,9 @@ mod tests { #[test] fn test_collect_data() { let mut diskstats = DiskstatsRaw::new(); + let params = CollectorParams::new(); - diskstats.collect_data().unwrap(); + diskstats.collect_data(¶ms).unwrap(); assert!(!diskstats.data.is_empty()); } @@ -383,7 +384,9 @@ mod tests { for key in DiskstatKeys::iter() { key_map.insert(key.to_string(), 0); } - stat.collect_data().unwrap(); + let params = CollectorParams::new(); + + stat.collect_data(¶ms).unwrap(); let processed_stat = Diskstats::new() .process_raw_data(Data::DiskstatsRaw(stat)) .unwrap(); @@ -407,8 +410,9 @@ mod tests { let mut buffer: Vec = Vec::::new(); let mut diskstat = DiskstatsRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - diskstat.collect_data().unwrap(); + diskstat.collect_data(¶ms).unwrap(); buffer.push(Data::DiskstatsRaw(diskstat)); processed_buffer.push( Diskstats::new() @@ -428,9 +432,10 @@ mod tests { let mut diskstat_zero = DiskstatsRaw::new(); let mut diskstat_one = DiskstatsRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - diskstat_zero.collect_data().unwrap(); - diskstat_one.collect_data().unwrap(); + diskstat_zero.collect_data(¶ms).unwrap(); + diskstat_one.collect_data(¶ms).unwrap(); buffer.push(Data::DiskstatsRaw(diskstat_zero)); buffer.push(Data::DiskstatsRaw(diskstat_one)); for buf in buffer { diff --git a/src/data/flamegraphs.rs b/src/data/flamegraphs.rs index ea9740d7..9dd364c8 100644 --- a/src/data/flamegraphs.rs +++ b/src/data/flamegraphs.rs @@ -163,11 +163,12 @@ impl GetData for Flamegraph { fn init_flamegraph() { let flamegraph_raw = FlamegraphRaw::new(); let file_name = FLAMEGRAPHS_FILE_NAME.to_string(); - let dt = DataType::new( + let mut dt = DataType::new( Data::FlamegraphRaw(flamegraph_raw.clone()), file_name.clone(), false, ); + dt.is_profile_option(); let flamegraph = Flamegraph::new(); let js_file_name = file_name.clone() + ".js"; let mut dv = DataVisualizer::new( diff --git a/src/data/interrupts.rs b/src/data/interrupts.rs index f38598ff..2defc056 100644 --- a/src/data/interrupts.rs +++ b/src/data/interrupts.rs @@ -1,6 +1,6 @@ extern crate ctor; -use crate::data::{CollectData, Data, DataType, ProcessedData, TimeEnum}; +use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -29,7 +29,7 @@ impl InterruptDataRaw { } impl CollectData for InterruptDataRaw { - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { self.time = TimeEnum::DateTime(Utc::now()); self.data = String::new(); self.data = std::fs::read_to_string("/proc/interrupts")?; @@ -343,15 +343,16 @@ fn init_interrupts() { #[cfg(test)] mod tests { use super::{InterruptData, InterruptDataRaw, InterruptLine, InterruptLineData}; - use crate::data::{CollectData, Data, ProcessedData}; + use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; use crate::get_file; use crate::visualizer::{DataVisualizer, GetData}; #[test] fn test_collect_data() { let mut id = InterruptDataRaw::new(); + let params = CollectorParams::new(); - id.collect_data().unwrap(); + id.collect_data(¶ms).unwrap(); assert!(!id.data.is_empty()); } @@ -360,8 +361,9 @@ mod tests { let mut buffer: Vec = Vec::::new(); let mut id_raw = InterruptDataRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - id_raw.collect_data().unwrap(); + id_raw.collect_data(¶ms).unwrap(); buffer.push(Data::InterruptDataRaw(id_raw)); for buf in buffer { @@ -399,9 +401,10 @@ mod tests { let mut id_zero = InterruptDataRaw::new(); let mut id_one = InterruptDataRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - id_zero.collect_data().unwrap(); - id_one.collect_data().unwrap(); + id_zero.collect_data(¶ms).unwrap(); + id_one.collect_data(¶ms).unwrap(); buffer.push(Data::InterruptDataRaw(id_zero)); buffer.push(Data::InterruptDataRaw(id_one)); @@ -420,8 +423,9 @@ mod tests { let mut buffer: Vec = Vec::::new(); let mut id = InterruptDataRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - id.collect_data().unwrap(); + id.collect_data(¶ms).unwrap(); buffer.push(Data::InterruptDataRaw(id)); for buf in buffer { diff --git a/src/data/java_profile.rs b/src/data/java_profile.rs new file mode 100644 index 00000000..6f905016 --- /dev/null +++ b/src/data/java_profile.rs @@ -0,0 +1,328 @@ +extern crate ctor; + +use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData}; +use crate::visualizer::{DataVisualizer, GetData}; +use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; +use anyhow::Result; +use ctor::ctor; +use log::{debug, error, trace}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::io::Write; +use std::path::PathBuf; +use std::process::{Child, Command}; +use std::sync::Mutex; +use std::{fs, fs::File}; + +pub static JAVA_PROFILE_FILE_NAME: &str = "java_profile"; + +lazy_static! { + pub static ref ASPROF_CHILDREN: Mutex> = Mutex::new(Vec::new()); +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct JavaProfileRaw { + process_map: HashMap>, +} + +impl JavaProfileRaw { + pub fn new() -> Self { + JavaProfileRaw { + process_map: HashMap::new(), + } + } + + fn launch_asprof(&self, jids: Vec, params: CollectorParams) -> Result<()> { + let data_dir = PathBuf::from(params.data_dir.clone()); + for jid in &jids { + let mut html_loc = data_dir.clone(); + html_loc.push(format!("{}-java-flamegraph-{}.html", params.run_name, jid)); + + match Command::new("asprof") + .args([ + "-d", + &(params.collection_time - params.elapsed_time).to_string(), + "-f", + html_loc.to_str().unwrap(), + jid.as_str(), + ]) + .spawn() + { + Err(e) => { + return Err(PDError::DependencyError(format!( + "'asprof' command failed. {}", + e.to_string() + )) + .into()); + } + Ok(child) => { + debug!( + "Recording asprof profiling data for '{}' with PID, {}.", + self.process_map + .get(jid.as_str()) + .unwrap_or(&vec![String::from("JVM")])[0], + jid + ); + ASPROF_CHILDREN.lock().unwrap().push(child); + } + } + } + Ok(()) + } + + fn get_jids(&mut self, arg: &str) -> Vec { + let mut jids: Vec = Vec::new(); + for (key, value) in self.process_map.clone().into_iter() { + if arg == value[0] { + jids.push(key); + } + } + jids + } + + fn update_process_map(&mut self) -> Result { + debug!("Running jps (may incur utilization spike)..."); + let jps_cmd = Command::new("jps").output(); + /* + Output of jps: + lvmid [ classname | JARfilename | "Unknown"] + lvmid [ classname | JARfilename | "Unknown"] + . + . + lvmid [ classname | JARfilename | "Unknown"] + */ + match jps_cmd { + Ok(jps_out) => { + let jps_str = String::from_utf8(jps_out.stdout).unwrap_or_default(); + let jps: Vec<&str> = jps_str.split_whitespace().collect(); + for i in (0..jps.len()).step_by(2) { + if jps[i + 1] != "Jps" { + self.process_map + .insert(String::from(jps[i]), vec![String::from(jps[i + 1])]); + } + } + Ok(jps_str) + } + Err(e) => Err(PDError::DependencyError(format!( + "Jps command failed. {}", + e.to_string() + ))), + } + } + + fn launch_pgrep(&mut self) -> Result, PDError> { + let pgrep_cmd = Command::new("pgrep").arg("java").output(); + match pgrep_cmd { + Ok(pgrep_out) => { + let pgrep_str = String::from_utf8(pgrep_out.stdout).unwrap(); + return Ok(pgrep_str + .split_whitespace() + .map(|s| s.to_string()) + .collect()); + } + Err(e) => Err(PDError::DependencyError(format!( + "pgrep command failed. {}", + e.to_string() + ))), + } + } +} + +impl CollectData for JavaProfileRaw { + fn prepare_data_collector(&mut self, params: CollectorParams) -> Result<()> { + let mut jids: Vec = Vec::new(); + let pgrep: Vec = self.launch_pgrep()?; + for pid in pgrep { + if self.process_map.contains_key(&pid) { + continue; + } + self.process_map + .insert(pid.clone(), vec![String::from("Could not resolve name!")]); + } + + let jps_str = self.update_process_map()?; + let jps: Vec<&str> = jps_str.split_whitespace().collect(); + + let jprofile_value = params.profile.get(JAVA_PROFILE_FILE_NAME); + if let Some(value) = jprofile_value { + match value.as_str() { + "jps" => { + jids = self.process_map.clone().into_keys().collect(); + debug!("Jps will be run if new JVM is started during aperf record to resolve process names.",); + } + _ => { + let args: Vec<&str> = value.split(',').collect(); + for arg in args { + if !jps.contains(&arg) { + error!("No JVM with name/PID '{}'.", arg); + continue; + } + jids = self.get_jids(arg); + } + } + } + } + + self.launch_asprof(jids, params.clone()) + } + + fn collect_data(&mut self, params: &CollectorParams) -> Result<()> { + let jprofile = params.profile.get(JAVA_PROFILE_FILE_NAME).unwrap().as_str(); + if jprofile != "jps" { + return Ok(()); + } + + let pgrep_pids: Vec = self.launch_pgrep()?; + + let mut jids: Vec = Vec::new(); + for pid in pgrep_pids { + if self.process_map.contains_key(pid.as_str()) { + continue; + } + self.process_map + .insert(pid.clone(), vec![String::from("Could not resolve name!")]); + jids.push(pid.clone()); + } + + if jids.is_empty() || params.elapsed_time >= params.collection_time { + return Ok(()); + } + + self.update_process_map()?; + self.launch_asprof(jids, params.clone()) + } + + fn finish_data_collection(&mut self, params: CollectorParams) -> Result<()> { + trace!("Waiting for asprof profile collection to complete..."); + while ASPROF_CHILDREN.lock().unwrap().len() > 0 { + match ASPROF_CHILDREN.lock().unwrap().pop().unwrap().wait() { + Err(e) => { + error!("'asprof' did not exit successfully: {}", e); + return Ok(()); + } + Ok(_) => trace!("'asprof' executed successfully."), + } + } + + let data_dir = PathBuf::from(params.data_dir.clone()); + let mut jps_map = File::create( + data_dir + .clone() + .join(format!("{}-jps-map.json", params.run_name)), + )?; + write!(jps_map, "{}", serde_json::to_string(&self.process_map)?)?; + + Ok(()) + } + + fn after_data_collection(&mut self, _params: CollectorParams) -> Result<()> { + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct JavaProfile { + pub data: String, +} + +impl JavaProfile { + pub fn new() -> Self { + JavaProfile { + data: String::new(), + } + } +} + +impl GetData for JavaProfile { + fn custom_raw_data_parser( + &mut self, + params: crate::visualizer::ReportParams, + ) -> Result> { + let mut processes_loc = PathBuf::from(params.data_dir.clone()); + processes_loc.push(format!("{}-jps-map.json", params.run_name)); + let processes_json = + fs::read_to_string(processes_loc.to_str().unwrap()).unwrap_or(String::new()); + let mut process_map: HashMap> = + serde_json::from_str(&processes_json).unwrap_or(HashMap::new()); + let process_list: Vec = process_map.clone().into_keys().collect(); + + for process in process_list { + let mut fg_loc = params.report_dir.clone(); + fg_loc.push(format!( + "data/js/{}-java-flamegraph-{}.html", + params.run_name, process + )); + let mut html_loc = PathBuf::from(params.data_dir.clone()); + html_loc.push(format!( + "{}-java-flamegraph-{}.html", + params.run_name, process + )); + let html = fs::read_to_string(html_loc.to_str().unwrap()) + .unwrap_or(String::from("No data collected.")); + let mut fg_file = File::create(fg_loc.clone())?; + write!(fg_file, "{}", html)?; + + process_map + .entry(process) + .and_modify(|v| v.push(html.len().to_string())); + } + + let mut java_profile_data = JavaProfile::new(); + java_profile_data.data = serde_json::to_string(&process_map)?; + let processed_data = vec![ProcessedData::JavaProfile(java_profile_data)]; + Ok(processed_data) + } + + fn get_calls(&mut self) -> Result> { + Ok(vec!["values".to_string()]) + } + + fn get_data(&mut self, buffer: Vec, query: String) -> Result { + let mut values = Vec::new(); + for data in buffer { + match data { + ProcessedData::JavaProfile(ref value) => values.push(value.clone()), + _ => panic!("Invalid Data type in file"), + } + } + let param: Vec<(String, String)> = serde_urlencoded::from_str(&query).unwrap(); + let (_, req_str) = ¶m[1]; + + match req_str.as_str() { + "values" => Ok(values[0].data.to_string()), + _ => panic!("Unsupported API"), + } + } +} + +#[ctor] +fn init_java_profile() { + let java_profile_raw: JavaProfileRaw = JavaProfileRaw::new(); + let file_name = JAVA_PROFILE_FILE_NAME.to_string(); + let mut dt = DataType::new( + Data::JavaProfileRaw(java_profile_raw.clone()), + file_name.clone(), + false, + ); + dt.is_profile_option(); + + let java_profile = JavaProfile::new(); + let mut dv = DataVisualizer::new( + ProcessedData::JavaProfile(java_profile), + file_name.clone(), + String::new(), + String::new(), + file_name.clone(), + ); + dv.has_custom_raw_data_parser(); + + PERFORMANCE_DATA + .lock() + .unwrap() + .add_datatype(file_name.clone(), dt); + + VISUALIZATION_DATA + .lock() + .unwrap() + .add_visualizer(file_name.clone(), dv); +} diff --git a/src/data/kernel_config.rs b/src/data/kernel_config.rs index cee10948..89caac03 100644 --- a/src/data/kernel_config.rs +++ b/src/data/kernel_config.rs @@ -13,6 +13,8 @@ use std::fs::OpenOptions; use std::io::{self, BufRead, BufReader}; use std::path::Path; +use super::CollectorParams; + pub static KERNEL_CONFIG_FILE_NAME: &str = "kernel_config"; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -93,7 +95,7 @@ fn get_kernel_config_data() -> Result> { } impl CollectData for KernelConfig { - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { let time_now = Utc::now(); let mut kernel_data_processed: Vec = Vec::new(); let mut comments = Vec::new(); @@ -255,14 +257,15 @@ fn init_kernel_config() { #[cfg(test)] mod tests { use super::{KernelConfig, KernelConfigEntryGroup}; - use crate::data::{CollectData, Data, ProcessedData}; + use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; use crate::visualizer::GetData; #[test] fn test_collect_data() { let mut kernel_config = KernelConfig::new(); + let params = CollectorParams::new(); - kernel_config.collect_data().unwrap(); + kernel_config.collect_data(¶ms).unwrap(); assert!(!kernel_config.kernel_config_data.is_empty()); } @@ -271,8 +274,9 @@ mod tests { let mut buffer: Vec = Vec::::new(); let mut kernel_config = KernelConfig::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - kernel_config.collect_data().unwrap(); + kernel_config.collect_data(¶ms).unwrap(); buffer.push(Data::KernelConfig(kernel_config)); processed_buffer.push( KernelConfig::new() diff --git a/src/data/meminfodata.rs b/src/data/meminfodata.rs index 063c41b8..5202cddb 100644 --- a/src/data/meminfodata.rs +++ b/src/data/meminfodata.rs @@ -1,6 +1,6 @@ extern crate ctor; -use crate::data::{CollectData, Data, DataType, ProcessedData, TimeEnum}; +use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; use crate::visualizer::{DataVisualizer, GetData, GraphLimitType, GraphMetadata}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -39,7 +39,7 @@ impl MeminfoDataRaw { } impl CollectData for MeminfoDataRaw { - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { self.time = TimeEnum::DateTime(Utc::now()); self.data = String::new(); self.data = std::fs::read_to_string("/proc/meminfo")?; @@ -471,7 +471,7 @@ fn init_meminfo() { #[cfg(test)] mod tests { use super::{EndMemValues, MeminfoData, MeminfoDataRaw, MeminfoKeys}; - use crate::data::{CollectData, Data, ProcessedData}; + use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; use crate::visualizer::GetData; use std::collections::HashMap; use strum::IntoEnumIterator; @@ -479,8 +479,9 @@ mod tests { #[test] fn test_collect_data() { let mut meminfodata_raw = MeminfoDataRaw::new(); + let params = CollectorParams::new(); - meminfodata_raw.collect_data().unwrap(); + meminfodata_raw.collect_data(¶ms).unwrap(); assert!(!meminfodata_raw.data.is_empty()); } @@ -491,7 +492,8 @@ mod tests { for key in MeminfoKeys::iter() { key_map.insert(key.to_string(), 0); } - meminfodata_raw.collect_data().unwrap(); + let params = CollectorParams::new(); + meminfodata_raw.collect_data(¶ms).unwrap(); let processed_data = MeminfoData::new() .process_raw_data(Data::MeminfoDataRaw(meminfodata_raw)) .unwrap(); @@ -515,8 +517,9 @@ mod tests { let mut buffer: Vec = Vec::new(); let mut meminfodata_raw = MeminfoDataRaw::new(); let mut processed_buffer: Vec = Vec::new(); + let params = CollectorParams::new(); - meminfodata_raw.collect_data().unwrap(); + meminfodata_raw.collect_data(¶ms).unwrap(); buffer.push(Data::MeminfoDataRaw(meminfodata_raw)); processed_buffer.push( MeminfoData::new() @@ -536,9 +539,10 @@ mod tests { let mut meminfodata_raw_zero = MeminfoDataRaw::new(); let mut meminfodata_raw_one = MeminfoDataRaw::new(); let mut processed_buffer: Vec = Vec::new(); + let params = CollectorParams::new(); - meminfodata_raw_zero.collect_data().unwrap(); - meminfodata_raw_one.collect_data().unwrap(); + meminfodata_raw_zero.collect_data(¶ms).unwrap(); + meminfodata_raw_one.collect_data(¶ms).unwrap(); buffer.push(Data::MeminfoDataRaw(meminfodata_raw_zero)); buffer.push(Data::MeminfoDataRaw(meminfodata_raw_one)); for buf in buffer { diff --git a/src/data/netstat.rs b/src/data/netstat.rs index 010ef216..c555500a 100644 --- a/src/data/netstat.rs +++ b/src/data/netstat.rs @@ -1,6 +1,6 @@ extern crate ctor; -use crate::data::{CollectData, Data, DataType, ProcessedData, TimeEnum}; +use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; use crate::visualizer::{DataVisualizer, GetData, GraphLimitType, GraphMetadata}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -29,7 +29,7 @@ impl NetstatRaw { } impl CollectData for NetstatRaw { - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { self.time = TimeEnum::DateTime(Utc::now()); self.data = String::new(); self.data = std::fs::read_to_string("/proc/net/netstat")?; @@ -211,14 +211,15 @@ fn init_netstat() { #[cfg(test)] mod tests { use super::{EndNetData, Netstat, NetstatRaw}; - use crate::data::{CollectData, Data, ProcessedData, TimeEnum}; + use crate::data::{CollectData, CollectorParams, Data, ProcessedData, TimeEnum}; use crate::visualizer::GetData; #[test] fn test_collect_data() { let mut netstat = NetstatRaw::new(); + let params = CollectorParams::new(); - netstat.collect_data().unwrap(); + netstat.collect_data(¶ms).unwrap(); assert!(!netstat.data.is_empty()); } @@ -227,8 +228,9 @@ mod tests { let mut buffer: Vec = Vec::::new(); let mut netstat = NetstatRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - netstat.collect_data().unwrap(); + netstat.collect_data(¶ms).unwrap(); buffer.push(Data::NetstatRaw(netstat)); processed_buffer.push(Netstat::new().process_raw_data(buffer[0].clone()).unwrap()); let json = Netstat::new() @@ -243,8 +245,9 @@ mod tests { let mut buffer: Vec = Vec::::new(); let mut netstat = NetstatRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - netstat.collect_data().unwrap(); + netstat.collect_data(¶ms).unwrap(); buffer.push(Data::NetstatRaw(netstat)); processed_buffer.push(Netstat::new().process_raw_data(buffer[0].clone()).unwrap()); let json = Netstat::new() diff --git a/src/data/perf_profile.rs b/src/data/perf_profile.rs index 1e064d5f..30036edc 100644 --- a/src/data/perf_profile.rs +++ b/src/data/perf_profile.rs @@ -68,7 +68,7 @@ impl CollectData for PerfProfileRaw { Ok(()) } - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { Ok(()) } @@ -158,11 +158,12 @@ impl GetData for PerfProfile { fn init_perf_profile() { let perf_profile_raw = PerfProfileRaw::new(); let file_name = PERF_PROFILE_FILE_NAME.to_string(); - let dt = DataType::new( + let mut dt = DataType::new( Data::PerfProfileRaw(perf_profile_raw.clone()), file_name.clone(), false, ); + dt.is_profile_option(); let perf_profile = PerfProfile::new(); let js_file_name = file_name.clone() + ".js"; let mut dv = DataVisualizer::new( diff --git a/src/data/perf_stat.rs b/src/data/perf_stat.rs index 2dde3f66..e6970827 100644 --- a/src/data/perf_stat.rs +++ b/src/data/perf_stat.rs @@ -264,7 +264,7 @@ impl CollectData for PerfStatRaw { Ok(()) } - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { self.time = TimeEnum::DateTime(Utc::now()); self.data = String::new(); let cpu_groups = &mut *CPU_CTR_GROUPS.lock().unwrap(); @@ -554,7 +554,7 @@ mod tests { let mut perf_stat = PerfStatRaw::new(); let params = CollectorParams::new(); - match perf_stat.prepare_data_collector(params) { + match perf_stat.prepare_data_collector(params.clone()) { Err(e) => { if let Some(os_error) = e.downcast_ref::() { match os_error.kind() { @@ -567,7 +567,7 @@ mod tests { } } Ok(_) => { - perf_stat.collect_data().unwrap(); + perf_stat.collect_data(¶ms).unwrap(); assert!(!perf_stat.data.is_empty()); } } @@ -580,7 +580,7 @@ mod tests { let mut processed_buffer: Vec = Vec::new(); let params = CollectorParams::new(); - match perf_stat.prepare_data_collector(params) { + match perf_stat.prepare_data_collector(params.clone()) { Err(e) => { if let Some(os_error) = e.downcast_ref::() { match os_error.kind() { @@ -593,7 +593,7 @@ mod tests { } } Ok(_) => { - perf_stat.collect_data().unwrap(); + perf_stat.collect_data(¶ms).unwrap(); buffer.push(Data::PerfStatRaw(perf_stat)); for buf in buffer { processed_buffer.push(PerfStat::new().process_raw_data(buf).unwrap()); diff --git a/src/data/processes.rs b/src/data/processes.rs index ab8f670e..7ebbbe95 100644 --- a/src/data/processes.rs +++ b/src/data/processes.rs @@ -51,7 +51,7 @@ impl CollectData for ProcessesRaw { Ok(()) } - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { let ticks_per_second: u64 = *TICKS_PER_SECOND.lock().unwrap(); self.time = TimeEnum::DateTime(Utc::now()); self.data = String::new(); @@ -345,8 +345,8 @@ mod process_test { fn test_collect_data() { let mut processes = ProcessesRaw::new(); let params = CollectorParams::new(); - processes.prepare_data_collector(params).unwrap(); - processes.collect_data().unwrap(); + processes.prepare_data_collector(params.clone()).unwrap(); + processes.collect_data(¶ms).unwrap(); assert!(!processes.data.is_empty()); } @@ -361,9 +361,11 @@ mod process_test { processes_zero .prepare_data_collector(params.clone()) .unwrap(); - processes_one.prepare_data_collector(params).unwrap(); - processes_zero.collect_data().unwrap(); - processes_one.collect_data().unwrap(); + processes_one + .prepare_data_collector(params.clone()) + .unwrap(); + processes_zero.collect_data(¶ms).unwrap(); + processes_one.collect_data(¶ms).unwrap(); buffer.push(Data::ProcessesRaw(processes_zero)); buffer.push(Data::ProcessesRaw(processes_one)); diff --git a/src/data/sysctldata.rs b/src/data/sysctldata.rs index af5fc44f..f4ca43b3 100644 --- a/src/data/sysctldata.rs +++ b/src/data/sysctldata.rs @@ -1,6 +1,6 @@ extern crate ctor; -use crate::data::{CollectData, Data, DataType, ProcessedData, TimeEnum}; +use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -44,7 +44,7 @@ fn can_collect(name: String) -> bool { } impl CollectData for SysctlData { - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { let ctls = sysctl::CtlIter::root().filter_map(Result::ok); for ctl in ctls { let flags = match ctl.flags() { @@ -139,23 +139,25 @@ fn init_sysctl() { #[cfg(test)] mod tests { use super::{SysctlData, DONT_COLLECT}; - use crate::data::{CollectData, Data, ProcessedData}; + use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; use crate::visualizer::GetData; use std::collections::BTreeMap; #[test] fn test_collect_data() { let mut sysctl = SysctlData::new(); + let params = CollectorParams::new(); - sysctl.collect_data().unwrap(); + sysctl.collect_data(¶ms).unwrap(); assert!(!sysctl.sysctl_data.is_empty()); } #[test] fn test_dont_collect() { let mut sysctl = SysctlData::new(); + let params = CollectorParams::new(); - sysctl.collect_data().unwrap(); + sysctl.collect_data(¶ms).unwrap(); let keys: Vec = sysctl.sysctl_data.keys().cloned().collect(); for key in keys { for item in DONT_COLLECT { @@ -171,8 +173,9 @@ mod tests { let mut buffer: Vec = Vec::::new(); let mut sysctl = SysctlData::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - sysctl.collect_data().unwrap(); + sysctl.collect_data(¶ms).unwrap(); buffer.push(Data::SysctlData(sysctl)); processed_buffer.push( SysctlData::new() diff --git a/src/data/systeminfo.rs b/src/data/systeminfo.rs index 609c6b1f..247c1f53 100644 --- a/src/data/systeminfo.rs +++ b/src/data/systeminfo.rs @@ -1,6 +1,6 @@ extern crate ctor; -use crate::data::{CollectData, Data, DataType, ProcessedData, TimeEnum}; +use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; use crate::visualizer::{DataVisualizer, GetData}; use crate::{PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -107,7 +107,7 @@ impl EC2Metadata { } impl CollectData for SystemInfo { - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { let mut sys = System::new(); sys.refresh_system(); @@ -259,14 +259,15 @@ fn init_systeminfo() { #[cfg(test)] mod tests { use super::{SUTConfigEntry, SystemInfo}; - use crate::data::{CollectData, Data, ProcessedData}; + use crate::data::{CollectData, CollectorParams, Data, ProcessedData}; use crate::visualizer::GetData; #[test] fn test_collect_data() { let mut systeminfo = SystemInfo::new(); + let params = CollectorParams::new(); - systeminfo.collect_data().unwrap(); + systeminfo.collect_data(¶ms).unwrap(); assert_ne!(systeminfo.total_cpus, 0); assert_ne!(systeminfo.system_name, String::new()); assert_ne!(systeminfo.kernel_version, String::new()); @@ -279,8 +280,9 @@ mod tests { let mut buffer: Vec = Vec::::new(); let mut system_info = SystemInfo::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - system_info.collect_data().unwrap(); + system_info.collect_data(¶ms).unwrap(); buffer.push(Data::SystemInfo(system_info)); processed_buffer.push( SystemInfo::new() diff --git a/src/data/vmstat.rs b/src/data/vmstat.rs index 5b3fecd5..29cb4d74 100644 --- a/src/data/vmstat.rs +++ b/src/data/vmstat.rs @@ -1,6 +1,6 @@ extern crate ctor; -use crate::data::{CollectData, Data, DataType, ProcessedData, TimeEnum}; +use crate::data::{CollectData, CollectorParams, Data, DataType, ProcessedData, TimeEnum}; use crate::visualizer::{DataVisualizer, GetData, GraphLimitType, GraphMetadata}; use crate::{PDError, PERFORMANCE_DATA, VISUALIZATION_DATA}; use anyhow::Result; @@ -29,7 +29,7 @@ impl VmstatRaw { } impl CollectData for VmstatRaw { - fn collect_data(&mut self) -> Result<()> { + fn collect_data(&mut self, _params: &CollectorParams) -> Result<()> { self.time = TimeEnum::DateTime(Utc::now()); self.data = String::new(); self.data = std::fs::read_to_string("/proc/vmstat")?; @@ -197,14 +197,15 @@ fn init_vmstat() { #[cfg(test)] mod tests { use super::{EndVmstatData, Vmstat, VmstatRaw}; - use crate::data::{CollectData, Data, ProcessedData, TimeEnum}; + use crate::data::{CollectData, CollectorParams, Data, ProcessedData, TimeEnum}; use crate::visualizer::GetData; #[test] fn test_collect_data() { let mut vmstat = VmstatRaw::new(); + let params = CollectorParams::new(); - vmstat.collect_data().unwrap(); + vmstat.collect_data(¶ms).unwrap(); assert!(!vmstat.data.is_empty()); } @@ -213,8 +214,9 @@ mod tests { let mut buffer: Vec = Vec::::new(); let mut vmstat = VmstatRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - vmstat.collect_data().unwrap(); + vmstat.collect_data(¶ms).unwrap(); buffer.push(Data::VmstatRaw(vmstat)); processed_buffer.push(Vmstat::new().process_raw_data(buffer[0].clone()).unwrap()); let json = Vmstat::new() @@ -229,8 +231,9 @@ mod tests { let mut buffer: Vec = Vec::::new(); let mut vmstat = VmstatRaw::new(); let mut processed_buffer: Vec = Vec::::new(); + let params = CollectorParams::new(); - vmstat.collect_data().unwrap(); + vmstat.collect_data(¶ms).unwrap(); buffer.push(Data::VmstatRaw(vmstat)); processed_buffer.push(Vmstat::new().process_raw_data(buffer[0].clone()).unwrap()); let json = Vmstat::new() diff --git a/src/html_files/flamegraphs.ts b/src/html_files/flamegraphs.ts index 368e242a..f2e69585 100644 --- a/src/html_files/flamegraphs.ts +++ b/src/html_files/flamegraphs.ts @@ -1,4 +1,30 @@ -let got_flamegraphs_data = false; +let got_flamegraphs_data: boolean|string = "none"; + +function getJavaFlamegraphInfo(run, container_id, run_data){ + let data = JSON.parse(run_data['values']); + let sorted = Object.keys(data).sort(function(x,y){ + return data[y][1] - data[x][1]; + }); + + if(sorted.length == 0){ + var h3 = document.createElement('h3'); + h3.innerText = `No data collected.`; + addElemToNode(container_id, h3); + } + + for(let key of sorted){ + let value = data[key][0]; + var h3 = document.createElement('h3'); + h3.style.textAlign = "center"; + h3.innerText = `JVM: ${value}, PID: ${key}`; + addElemToNode(container_id, h3); + var div = document.createElement('iframe'); + div.src = `data/js/${run}-java-flamegraph-${key}.html`; + div.style.width = `100%`; + div.style.height = `100vh`; + addElemToNode(container_id, div); + } +} function getFlamegraphInfo(run, container_id) { var div = document.createElement('iframe'); @@ -8,17 +34,29 @@ function getFlamegraphInfo(run, container_id) { addElemToNode(container_id, div); } -function flamegraphs() { - if (got_flamegraphs_data) { +function flamegraphs(set: boolean|string) { + if (set == got_flamegraphs_data) { return; + } else if (typeof(set) == "boolean") { + set = "flamegraphs"; } + got_flamegraphs_data = set; clear_and_create('flamegraphs'); - for (let i = 0; i < flamegraph_raw_data['runs'].length; i++) { - let run_name = flamegraph_raw_data['runs'][i]['name']; + let raw_data = (set == 'flamegraphs') ? flamegraph_raw_data : java_profile_raw_data; + for (let i = 0; i < raw_data['runs'].length; i++) { + let run_name = raw_data['runs'][i]['name']; let elem_id = `${run_name}-flamegraphs-per-data`; setTimeout(() => { - getFlamegraphInfo(run_name, elem_id); + switch(set){ + case 'flamegraphs': + getFlamegraphInfo(run_name, elem_id); + break; + case 'javaprofile': + getJavaFlamegraphInfo(run_name, elem_id, raw_data['runs'][i]['key_values']); + break; + default: + return; + } }, 0); } - got_flamegraphs_data = true; } diff --git a/src/html_files/index.html b/src/html_files/index.html index 184deb5f..7f1b6593 100644 --- a/src/html_files/index.html +++ b/src/html_files/index.html @@ -33,6 +33,10 @@
+
+ perf + java +
@@ -114,6 +118,7 @@

Hide N/A and all-zero graphs:

+ diff --git a/src/html_files/index.ts b/src/html_files/index.ts index 9bc35775..d5d44a33 100644 --- a/src/html_files/index.ts +++ b/src/html_files/index.ts @@ -15,7 +15,7 @@ DataTypes.set('netstat', {name: 'netstat', hideClass: 'netstatHide', trueId: 'ne 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('flamegraphs', {name: 'flamegraphs', hideClass: '', trueId: '', callback: flamegraphs}); +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}); DataTypes.set('perfstat', {name: 'perfstat', hideClass: '', trueId: '', callback: perfStat}); @@ -58,9 +58,11 @@ for (var i=0; i < elems.length; i++) { // Set Click listener DataTypes.forEach((datatype: DataType, key: string) => { - var elems = document.getElementsByClassName(`${datatype.name}-button`); - for (var j = 0; j < elems.length; j++) { - elems[j].addEventListener("click", function (evn: Event) { + var button_elems = document.getElementsByClassName(`${datatype.name}-button`); + var select_elems = document.getElementsByClassName(`${datatype.name}-select`); + + for (var j = 0; j < button_elems.length; j++) { + button_elems[j].addEventListener("click", function (evn: Event) { if (this.id == datatype.trueId) { datatype.callback(true); } else { @@ -68,6 +70,12 @@ DataTypes.forEach((datatype: DataType, key: string) => { } }) } + + for (var j = 0; j < select_elems.length; j++) { + select_elems[j].addEventListener("click", function (evn: Event) { + datatype.callback(this.id) + }) + } }); var run_width = 100; diff --git a/src/html_files/utils.ts b/src/html_files/utils.ts index 1a7e56a8..97d29989 100644 --- a/src/html_files/utils.ts +++ b/src/html_files/utils.ts @@ -13,6 +13,7 @@ declare let netstat_raw_data; declare let perf_profile_raw_data; declare let flamegraph_raw_data; declare let aperf_run_stats_raw_data; +declare let java_profile_raw_data; let all_run_keys: Array = new Array(); let key_limits: Map = new Map(); diff --git a/src/lib.rs b/src/lib.rs index 1b9e6e5b..a0cd76ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ use serde_json::{self}; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::Mutex; -use std::{fs, time}; +use std::{fs, process, time}; use thiserror::Error; use timerfd::{SetTimeFlags, TimerFd, TimerState}; @@ -88,6 +88,9 @@ pub enum PDError { #[error("Error getting Meminfo values for {}", .0)] VisualizerMeminfoValueGetError(String), + + #[error("Dependency error: {}", .0)] + DependencyError(String), } lazy_static! { @@ -189,19 +192,23 @@ impl PerformanceData { pub fn prepare_data_collectors(&mut self) -> Result<()> { let mut remove_entries: Vec = Vec::new(); - if !self.init_params.profile { - self.collectors - .remove(data::perf_profile::PERF_PROFILE_FILE_NAME); - self.collectors - .remove(data::flamegraphs::FLAMEGRAPHS_FILE_NAME); - } for (name, datatype) in self.collectors.iter_mut() { if datatype.is_static { continue; + } else if datatype.is_profile_option + && !self.init_params.profile.contains_key(name.as_str()) + { + remove_entries.push(name.clone()); + continue; } match datatype.prepare_data_collector() { Err(e) => { + if datatype.is_profile_option { + error!("{}", e.to_string()); + error!("Aperf exiting..."); + process::exit(1); + } error!( "Excluding {} from collection. Error msg: {}", name, @@ -258,6 +265,8 @@ impl PerformanceData { continue; } + datatype.collector_params.elapsed_time = start.elapsed().as_secs(); + aperf_collect_data.measure(name.clone() + "-collect", || -> Result<()> { datatype.collect_data()?; Ok(()) @@ -395,6 +404,9 @@ impl VisualizationData { pub fn get_all_js_files(&mut self) -> Result> { let mut ret = Vec::new(); for (name, visualizer) in self.visualizers.iter() { + if visualizer.js_file_name.is_empty() { + continue; + } let file = self .js_files .get(&visualizer.js_file_name) @@ -461,7 +473,7 @@ pub struct InitParams { pub time_str: String, pub dir_name: String, pub period: u64, - pub profile: bool, + pub profile: HashMap, pub interval: u64, pub run_name: String, pub collector_version: String, @@ -497,7 +509,7 @@ impl InitParams { time_str, dir_name, period: 0, - profile: false, + profile: HashMap::new(), interval: 0, run_name, collector_version, diff --git a/src/record.rs b/src/record.rs index ed468a9c..83e515ce 100644 --- a/src/record.rs +++ b/src/record.rs @@ -1,4 +1,4 @@ -use crate::{InitParams, PERFORMANCE_DATA}; +use crate::{data, InitParams, PERFORMANCE_DATA}; use anyhow::Result; use clap::Args; use log::{debug, error, info}; @@ -17,9 +17,13 @@ pub struct Record { #[clap(short, long, value_parser, default_value_t = 10)] pub period: u64, - /// Gather profiling data using the 'perf' binary. - #[clap(long, value_parser, default_value_t = false)] + /// Gather profiling data using 'perf' binary. + #[clap(long, value_parser)] pub profile: bool, + + /// Profile JVMs using async-profiler. Specify args using comma separated values. Profiles all JVMs if no args are provided. + #[clap(long, value_parser, default_missing_value = Some("jps"), value_names = &["PID/Name>,,...,, } fn prepare_data_collectors() -> Result<()> { @@ -56,9 +60,28 @@ pub fn record(record: &Record) -> Result<()> { } let mut params = InitParams::new(run_name); params.period = record.period; - params.profile = record.profile; params.interval = record.interval; + match &record.profile_java { + Some(j) => { + params.profile.insert( + String::from(data::java_profile::JAVA_PROFILE_FILE_NAME), + j.clone(), + ); + } + None => {} + } + if record.profile { + params.profile.insert( + String::from(data::perf_profile::PERF_PROFILE_FILE_NAME), + String::new(), + ); + params.profile.insert( + String::from(data::flamegraphs::FLAMEGRAPHS_FILE_NAME), + String::new(), + ); + } + PERFORMANCE_DATA.lock().unwrap().set_params(params); PERFORMANCE_DATA.lock().unwrap().init_collectors()?; info!("Starting Data collection..."); diff --git a/tests/test_aperf.rs b/tests/test_aperf.rs index 33c5fb9a..24467b2d 100644 --- a/tests/test_aperf.rs +++ b/tests/test_aperf.rs @@ -32,6 +32,7 @@ fn test_record() { interval: 1, period: 2, profile: false, + profile_java: None, }; record(&rec).unwrap(); @@ -57,6 +58,7 @@ fn test_report() { interval: 1, period: 2, profile: false, + profile_java: None, }; record(&rec).unwrap();