Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Ensogl stats with profiling framework #3388

Merged
merged 10 commits into from
Apr 21, 2022

Conversation

MichaelMauderer
Copy link
Contributor

Pull Request Description

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:

Peek.2022-04-08.14-08.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.

Checklist

Please include the following checklist in your PR:

  • The documentation has been updated if necessary.
  • All code conforms to the Scala, Java, and Rust style guides.
  • All code has been tested:
    • Unit tests have been written where possible.
    • If GUI codebase was changed: Enso GUI was tested when built using BOTH ./run dist and ./run watch.

@MichaelMauderer MichaelMauderer self-assigned this Apr 8, 2022
@MichaelMauderer MichaelMauderer requested a review from kazcw April 8, 2022 12:15
Base automatically changed from wip/michaelmauderer/backend_communication_profiling to develop April 19, 2022 11:30
@MichaelMauderer MichaelMauderer force-pushed the wip/michaelmauderer/ensogl_stats_181151201 branch from 334456a to a78eb90 Compare April 19, 2022 12:40
@MichaelMauderer MichaelMauderer marked this pull request as ready for review April 19, 2022 14:09

fn align_mark(mut mark: profiler_flame_graph::Mark, origin_x: f64) -> profiler_flame_graph::Mark {
mark.position -= origin_x;
mark.position += X_SCALE;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+?



thread_local! {
/// A common preamble used to start every shader program.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment from somewhere else?

@@ -146,6 +149,29 @@ impl FlameGraph {
pub fn marks(&self) -> &[Mark] {
&self.marks
}

/// Add additional blocks to the visualisation.
pub fn add_blocks<Blocks: Iterator<Item = profiler_flame_graph::Block>>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API would be simpler with a add_block interface here; caller can handle the looping

}

/// Add additional marks to the visualisation.
pub fn add_marks<Marks: Iterator<Item = profiler_flame_graph::Mark>>(&mut self, marks: Marks) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

.iter_metadata()
.filter(|metadata| matches!(metadata.data, Metadata::RenderStats(_)));
for (prev, current) in render_stats.tuple_windows() {
if let Metadata::RenderStats(data) = current.data {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The filter/if let seem redundant here; why not use a filter_map operation to get a correctly-type iterator?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm missing something, but using filter_map, I end up with something like

 profile
        .iter_metadata()
        .filter(|metadata| matches!(metadata.data, Metadata::RenderStats(_)))
        .tuple_windows()
        .filter_map(|(prev, current)| {
            if let Metadata::RenderStats(data) = current.data {
                Some((prev, current, data))
            } else {
                None
            }
        })
        .for_each(|(prev, current, data)| {
            let start = prev.mark.into_ms();
            let end = current.mark.into_ms();
            let row = -1;
            let label = format!("{:#?}", data);
            let theme_color = match data.fps {
                fps if fps > 55.0 => COLOR_FPS_GOOD,
                fps if fps > 25.0 => COLOR_FPS_MEDIUM,
                _ => COLOR_FPS_BAD,
            };
            let block = profiler_flame_graph::Block { start, end, row, label, theme_color };
            blocks.push(block);
        });

This doesn't seem really any better. Instead, we have a three tuple as an additional step. I don't see a good other way to retrieve the content of the Metadata Enum in some more elegant way, and we do need both previous, current and the data to generate the block.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could do this:

    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);
    }

I have named enso_profiler_data::Metadata and its fields poorly; in a future PR I think I will rename it to Timestamped with a time field, so that usages like this would be more transparent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Neat! Didn't think of re-using the Metadata with the inner data type. I'll do that its clearer.

lib/rust/ensogl/example/profiling-run-graph/src/lib.rs Outdated Show resolved Hide resolved
pub state: State,
pub label: String,
/// Indicates the name of the color for lookup in the theme.
pub theme_color: &'static str,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i liked the abstraction State provided here--profiler_flame_graph::Block was a high-level description of an element of a graph, and shape_from_block lowered it to the ensogl flame_graph::Block, which was where the rendering details came in.

I see that a simple enum like State no longer works now that different types of graph need to classify Blocks differently. I think although it would be a little more complex than this lowering-early approach, it might be worth introducing a parameterization like profiler_flame_graph::Block<BlockType> (where ensogl's flame_graph contains something like impl From<SomeBlockType> for ThemeColor for each concrete BlockType) in order to maintain the clear boundary between the responsibilities of the two crates and their Block types.

This would require fixing a bit of a hack from my original implementation of hybrid graph: There shouldn't be a hybrid graph type containing blocks arranged according to different disciplines; it would be better for the render-profile scene to draw a callgraph, draw a rungraph under it, and draw the new 1-bar-high frametime graph in the stack too. I just did it the current way because it was easier due to my inexperience with Enso's FRP networks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent explanation @kazcw! Thanks for writing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an excellent approach! I made the changes. There are a few tweaks that deviate from your proposal, for example, not using From due to orphan rule issues, but a new trait, which seemed nicer than a newtype to make the conversion work. But have a look and let me know if you think that can still be improved.

This would require fixing a bit of a hack from my original implementation of hybrid graph: There shouldn't be a hybrid graph type containing blocks arranged according to different disciplines; it would be better for the render-profile scene to draw a callgraph, draw a rungraph under it, and draw the new 1-bar-high frametime graph in the stack too. I just did it the current way because it was easier due to my inexperience with Enso's FRP networks.

Adding Sections like this is on my agenda for the visualization, but I think here it is not yet needed. This change will probably come for the multiprocess visualization.

let label = format!("{:#?}", data);
let theme_color = match data.fps {
fps if fps > 55.0 => COLOR_FPS_GOOD,
fps if fps > 25.0 => COLOR_FPS_MEDIUM,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think "medium" is the right word between "good" and "bad". I cannot think of a good word now, but "medium" sounds off too me. To be confirmed with a native speaker.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the terminology a bit and instead of “FPS” I am using Performance. For that, “Medium” seems to fit better.

pub state: State,
pub label: String,
/// Indicates the name of the color for lookup in the theme.
pub theme_color: &'static str,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent explanation @kazcw! Thanks for writing it.

@MichaelMauderer MichaelMauderer requested a review from kazcw April 20, 2022 10:24
.iter_metadata()
.filter(|metadata| matches!(metadata.data, Metadata::RenderStats(_)));
for (prev, current) in render_stats.tuple_windows() {
if let Metadata::RenderStats(data) = current.data {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could do this:

    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);
    }

I have named enso_profiler_data::Metadata and its fields poorly; in a future PR I think I will rename it to Timestamped with a time field, so that usages like this would be more transparent.

@MichaelMauderer MichaelMauderer added the CI: Ready to merge This PR is eligible for automatic merge label Apr 21, 2022
@mergify mergify bot merged commit e8342b0 into develop Apr 21, 2022
@mergify mergify bot deleted the wip/michaelmauderer/ensogl_stats_181151201 branch April 21, 2022 09:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI: Ready to merge This PR is eligible for automatic merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants