Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions src/analytics/rule_templates/profile_metadata_comparison_rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ impl Analyze for ProfileMetadataComparisonRule {

// Map base values from all profiles in the first record
let base_values: HashMap<String, String> =
if let AperfData::Graph(graph_data) = base_run_data {
graph_data
.profiler_data_map
if let AperfData::Profile(profiling_data) = base_run_data {
profiling_data
.profilers
.iter()
.filter_map(|(key, profiler_data)| {
profiler_data
.filter_map(|(key, profiler)| {
profiler
.metadata
.key_value_groups
.get(self.group)
Expand Down Expand Up @@ -114,15 +114,15 @@ impl Analyze for ProfileMetadataComparisonRule {
continue;
}

let AperfData::Graph(graph_data) = run_data else {
let AperfData::Profile(profiling_data) = run_data else {
continue;
};

let comparison_values: HashMap<String, String> = graph_data
.profiler_data_map
let comparison_values: HashMap<String, String> = profiling_data
.profilers
.iter()
.filter_map(|(key, profiler_data)| {
profiler_data
.filter_map(|(key, profiler)| {
profiler
.metadata
.key_value_groups
.get(self.group)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ impl Analyze for ProfileMetadataExpectedRule {
};

for (run_name, run_data) in &processed_data.runs {
let AperfData::Graph(graph_data) = run_data else {
let AperfData::Profile(profiling_data) = run_data else {
continue;
};
for (key, profiler_data) in &graph_data.profiler_data_map {
let metadata_value = profiler_data
for (key, profiler) in &profiling_data.profilers {
let metadata_value = profiler
.metadata
.key_value_groups
.get(self.group)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use std::fmt::Formatter;
/// threshold - % samples to generate finding
pub struct ProfileStackFrameThresholdRule {
pub rule_name: &'static str,
pub graph_group: &'static str,
pub profile_type: &'static str,
pub stack_frame: &'static [&'static [&'static str]],
pub frame_type: Option<FrameType>,
pub thread_states: &'static [ThreadState],
Expand All @@ -28,7 +28,7 @@ pub struct ProfileStackFrameThresholdRule {
macro_rules! profile_stack_frame_threshold {
{
name: $rule_name:literal,
graph_group: $graph_group:literal,
profile_type: $profile_type:literal,
stack_frame: [$([$($frame:literal),+]),+],
frame_type: $frame_type:expr,
$(thread_states: [$($state:expr),*],)?
Expand All @@ -41,7 +41,7 @@ macro_rules! profile_stack_frame_threshold {
AnalyticalRule::ProfileStackFrameThresholdRule(
ProfileStackFrameThresholdRule {
rule_name: $rule_name,
graph_group: $graph_group,
profile_type: $profile_type,
stack_frame: &[$(&[$($frame),+]),+],
frame_type: $frame_type,
thread_states: &[$($($state),*)?],
Expand Down Expand Up @@ -70,23 +70,23 @@ impl Analyze for ProfileStackFrameThresholdRule {
_processed_data_accessor: &mut ProcessedDataAccessor,
) {
for (run_name, run_data) in &processed_data.runs {
let AperfData::Graph(graph_data) = run_data else {
let AperfData::Profile(profiling_data) = run_data else {
continue;
};

for (key, profiler_data) in &graph_data.profiler_data_map {
if !profiler_data.profiles.contains_key(self.graph_group) {
for (key, profiler) in &profiling_data.profilers {
if !profiler.profiles.contains_key(self.profile_type) {
continue;
}
let total_samples =
profiler_data.get_total_samples(self.graph_group, self.thread_states);
profiler.get_total_samples(self.profile_type, self.thread_states);

let (sample_count, matched_pattern) =
self.stack_frame
.iter()
.fold((0u64, None), |(acc, acc_pat), pattern| {
let count = profiler_data.get_samples(
self.graph_group,
let count = profiler.get_samples(
self.profile_type,
pattern,
self.frame_type,
self.thread_states,
Expand Down Expand Up @@ -120,7 +120,7 @@ impl Analyze for ProfileStackFrameThresholdRule {
};
let finding_description = format!(
"Stack pattern {} accounts for {:.2}% of samples in {} (threshold: {:.2}%)",
pattern_str, percentage, self.graph_group, self.threshold
pattern_str, percentage, self.profile_type, self.threshold
);

report_findings.insert_finding(
Expand Down
2 changes: 1 addition & 1 deletion src/analytics/rules/flamegraphs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ impl AnalyzeData for Flamegraph {
fn get_analytical_rules(&self) -> Vec<crate::analytics::AnalyticalRule> {
vec![profile_stack_frame_threshold! {
name: "Place Holder",
graph_group: "default",
profile_type: "default",
stack_frame: [["place_holder_frame"]],
frame_type: None,
thread_states: [],
Expand Down
2 changes: 1 addition & 1 deletion src/analytics/rules/java_profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl AnalyzeData for JavaProfile {
// If rule for cpu or alloc group, use only ThreadState::AsyncDefault
profile_stack_frame_threshold! {
name: "Excessive Exceptions",
graph_group: "cpu",
profile_type: "cpu",
stack_frame: [[r"java\.lang\.(Throwable\.(fillInStackTrace|getStackTrace|getOurStackTrace)|StackTraceElement\.<init>)|\w+(\.\w+)*\.((\w*Exception|\w*Error|Throwable)\.<init>)"]],
frame_type: None,
thread_states: [ThreadState::AsyncDefault],
Expand Down
77 changes: 23 additions & 54 deletions src/data/common/data_formats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub enum DataFormat {
TimeSeries,
Text,
KeyValue,
Graph,
Profile,
Unknown,
}

Expand Down Expand Up @@ -45,7 +45,7 @@ pub enum AperfData {
TimeSeries(TimeSeriesData),
Text(TextData),
KeyValue(KeyValueData),
Graph(GraphData),
Profile(ProfilingData),
}

impl AperfData {
Expand All @@ -54,7 +54,7 @@ impl AperfData {
AperfData::TimeSeries(_) => DataFormat::TimeSeries,
AperfData::Text(_) => DataFormat::Text,
AperfData::KeyValue(_) => DataFormat::KeyValue,
AperfData::Graph(_) => DataFormat::Graph,
AperfData::Profile(_) => DataFormat::Profile,
}
}
}
Expand Down Expand Up @@ -152,47 +152,6 @@ pub struct KeyValueGroup {
pub key_values: HashMap<String, String>,
}

// ------------------------------------------ GRAPH DATA -------------------------------------------
/// Data types falling into this format produce one or more HTML or SVG files at the end of a
/// recording run, which are to be rendered through IFrame in the report. The graphs can be
/// categorized into different groups, so that only one group of graphs are shown in the report
/// at a time.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct GraphData {
pub graph_groups: Vec<GraphGroup>,
pub profiler_data_map: HashMap<String, ProfilerData>,
}

/// Contents of a graph group, which contains all graphs to be displayed together.
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct GraphGroup {
/// Name of the graph group.
pub group_name: String,
/// A map from graph names to all graphs within the group.
pub graphs: HashMap<String, Graph>,
}

/// Information about a graph.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Graph {
/// The name of the graph.
pub graph_name: String,
/// The relative path to graph (value of the IFrame's src attribute).
pub graph_path: String,
/// The size of the graph, which can be used for graph ordering in the report.
pub graph_size: Option<u64>,
}

impl Graph {
pub fn new(graph_name: String, graph_path: String, graph_size: Option<u64>) -> Self {
Graph {
graph_name,
graph_path,
graph_size,
}
}
}

// ------------------------------------------- TEXT DATA -------------------------------------------
/// Data types falling into this format produce human-readable, formatted, texts, which can be
/// displayed in the report directly.
Expand All @@ -202,14 +161,24 @@ pub struct TextData {
pub lines: Vec<String>,
}

// ---------------------------------------- PROFILER DATA ------------------------------------------
/// Time-bucketed profiling data supporting JFR and perf record sources.
/// Enables per-block sample lookups (for heatmaps and time range selection) and call graph traversal
/// (for self/total sample computation).
///
/// See [`crate::profiling`] for the implementation of profile building and querying.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfilerData {
// ---------------------------------------- PROFILING DATA ------------------------------------------
/// Data types falling into this format collect profiling data from one or more profiled
/// targets (e.g. JVMs for java profiling, and system for perf profiling). Each target is
/// represented by a [`Profiler`] holding metadata and a map of [`Profile`]s keyed by profiling
/// type (e.g. "cpu", "wall", "allocation"). Each [`Profile`] carries:
/// - Time bucketed sample data with total counts for the entire profile
/// - Calling context tree used to analyze a selected time range
/// - A [`ProfileGraph`](crate::profiling::ProfileGraph) pointing to a pre-rendered HTML/SVG
/// file displayed via IFrame in the report (legacy path, to be removed once native
/// rendering is complete).
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ProfilingData {
/// Map from profiler name to its profiler data
pub profilers: HashMap<String, Profiler>,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Profiler {
/// Start time of the profile in milliseconds since epoch
pub start_time_ms: i64,
/// Duration of each block in milliseconds
Expand All @@ -220,9 +189,9 @@ pub struct ProfilerData {
pub profiles: HashMap<String, Profile>,
}

impl ProfilerData {
impl Profiler {
pub fn new(start_time_ms: i64, block_width_ms: u64) -> Self {
ProfilerData {
Profiler {
start_time_ms,
block_width_ms,
metadata: KeyValueData::default(),
Expand Down
6 changes: 3 additions & 3 deletions src/data/common/processed_data_accessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl ProcessedDataAccessor {
DataFormat::TimeSeries => self.time_series_data_json_string(processed_data),
DataFormat::KeyValue => self.key_value_data_json_string(processed_data),
DataFormat::Text => self.text_data_json_string(processed_data),
DataFormat::Graph => self.graph_data_json_string(processed_data),
DataFormat::Profile => self.profiler_data_json_string(processed_data),
DataFormat::Unknown => serde_json::to_string(processed_data).unwrap(),
}
}
Expand Down Expand Up @@ -228,8 +228,8 @@ impl ProcessedDataAccessor {
serde_json::to_string(processed_data).unwrap()
}

/// Serializes the processed graph data into JSON string.
pub fn graph_data_json_string(&mut self, processed_data: &ProcessedData) -> String {
/// Serializes the processed profiler data into JSON string.
pub fn profiler_data_json_string(&mut self, processed_data: &ProcessedData) -> String {
serde_json::to_string(processed_data).unwrap()
}

Expand Down
47 changes: 25 additions & 22 deletions src/data/flamegraphs.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::data::common::data_formats::{AperfData, Graph, GraphData, GraphGroup};
use crate::data::common::data_formats::{AperfData, Profiler, ProfilingData};
use crate::data::{Data, ProcessData};
use crate::profiling::{Profile, ProfileGraph};
use crate::visualizer::ReportParams;
use anyhow::Result;
use log::error;
Expand Down Expand Up @@ -85,7 +86,7 @@ impl CollectData for FlamegraphRaw {
}
Ok(_) => {
info!("Creating flamegraph...");
// TODO: extract metadata from perf record and generate script -> ProfilerData
// TODO: extract metadata from perf record and generate script -> ProfilingData
let script_loc = data_dir.join("script.out");
let out = Command::new("perf")
.stdout(File::create(&script_loc)?)
Expand All @@ -99,7 +100,7 @@ impl CollectData for FlamegraphRaw {
}
Ok(_) => {
let collapse_loc = data_dir.join("collapse.out");
// TODO: move flamegraph generation to report phase using ProfilerData (so user specifies time range)
// TODO: move flamegraph generation to report phase using ProfilingData (so user specifies time range)
Folder::default().collapse_file(
Some(script_loc.clone()),
File::create(&collapse_loc)?,
Expand Down Expand Up @@ -158,50 +159,52 @@ impl ProcessData for Flamegraph {
params: ReportParams,
_raw_data: Vec<Data>,
) -> Result<AperfData> {
fn copy_and_add_to_graph_group(
fn copy_and_add_to_profiler(
params: &ReportParams,
filename: String,
graph_data: &mut GraphData,
graph_group_name: String,
profiling_data: &mut ProfilingData,
profiler_name: String,
profile_name: String,
) {
let source_path = params.data_dir.join(&filename);
let relative_dest_path = PathBuf::from("data/js").join(filename);
let dest_path = params.report_dir.join(relative_dest_path.clone());

if source_path.exists() {
if let Ok(_) = std::fs::copy(&source_path, &dest_path) {
let mut graph_group = GraphGroup::default();
graph_group.group_name = graph_group_name.clone();
graph_group.graphs.insert(
String::new(),
Graph::new(
format!("Kernel Profiling Flamegraph ({graph_group_name})"),
let profiler = profiling_data
.profilers
.entry(profiler_name.clone())
.or_insert_with(Profiler::default);
profiler.profiles.insert(
profile_name.clone(),
Profile::with_graph(ProfileGraph::new(
format!("Kernel Profiling Flamegraph ({profiler_name})"),
relative_dest_path.into_os_string().into_string().unwrap(),
None,
),
)),
);
graph_data.graph_groups.push(graph_group);
}
}
}

let mut graph_data = GraphData::default();
let mut profiling_data = ProfilingData::default();

// TODO: Populate graph_data profiler_data_map from record serialized profiler data

copy_and_add_to_graph_group(
copy_and_add_to_profiler(
&params,
format!("{}-flamegraph.svg", params.run_name),
&mut graph_data,
&mut profiling_data,
String::from("perf"),
String::from("default"),
);
copy_and_add_to_graph_group(
copy_and_add_to_profiler(
&params,
format!("{}-reverse-flamegraph.svg", params.run_name),
&mut graph_data,
&mut profiling_data,
String::from("perf"),
String::from("reverse"),
);

Ok(AperfData::Graph(graph_data))
Ok(AperfData::Profile(profiling_data))
}
}
Loading
Loading