Backend Communication Profiling (#3382)

This commit is contained in:
Michael Mauderer 2022-04-19 13:30:29 +02:00 committed by GitHub
parent 059bb8c7e9
commit 24e0f33d8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 710 additions and 177 deletions

2
.github/CODEOWNERS vendored
View File

@ -8,7 +8,7 @@ CHANGELOG.md
rust-toolchain.toml @MichaelMauderer @4e6 @mwu-tow @farmaazon
rustfmt.toml @MichaelMauderer @4e6 @mwu-tow @farmaazon
Cargo.lock @MichaelMauderer @4e6 @mwu-tow @farmaazon @wdanilo
Cargo.toml @MichaelMauderer @4e6 @mwu-tow @farmaazon
Cargo.toml @MichaelMauderer @4e6 @mwu-tow @farmaazon @wdanilo
/lib/rust/ @MichaelMauderer @4e6 @mwu-tow @farmaazon @wdanilo
/lib/rust/ensogl/ @MichaelMauderer @wdanilo @farmaazon
/integration-test/ @MichaelMauderer @wdanilo @farmaazon

42
Cargo.lock generated
View File

@ -898,6 +898,8 @@ dependencies = [
"serde",
"serde_json",
"sha3",
"strum",
"strum_macros",
"tokio",
"uuid",
"wasm-bindgen-test",
@ -1191,6 +1193,14 @@ dependencies = [
"syn",
]
[[package]]
name = "enso-profiler-metadata"
version = "0.1.0"
dependencies = [
"enso-profiler",
"serde",
]
[[package]]
name = "enso-shapely"
version = "0.2.0"
@ -1339,6 +1349,7 @@ dependencies = [
"num-traits",
"num_enum",
"rustc-hash",
"serde",
"shrinkwraprs 0.3.0",
"smallvec 1.8.0",
"typenum",
@ -1472,7 +1483,9 @@ version = "0.1.0"
dependencies = [
"enso-frp",
"enso-profiler",
"enso-profiler-data",
"enso-profiler-flame-graph",
"enso-profiler-metadata",
"enso-web",
"ensogl-core",
"ensogl-flame-graph",
@ -1480,7 +1493,10 @@ dependencies = [
"ensogl-text",
"ensogl-text-msdf-sys",
"futures 0.3.21",
"serde",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
@ -2136,6 +2152,12 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.1.19"
@ -2455,6 +2477,7 @@ name = "json-rpc"
version = "0.1.0"
dependencies = [
"enso-prelude",
"enso-profiler",
"enso-shapely",
"enso-web",
"failure",
@ -3927,6 +3950,25 @@ dependencies = [
"wasm-bindgen-test",
]
[[package]]
name = "strum"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8"
[[package]]
name = "strum_macros"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "1.0.86"

View File

@ -5,6 +5,7 @@
# where plausible.
members = [
"app/gui",
"app/gui/enso-profiler-metadata",
"build/enso-formatter",
"build/rust-scripts",
"lib/rust/*",

View File

@ -8,21 +8,23 @@ edition = "2021"
crate-type = ["cdylib", "rlib"]
[dependencies]
chrono = { version = "0.4", features = ["serde"] }
enso-data-structures = { path = "../../../../lib/rust/data-structures" }
enso-logger = { path = "../../../../lib/rust/logger"}
enso-prelude = { path = "../../../../lib/rust/prelude"}
enso-shapely = { path = "../../../../lib/rust/shapely"}
enso-text = { path = "../../../../lib/rust/text"}
json-rpc = { path = "../../../../lib/rust/json-rpc" }
chrono = { version = "0.4", features = ["serde"] }
failure = { version = "0.1.8" }
flatbuffers = { version = "0.5" }
futures = { version = "0.3.1" }
mockall = { version = "0.7.1", features = ["nightly"] }
hex = { version = "0.4.2" }
json-rpc = { path = "../../../../lib/rust/json-rpc" }
mockall = { version = "0.7.1", features = ["nightly"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
sha3 = { version = "0.8.2" }
strum = "0.24.0"
strum_macros = "0.24.0"
uuid = { version = "0.8", features = ["serde", "v4", "wasm-bindgen"] }
[dev-dependencies]

View File

@ -2,6 +2,7 @@
use crate::language_server::*;
use strum_macros::IntoStaticStr;
// =============
@ -105,7 +106,7 @@ impl Path {
// ====================
/// Notification generated by the Language Server.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, IntoStaticStr)]
#[serde(tag = "method", content = "params")]
pub enum Notification {
/// Filesystem event occurred for a watched path.

View File

@ -0,0 +1,9 @@
[package]
name = "enso-profiler-metadata"
version = "0.1.0"
edition = "2021"
[dependencies]
enso-profiler = { path = "../../../lib/rust/profiler"}
serde = { version = "1" }

View File

@ -0,0 +1,25 @@
//! Enso specific metadata logging utilities for use with the profiling framework.
use serde::Serializer;
use std::fmt::Display;
use std::fmt::Formatter;
// ================
// === Metadata ===
// ================
/// Metadata that is logged within the Enso core libraries.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
pub enum Metadata {
/// An RPC event that was received from the backend.
RpcEvent(String),
}
impl Display for Metadata {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Metadata::RpcEvent(name) => f.collect_str(name),
}
}
}

View File

@ -95,7 +95,7 @@ pub mod prelude {
pub use crate::model;
pub use crate::model::traits::*;
pub use enso_profiler as profiler;
pub use enso_profiler;
pub use enso_profiler::prelude::*;
pub use engine_protocol::prelude::BoxFuture;

View File

@ -28,6 +28,22 @@ use parser::Parser;
// =================
// === Profiling ===
// =================
thread_local! {
/// A common preamble used to start every shader program.
static RPC_EVENT_LOGGER: enso_profiler::MetadataLogger<& 'static str> = enso_profiler::MetadataLogger::new("RpcEvent");
}
/// Log an RPC Event to the profiling framework.
pub fn log_rpc_event(event_name: &'static str) {
RPC_EVENT_LOGGER.with(|logger| logger.log(event_name));
}
// =================================
// === ExecutionContextsRegistry ===
// =================================
@ -457,6 +473,14 @@ impl Project {
debug!(logger, "Received an event from the json-rpc protocol: {event:?}");
use engine_protocol::language_server::Event;
use engine_protocol::language_server::Notification;
// Profiler logging
if let Event::Notification(notification) = &event {
let name: &'static str = notification.into();
log_rpc_event(name);
}
// Event Handling
match event {
Event::Notification(Notification::FileEvent(_)) => {}
Event::Notification(Notification::ExpressionUpdates(updates)) => {

View File

@ -10,6 +10,7 @@ use ensogl::prelude::*;
use crate::GraphEditorModelWithNetwork;
use crate::NodeId;
use crate::application::command::FrpNetworkProvider;
use enso_frp as frp;
use ensogl::animation::easing::EndStatus::Normal;
use ensogl::display::Scene;
@ -17,7 +18,6 @@ use ensogl::Animation;
use ensogl::Easing;
/// Describes the "speed" of growth/shrink animation.
///
/// To determine the duration of the blending animation, we divide the length of the camera path by
@ -37,7 +37,7 @@ pub fn initialize_edited_node_animator(
frp: &crate::Frp,
scene: &Scene,
) {
let network = &frp.network;
let network = &frp.network();
let out = &frp.output;
let searcher_cam = scene.layers.node_searcher.camera();
let edited_node_cam = scene.layers.edited_node.camera();

View File

@ -50,6 +50,7 @@ use crate::component::visualization::MockDataGenerator3D;
use crate::data::enso;
pub use crate::node::profiling::Status as NodeProfilingStatus;
use crate::application::command::FrpNetworkProvider;
use enso_config::ARGS;
use enso_frp as frp;
use ensogl::application;
@ -72,7 +73,6 @@ use ensogl::DEPRECATED_Tween;
use ensogl_hardcoded_theme as theme;
// ===============
// === Prelude ===
// ===============
@ -424,7 +424,7 @@ pub struct NodeSource {
pub node: NodeId,
}
ensogl::define_endpoints! {
ensogl::define_endpoints_2! {
Input {
// === General ===
/// Cancel the operation being currently performed. Often mapped to the escape key.
@ -711,7 +711,7 @@ ensogl::define_endpoints! {
}
}
impl application::command::FrpNetworkProvider for GraphEditor {
impl FrpNetworkProvider for GraphEditor {
fn network(&self) -> &frp::Network {
&self.model.network
}
@ -1337,7 +1337,7 @@ impl Deref for GraphEditorModelWithNetwork {
impl GraphEditorModelWithNetwork {
/// Constructor.
pub fn new(app: &Application, cursor: cursor::Cursor, frp: &Frp) -> Self {
let network = frp.network.clone_ref(); // FIXME make weak
let network = frp.network().clone_ref(); // FIXME make weak
let model = GraphEditorModel::new(app, cursor, frp);
Self { model, network }
}
@ -1391,7 +1391,7 @@ impl GraphEditorModelWithNetwork {
let first_detached = self.edges.detached_target.is_empty();
self.edges.detached_target.insert(edge_id);
if first_detached {
self.frp.source.on_some_edges_targets_unset.emit(());
self.frp.private.output.on_some_edges_targets_unset.emit(());
}
edge_id
}
@ -1406,7 +1406,7 @@ impl GraphEditorModelWithNetwork {
let first_detached = self.edges.detached_source.is_empty();
self.edges.detached_source.insert(edge_id);
if first_detached {
self.frp.source.on_some_edges_sources_unset.emit(());
self.frp.private.output.on_some_edges_sources_unset.emit(());
}
edge_id
}
@ -1444,7 +1444,7 @@ struct NodeCreationContext<'a> {
tooltip_update: &'a frp::Any<tooltip::Style>,
output_press: &'a frp::Source<EdgeEndpoint>,
input_press: &'a frp::Source<EdgeEndpoint>,
output: &'a FrpEndpoints,
output: &'a api::private::Output,
}
impl GraphEditorModelWithNetwork {
@ -1477,7 +1477,6 @@ impl GraphEditorModelWithNetwork {
}
fn new_node(&self, ctx: &NodeCreationContext) -> Node {
use ensogl::application::command::FrpNetworkProvider;
let view = component::Node::new(&self.app, self.vis_registry.clone_ref());
let node = Node::new(view);
let node_model = node.model();
@ -1499,10 +1498,10 @@ impl GraphEditorModelWithNetwork {
eval_ node.background_press(touch.nodes.down.emit(node_id));
hovered <- node.output.hover.map (move |t| Some(Switch::new(node_id,*t)));
output.source.node_hovered <+ hovered;
output.node_hovered <+ hovered;
eval node.comment ([model](comment)
model.frp.source.node_comment_set.emit((node_id,comment.clone()))
model.frp.private.output.node_comment_set.emit((node_id,comment.clone()))
);
node.set_output_expression_visibility <+ self.frp.nodes_labels_visible;
@ -1523,12 +1522,12 @@ impl GraphEditorModelWithNetwork {
eval node_model.input.frp.on_port_hover ([model](t) {
let crumbs = t.on();
let target = crumbs.map(|c| EdgeEndpoint::new(node_id,c.clone()));
model.frp.source.hover_node_input.emit(target);
model.frp.private.output.hover_node_input.emit(target);
});
eval node_model.output.frp.on_port_hover ([model](hover) {
let output = hover.on().map(|crumbs| EdgeEndpoint::new(node_id,crumbs.clone()));
model.frp.source.hover_node_output.emit(output);
model.frp.private.output.hover_node_output.emit(output);
});
let neutral_color = model.styles_frp.get_color(theme::code::types::any::selection);
@ -1547,18 +1546,18 @@ impl GraphEditorModelWithNetwork {
)
));
eval node.expression((t) output.source.node_expression_set.emit((node_id,t.into())));
eval node.expression((t) model.frp.private.output.node_expression_set.emit((node_id,t.into())));
// === Actions ===
eval node.view.freeze ((is_frozen) {
output.source.node_action_freeze.emit((node_id,*is_frozen));
model.frp.private.output.node_action_freeze.emit((node_id,*is_frozen));
});
let set_node_disabled = &node.set_disabled;
eval node.view.skip ([set_node_disabled,output](is_skipped) {
output.source.node_action_skip.emit((node_id,*is_skipped));
eval node.view.skip ([set_node_disabled,model](is_skipped) {
model.frp.private.output.node_action_skip.emit((node_id,*is_skipped));
set_node_disabled.emit(is_skipped);
});
@ -1572,11 +1571,11 @@ impl GraphEditorModelWithNetwork {
selected <- vis_is_selected.on_true();
deselected <- vis_is_selected.on_false();
output.source.visualization_preprocessor_changed <+
output.visualization_preprocessor_changed <+
node_model.visualization.frp.preprocessor.map(move |preprocessor|
(node_id,preprocessor.clone()));
output.source.on_visualization_select <+ selected.constant(Switch::On(node_id));
output.source.on_visualization_select <+ deselected.constant(Switch::Off(node_id));
output.on_visualization_select <+ selected.constant(Switch::On(node_id));
output.on_visualization_select <+ deselected.constant(Switch::Off(node_id));
metadata <- any(...);
metadata <+ node_model.visualization.frp.preprocessor.map(visualization::Metadata::new);
@ -1586,8 +1585,8 @@ impl GraphEditorModelWithNetwork {
// new one has been enabled.
// TODO: Create a better API for updating the controller about visualisation changes
// (see #896)
output.source.visualization_hidden <+ visualization_hidden.constant(node_id);
output.source.visualization_shown <+
output.visualization_hidden <+ visualization_hidden.constant(node_id);
output.visualization_shown <+
visualization_shown.map2(&metadata,move |_,metadata| (node_id,metadata.clone()));
@ -1596,7 +1595,7 @@ impl GraphEditorModelWithNetwork {
&node.visualization_enabled, &node.visualization_path,
move |_init, is_enabled, path| (node_id, is_enabled.and_option(path.clone()))
);
output.source.enabled_visualization_path <+ enabled_visualization_path;
output.enabled_visualization_path <+ enabled_visualization_path;
// === View Mode ===
@ -1651,7 +1650,7 @@ pub struct GraphEditorModel {
tooltip: Tooltip,
touch_state: TouchState,
visualisations: Visualisations,
frp: FrpEndpoints,
frp: Frp,
profiling_statuses: profiling::Statuses,
profiling_button: component::profiling::Button,
styles_frp: StyleWatchFrp,
@ -1664,7 +1663,7 @@ pub struct GraphEditorModel {
impl GraphEditorModel {
#[allow(missing_docs)] // FIXME[everyone] All pub functions should have docs.
pub fn new(app: &Application, cursor: cursor::Cursor, frp: &Frp) -> Self {
let network = &frp.network;
let network = frp.network();
let scene = &app.display.default_scene;
let logger = Logger::new("GraphEditor");
let display_object = display::object::Instance::new(&logger);
@ -1675,7 +1674,7 @@ impl GraphEditorModel {
let touch_state = TouchState::new(network, &scene.mouse.frp);
let breadcrumbs = component::Breadcrumbs::new(app.clone_ref());
let app = app.clone_ref();
let frp = frp.output.clone_ref();
let frp = frp.clone_ref();
let navigator = Navigator::new(scene, &scene.camera());
let tooltip = Tooltip::new(&app);
let profiling_statuses = profiling::Statuses::new();
@ -1840,7 +1839,7 @@ impl GraphEditorModel {
let node_id = node_id.into();
self.nodes.remove(&node_id);
self.nodes.selected.remove_item(&node_id);
self.frp.source.on_visualization_select.emit(Switch::Off(node_id));
self.frp.private.output.on_visualization_select.emit(Switch::Off(node_id));
}
fn node_in_edges(&self, node_id: impl Into<NodeId>) -> Vec<EdgeId> {
@ -1925,7 +1924,7 @@ impl GraphEditorModel {
self.refresh_edge_position(edge_id);
self.refresh_edge_source_size(edge_id);
if first_detached {
self.frp.source.on_some_edges_sources_unset.emit(());
self.frp.private.output.on_some_edges_sources_unset.emit(());
}
}
}
@ -1942,7 +1941,7 @@ impl GraphEditorModel {
self.edges.detached_target.remove(&edge_id);
let all_attached = self.edges.detached_target.is_empty();
if all_attached {
self.frp.source.on_all_edges_targets_set.emit(());
self.frp.private.output.on_all_edges_targets_set.emit(());
}
edge.view.frp.target_attached.emit(true);
@ -1962,7 +1961,7 @@ impl GraphEditorModel {
edge.view.frp.target_attached.emit(false);
self.refresh_edge_position(edge_id);
if first_detached {
self.frp.source.on_some_edges_targets_unset.emit(());
self.frp.private.output.on_some_edges_targets_unset.emit(());
}
};
}
@ -2003,10 +2002,10 @@ impl GraphEditorModel {
let no_detached_sources = self.edges.detached_source.is_empty();
let no_detached_targets = self.edges.detached_target.is_empty();
if no_detached_targets {
self.frp.source.on_all_edges_targets_set.emit(());
self.frp.private.output.on_all_edges_targets_set.emit(());
}
if no_detached_sources {
self.frp.source.on_all_edges_sources_set.emit(());
self.frp.private.output.on_all_edges_sources_set.emit(());
}
}
@ -2398,9 +2397,9 @@ impl GraphEditor {
}
impl Deref for GraphEditor {
type Target = Frp;
type Target = api::Public;
fn deref(&self) -> &Self::Target {
&self.frp
&self.frp.public
}
}
@ -2504,7 +2503,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
let cursor = &app.cursor;
let frp = Frp::new();
let model = GraphEditorModelWithNetwork::new(app, cursor.clone_ref(), &frp);
let network = &frp.network;
let network = frp.network();
let nodes = &model.nodes;
let edges = &model.edges;
let inputs = &model.frp;
@ -2512,7 +2511,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
let touch = &model.touch_state;
let vis_registry = &model.vis_registry;
let logger = &model.logger;
let out = &frp.output;
let out = &frp.private.output;
let selection_controller = &model.selection_controller;
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
@ -2538,7 +2537,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
eval_ disable_navigator ( model.navigator.disable() );
eval_ enable_navigator ( model.navigator.enable() );
out.source.navigator_active <+ inputs.set_navigator_disabled
out.navigator_active <+ inputs.set_navigator_disabled
|| out.some_visualisation_selected;
}
@ -2576,7 +2575,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
// Go level up on background click.
enter_on_background <= target_to_enter.map(|target| target.is_background().as_some(()));
out.source.node_exited <+ enter_on_background;
out.node_exited <+ enter_on_background;
// Go level down on node double click.
enter_on_node <= target_to_enter.map(|target| target.is_symbol().as_some(()));
@ -2584,7 +2583,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
enter_node <- enter_on_node.gate_not(&output_port_is_hovered);
node_switch_to_enter <- out.node_hovered.sample(&enter_node).unwrap();
node_to_enter <- node_switch_to_enter.map(|switch| switch.on().cloned()).unwrap();
out.source.node_entered <+ node_to_enter;
out.node_entered <+ node_to_enter;
}
@ -2648,7 +2647,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
let node_input_touch = TouchNetwork::<EdgeEndpoint>::new(network,mouse);
let node_output_touch = TouchNetwork::<EdgeEndpoint>::new(network,mouse);
node_expression_set <- source();
out.source.node_expression_set <+ node_expression_set;
out.node_expression_set <+ node_expression_set;
on_output_connect_drag_mode <- node_output_touch.down.constant(true);
on_output_connect_follow_mode <- node_output_touch.selected.constant(false);
@ -2661,7 +2660,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
on_detached_edge <- any(&inputs.on_some_edges_targets_unset,&inputs.on_some_edges_sources_unset);
has_detached_edge <- bool(&out.on_all_edges_endpoints_set,&on_detached_edge);
out.source.has_detached_edge <+ has_detached_edge;
out.has_detached_edge <+ has_detached_edge;
eval node_input_touch.down ((target) model.frp.press_node_input.emit(target));
eval node_output_touch.down ((target) model.frp.press_node_output.emit(target));
@ -2716,8 +2715,8 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
on_edge_source_unset <= edge_source_click.map(f!(((id,_)) model.with_edge_source(*id,|t|(*id,t))));
on_edge_target_unset <= edge_target_click.map(f!(((id,_)) model.with_edge_target(*id,|t|(*id,t))));
out.source.on_edge_source_unset <+ on_edge_source_unset;
out.source.on_edge_target_unset <+ on_edge_target_unset;
out.on_edge_source_unset <+ on_edge_source_unset;
out.on_edge_target_unset <+ on_edge_target_unset;
}
@ -2755,13 +2754,13 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
Some(model.new_edge_from_input(&edge_mouse_down,&edge_over,&edge_out))
})).unwrap();
out.source.on_edge_add <+ new_output_edge;
out.on_edge_add <+ new_output_edge;
new_edge_source <- new_output_edge.map2(&node_output_touch.down, move |id,target| (*id,target.clone()));
out.source.on_edge_source_set <+ new_edge_source;
out.on_edge_source_set <+ new_edge_source;
out.source.on_edge_add <+ new_input_edge;
out.on_edge_add <+ new_input_edge;
new_edge_target <- new_input_edge.map2(&node_input_touch.down, move |id,target| (*id,target.clone()));
out.source.on_edge_target_set <+ new_edge_target;
out.on_edge_target_set <+ new_edge_target;
}
@ -2775,36 +2774,36 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
clicked_to_drop_edge <- was_edge_detached_when_background_selected.on_true();
clicked_to_abort_edit <- was_edge_detached_when_background_selected.on_false();
out.source.on_edge_source_set <+ inputs.set_edge_source;
out.source.on_edge_target_set <+ inputs.set_edge_target;
out.on_edge_source_set <+ inputs.set_edge_source;
out.on_edge_target_set <+ inputs.set_edge_target;
let endpoints = inputs.connect_nodes.clone_ref();
edge <- endpoints . map(f_!(model.new_edge_from_output(&edge_mouse_down,&edge_over,&edge_out)));
new_edge_source <- endpoints . _0() . map2(&edge, |t,id| (*id,t.clone()));
new_edge_target <- endpoints . _1() . map2(&edge, |t,id| (*id,t.clone()));
out.source.on_edge_add <+ edge;
out.source.on_edge_source_set <+ new_edge_source;
out.source.on_edge_target_set <+ new_edge_target;
out.on_edge_add <+ edge;
out.on_edge_source_set <+ new_edge_source;
out.on_edge_target_set <+ new_edge_target;
detached_edges_without_targets <= attach_all_edge_inputs.map(f_!(model.take_edges_with_detached_targets()));
detached_edges_without_sources <= attach_all_edge_outputs.map(f_!(model.take_edges_with_detached_sources()));
new_edge_target <- detached_edges_without_targets.map2(&attach_all_edge_inputs, |id,t| (*id,t.clone()));
out.source.on_edge_target_set <+ new_edge_target;
out.on_edge_target_set <+ new_edge_target;
new_edge_source <- detached_edges_without_sources.map2(&attach_all_edge_outputs, |id,t| (*id,t.clone()));
out.source.on_edge_source_set <+ new_edge_source;
out.on_edge_source_set <+ new_edge_source;
on_new_edge_source <- new_edge_source.constant(());
on_new_edge_target <- new_edge_target.constant(());
overlapping_edges <= out.on_edge_target_set._1().map(f!((t) model.overlapping_edges(t)));
out.source.on_edge_drop <+ overlapping_edges;
out.on_edge_drop <+ overlapping_edges;
drop_on_bg_up <- background_up.gate(&connect_drag_mode);
drop_edges <- any (drop_on_bg_up,clicked_to_drop_edge);
edge_dropped_to_create_node <= drop_edges.map(f_!(model.edges_with_detached_targets()));
out.source.on_edge_drop_to_create_node <+ edge_dropped_to_create_node;
out.on_edge_drop_to_create_node <+ edge_dropped_to_create_node;
remove_all_detached_edges <- any (drop_edges, inputs.drop_dragged_edge);
edge_to_remove_without_targets <= remove_all_detached_edges.map(f_!(model.take_edges_with_detached_targets()));
@ -2824,7 +2823,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
|v| v.clone());
removed_edges_on_node_creation_from_port <= start_node_creation_from_port.map(f_!(
model.model.clear_all_detached_edges()));
out.source.on_edge_drop <+ removed_edges_on_node_creation_from_port;
out.on_edge_drop <+ removed_edges_on_node_creation_from_port;
input_add_node_way <- inputs.add_node.constant(WayOfCreatingNode::AddNodeEvent);
input_start_creation_way <- inputs.start_node_creation.constant(
@ -2852,7 +2851,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
};
model.create_node(&ctx, way, *mouse_pos)
}));
out.source.node_added <+ new_node.map(|&(id, src, should_edit)| (id, src, should_edit));
out.node_added <+ new_node.map(|&(id, src, should_edit)| (id, src, should_edit));
node_to_edit_after_adding <- new_node.filter_map(|&(id,_,cond)| cond.as_some(id));
}
@ -2870,16 +2869,16 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
node_being_edited <- out.node_being_edited.map(|n| n.unwrap_or_default());
// The "finish" events must be emitted before "start", to properly cover the "switch" case.
out.source.node_editing_finished <+ node_being_edited.sample(&stop_edit);
out.source.node_editing_finished <+ node_being_edited.sample(&edit_switch);
out.source.node_editing_started <+ edit_node;
out.node_editing_finished <+ node_being_edited.sample(&stop_edit);
out.node_editing_finished <+ node_being_edited.sample(&edit_switch);
out.node_editing_started <+ edit_node;
out.source.node_being_edited <+ out.node_editing_started.map(|n| Some(*n));;
out.source.node_being_edited <+ out.node_editing_finished.constant(None);
out.source.node_editing <+ out.node_being_edited.map(|t|t.is_some());
out.node_being_edited <+ out.node_editing_started.map(|n| Some(*n));;
out.node_being_edited <+ out.node_editing_finished.constant(None);
out.node_editing <+ out.node_being_edited.map(|t|t.is_some());
out.source.node_edit_mode <+ edit_mode;
out.source.nodes_labels_visible <+ out.node_edit_mode || node_in_edit_mode;
out.node_edit_mode <+ edit_mode;
out.nodes_labels_visible <+ out.node_edit_mode || node_in_edit_mode;
eval out.node_editing_started ([model] (id) {
if let Some(node) = model.nodes.get_cloned_ref(id) {
@ -2950,7 +2949,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
nodes_to_remove <- any (all_nodes, selected_nodes);
eval nodes_to_remove ((node_id) inputs.remove_all_node_edges.emit(node_id));
out.source.node_removed <+ nodes_to_remove;
out.node_removed <+ nodes_to_remove;
}
@ -2964,7 +2963,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
nodes_to_collapse <- inputs.collapse_selected_nodes . map(move |_|
(model_clone.nodes.all_selected(),empty_id)
);
out.source.nodes_collapsed <+ nodes_to_collapse;
out.nodes_collapsed <+ nodes_to_collapse;
}
@ -2972,7 +2971,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
frp::extend! { network
set_node_expression_string <- inputs.set_node_expression.map(|(id,expr)| (*id,expr.code.clone()));
out.source.node_expression_set <+ set_node_expression_string;
out.node_expression_set <+ set_node_expression_string;
}
@ -3081,7 +3080,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
main_tgt_pos_diff <- node_tgt_pos.map2(&main_tgt_pos_prev,|t,s|t-s).gate_not(&just_pressed);
drag_tgt <= drag_tgts.sample(&main_tgt_pos_diff);
tgt_new_pos <- drag_tgt.map2(&main_tgt_pos_diff,f!((id,tx) model.node_pos_mod(id,*tx)));
out.source.node_position_set <+ tgt_new_pos;
out.node_position_set <+ tgt_new_pos;
// === Batch Update ===
@ -3089,7 +3088,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
after_drag <- touch.nodes.up.gate_not(&just_pressed);
tgt_after_drag <= drag_tgts.sample(&after_drag);
tgt_after_drag_new_pos <- tgt_after_drag.map(f!([model](id)(*id,model.node_position(id))));
out.source.node_position_set_batched <+ tgt_after_drag_new_pos;
out.node_position_set_batched <+ tgt_after_drag_new_pos;
// === Mouse style ===
@ -3102,8 +3101,8 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
// === Set Node Position ===
out.source.node_position_set <+ inputs.set_node_position;
out.source.node_position_set_batched <+ inputs.set_node_position;
out.node_position_set <+ inputs.set_node_position;
out.node_position_set_batched <+ inputs.set_node_position;
eval out.node_position_set (((id,pos)) model.set_node_position(id,*pos));
}
@ -3221,7 +3220,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
}
});
out.source.some_visualisation_selected <+ out.on_visualization_select.map(f_!([model] {
out.some_visualisation_selected <+ out.on_visualization_select.map(f_!([model] {
!model.visualisations.selected.is_empty()
}));
};
@ -3312,10 +3311,10 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
}
});
out.source.visualization_fullscreen <+ viz_fullscreen_on.map(|id| Some(*id));
out.source.visualization_fullscreen <+ inputs.close_fullscreen_visualization.constant(None);
out.visualization_fullscreen <+ viz_fullscreen_on.map(|id| Some(*id));
out.visualization_fullscreen <+ inputs.close_fullscreen_visualization.constant(None);
out.source.is_fs_visualization_displayed <+ out.visualization_fullscreen.map(Option::is_some);
out.is_fs_visualization_displayed <+ out.visualization_fullscreen.map(Option::is_some);
// === Register Visualization ===
@ -3329,17 +3328,17 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
vis_registry.remove_all_visualizations();
vis_registry.add_default_visualizations();
});
out.source.visualization_registry_reload_requested <+ inputs.reload_visualization_registry;
out.visualization_registry_reload_requested <+ inputs.reload_visualization_registry;
// === Entering and Exiting Nodes ===
node_to_enter <= inputs.enter_selected_node.map(f_!(model.nodes.last_selected()));
out.source.node_entered <+ node_to_enter;
out.node_entered <+ node_to_enter;
removed_edges_on_enter <= out.node_entered.map(f_!(model.model.clear_all_detached_edges()));
out.source.node_exited <+ inputs.exit_node;
out.node_exited <+ inputs.exit_node;
removed_edges_on_exit <= out.node_exited.map(f_!(model.model.clear_all_detached_edges()));
out.source.on_edge_drop <+ any(removed_edges_on_enter,removed_edges_on_exit);
out.on_edge_drop <+ any(removed_edges_on_enter,removed_edges_on_exit);
@ -3370,15 +3369,15 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
is_only_tgt_not_set <-
out.on_edge_source_set.map(f!(((id,_)) model.with_edge_map_target(*id,|_|()).is_none()));
out.source.on_edge_source_set_with_target_not_set <+ out.on_edge_source_set.gate(&is_only_tgt_not_set);
out.source.on_edge_only_target_not_set <+ out.on_edge_source_set_with_target_not_set._0();
out.source.on_edge_only_target_not_set <+ out.on_edge_target_unset._0();
out.on_edge_source_set_with_target_not_set <+ out.on_edge_source_set.gate(&is_only_tgt_not_set);
out.on_edge_only_target_not_set <+ out.on_edge_source_set_with_target_not_set._0();
out.on_edge_only_target_not_set <+ out.on_edge_target_unset._0();
is_only_src_not_set <-
out.on_edge_target_set.map(f!(((id,_)) model.with_edge_map_source(*id,|_|()).is_none()));
out.source.on_edge_target_set_with_source_not_set <+ out.on_edge_target_set.gate(&is_only_src_not_set);
out.source.on_edge_only_source_not_set <+ out.on_edge_target_set_with_source_not_set._0();
out.source.on_edge_only_source_not_set <+ out.on_edge_source_unset._0();
out.on_edge_target_set_with_source_not_set <+ out.on_edge_target_set.gate(&is_only_src_not_set);
out.on_edge_only_source_not_set <+ out.on_edge_target_set_with_source_not_set._0();
out.on_edge_only_source_not_set <+ out.on_edge_source_unset._0();
let neutral_color = model.model.styles_frp.get_color(theme::code::types::any::selection);
eval out.on_edge_source_set ([model,neutral_color]((id, _))
@ -3399,10 +3398,10 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
some_edge_sources_unset <- out.on_all_edges_sources_set ?? out.on_some_edges_sources_unset;
some_edge_targets_unset <- out.on_all_edges_targets_set ?? out.on_some_edges_targets_unset;
some_edge_endpoints_unset <- out.some_edge_targets_unset || out.some_edge_sources_unset;
out.source.some_edge_sources_unset <+ some_edge_sources_unset;
out.source.some_edge_targets_unset <+ some_edge_targets_unset;
out.source.some_edge_endpoints_unset <+ some_edge_endpoints_unset;
out.source.on_all_edges_endpoints_set <+ out.some_edge_endpoints_unset.on_false();
out.some_edge_sources_unset <+ some_edge_sources_unset;
out.some_edge_targets_unset <+ some_edge_targets_unset;
out.some_edge_endpoints_unset <+ some_edge_endpoints_unset;
out.on_all_edges_endpoints_set <+ out.some_edge_endpoints_unset.on_false();
// === Endpoints ===
@ -3413,10 +3412,10 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
edge_endpoint_set <- any(out.on_edge_source_set,out.on_edge_target_set)._0();
both_endpoints_set <- edge_endpoint_set.map(f!((id) model.is_connection(id)));
new_edge_with_both_endpoints_set <- edge_endpoint_set.gate(&both_endpoints_set);
out.source.on_edge_endpoints_set <+ new_edge_with_both_endpoints_set;
out.source.on_edge_endpoint_set <+ any(out.on_edge_source_set,out.on_edge_target_set);
out.source.on_edge_endpoint_unset <+ any(out.on_edge_source_unset,out.on_edge_target_unset);
out.source.on_edge_endpoint_unset <+ any(edge_source_drop,edge_target_drop);
out.on_edge_endpoints_set <+ new_edge_with_both_endpoints_set;
out.on_edge_endpoint_set <+ any(out.on_edge_source_set,out.on_edge_target_set);
out.on_edge_endpoint_unset <+ any(out.on_edge_source_unset,out.on_edge_target_unset);
out.on_edge_endpoint_unset <+ any(edge_source_drop,edge_target_drop);
// === Drop ===
@ -3433,14 +3432,14 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
eval out.node_deselected ((id) model.nodes.deselect(id));
eval out.node_removed ((id) model.remove_node(id));
model.profiling_statuses.remove <+ out.node_removed;
out.source.on_visualization_select <+ out.node_removed.map(|&id| Switch::Off(id));
out.on_visualization_select <+ out.node_removed.map(|&id| Switch::Off(id));
eval inputs.set_node_expression (((id,expr)) model.set_node_expression(id,expr));
port_to_refresh <= inputs.set_node_expression.map(f!(((id,_))model.node_in_edges(id)));
eval port_to_refresh ((id) model.set_edge_target_connection_status(*id,true));
// === Remove implementation ===
out.source.node_removed <+ inputs.remove_node;
out.node_removed <+ inputs.remove_node;
}
@ -3452,7 +3451,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
input_edges_to_rm <= rm_input_edges . map(f!((node_id) model.node_in_edges(node_id)));
output_edges_to_rm <= rm_output_edges . map(f!((node_id) model.node_out_edges(node_id)));
edges_to_rm <- any (inputs.remove_edge, input_edges_to_rm, output_edges_to_rm);
out.source.on_edge_drop <+ edges_to_rm;
out.on_edge_drop <+ edges_to_rm;
}
@ -3546,7 +3545,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
}
);
file_dropped <= files_with_positions;
out.source.file_dropped <+ file_dropped;
out.file_dropped <+ file_dropped;
}
@ -3557,8 +3556,8 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
let profiling_mode_transition = Animation::new(network);
frp::extend! { network
out.source.view_mode <+ frp.toggle_profiling_mode.map2(&frp.view_mode,|_,&mode| mode.switch());
out.source.view_mode <+ model.profiling_button.view_mode;
out.view_mode <+ frp.toggle_profiling_mode.map2(&frp.view_mode,|_,&mode| mode.switch());
out.view_mode <+ model.profiling_button.view_mode;
model.profiling_button.set_view_mode <+ out.view_mode.on_change();
_eval <- all_with(&out.view_mode,&neutral_color,f!((_,neutral_color)
@ -3588,13 +3587,13 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
let default_y_gap = styles.get_number_or(default_y_gap_path, 0.0);
let min_x_spacing = styles.get_number_or(min_x_spacing_path, 0.0);
frp::extend! { network
frp.source.default_x_gap_between_nodes <+ default_x_gap;
frp.source.default_y_gap_between_nodes <+ default_y_gap;
frp.source.min_x_spacing_for_new_nodes <+ min_x_spacing;
frp.private.output.default_x_gap_between_nodes <+ default_x_gap;
frp.private.output.default_y_gap_between_nodes <+ default_y_gap;
frp.private.output.min_x_spacing_for_new_nodes <+ min_x_spacing;
}
frp.source.default_x_gap_between_nodes.emit(default_x_gap.value());
frp.source.default_y_gap_between_nodes.emit(default_y_gap.value());
frp.source.min_x_spacing_for_new_nodes.emit(min_x_spacing.value());
frp.private.output.default_x_gap_between_nodes.emit(default_x_gap.value());
frp.private.output.default_y_gap_between_nodes.emit(default_y_gap.value());
frp.private.output.min_x_spacing_for_new_nodes.emit(min_x_spacing.value());
@ -3603,7 +3602,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
// ==================
frp::extend! { network
out.source.debug_mode <+ frp.set_debug_mode;
out.debug_mode <+ frp.set_debug_mode;
limit_max_zoom <- frp.set_debug_mode.on_false();
unlimit_max_zoom <- frp.set_debug_mode.on_true();

View File

@ -241,7 +241,7 @@ fn get_nodes_in_bounding_box(bounding_box: &BoundingBox, nodes: &Nodes) -> Vec<N
/// Return an FRP endpoint that indicates the current selection mode. This method sets up the logic
/// for deriving the selection mode from the graph editor FRP.
pub fn get_mode(network: &frp::Network, editor: &crate::FrpEndpoints) -> frp::stream::Stream<Mode> {
pub fn get_mode(network: &frp::Network, editor: &crate::Frp) -> frp::stream::Stream<Mode> {
frp::extend! { network
let multi_select_flag = crate::enable_disable_toggle
@ -307,7 +307,7 @@ pub struct Controller {
impl Controller {
pub fn new(
editor: &crate::FrpEndpoints,
editor: &crate::Frp,
cursor: &Cursor,
mouse: &frp::io::Mouse,
touch: &TouchState,
@ -317,6 +317,7 @@ impl Controller {
let selection_mode = get_mode(&network, editor);
let cursor_selection_nodes = node_set::Set::new();
let editor = &editor.private;
frp::extend! { network
deselect_all_nodes <- any_(...);
@ -324,16 +325,16 @@ impl Controller {
enable_area_selection <- source();
// === Graph Editor Internal API ===
eval editor.select_node ((node_id) nodes.select(node_id));
eval editor.deselect_node ((node_id) nodes.select(node_id));
editor.source.node_selected <+ editor.select_node;
editor.source.node_deselected <+ editor.deselect_node;
eval editor.input.select_node ((node_id) nodes.select(node_id));
eval editor.input.deselect_node ((node_id) nodes.select(node_id));
editor.output.node_selected <+ editor.input.select_node;
editor.output.node_deselected <+ editor.input.deselect_node;
// === Selection Box & Mouse IO ===
on_press_style <- mouse.down_primary . constant(cursor::Style::new_press());
on_release_style <- mouse.up_primary . constant(cursor::Style::default());
edit_mode <- bool(&editor.edit_mode_off,&editor.edit_mode_on);
edit_mode <- bool(&editor.input.edit_mode_off,&editor.input.edit_mode_on);
not_edit_mode <- edit_mode.not();
should_area_select <- not_edit_mode && enable_area_selection;
@ -399,20 +400,20 @@ impl Controller {
|info,mode| mode.area_should_deselect(info.was_selected)
);
editor.source.node_selected <+ node_added.gate(&should_select);
editor.source.node_deselected <+ node_added.gate(&should_deselect);
editor.output.node_selected <+ node_added.gate(&should_select);
editor.output.node_deselected <+ node_added.gate(&should_deselect);
// Node leaves selection area, revert to previous selection state.
node_removed <- cursor_selection_nodes.removed.map(f!([](node_info) {
if !node_info.was_selected { Some(node_info.node) } else {None}
})).unwrap();
editor.source.node_deselected <+ node_removed;
editor.output.node_deselected <+ node_removed;
// === Single Node Selection Box & Mouse IO ===
should_not_select <- edit_mode || editor.some_edge_endpoints_unset;
should_not_select <- edit_mode || editor.output.some_edge_endpoints_unset;
node_to_select_non_edit <- touch.nodes.selected.gate_not(&should_not_select);
node_to_select_edit <- touch.nodes.down.gate(&edit_mode);
node_to_select <- any(node_to_select_non_edit,
@ -429,17 +430,17 @@ impl Controller {
deselect_on_select <- node_to_select.gate_not(&keep_selection);
deselect_all_nodes <+ deselect_on_select;
deselect_all_nodes <+ editor.deselect_all_nodes;
deselect_all_nodes <+ editor.input.deselect_all_nodes;
deselect_on_bg_press <- touch.background.selected.gate_not(&keep_selection);
deselect_all_nodes <+ deselect_on_bg_press;
all_nodes_to_deselect <= deselect_all_nodes.map(f_!(nodes.selected.mem_take()));
editor.source.node_deselected <+ all_nodes_to_deselect;
editor.output.node_deselected <+ all_nodes_to_deselect;
node_selected <- node_to_select.gate(&should_select);
node_deselected <- node_to_select.gate(&should_deselect);
editor.source.node_selected <+ node_selected;
editor.source.node_deselected <+ node_deselected;
editor.output.node_selected <+ node_selected;
editor.output.node_deselected <+ node_deselected;
// === Output bindings ===
cursor_style <- any(on_press_style,on_release_style,cursor_selection);

View File

@ -5,12 +5,16 @@ use ensogl_core::prelude::*;
use ensogl::frp;
use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::data::color::Lcha;
use ensogl_core::display;
use ensogl_core::display::shape::StyleWatchFrp;
use ensogl_gui_component::component;
use ensogl_gui_component::component::ComponentView;
use ensogl_text as text;
use super::BASE_TEXT_SIZE;
// =======================
@ -31,11 +35,11 @@ const EMPTY_LABEL: &str = "<No Label>";
mod background {
use super::*;
ensogl_core::define_shape_system! {
(style:Style) {
(style:Style,color_rgba:Vector4<f32>) {
let width : Var<Pixels> = "input_size.x".into();
let height : Var<Pixels> = "input_size.y".into();
let zoom = Var::<f32>::from("1.0/zoom()");
let base_color = style.get_color("flame_graph_color");
let color = Var::<color::Rgba>::from(color_rgba);
let shape = Rect((&width,&height));
@ -49,7 +53,7 @@ mod background {
let shape = shape - left;
let shape = shape - right;
let shape = shape.fill(base_color);
let shape = shape.fill(color);
(shape).into()
}
@ -65,7 +69,8 @@ mod background {
ensogl_core::define_endpoints_2! {
Input {
set_content(String),
set_size(Vector2)
set_size(Vector2),
set_color(Lcha)
}
Output {}
}
@ -77,6 +82,7 @@ impl component::Frp<Model> for Frp {
frp::extend! { network
eval api.input.set_content((t) model.set_content(t));
eval api.input.set_size((size) model.set_size(*size));
eval api.input.set_color((color) model.set_color(*color));
is_hovered <- bool(&background.mouse_out, &background.mouse_over);
eval is_hovered((hovered) model.set_label_visible(*hovered));
@ -146,6 +152,9 @@ impl Model {
let text_layer = &self.app.display.default_scene.layers.tooltip_text;
label.add_to_scene_layer(text_layer);
label.set_default_text_size(text::Size(
BASE_TEXT_SIZE / self.app.display.default_scene.camera().zoom(),
));
let text_size = label.height.value();
let text_origin = Vector2(
TEXT_OFFSET_X - self.background.size.get().x / 2.0,
@ -156,6 +165,10 @@ impl Model {
self.label.set(label);
}
fn set_color(&self, color: Lcha) {
self.background.color_rgba.set(color::Rgba::from(color).into());
}
}
impl display::Object for Model {

View File

@ -22,7 +22,12 @@ use ensogl_core::display;
mod block;
mod mark;
use enso_profiler_flame_graph::State;
use ensogl_core::data::color;
use ensogl_core::display::shape::StyleWatchFrp;
use mark::Mark;
pub use block::Block;
@ -34,6 +39,7 @@ pub use block::Block;
const ROW_HEIGHT: f64 = 20.0;
const ROW_PADDING: f64 = 5.0;
pub(crate) const BASE_TEXT_SIZE: f32 = 18.0;
@ -48,6 +54,7 @@ const ROW_PADDING: f64 = 5.0;
pub struct FlameGraph {
display_object: display::object::Instance,
blocks: Vec<Block>,
marks: Vec<Mark>,
}
/// Instantiate a `Block` shape for the given block data from the profiler.
@ -63,33 +70,82 @@ 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
}
/// Instantiate a `Mark` shape for the given block data from the profiler.
fn shape_from_mark(mark: profiler_flame_graph::Mark, app: &Application) -> Mark {
let component = app.new_view::<Mark>();
let x = mark.position as f32;
let y = 0.0;
let pos = Vector2::new(x, y);
let label = format!("{} ({:.1}ms)", mark.label, mark.position);
component.set_content.emit(label);
component.set_position_xy(pos);
component
}
const MIN_INTERVAL_TIME_MS: f64 = 0.0;
const X_SCALE: f64 = 1.0;
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 min_time =
data.blocks.iter().map(|block| block.start.floor() as u32).min().unwrap_or_default();
let blocks = data.blocks.into_iter().filter(|block| block.width() > MIN_INTERVAL_TIME_MS);
let marks = data.marks;
let blocks_zero_aligned = data.blocks.into_iter().map(|mut block| {
block.start -= min_time as f64;
block.end -= min_time as f64;
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));
Self { display_object, blocks }
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();
marks.iter().for_each(|item| display_object.add_child(item));
Self { display_object, blocks, marks }
}
/// Return a reference to the blocks that make up the flame graph.
pub fn blocks(&self) -> &[Block] {
&self.blocks
}
/// Return a reference to the marks that make up the flame graph.
pub fn marks(&self) -> &[Mark] {
&self.marks
}
}
impl display::Object for FlameGraph {

View File

@ -0,0 +1,189 @@
//! A single mark component that is used to build up a flame graph.
use ensogl_core::display::shape::*;
use ensogl_core::prelude::*;
use ensogl::frp;
use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::display;
use ensogl_core::display::shape::StyleWatchFrp;
use ensogl_gui_component::component;
use ensogl_gui_component::component::ComponentView;
use ensogl_text as text;
use super::BASE_TEXT_SIZE;
// =================
// === Constants ===
// =================
const EMPTY_LABEL: &str = "<No Label>";
const MARK_WIDTH: f32 = 2.0;
const MARK_HOVER_AREA_WIDTH: f32 = 20.0;
/// Invisible dummy color to catch hover events.
const HOVER_COLOR: color::Rgba = color::Rgba::new(1.0, 0.0, 0.0, 0.000_001);
const INFINITE: f32 = 99999.0;
const TEXT_OFFSET_X: f32 = MARK_WIDTH + 8.0;
// =========================
// === Shape Definition ===
// =========================
mod background {
use super::*;
ensogl_core::define_shape_system! {
(style:Style) {
let width : Var<Pixels> = MARK_WIDTH.px();
let height : Var<Pixels> = INFINITE.px();
let zoom = &Var::<f32>::from("1.0/zoom()");
let base_color = style.get_color("flame_graph_mark_color");
let shape = Rect((&width*zoom,&height));
let shape = shape.fill(base_color);
let hover_area = shape.grow(zoom * MARK_HOVER_AREA_WIDTH.px());
let hover_area = hover_area.fill(HOVER_COLOR);
(shape + hover_area).into()
}
}
}
// ===========
// === FRP ===
// ===========
ensogl_core::define_endpoints_2! {
Input {
set_content(String),
}
Output {}
}
impl component::Frp<Model> for Frp {
fn init(api: &Self::Private, app: &Application, model: &Model, _style: &StyleWatchFrp) {
let network = &api.network;
let background = &model.background.events;
let cursor_pos = app.cursor.frp.scene_position.clone_ref();
frp::extend! { network
eval api.input.set_content((t) model.set_content(t));
is_hovered <- bool(&background.mouse_out, &background.mouse_over);
eval is_hovered((hovered) model.set_label_visible(*hovered));
on_mouse_over_pos <- cursor_pos.gate(&is_hovered);
eval on_mouse_over_pos((pos) model.set_label_y(pos.y));
}
model.set_size(Vector2::new(MARK_WIDTH + 2.0 * MARK_HOVER_AREA_WIDTH, INFINITE));
}
}
// =============
// === Model ===
// =============
#[derive(Clone, CloneRef, Debug)]
pub struct Model {
app: Application,
background: background::View,
label: Rc<RefCell<Option<text::Area>>>,
display_object: display::object::Instance,
text: Rc<RefCell<Option<String>>>,
}
impl component::Model for Model {
fn label() -> &'static str {
"FlameGraphMark"
}
fn new(app: &Application, logger: &Logger) -> Self {
let scene = &app.display.default_scene;
let display_object = display::object::Instance::new(&logger);
let label = default();
let text = default();
let background = background::View::new(&logger);
display_object.add_child(&background);
scene.layers.tooltip.add_exclusive(&background);
let app = app.clone_ref();
Model { app, background, label, display_object, text }
}
}
impl Model {
fn set_size(&self, size: Vector2) {
self.background.size.set(size);
}
fn set_content(&self, t: &str) {
self.text.set(t.to_owned());
if let Some(label) = self.label.borrow().deref() {
label.set_content(t.to_owned())
}
}
fn set_label_visible(&self, visible: bool) {
if visible {
self.enable_label();
} else {
self.label.take().for_each(|label| label.unset_parent())
}
}
fn enable_label(&self) {
let label = self.app.new_view::<text::Area>();
self.add_child(&label);
let text_layer = &self.app.display.default_scene.layers.tooltip_text;
label.add_to_scene_layer(text_layer);
label.set_default_text_size(text::Size(
BASE_TEXT_SIZE / self.app.display.default_scene.camera().zoom(),
));
label.set_position_x(TEXT_OFFSET_X);
label.set_content(self.label_text());
self.label.set(label);
}
fn set_label_y(&self, y: f32) {
if let Some(label) = self.label.deref().borrow().as_ref() {
label.set_position_y(y)
}
}
fn label_text(&self) -> String {
self.text.borrow().clone().unwrap_or_else(|| EMPTY_LABEL.to_owned())
}
}
impl display::Object for Model {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
// =================
// === Component ===
// =================
#[allow(missing_docs)]
pub type Mark = ComponentView<Model, Frp>;

View File

@ -39,6 +39,7 @@ nalgebra = { version = "0.26.1" }
num_enum = { version = "0.5.1" }
num-traits = { version = "0.2" }
rustc-hash = { version = "1.0.1" }
serde = { version = "1" }
shrinkwraprs = { version = "0.3.0" }
smallvec = { version = "1.0.0" }
typenum = { version = "1.11.2" }

View File

@ -1110,7 +1110,7 @@ macro_rules! define_endpoints_2_normalized_public {
use $crate::application::command::*;
$crate::frp::extend! { $output_opts network
$( $out_field <- private_output.$out_field.sampler(); )*
$( $out_field <- private_output.$out_field.profile().sampler(); )*
}
let mut status_map : HashMap<String,$crate::frp::Sampler<bool>> = default();
let mut command_map : HashMap<String,Command> = default();

View File

@ -7,10 +7,11 @@ edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
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-web = { path = "../../../web" }
ensogl-core = { path = "../../core" }
@ -19,5 +20,17 @@ ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" }
ensogl-text = { path = "../../component/text" }
ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" }
futures = "0.3"
serde = "1"
wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] }
wasm-bindgen-futures = "0.4"
[dependencies.web-sys]
version = "0.3"
features = [
'Headers',
'Request',
'RequestInit',
'RequestMode',
'Response',
'Window',
]

View File

@ -1,4 +1,7 @@
//! Demo scene showing a sample flame graph.
//! Demo scene showing a sample flame graph. Can be used to display a log file, if you have one.
//! To do so, set the `PROFILER_LOG_NAME` to contain the profiling log name and it
//! will be used for rendering the visualisation. See the docs of `PROFILER_LOG_NAME` for more
//! information.
// === Standard Linter Configuration ===
#![deny(non_ascii_idents)]
@ -9,15 +12,16 @@
#![warn(missing_docs)]
#![warn(trivial_casts)]
#![warn(trivial_numeric_casts)]
#![warn(unused_import_braces)]
#![warn(unused_qualifications)]
#![allow(unused_qualifications)]
use ensogl_core::prelude::*;
use wasm_bindgen::prelude::*;
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_metadata::Metadata;
use ensogl_core::application::Application;
use ensogl_core::data::color;
use ensogl_core::display::navigation::navigator::Navigator;
@ -27,6 +31,12 @@ use ensogl_core::display::Scene;
use ensogl_core::system::web;
use ensogl_flame_graph as flame_graph;
/// 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;
// ===================
@ -36,7 +46,7 @@ use ensogl_flame_graph as flame_graph;
/// The example entry point.
#[entry_point]
#[allow(dead_code)]
pub fn main() {
pub async fn main() {
web::forward_panic_hook_to_console();
web::set_stack_trace_limit();
@ -48,10 +58,21 @@ pub fn main() {
init_theme(scene);
// Generate Test data
futures::executor::block_on(start_project());
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 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 measurements = profiler_flame_graph::Graph::take_from_log();
let flame_graph = flame_graph::FlameGraph::from_data(measurements, app);
world.add_child(&flame_graph);
@ -76,7 +97,9 @@ fn init_theme(scene: &Scene) {
let theme_manager = theme::Manager::from(&scene.style_sheet);
let theme = theme::Theme::new();
theme.set("flame_graph_color", color::Rgb::new(1.0, 45.0 / 255.0, 0.0));
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_manager.register("theme", theme);
@ -87,10 +110,50 @@ fn init_theme(scene: &Scene) {
}
// ============================
// === Profiler Log Reading ===
// ============================
/// Read the `PROFILER_LOG_NAME` data from a file.
async fn get_data_raw() -> Option<String> {
use wasm_bindgen::JsCast;
let url = &["assets/", PROFILER_LOG_NAME?].concat();
let mut opts = web_sys::RequestInit::new();
opts.method("GET");
opts.mode(web_sys::RequestMode::Cors);
let request = web_sys::Request::new_with_str_and_init(url, &opts).unwrap();
request.headers().set("Accept", "application/json").unwrap();
let window = web_sys::window().unwrap();
let response = window.fetch_with_request(&request);
let response = wasm_bindgen_futures::JsFuture::from(response).await.unwrap();
assert!(response.is_instance_of::<web_sys::Response>());
let response: web_sys::Response = response.dyn_into().unwrap();
let data = response.text().unwrap();
let data = wasm_bindgen_futures::JsFuture::from(data).await.unwrap();
data.as_string()
}
async fn get_log_data() -> Option<Profile<Metadata>> {
let data = get_data_raw().await;
data.and_then(|data| data.parse().ok())
}
// ==========================
// === Dummy Computations ===
// ==========================
async fn create_dummy_data() -> Profile<Metadata> {
start_project().await;
let log = profiler::internal::take_log();
let profile: Result<Profile<Metadata>, _> = log.parse();
profile.expect("Failed to deserialize profiling event log.")
}
/// A dummy computation that is intended to take some time based on input (where a higher number
///takes longer).
fn work(n: u32) {

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 = {path = "../profiler"}
futures = { version = "0.3.1" }
failure = { version = "0.1.6" }
serde = { version = "1.0.0", features = ["derive"] }

View File

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

View File

@ -89,11 +89,28 @@ macro_rules! make_rpc_methods {
$(fn $method<'a>(&'a self, $($param_name:&'a $param_ty),*)
-> std::pin::Pin<Box<dyn Future<Output=Result<$result>>>> {
use json_rpc::api::RemoteMethodCall;
use $crate::enso_profiler as profiler;
use $crate::enso_profiler::internal::StartState;
use $crate::enso_profiler::internal::Profiler;
let label = profiler::internal::Label(stringify!($method));
let parent = profiler::internal::EventId::implicit();
let now = Some(profiler::internal::Timestamp::now());
let profiler = profiler::Task::start(parent, label, now, StartState::Active);
let phantom = std::marker::PhantomData;
let input = $method_input { phantom, $($param_name:&$param_name),* };
let input_json = serde_json::to_value(input).unwrap();
let name = $method_input::NAME;
let result_fut = self.handler.borrow().open_request_with_json(name,&input_json);
profiler.pause();
let result_fut = result_fut.map(move |value| {
profiler.resume();
profiler.finish();
value
});
Box::pin(result_fut)
})*

View File

@ -223,13 +223,16 @@ impl<M> Profile<M> {
pub fn root_measurement(&self) -> &Measurement {
self.measurements.last().unwrap()
}
}
impl<M> Profile<M> {
/// A virtual interval containing the top-level intervals as children.
pub fn root_interval(&self) -> &ActiveInterval<M> {
self.intervals.last().unwrap()
}
/// Iterate over only the metadata stored in the profile.
pub fn iter_metadata(&self) -> impl Iterator<Item = &Metadata<M>> {
self.intervals.iter().flat_map(|interval| interval.metadata.iter())
}
}

View File

@ -6,15 +6,25 @@
#![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;
// ==================
// === 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 {
Active,
Paused,
}
/// A `Block` contains the data required to render a single block of a frame graph.
#[derive(Clone, Debug)]
pub struct Block {
@ -26,6 +36,8 @@ pub struct Block {
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,
}
impl Block {
@ -36,6 +48,20 @@ impl Block {
}
// ==================
// === Mark Data ===
// ==================
/// A `Mark` contains the data required to render a mark that indicates a labeled point in time.
#[derive(Clone, Debug)]
pub struct Mark {
/// X coordinate of the mark.
pub position: f64,
/// The label to be displayed with the mark.
pub label: String,
}
// ==================
// === Graph Data ===
@ -47,6 +73,8 @@ impl Block {
pub struct Graph {
/// Collection of all blocks making up the flame graph.
pub blocks: Vec<Block>,
/// Collection of marks that can be shown in the flame graph.
pub marks: Vec<Mark>,
}
impl Graph {
@ -100,7 +128,8 @@ impl<'p, Metadata> CallgraphBuilder<'p, Metadata> {
builder.visit_interval(*child, 0);
}
let Self { blocks, .. } = builder;
Graph { blocks }
let marks = Vec::default();
Graph { blocks, marks }
}
}
@ -115,7 +144,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 });
self.blocks.push(Block { start, end, label, row, state: Active });
for child in &active.children {
self.visit_interval(*child, row + 1);
}
@ -146,7 +175,8 @@ impl<'p, Metadata> RungraphBuilder<'p, Metadata> {
builder.visit_measurement(*child);
}
let Self { blocks, .. } = builder;
Graph { blocks }
let marks = Vec::default();
Graph { blocks, marks }
}
}
@ -158,26 +188,66 @@ impl<'p, Metadata> RungraphBuilder<'p, Metadata> {
if measurement.intervals.len() >= 2 {
let row = self.next_row;
self.next_row += 1;
for active in &measurement.intervals {
let active = &self.profile[*active];
let start = active.interval.start.into_ms();
let mut end = active.interval.end.map(|mark| mark.into_ms()).unwrap_or(f64::MAX);
// The rungraph view should illustrate every time an async task wakes up, because
// wakeups reveal the dependencies between tasks: A task is always awake before
// and after awaiting another task.
//
// These wakeups are often momentary, when there is no (or trivial) work between
// .await points. The real intervals would often be difficult or impossible to see,
// so here we enlarge short intervals to a value that will make them visible at a
// moderate zoom level.
const DURATION_FLOOR_MS: f64 = 3.0;
if end < start + DURATION_FLOOR_MS {
end = start + DURATION_FLOOR_MS;
let window_size = 2; // Current and next element.
for window in measurement.intervals.windows(window_size) {
if let [current, next] = window {
let current = &self.profile[*current];
let next = &self.profile[*next];
let current_start = current.interval.start.into_ms();
let current_end =
current.interval.end.map(|mark| mark.into_ms()).unwrap_or(f64::MAX);
let next_start = next.interval.start.into_ms();
let active_interval = [current_start, current_end];
let sleep_interval = [current_end, next_start];
let label_active = self.profile[current.measurement].label.to_string();
let label_sleep =
format!("{} (inactive)", self.profile[current.measurement].label);
self.blocks.push(Block {
start: active_interval[0],
end: active_interval[1],
label: label_active,
row,
state: Active,
});
self.blocks.push(Block {
start: sleep_interval[0],
end: sleep_interval[1],
label: label_sleep,
row,
state: Paused,
});
}
let label = self.profile[active.measurement].label.to_string();
self.blocks.push(Block { start, end, label, row });
}
// Add first inactive interval.
let first = measurement.intervals.first().unwrap(); // There are at least two intervals.
let first = &self.profile[*first];
self.blocks.push(Block {
start: measurement.created.into_ms(),
end: first.interval.start.into_ms(),
label: self.profile[first.measurement].label.to_string(),
row,
state: Paused,
});
// Add last active interval.
let last = measurement.intervals.last().unwrap(); // There are at least two intervals.
let last = &self.profile[*last];
self.blocks.push(Block {
start: last.interval.start.into_ms(),
end: last.interval.end.map(|end| end.into_ms()).unwrap_or(f64::INFINITY),
label: self.profile[last.measurement].label.to_string(),
row,
state: Active,
});
}
// Recourse through children.
for child in &measurement.children {
self.visit_measurement(*child);
}
@ -201,7 +271,8 @@ fn new_hybrid_graph<Metadata>(profile: &data::Profile<Metadata>) -> Graph {
callgraph.visit_interval(*child, next_row);
}
let CallgraphBuilder { blocks, .. } = callgraph;
Graph { blocks }
let marks = Vec::default();
Graph { blocks, marks }
}
@ -231,7 +302,8 @@ impl From<FlamegraphBuilder> for Graph {
grapher.visit_frame(frame, label.to_string(), 0);
}
let FlamegraphGrapher { blocks, .. } = grapher;
Self { blocks }
let marks = Vec::default();
Self { blocks, marks }
}
}
@ -246,7 +318,7 @@ impl FlamegraphGrapher {
fn visit_frame(&mut self, frame: &data::aggregate::Frame, label: String, row: u32) {
let start = self.time;
let end = self.time + frame.total_duration();
self.blocks.push(Block { start, end, label, row });
self.blocks.push(Block { start, end, label, row, state: State::Active });
for (label, frame) in &frame.children {
self.visit_frame(frame, label.to_string(), row + 1);
}