Skip to content

Commit

Permalink
Merge pull request #139 from timholy/teh/fg0.2
Browse files Browse the repository at this point in the history
Leverage FlameGraphs 0.2
timholy authored Feb 10, 2020
2 parents 26e550d + f48c65f commit 6c631e6
Showing 4 changed files with 108 additions and 113 deletions.
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 <tim.holy@gmail.com>"]
version = "0.6.2"
version = "0.6.3"

[deps]
Cairo = "159f3aea-2a34-519c-b102-8c37f9878175"
@@ -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"
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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:
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
@@ -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)
@@ -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)
@@ -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
@@ -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")

2 comments on commit 6c631e6

@timholy
Copy link
Owner Author

Choose a reason for hiding this comment

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

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

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

Registration pull request created: JuliaRegistries/General/9248

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if Julia TagBot is installed, or can be done manually through the github interface, or via:

git tag -a v0.6.3 -m "<description of version>" 6c631e640106d8648089b08b3a791f182e529259
git push origin v0.6.3

Please sign in to comment.