Integrate Ensogl stats with profiling framework (#3388)

Add logging of EnsoGL performance stats to the profiling framework. Also extends the visualization in the debug scene to show an overview of the performance stats. We now render a timeline of blocks that indicate by their colour the rough FPS range we are in:

https://user-images.githubusercontent.com/1428930/162433094-57fbb61a-b502-43bb-8815-b7fc992d3862.mp4

# Important Notes
[ci no changelog needed]

Needs to be merged after https://github.com/enso-org/enso/pull/3382 as it requires some changes about metadata logging from there. That is why this PR is currently still in draft mode and based on that branch.
This commit is contained in:
Michael Mauderer 2022-04-21 11:38:26 +02:00 committed by GitHub
parent fecaa81551
commit e8342b04c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 302 additions and 105 deletions

2
Cargo.lock generated
View File

@ -1198,6 +1198,7 @@ name = "enso-profiler-metadata"
version = "0.1.0"
dependencies = [
"enso-profiler",
"ensogl-core",
"serde",
]
@ -2478,6 +2479,7 @@ version = "0.1.0"
dependencies = [
"enso-prelude",
"enso-profiler",
"enso-profiler-data",
"enso-shapely",
"enso-web",
"failure",

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
ensogl-core = { path = "../../../lib/rust/ensogl/core"}
enso-profiler = { path = "../../../lib/rust/profiler"}
serde = { version = "1" }

View File

@ -10,16 +10,19 @@ use std::fmt::Formatter;
// ================
/// Metadata that is logged within the Enso core libraries.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Metadata {
/// An RPC event that was received from the backend.
RpcEvent(String),
/// Performance stats gathered from the EnsoGL rendering engine.
RenderStats(ensogl_core::debug::StatsData),
}
impl Display for Metadata {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Metadata::RpcEvent(name) => f.collect_str(name),
Metadata::RenderStats(stats) => f.collect_str(&format!("{:#?}", stats)),
}
}
}

View File

@ -33,7 +33,7 @@ use parser::Parser;
// =================
thread_local! {
/// A common preamble used to start every shader program.
/// Profiling metadata logger for RPC event names..
static RPC_EVENT_LOGGER: enso_profiler::MetadataLogger<& 'static str> = enso_profiler::MetadataLogger::new("RpcEvent");
}

View File

@ -198,7 +198,7 @@ commands.build.rust = async function (argv) {
console.log('Minimizing the WASM binary.')
await gzip(paths.wasm.main, paths.wasm.mainGz)
const limitMb = 4.05
const limitMb = 4.06
await checkWasmSize(paths.wasm.mainGz, limitMb)
}
// Copy WASM files from temporary directory to Webpack's `dist` directory.

View File

