Skip to content

Commit

Permalink
Integrate Ensogl stats with profiling framework (#3388)
Browse files Browse the repository at this point in the history
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 #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.
  • Loading branch information
MichaelMauderer authored Apr 21, 2022
1 parent fecaa81 commit e8342b0
Show file tree
Hide file tree
Showing 13 changed files with 297 additions and 100 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/gui/enso-profiler-metadata/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

5 changes: 4 additions & 1 deletion app/gui/enso-profiler-metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
}
}
}
2 changes: 1 addition & 1 deletion app/gui/src/model/project/synchronized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down
2 changes: 1 addition & 1 deletion build/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
161 changes: 126 additions & 35 deletions lib/rust/ensogl/component/flame-graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;



Expand All @@ -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 ===
// ===================
Expand All @@ -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);
Expand All @@ -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
}

Expand All @@ -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 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();
blocks.iter().for_each(|item| display_object.add_child(item));
let origin_x = activity_blocks
.clone()
.map(|block| block.start.floor() as u32)
.min()
.unwrap_or_default() as f64;

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.
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion lib/rust/ensogl/core/src/debug/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),*
Expand Down
18 changes: 17 additions & 1 deletion lib/rust/ensogl/core/src/display/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ===
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion lib/rust/ensogl/example/profiling-run-graph/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
Loading

0 comments on commit e8342b0

Please sign in to comment.