[ANN] More flexible & user-friendly profile visualization (ProfileView 0.6)

I’ve recently completed a major rewrite of ProfileView.jl, a package for visualizing profiling data as a tool for understanding performance bottlenecks. ProfileView was written back in the Julia 0.1 days (yes, back in 2013) and it was long overdue for a facelift.

Several changes should be visible to users:

  • Support for Jupyter/IJulia and writing SVGs has moved to the ProfileSVG package. It should be noted that this package is looking for a maintainer who actually knows something about SVG.
  • ProfileView will now use one bar per source location. Formerly a whole stack of inlined calls got scrunched together into a single bar; as Julia’s inilining has gotten more aggressive, this was starting to cause serious usability problems. Now these calls have been virtually “de-inlined” and treated separately, allowing you to right-click on bars and be taken to the source location in your editor.
  • There are now extensive user-customizable coloration schemes thanks to the new FlameGraphs package. For example, you can color bars according to their module-of-origin.
  • The API has changed slightly, and there is much more extensive documentation of all the supporting infrastructure.

The code should also be much cleaner and easier to work with; for people who want more from their profiling, now is a good time to jump in with contributions. It should also be noted that FlameGraphs.jl (which is where most of the “heavy lifting” is done) is deliberately GUI-agnostic, and in principle can be leveraged by other front-ends.

All this goodness is available as ProfileView 0.6.0 and growing family of support packages.

53 Likes

Thanks for working on this!

This is awesome! I had actually hoped for a long time that the core processing stuff could be factored into its own package.

Here is what I plan to do with this: I hope to write a converter that uses vega-lite to show a flame graph, and then we should be able to integrate that into VS Code pretty easily.

So that is the theory, right now I’m stuck in that I don’t really understand how I I’m supposed to traverse the tree I get back from flamegraph(). I see that the documentation points me to AbstractTrees.jl, but that package doesn’t really explain how one would traverse a tree either :slight_smile: What I’m looking for is simply a way to traverse all nodes, and for each node get all the data for that node and a depth level how deep this is in the tree. Any help would be appreciated!

2 Likes

You can use the traversal rules of AbstractTrees (which was revitalized for this project but could still use more documentation). But I fail to mention in FlameGraph’s docs that the tree is encoded as a LeftChildRightSiblingTree (also split out during this project). The showtree demo should do exactly what you’re asking for.

Thank you very much for this! In particular the de-inlining is much better. I tried the StackFrameCategory, but didn’t find it very useful, because 1) the reddening of type-unstable code makes it more confusing 2) it treats all packages on an equal footing 3) the lack of boundaries make it harder to distinguish different stacks when there is no color variation.

Ideally, 1) maybe do something like a striped pattern instead? That way the color is preserved
2) different color for each package?
3) related to https://github.com/timholy/ProfileView.jl/issues/81, having boundaries would fix both

My sense is that different people are going to want to color different modules differently. Thus the main point of StackFrameCategory is that you can easily add your own customizations. I just submitted a PR showing how you can color packages however you want. Copying that here:

Examples

using Plots, Profile, FlameGraphs
@profile plot(rand(5))    # "time to first plot"
g = flamegraph(C=true)
img = flamepixels(StackFrameCategory(), g)

Or you can tweak the coloration yourself:

function modcat(mod)
    mod == Plots && return colorant"purple"
    return nothing
end
img = flamepixels(StackFrameCategory(modcat), g)

Of course you can check for a whole zoo of different modules and color each of them differently. loccat is also customizable, of course.

The default behavior is only a couple of lines long, so if you have a better idea for how it should behave, I’d love a PR!

Here is a version that plots things with VegaLite.jl:

using FlameGraphs, VegaLite, LeftChildRightSiblingTrees

function _add_node_to_data!(data, node, indent)
    push!(data, (level=indent, status=node.data.status==0 ? "Default" : node.data.status==1 ? "Runtime dispatch" : "Garbage collection", x1=node.data.span.start, x2=node.data.span.stop, sf=string(node.data.sf)))
    for child in node
        _add_node_to_data!(data, child, indent-1)
    end
end

function _plotflamegraph(node)
    data = Vector{NamedTuple{(:level, :status, :x1, :x2, :sf), Tuple{Int,String,Int,Int,String}}}(undef, 0)    
    _add_node_to_data!(data, node, 0)
    
    return data |> @vlplot(:bar, x={:x1, axis=nothing}, x2=:x2, y={"level:o", axis=nothing}, width=900, height=400, color={"status:n", legend={title=nothing, orient=:bottom}}, tooltip=:sf, title="Profile Results")
end

function our_view()
    g = flamegraph()
    _plotflamegraph(g)
end

Output in JupyterLab looks like this:

It has full interactive tooltip support, and it should be fairly easy to also add things like pan/zoom. I’m not sure, but this might generally be a better option than the SVG story for JupyterLab?

I’ll probably put this into its own package, parallel to the ProfileSVG.jl package, something like ProfileVega.jl, maybe? An alternative would be that we add a Base.show method for the vega lite MIME type to FlameGraphs.jl itself. I think we could do that in a way that wouldn’t add any additional dependency to FlaameGraphs.jl, in particular we could easily do this without a dependency on VegaLite.jl. Not sure whether that makes sense, though…

I will certainly also add this to the VS Code extension out-of-the-box, with an ability that a click on a bar opens the corresponding source file. Still thinking whether/how we would also add an Electron.jl based version…

13 Likes

With the rewrite one of my major goals was trying to put things into a format that would make this kind of stuff pretty easy. By pulling it off in 20 lines of code, I think you just took it to a whole new level! Nice work!

I’d be thrilled to suggest ProfileVega as the go-to solution for JupyterLab or whatever. And adding methods to FlameGraph that don’t add a dependency for a particular front-end would be fine.

3 Likes

And now as a package: ProfileVega.jl. I pretty much copy/pasted the ProfileSVG.jl README, i.e. the API should be almost identical :slight_smile:

The figures should show properly in any Jupyter client, but the interactive tooltips will only work in JupyterLab and nteract and other Jupyter clients that support the vega/vega-lite MIME type.

I’ll register soon, but would be great if some folks could try this and report back if there are any problems. If it works, please also report back.

@tim.holy maybe we should try it out for a while, and then can decide whether we want to make it a general recommendation for some clients?

I’ve also added two issues with really simple improvements, if anyone wants to help, those would be great opportunities for PRs.

8 Likes