FlameGraphs.jl
FlameGraphs is a package that adds functionality to Julia's Profile
standard library. It is directed at the algorithmic side of producing flame graphs, but includes some "format agnostic" rendering code. FlameGraphs is used by IDEs like Juno and visualization packages like ProfileView, ProfileVega, and ProfileSVG.
Computing a flame graph
The core function of FlameGraphs is to compute a tree representation of a set of backtraces collected by Julia's sampling profiler. For a demonstration we'll use the following function:
julia> function profile_test(n)
for i = 1:n
A = randn(100,100,20)
m = maximum(A)
Am = mapslices(sum, A; dims=2)
B = A[:,:,5]
Bsort = mapslices(sort, B; dims=1)
b = rand(100)
C = B.*b
end
end
profile_test (generic function with 1 method)
julia> profile_test(1) # run once to compile
julia> using Profile, FlameGraphs
julia> Profile.clear(); @profile profile_test(10) # collect profiling data
julia> g = flamegraph()
Node(FlameGraphs.NodeData(ip:0x0, 0x01, 1:62))
This may not be very informative on its own; the only thing this communicates clearly is that 62 samples (separate backtraces) were collected during profiling. (If you run this example yourself, you might get a different number of samples depending on how fast your machine is and which operating system you use.) It becomes more meaningful with
julia> using AbstractTrees
julia> print_tree(g)
FlameGraphs.NodeData(ip:0x0, 0x01, 1:62)
├─ FlameGraphs.NodeData(randn(::Random.MersenneTwister, ::Type{Float64}) at normal.jl:167, 0x00, 62:62)
└─ FlameGraphs.NodeData(eval(::Module, ::Any) at boot.jl:330, 0x01, 1:61)
├─ FlameGraphs.NodeData(profile_test(::Int64) at REPL[2]:3, 0x00, 1:15)
│ └─ FlameGraphs.NodeData(randn at normal.jl:190 [inlined], 0x00, 1:15)
│ └─ FlameGraphs.NodeData(randn(::Random.MersenneTwister, ::Type{Float64}, ::Int64, ::Int64, ::Vararg{Int64,N} where N) at normal.jl:184, 0x01, 1:15)
│ └─ FlameGraphs.NodeData(randn!(::Random.MersenneTwister, ::Array{Float64,3}) at normal.jl:173, 0x00, 2:15)
[...]
Each node of the tree consists of a StackFrame
indicating the file, function, and line number of a particular entry in one or more backtraces, a status flag, and a range that corresponds to the horizontal span of a particular node when the graph is rendered. See FlameGraphs.NodeData
for more information. (For developers, g
is a left-child, right-sibling tree.)
flamegraph
has several options that can be used to control how it computes the graph.
Rendering a flame graph
You can create a "bitmap" representation of the flame graph with flamepixels
:
julia> img = flamepixels(g)
125×20 Array{RGB{N0f8},2} with eltype ColorTypes.RGB{FixedPointNumbers.Normed{UInt8,8}}:
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(1.0,1.0,1.0) … RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.62,0.62,0.62) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.62,0.62,0.62) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.62,0.62,0.62) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.62,0.62,0.62) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.62,0.62,0.62) … RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.62,0.62,0.62) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.62,0.62,0.62) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.62,0.62,0.62) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.62,0.62,0.62) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
⋮ ⋱
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.62,0.62,0.62) … RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.659,0.635,0.0) … RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.804,0.725,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
RGB{N0f8}(1.0,0.0,0.0) RGB{N0f8}(0.804,0.725,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0) RGB{N0f8}(1.0,1.0,1.0)
You can display this in an image viewer, but for interactive exploration ProfileView.jl
adds additional features.
The coloration scheme can be customized as described in the documentation for flamepixels
, with the default coloration provided by FlameColors
. This default uses cycling colors to distinguish different stack frames, while coloring runtime dispatch red and garbage-collection orange. If we profile "time to first plot,"
using Plots, Profile, FlameGraphs
@profile plot(rand(5)) # "time to first plot"
g = flamegraph(C=true)
julia> img = flamepixels(g);
we might get something like this:
The following is an example of a customized coloration:
julia> fcolor = FlameColors(colorbg=colorant"#444", colorfont=colorant"azure",
colorsrt=colorant"lavender", colorsgc=nothing);
julia> img = flamepixels(fcolor, g);
An alternative is to color frames by their category:
julia> img = flamepixels(StackFrameCategory(), g);
in which case we might get something like this:
In this plot, dark gray indicates time spent in Core.Compiler
(mostly inference), yellow in LLVM, orange other ccall
s, light blue in Base
, and red is uncategorized (mostly package code).
StackFrameCategory
allows you to customize these choices and recognize arbitrary modules.