@ -24,12 +24,14 @@ use ensogl_core::display;
mod block;
mod mark;
use enso_profiler_flame_graph::State;
use enso_profiler_flame_graph::Activity;
use enso_profiler_flame_graph::Performance;
use ensogl_core::data::color;
use ensogl_core::display::shape::StyleWatchFrp;
use mark::Mark;
use ensogl_core::display::style;
pub use block::Block;
pub use mark::Mark;
@ -43,6 +45,61 @@ pub(crate) const BASE_TEXT_SIZE: f32 = 18.0;
// ==============
// === Colors ===
// ==============
/// Theme path for the color of an activity block that is active.
pub const COLOR_BLOCK_ACTIVE: &str = "flame_graph_block_color_active";
/// Theme path for the color of an activity block that is paused.
pub const COLOR_BLOCK_PAUSED: &str = "flame_graph_block_color_paused";
/// Theme path for the color of a performance block that indicates good performance.
pub const COLOR_PERFORMANCE_GOOD: &str = "flame_graph_block_color_performance_good";
/// Theme path for the color of a performance block that indicates medium performance.
pub const COLOR_PERFORMANCE_MEDIUM: &str = "flame_graph_block_color_performance_medium";
/// Theme path for the color of a performance block that indicates bad performance..
pub const COLOR_PERFORMANCE_BAD: &str = "flame_graph_block_color_performance_bad";
/// Theme path for the color that is sued to color a mark.
pub const COLOR_MARK_DEFAULT: &str = "flame_graph_mark_color";
/// Trait that allows retrieval of a style::Path.
pub trait IntoThemePath {
/// Return the `style::Path` associated with this object.
fn theme_path(&self) -> style::Path;
}
impl IntoThemePath for Activity {
fn theme_path(&self) -> style::Path {
match self {
Activity::Active => COLOR_BLOCK_ACTIVE,
Activity::Paused => COLOR_BLOCK_PAUSED,
}
.into()
}
}
impl IntoThemePath for Performance {
fn theme_path(&self) -> style::Path {
match self {
Performance::Good => COLOR_PERFORMANCE_GOOD,
Performance::Medium => COLOR_PERFORMANCE_MEDIUM,
Performance::Bad => COLOR_PERFORMANCE_BAD,
}
.into()
}
}
impl<BlockType: IntoThemePath> IntoThemePath for profiler_flame_graph::Block<BlockType> {
fn theme_path(&self) -> style::Path {
self.block_type.theme_path()
}
}
// ===================
// === Flame Graph ===
// ===================
@ -55,12 +112,22 @@ pub struct FlameGraph {
display_object: display::object::Instance,
blocks: Vec<Block>,
marks: Vec<Mark>,
origin_x: f64,
app: Application,
}
/// Instantiate a `Block` shape for the given block data from the profiler.
fn shape_from_block(block: profiler_flame_graph::Block, app: &Application) -> Block {
pub fn shape_from_block<BlockType: IntoThemePath>(
block: profiler_flame_graph::Block<BlockType>,
app: &Application,
) -> Block {
let component = app.new_view::<Block>();
let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let color: color::Rgba = style.get_color(block.theme_path()).value();
let color: color::Lcha = color.into();
component.set_color(color);
let size = Vector2::new(block.width() as f32, ROW_HEIGHT as f32);
let x = block.start + block.width() / 2.0;
let y = block.row as f64 * (ROW_HEIGHT + ROW_PADDING);
@ -70,15 +137,6 @@ fn shape_from_block(block: profiler_flame_graph::Block, app: &Application) -> Bl
component.set_size.emit(size);
component.set_position_xy(pos);
let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet);
let color: color::Rgba = match block.state {
State::Active => style.get_color("flame_graph_block_color_active").value(),
State::Paused => style.get_color("flame_graph_block_color_paused").value(),
};
let color: color::Lcha = color.into();
component.set_color(color);
component
}
@ -100,41 +158,55 @@ fn shape_from_mark(mark: profiler_flame_graph::Mark, app: &Application) -> Mark
const MIN_INTERVAL_TIME_MS: f64 = 0.0;
const X_SCALE: f64 = 1.0;
fn align_block<BlockType>(
mut block: profiler_flame_graph::Block<BlockType>,
origin_x: f64,
) -> profiler_flame_graph::Block<BlockType> {
// Shift
block.start -= origin_x;
block.end -= origin_x;
// Scale
block.start *= X_SCALE;
block.end *= X_SCALE;
block
}
fn align_mark(mut mark: profiler_flame_graph::Mark, origin_x: f64) -> profiler_flame_graph::Mark {
mark.position -= origin_x;
mark.position *= X_SCALE;
mark
}
impl FlameGraph {
/// Create a `FlameGraph` EnsoGL component from the given graph data from the profiler.
pub fn from_data(data: profiler_flame_graph::Graph, app: &Application) -> Self {
let logger = Logger::new("FlameGraph");
let display_object = display::object::Instance::new(&logger);
let blocks = data.blocks.into_iter().filter(|block| block.width() > MIN_INTERVAL_TIME_MS);
let activity_blocks =
data.activity_blocks.into_iter().filter(|block| block.width() > MIN_INTERVAL_TIME_MS);
let performance_blocks = data.performance_blocks.into_iter();
let marks = data.marks;
let origin_x =
blocks.clone().map(|block| block.start.floor() as u32).min().unwrap_or_default();
let origin_x = activity_blocks
.clone()
.map(|block| block.start.floor() as u32)
.min()
.unwrap_or_default() as f64;
let blocks_zero_aligned = blocks.clone().map(|mut block| {
// Shift
block.start -= origin_x as f64;
block.end -= origin_x as f64;
// Scale
block.start *= X_SCALE;
block.end *= X_SCALE;
block
});
let blocks = blocks_zero_aligned.map(|block| shape_from_block(block, app)).collect_vec();
let activity_block_shapes =
activity_blocks.map(|block| shape_from_block(align_block(block, origin_x), app));
let performance_block_shapes =
performance_blocks.map(|block| shape_from_block(align_block(block, origin_x), app));
let blocks = activity_block_shapes.chain(performance_block_shapes).collect_vec();
blocks.iter().for_each(|item| display_object.add_child(item));
let blocks_marks_aligned = marks.into_iter().map(|mut mark| {
mark.position -= origin_x as f64;
mark.position *= X_SCALE;
mark
});
let marks: Vec<_> =
blocks_marks_aligned.into_iter().map(|mark| shape_from_mark(mark, app)).collect();
let marks: Vec<_> = marks.into_iter().map(|mark| shape_from_mark(mark, app)).collect();
marks.iter().for_each(|item| display_object.add_child(item));
Self { display_object, blocks, marks }
let app = app.clone_ref();
Self { display_object, blocks, marks, origin_x, app }
}
/// Return a reference to the blocks that make up the flame graph.
@ -146,6 +218,25 @@ impl FlameGraph {
pub fn marks(&self) -> &[Mark] {
&self.marks
}
/// Add an additional activity block to the visualisation.
pub fn add_block<BlockType: IntoThemePath>(
&mut self,
block: profiler_flame_graph::Block<BlockType>,
) {
let block = align_block(block, self.origin_x);
let shape = shape_from_block(block, &self.app);
self.display_object.add_child(&shape);
self.blocks.push(shape);
}
/// Add additional mark to the visualisation.
pub fn add_mark(&mut self, mark: profiler_flame_graph::Mark) {
let mark = align_mark(mark, self.origin_x);
let shape = shape_from_mark(mark, &self.app);
self.display_object.add_child(&shape);
self.marks.push(shape);
}
}
impl display::Object for FlameGraph {

View File

@ -158,7 +158,7 @@ macro_rules! gen_stats {
// === StatsData ===
/// Raw data of all the gathered stats.
#[derive(Debug,Default,Clone,Copy)]
#[derive(Debug,Default,Clone,Copy,serde::Serialize,serde::Deserialize)]
#[allow(missing_docs)]
pub struct StatsData {
$(pub $field : $field_type),*

View File

@ -55,6 +55,22 @@ impl Uniforms {
}
// =========================
// === Metadata Profiler ===
// =========================
thread_local! {
/// Profiling metadata logger for `StatsData`.
static RENDER_STATS_LOGGER: enso_profiler::MetadataLogger<StatsData> = enso_profiler::MetadataLogger::new("RenderStats");
}
/// Log rendering stats to the profiling framework.
pub fn log_render_stats(stats: StatsData) {
RENDER_STATS_LOGGER.with(|logger| logger.log(stats));
}
// =============
// === World ===
@ -204,9 +220,9 @@ impl WorldData {
let uniforms = Uniforms::new(&default_scene.variables);
let debug_hotkeys_handle = default();
let garbage_collector = default();
let stats_draw_handle = on.prev_frame_stats.add(f!([stats_monitor] (stats: &StatsData) {
stats_monitor.sample_and_draw(stats);
log_render_stats(*stats)
}));
Self {

View File

@ -11,8 +11,8 @@ crate-type = ["cdylib", "rlib"]
enso-frp = { path = "../../../frp" }
enso-profiler = { path = "../../../profiler" }
enso-profiler-data = { path = "../../../profiler/data" }
enso-profiler-metadata = { path = "../../../../../app/gui/enso-profiler-metadata" }
enso-profiler-flame-graph = { path = "../../../profiler/flame-graph" }
enso-profiler-metadata = { path = "../../../../../app/gui/enso-profiler-metadata" }
enso-web = { path = "../../../web" }
ensogl-core = { path = "../../core" }
ensogl-flame-graph = { path = "../../component/flame-graph" }

View File

@ -21,6 +21,7 @@ use enso_profiler as profiler;
use enso_profiler::profile;
use enso_profiler_data::Profile;
use enso_profiler_flame_graph as profiler_flame_graph;
use enso_profiler_flame_graph::Performance;
use enso_profiler_metadata::Metadata;
use ensogl_core::application::Application;
use ensogl_core::data::color;
@ -30,12 +31,23 @@ use ensogl_core::display::style::theme;
use ensogl_core::display::Scene;
use ensogl_core::system::web;
use ensogl_flame_graph as flame_graph;
use ensogl_flame_graph::COLOR_BLOCK_ACTIVE;
use ensogl_flame_graph::COLOR_BLOCK_PAUSED;
use ensogl_flame_graph::COLOR_MARK_DEFAULT;
use ensogl_flame_graph::COLOR_PERFORMANCE_BAD;
use ensogl_flame_graph::COLOR_PERFORMANCE_GOOD;
use ensogl_flame_graph::COLOR_PERFORMANCE_MEDIUM;
// =================
// === Constants ===
// =================
/// Content of a profiler log, that will be rendered. If this is `None` some dummy data will be
/// generated and rendered. The file must be located in the assets subdirectory that is
/// served by the webserver, i.e. `enso/dist/content` or `app/ide-desktop/lib/content/assets`.
/// For example use `Some("profile.json"))`.
const PROFILER_LOG_NAME: Option<&str> = None;
const PROFILER_LOG_NAME: Option<&str> = Some("profile.json");
@ -61,19 +73,15 @@ pub async fn main() {
let profile =
if let Some(profile) = get_log_data().await { profile } else { create_dummy_data().await };
let mut measurements = profiler_flame_graph::Graph::new_hybrid_graph(&profile);
let measurements = profiler_flame_graph::Graph::new_hybrid_graph(&profile);
let marks = profile
.iter_metadata()
.map(|metadata: &enso_profiler_data::Metadata<Metadata>| {
let position = metadata.mark.into_ms();
let label = metadata.data.to_string();
profiler_flame_graph::Mark { position, label }
})
.collect();
measurements.marks = marks;
let mut flame_graph = flame_graph::FlameGraph::from_data(measurements, app);
let flame_graph = flame_graph::FlameGraph::from_data(measurements, app);
let marks = make_marks_from_profile(&profile);
marks.into_iter().for_each(|mark| flame_graph.add_mark(mark));
let performance_blocks = make_rendering_performance_blocks(&profile);
performance_blocks.into_iter().for_each(|block| flame_graph.add_block(block));
world.add_child(&flame_graph);
scene.add_child(&flame_graph);
@ -97,16 +105,66 @@ fn init_theme(scene: &Scene) {
let theme_manager = theme::Manager::from(&scene.style_sheet);
let theme = theme::Theme::new();
theme.set("flame_graph_block_color_active", color::Lcha::blue_green(0.5, 0.8));
theme.set("flame_graph_block_color_paused", color::Lcha::blue_green(0.8, 0.0));
theme.set("flame_graph_mark_color", color::Lcha::blue_green(0.9, 0.1));
theme.set(COLOR_BLOCK_ACTIVE, color::Lcha::blue_green(0.5, 0.8));
theme.set(COLOR_BLOCK_PAUSED, color::Lcha::blue_green(0.8, 0.0));
theme.set(COLOR_PERFORMANCE_BAD, color::Lcha::red(0.4, 0.5));
theme.set(COLOR_PERFORMANCE_GOOD, color::Lcha::green(0.8, 0.5));
theme.set(COLOR_PERFORMANCE_MEDIUM, color::Lcha::yellow(0.6, 0.5));
theme.set(COLOR_MARK_DEFAULT, color::Lcha::blue_green(0.9, 0.1));
theme_manager.register("theme", theme);
theme_manager.set_enabled(&["theme".to_string()]);
}
let style_watch = ensogl_core::display::shape::StyleWatch::new(&scene.style_sheet);
style_watch.get("flame_graph_color");
// ===========================
// === Metadata Processing ===
// ===========================
// Create marks for metadata. This will skip `RenderStats` as they are added separately as blocks.
fn make_marks_from_profile(profile: &Profile<Metadata>) -> Vec<profiler_flame_graph::Mark> {
profile
.iter_metadata()
.filter_map(|metadata: &enso_profiler_data::Metadata<Metadata>| {
let position = metadata.mark.into_ms();
match metadata.data {
Metadata::RenderStats(_) => None,
_ => {
let label = metadata.data.to_string();
Some(profiler_flame_graph::Mark { position, label })
}
}
})
.collect()
}
fn make_rendering_performance_blocks(
profile: &Profile<Metadata>,
) -> Vec<profiler_flame_graph::Block<Performance>> {
let mut blocks = Vec::default();
let render_stats = profile.iter_metadata().filter_map(|metadata| match metadata.data {
Metadata::RenderStats(data) => {
let mark = metadata.mark;
Some(enso_profiler_data::Metadata { mark, data })
}
_ => None,
});
for (prev, current) in render_stats.tuple_windows() {
let start = prev.mark.into_ms();
let end = current.mark.into_ms();
let row = -1;
let label = format!("{:#?}", current.data);
let block_type = match current.data.fps {
fps if fps > 55.0 => Performance::Good,
fps if fps > 25.0 => Performance::Medium,
_ => Performance::Bad,
};
let block = profiler_flame_graph::Block { start, end, row, label, block_type };
blocks.push(block);
}
blocks
}

View File

@ -11,6 +11,7 @@ crate-type = ["cdylib", "rlib"]
enso-prelude = { path = "../prelude", features = ["futures"]}
enso-shapely = { path = "../shapely"}
enso-web = { path = "../web" }
enso-profiler-data = {path = "../profiler/data"}
enso-profiler = {path = "../profiler"}
futures = { version = "0.3.1" }
failure = { version = "0.1.6" }

View File

@ -32,6 +32,7 @@ pub use api::RemoteMethodCall;
pub use api::Result;
pub use enso_prelude as prelude;
pub use enso_profiler;
pub use enso_profiler_data;
pub use enso_web as ensogl;
pub use error::RpcError;
pub use handler::Event;

View File

@ -6,41 +6,51 @@
#![deny(non_ascii_idents)]
#![warn(unsafe_code)]
use crate::State::Active;
use crate::State::Paused;
use enso_profiler as profiler;
use enso_profiler_data as data;
// =================
// === Constants ===
// =================
type RowNumber = i32;
// ==================
// === Block Data ===
// ==================
/// Indicates whether a block indicates an active interval or a paused interval. Paused intervals
/// are used to represent async tasks that are started and awaited, but that have made no progress.
#[derive(Copy, Clone, Debug)]
pub enum State {
pub enum Activity {
Active,
Paused,
}
/// A `Block` contains the data required to render a single block of a frame graph.
#[derive(Clone, Debug)]
pub struct Block {
/// Start x coordinate of the block.
pub start: f64,
/// End x coordinate of the block.
pub end: f64,
/// Row that the block should be placed in.
pub row: u32,
/// The label to be displayed with the block.
pub label: String,
/// Indicates what state this block represents (active/paused).
pub state: State,
#[derive(Copy, Clone, Debug)]
pub enum Performance {
Good,
Medium,
Bad,
}
impl Block {
/// A `Block` contains the data required to render a single block of a frame graph.
#[derive(Clone, Debug)]
pub struct Block<T> {
/// Start x coordinate of the block.
pub start: f64,
/// End x coordinate of the block.
pub end: f64,
/// Row that the block should be placed in.
pub row: RowNumber,
/// The label to be displayed with the block.
pub label: String,
/// Indicates the type of the block.
pub block_type: T,
}
impl<T> Block<T> {
/// Width of the block.
pub fn width(&self) -> f64 {
self.end - self.start
@ -72,9 +82,11 @@ pub struct Mark {
#[derive(Debug, Default)]
pub struct Graph {
/// Collection of all blocks making up the flame graph.
pub blocks: Vec<Block>,
pub activity_blocks: Vec<Block<Activity>>,
/// Collection of all blocks indicating performance characteristics.
pub performance_blocks: Vec<Block<Performance>>,
/// Collection of marks that can be shown in the flame graph.
pub marks: Vec<Mark>,
pub marks: Vec<Mark>,
}
impl Graph {
@ -115,7 +127,7 @@ impl Graph {
/// Build a graph that illustrates the call stack over time.
struct CallgraphBuilder<'p, Metadata> {
profile: &'p data::Profile<Metadata>,
blocks: Vec<Block>,
blocks: Vec<Block<Activity>>,
}
impl<'p, Metadata> CallgraphBuilder<'p, Metadata> {
@ -128,14 +140,17 @@ impl<'p, Metadata> CallgraphBuilder<'p, Metadata> {
builder.visit_interval(*child, 0);
}
let Self { blocks, .. } = builder;
let marks = Vec::default();
Graph { blocks, marks }
Graph {
activity_blocks: blocks,
marks: Vec::default(),
performance_blocks: Vec::default(),
}
}
}
impl<'p, Metadata> CallgraphBuilder<'p, Metadata> {
/// Create a block for an interval; recurse into children.
fn visit_interval(&mut self, active: data::IntervalId, row: u32) {
fn visit_interval(&mut self, active: data::IntervalId, row: RowNumber) {
let active = &self.profile[active];
let start = active.interval.start.into_ms();
let end = active.interval.end.map(|mark| mark.into_ms()).unwrap_or(f64::MAX);
@ -144,7 +159,7 @@ impl<'p, Metadata> CallgraphBuilder<'p, Metadata> {
return;
}
let label = self.profile[active.measurement].label.to_string();
self.blocks.push(Block { start, end, label, row, state: Active });
self.blocks.push(Block { start, end, label, row, block_type: Activity::Active });
for child in &active.children {
self.visit_interval(*child, row + 1);
}
@ -160,8 +175,8 @@ impl<'p, Metadata> CallgraphBuilder<'p, Metadata> {
/// Build a graph that illustrates async tasks over time.
struct RungraphBuilder<'p, Metadata> {
profile: &'p data::Profile<Metadata>,
blocks: Vec<Block>,
next_row: u32,
blocks: Vec<Block<Activity>>,
next_row: RowNumber,
}
impl<'p, Metadata> RungraphBuilder<'p, Metadata> {
@ -175,8 +190,11 @@ impl<'p, Metadata> RungraphBuilder<'p, Metadata> {
builder.visit_measurement(*child);
}
let Self { blocks, .. } = builder;
let marks = Vec::default();
Graph { blocks, marks }
Graph {
activity_blocks: blocks,
marks: Vec::default(),
performance_blocks: Vec::default(),
}
}
}
@ -211,7 +229,7 @@ impl<'p, Metadata> RungraphBuilder<'p, Metadata> {
end: active_interval[1],
label: label_active,
row,
state: Active,
block_type: Activity::Active,
});
self.blocks.push(Block {
@ -219,7 +237,7 @@ impl<'p, Metadata> RungraphBuilder<'p, Metadata> {
end: sleep_interval[1],
label: label_sleep,
row,
state: Paused,
block_type: Activity::Paused,
});
}
}
@ -232,7 +250,7 @@ impl<'p, Metadata> RungraphBuilder<'p, Metadata> {
end: first.interval.start.into_ms(),
label: self.profile[first.measurement].label.to_string(),
row,
state: Paused,
block_type: Activity::Paused,
});
// Add last active interval.
@ -243,7 +261,7 @@ impl<'p, Metadata> RungraphBuilder<'p, Metadata> {
end: last.interval.end.map(|end| end.into_ms()).unwrap_or(f64::INFINITY),
label: self.profile[last.measurement].label.to_string(),
row,
state: Active,
block_type: Activity::Active,
});
}
@ -271,8 +289,11 @@ fn new_hybrid_graph<Metadata>(profile: &data::Profile<Metadata>) -> Graph {
callgraph.visit_interval(*child, next_row);
}
let CallgraphBuilder { blocks, .. } = callgraph;
let marks = Vec::default();
Graph { blocks, marks }
Graph {
activity_blocks: blocks,
marks: Vec::default(),
performance_blocks: Vec::default(),
}
}
@ -302,23 +323,26 @@ impl From<FlamegraphBuilder> for Graph {
grapher.visit_frame(frame, label.to_string(), 0);
}
let FlamegraphGrapher { blocks, .. } = grapher;
let marks = Vec::default();
Self { blocks, marks }
Graph {
activity_blocks: blocks,
marks: Vec::default(),
performance_blocks: Vec::default(),
}
}
}
/// Builds a flamegraph [`Graph`] from [`data::aggregate::Frame`]s.
#[derive(Default)]
struct FlamegraphGrapher {
blocks: Vec<Block>,
blocks: Vec<Block<Activity>>,
time: f64,
}
impl FlamegraphGrapher {
fn visit_frame(&mut self, frame: &data::aggregate::Frame, label: String, row: u32) {
fn visit_frame(&mut self, frame: &data::aggregate::Frame, label: String, row: RowNumber) {
let start = self.time;
let end = self.time + frame.total_duration();
self.blocks.push(Block { start, end, label, row, state: State::Active });
self.blocks.push(Block { start, end, label, row, block_type: Activity::Active });
for (label, frame) in &frame.children {
self.visit_frame(frame, label.to_string(), row + 1);
}
@ -351,11 +375,11 @@ mod tests {
let profile: data::Profile<data::OpaqueMetadata> =
profiler::internal::take_log().parse().unwrap();
let flame_graph = Graph::new_callgraph(&profile);
assert_eq!(flame_graph.blocks.len(), 2);
assert_eq!(flame_graph.activity_blocks.len(), 2);
assert_eq!(flame_graph.blocks[1].row, 1);
assert!(flame_graph.blocks[1].label.contains("profiled_b"));
assert_eq!(flame_graph.blocks[0].row, 0);
assert!(flame_graph.blocks[0].label.contains("profiled_a"));
assert_eq!(flame_graph.activity_blocks[1].row, 1);
assert!(flame_graph.activity_blocks[1].label.contains("profiled_b"));
assert_eq!(flame_graph.activity_blocks[0].row, 0);
assert!(flame_graph.activity_blocks[0].label.contains("profiled_a"));
}
}