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

Leverage FlameGraphs 0.2 #139

Merged
merged 2 commits into from
Feb 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "ProfileView"
uuid = "c46f51b8-102a-5cf2-8d2c-8597cb0e0da7"
author = ["Tim Holy <[email protected]>"]
version = "0.6.2"
version = "0.6.3"

[deps]
Cairo = "159f3aea-2a34-519c-b102-8c37f9878175"
Expand All @@ -21,7 +21,7 @@ UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
Cairo = "0.6, 0.8, 1"
Colors = "0.9, 0.10, 0.11"
FileIO = "1"
FlameGraphs = "0.1"
FlameGraphs = "0.2"
Graphics = "0.4, 1"
Gtk = "0.18, 1"
GtkReactive = "0.7, 1"
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ contribution to its total run time.
- You can pan the view by clicking and dragging, or by scrolling your
mouse/trackpad (scroll=vertical, SHIFT-scroll=horizontal).

- The toolbar at the top contains two icons to load and save profile
data, respectively. Clicking the save icon will prompt you for a
filename; you should use extension `*.jlprof` for any file you save.
Launching `ProfileView.view(nothing)` opens a blank
window, which you can populate with saved data by clicking on the
"open" icon.

**NOTE**: ProfileView does not support the old JLD-based `*.jlprof` files anymore.
Use the format provided by FlameGraphs v0.2 and higher.

## Command-line options

The `view` command has the following syntax:
Expand Down
32 changes: 0 additions & 32 deletions README_save.md

This file was deleted.

175 changes: 96 additions & 79 deletions src/ProfileView.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,28 @@ You have several options to control the output, of which the major ones are:

See [FlameGraphs](https://github.com/timholy/FlameGraphs.jl) for more information.
"""
function view(fcolor, data::Vector{UInt64}=Profile.fetch(); lidict=nothing, C=false, combine=true, recur=:off, pruned=FlameGraphs.defaultpruned, kwargs...)
function view(fcolor, data::Vector{UInt64}; lidict=nothing, C=false, combine=true, recur=:off, pruned=FlameGraphs.defaultpruned, kwargs...)
g = flamegraph(data; lidict=lidict, C=C, combine=combine, recur=recur, pruned=pruned)
g === nothing && return nothing
return view(fcolor, g; kwargs...)
return view(fcolor, g; data=data, lidict=lidict, kwargs...)
end
function view(data::Vector{UInt64}=Profile.fetch(); kwargs...)
view(FlameGraphs.default_colors, data; kwargs...)
function view(fcolor; kwargs...)
data, lidict = Profile.retrieve()
view(fcolor, data; lidict=lidict, kwargs...)
end
function view(data::Vector{UInt64}; lidict=nothing, kwargs...)
view(FlameGraphs.default_colors, data; lidict=lidict, kwargs...)
end
function view(; kwargs...)
data, lidict = Profile.retrieve()
view(FlameGraphs.default_colors, data; lidict=lidict, kwargs...)
end

# This method allows user to open a *.jlprof file
view(::Nothing; kwargs...) = view(FlameGraphs.default_colors, Node(NodeData(StackTraces.UNKNOWN, 0, 1:0)); kwargs...)

function view(fcolor, g::Node{NodeData}; kwargs...)
function view(fcolor, g::Node{NodeData}; data=nothing, lidict=nothing, kwargs...)
gsig = Signal(g) # allow substitution by the open dialog
# Display in a window
c = canvas(UserUnit)
set_gtk_property!(widget(c), :expand, true)
Expand All @@ -83,8 +95,8 @@ function view(fcolor, g::Node{NodeData}; kwargs...)
push!(tb, tb_open)
push!(tb, tb_save_as)
# FIXME: likely have to do `allkwargs` in the two below (add in C, combine, recur)
signal_connect(open_cb, tb_open, "clicked", Nothing, (), false, (widget(c),kwargs))
signal_connect(save_as_cb, tb_save_as, "clicked", Nothing, (), false, (widget(c),g,kwargs))
signal_connect(open_cb, tb_open, "clicked", Nothing, (), false, (widget(c),gsig,kwargs))
signal_connect(save_as_cb, tb_save_as, "clicked", Nothing, (), false, (widget(c),data,lidict))
win = Window("Profile", 800, 600)
push!(win, bx)
GtkReactive.gc_preserve(win, c)
Expand All @@ -94,7 +106,8 @@ function view(fcolor, g::Node{NodeData}; kwargs...)
delete!(window_wrefs, win)
end

viewprof(fcolor, c, g; kwargs...)
fdraw = viewprof(fcolor, c, gsig; kwargs...)
GtkReactive.gc_preserve(win, fdraw)

# Ctrl-w and Ctrl-q destroy the window
signal_connect(win, "key-press-event") do w, evt
Expand All @@ -107,99 +120,103 @@ function view(fcolor, g::Node{NodeData}; kwargs...)
Gtk.showall(win)
end

function viewprof(fcolor, c, g; fontsize=14)
img = flamepixels(fcolor, g)
tagimg = flametags(g, img)
# The first column corresponds to the bottom row, which is our fake root node. Get rid of it.
img, tagimg = img[:,2:end], tagimg[:,2:end]
img24 = reverse(RGB24.(img), dims=2)
fv = XY(0.0..size(img24,1), 0.0..size(img24,2))
zr = Signal(ZoomRegion(fv, fv))
sigrb = init_zoom_rubberband(c, zr)
sigpd = init_pan_drag(c, zr)
sigzs = init_zoom_scroll(c, zr)
sigps = init_pan_scroll(c, zr)
surf = Cairo.CairoImageSurface(img24)
sigredraw = draw(c, zr) do widget, r
ctx = getgc(widget)
set_coordinates(ctx, r)
rectangle(ctx, BoundingBox(r.currentview))
set_source(ctx, surf)
p = Cairo.get_source(ctx)
Cairo.pattern_set_filter(p, Cairo.FILTER_NEAREST)
fill(ctx)
end
lasttextbb = BoundingBox(1,0,1,0)
sigmotion = map(c.mouse.motion) do btn
# Repair image from ovewritten text
if c.widget.is_realized && c.widget.is_sized
ctx = getgc(c)
if Graphics.width(lasttextbb) > 0
r = value(zr)
set_coordinates(ctx, r)
rectangle(ctx, lasttextbb)
set_source(ctx, surf)
p = Cairo.get_source(ctx)
Cairo.pattern_set_filter(p, Cairo.FILTER_NEAREST)
fill(ctx)
end
# Write the info
xu, yu = btn.position.x, btn.position.y
sf = gettag(xu, yu)
if sf != StackTraces.UNKNOWN
str = string(basename(string(sf.file)), ", ", sf.func, ": line ", sf.line)
set_source(ctx, fcolor(:font))
Cairo.set_font_face(ctx, "sans-serif $(fontsize)px")
xi = value(zr).currentview.x
xmin, xmax = minimum(xi), maximum(xi)
lasttextbb = deform(Cairo.text(ctx, xu, yu, str, halign = xu < (2xmin+xmax)/3 ? "left" : xu < (xmin+2xmax)/3 ? "center" : "right"), -2, 2, -2, 2)
end
reveal(c)
end
end
function viewprof(fcolor, c, gsig; fontsize=14)
# From a given position, find the underlying tag
function gettag(xu, yu)
function gettag(tagimg, xu, yu)
x = ceil(Int, Float64(xu))
y = ceil(Int, Float64(yu))
Y = size(tagimg, 2)
x = max(1, min(x, size(tagimg, 1)))
y = max(1, min(y, Y))
tagimg[x,Y-y+1]
end
# Left-click prints the full path, function, and line to the console
# Right-click calls the edit() function
sigshow = map(c.mouse.buttonpress) do btn
if btn.button == 1 || btn.button == 3
ctx = getgc(c)
xu, yu = btn.position.x, btn.position.y
sf = gettag(xu, yu)
if sf != StackTraces.UNKNOWN
if btn.button == 1
println(sf.file, ", ", sf.func, ": line ", sf.line)
elseif btn.button == 3
edit(string(sf.file), sf.line)
map(gsig) do g
isempty(g.data.span) && return nothing
img = flamepixels(fcolor, g)
tagimg = flametags(g, img)
# The first column corresponds to the bottom row, which is our fake root node. Get rid of it.
img, tagimg = img[:,2:end], tagimg[:,2:end]
img24 = reverse(RGB24.(img), dims=2)
fv = XY(0.0..size(img24,1), 0.0..size(img24,2))
zr = Signal(ZoomRegion(fv, fv))
sigrb = init_zoom_rubberband(c, zr)
sigpd = init_pan_drag(c, zr)
sigzs = init_zoom_scroll(c, zr)
sigps = init_pan_scroll(c, zr)
surf = Cairo.CairoImageSurface(img24)
sigredraw = draw(c, zr) do widget, r
ctx = getgc(widget)
set_coordinates(ctx, r)
rectangle(ctx, BoundingBox(r.currentview))
set_source(ctx, surf)
p = Cairo.get_source(ctx)
Cairo.pattern_set_filter(p, Cairo.FILTER_NEAREST)
fill(ctx)
end
lasttextbb = BoundingBox(1,0,1,0)
sigmotion = map(c.mouse.motion) do btn
# Repair image from ovewritten text
if c.widget.is_realized && c.widget.is_sized
ctx = getgc(c)
if Graphics.width(lasttextbb) > 0
r = value(zr)
set_coordinates(ctx, r)
rectangle(ctx, lasttextbb)
set_source(ctx, surf)
p = Cairo.get_source(ctx)
Cairo.pattern_set_filter(p, Cairo.FILTER_NEAREST)
fill(ctx)
end
# Write the info
xu, yu = btn.position.x, btn.position.y
sf = gettag(tagimg, xu, yu)
if sf != StackTraces.UNKNOWN
str = string(basename(string(sf.file)), ", ", sf.func, ": line ", sf.line)
set_source(ctx, fcolor(:font))
Cairo.set_font_face(ctx, "sans-serif $(fontsize)px")
xi = value(zr).currentview.x
xmin, xmax = minimum(xi), maximum(xi)
lasttextbb = deform(Cairo.text(ctx, xu, yu, str, halign = xu < (2xmin+xmax)/3 ? "left" : xu < (xmin+2xmax)/3 ? "center" : "right"), -2, 2, -2, 2)
end
reveal(c)
end
end
# Left-click prints the full path, function, and line to the console
# Right-click calls the edit() function
sigshow = map(c.mouse.buttonpress) do btn
if btn.button == 1 || btn.button == 3
ctx = getgc(c)
xu, yu = btn.position.x, btn.position.y
sf = gettag(tagimg, xu, yu)
if sf != StackTraces.UNKNOWN
if btn.button == 1
println(sf.file, ", ", sf.func, ": line ", sf.line)
elseif btn.button == 3
edit(string(sf.file), sf.line)
end
end
end
end
append!(c.preserved, [sigrb, sigpd, sigzs, sigps, sigredraw, sigmotion, sigshow])
return nothing
end
append!(c.preserved, [sigrb, sigpd, sigzs, sigps, sigredraw, sigmotion, sigshow])
return nothing
end

@guarded function open_cb(::Ptr, settings::Tuple)
c, kwargs = settings
c, gsig, kwargs = settings
selection = open_dialog("Load profile data", toplevel(c), ("*.jlprof","*"))
isempty(selection) && return nothing
data, lidict = load(File(format"JLD", selection), "li", "lidict")
return view(data; lidict=lidict, kwargs...)
data, lidict = load(selection)
push!(gsig, flamegraph(data; lidict=lidict, kwargs...))
return nothing
end

@guarded function save_as_cb(::Ptr, profdata::Tuple)
c, data, lidict = profdata
selection = save_dialog("Save profile data as JLD file", toplevel(c), ("*.jlprof",))
selection = save_dialog("Save profile data as *.jlprof file", toplevel(c), ("*.jlprof",))
isempty(selection) && return nothing
FileIO.save(File(format"JLD", selection), "li", data, "lidict", lidict)
nothing
FileIO.save(File(format"JLPROF", selection), data, lidict)
return nothing
end

# include("precompile.jl")
Expand Down