From 70dfbeadc03503db553612327bb860d16d7214c5 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 10 Feb 2020 13:03:17 -0600 Subject: [PATCH 1/2] Require FlameGraphs 0.2 This fixes #81 --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index b8ff0c8..9dc74ab 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ProfileView" uuid = "c46f51b8-102a-5cf2-8d2c-8597cb0e0da7" author = ["Tim Holy "] -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" From f48c65fadb74ced37631b660e75a2d137234517b Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 10 Feb 2020 13:03:42 -0600 Subject: [PATCH 2/2] Fix IO (fixes #73) --- README.md | 10 +++ README_save.md | 32 --------- src/ProfileView.jl | 175 +++++++++++++++++++++++++-------------------- 3 files changed, 106 insertions(+), 111 deletions(-) delete mode 100644 README_save.md diff --git a/README.md b/README.md index ae6a637..a440b95 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/README_save.md b/README_save.md deleted file mode 100644 index 77f008d..0000000 --- a/README_save.md +++ /dev/null @@ -1,32 +0,0 @@ -# This functionality is currently broken (looking for volunteers to fix it) - -(Once fixed it can move back to the README) - -- 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. Launching `ProfileView.view(nothing)` opens a blank - window; you can populate it with saved data by clicking on the - "open" icon. - -### Saving profile data manually - - If you're using the Gtk backend, the easiest approach is to click on - the "Save as" icon. - - From the REPL, you can save profile data for later viewing and analysis using the JLD file format. - The main trick is that the backtrace data, on its own, is only valid within a particular - julia session. To become portable, you have to save "line information" that looks - up the particular line number in the source code corresponding to a particular - machine instruction. Here's an example: - - ```julia - li, lidict = Profile.retrieve() - using JLD - @save "/tmp/foo.jlprof" li lidict - ``` - Now open a new julia session, and try the following: - ``` - using HDF5, JLD, ProfileView - @load "/tmp/foo.jlprof" - ProfileView.view(li, lidict=lidict) - ``` diff --git a/src/ProfileView.jl b/src/ProfileView.jl index 4f1269d..9997c8c 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -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,58 +120,9 @@ 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) @@ -166,40 +130,93 @@ function viewprof(fcolor, c, g; fontsize=14) 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")