mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 08:52:58 +03:00
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:
parent
fecaa81551
commit
e8342b04c3
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
@ -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" }
|
||||
|
||||
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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),*
|
||||
|
@ -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 {
|
||||
|
@ -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" }
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
@ -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" }
|
||||
|
@ -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;
|
||||
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user