From dcdaac33f97937b56c92209e5f3813fdd9b9963d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Fri, 10 Aug 2018 13:38:27 +0200 Subject: [PATCH 01/14] Fix Julia 0.7 deprecations. --- .travis.yml | 4 ++-- REQUIRE | 2 +- src/ProfileView.jl | 16 ++++++++-------- src/ProfileViewGtk.jl | 2 +- src/pvtree.jl | 2 +- src/tree.jl | 23 ++++++++++++----------- test/test.jl | 6 +++--- test/tree.jl | 5 +++-- 8 files changed, 31 insertions(+), 29 deletions(-) diff --git a/.travis.yml b/.travis.yml index abc5220..b52f862 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,8 @@ language: julia os: - linux julia: - - 0.5 - - 0.6 + - 0.7 + - 1.0 - nightly notifications: email: false diff --git a/REQUIRE b/REQUIRE index a4c0f68..d0177bd 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,4 +1,4 @@ -julia 0.5 +julia 0.7 Gtk GtkReactive 0.0.3 Cairo 0.5.1 diff --git a/src/ProfileView.jl b/src/ProfileView.jl index 7466e63..e654f29 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -14,13 +14,13 @@ using .PVTree include("svgwriter.jl") -immutable TagData +struct TagData ip::UInt status::Int end const TAGNONE = TagData(UInt(0), -1) -type ProfileData +mutable struct ProfileData img lidict imgtags @@ -33,11 +33,11 @@ const gccolor = colorant"red" const colors = distinguishable_colors(13, [bkg,fontcolor,gccolor], lchoices=Float64[65, 70, 75, 80], cchoices=Float64[0, 50, 60, 70], - hchoices=linspace(0, 330, 24))[4:end] + hchoices=range(0, stop=330, length=24))[4:end] function have_display() - !is_unix() && return true - is_apple() && return true + !Sys.isunix() && return true + Sys.isapple() && return true return haskey(ENV, "DISPLAY") end @@ -53,12 +53,12 @@ function __init__() @eval begin view(data = Profile.fetch(); C = false, lidict = nothing, colorgc = true, fontsize = 12, combine = true, pruned = []) = ProfileViewGtk.view(data; C=C, lidict=lidict, colorgc=colorgc, fontsize=fontsize, combine=combine, pruned=pruned) - closeall() = ProfileViewGtk.closeall() @doc """ closeall() Closes all windows opened by ProfileView. -""" -> closeall +""" + closeall() = ProfileViewGtk.closeall() end end pop!(LOAD_PATH) @@ -105,7 +105,7 @@ function prepare_data(data, lidict) bt, uip, counts, lidict, lkup end -prepare_data(::Void, ::Void) = nothing, nothing, nothing, nothing, nothing +prepare_data(::Nothing, ::Nothing) = nothing, nothing, nothing, nothing, nothing function prepare_image(bt, uip, counts, lidict, lkup, C, colorgc, combine, pruned) diff --git a/src/ProfileViewGtk.jl b/src/ProfileViewGtk.jl index 227c8a5..e9190ec 100644 --- a/src/ProfileViewGtk.jl +++ b/src/ProfileViewGtk.jl @@ -8,7 +8,7 @@ using Graphics using Gtk.GConstants.GdkModifierType: SHIFT, CONTROL, MOD1 -type ZoomCanvas +mutable struct ZoomCanvas bb::BoundingBox # in user-coordinates c::Canvas end diff --git a/src/pvtree.jl b/src/pvtree.jl index 7a60cae..28922f0 100644 --- a/src/pvtree.jl +++ b/src/pvtree.jl @@ -5,7 +5,7 @@ using ..Tree export PVData, buildgraph!, prunegraph! # ProfileView data we need attached to each node of the graph: -type PVData +mutable struct PVData ip::UInt # the instruction pointer hspan::UnitRange{Int} # horizontal span (used as the x-axis in display) status::Int # nonzero for special handling, (e.g., gc events) diff --git a/src/tree.jl b/src/tree.jl index ea8da73..62e01e8 100644 --- a/src/tree.jl +++ b/src/tree.jl @@ -1,6 +1,6 @@ module Tree -import Base: start, done, next, show +import Base: iterate, show export Node, addchild, @@ -16,14 +16,14 @@ export Node, # Any missing links (e.g., "c" does not have a sibling) link back to itself (c.sibling == c) # With this organization, no arrays need to be allocated. -type Node{T} +mutable struct Node{T} data::T parent::Node{T} child::Node{T} sibling::Node{T} # Constructor for the root of the tree - function (::Type{Node{T}}){T}(data::T) + function (::Type{Node{T}})(data::T) where T n = new{T}(data) n.parent = n n.child = n @@ -31,15 +31,15 @@ type Node{T} n end # Constructor for all others - function (::Type{Node{T}}){T}(data::T, parent::Node) + function (::Type{Node{T}})(data::T, parent::Node) where T n = new{T}(data, parent) n.child = n n.sibling = n n end end -Node{T}(data::T) = Node{T}(data) -Node{T}(data::T, parent::Node{T}) = Node{T}(data, parent) +Node(data::T) where {T} = Node{T}(data) +Node(data::T, parent::Node{T}) where {T} = Node{T}(data, parent) function lastsibling(sib::Node) newsib = sib.sibling @@ -50,7 +50,7 @@ function lastsibling(sib::Node) sib end -function addsibling{T}(oldersib::Node{T}, data::T) +function addsibling(oldersib::Node{T}, data::T) where T if oldersib.sibling != oldersib error("Truncation of sibling list") end @@ -59,7 +59,7 @@ function addsibling{T}(oldersib::Node{T}, data::T) youngersib end -function addchild{T}(parent::Node{T}, data::T) +function addchild(parent::Node{T}, data::T) where T newc = Node(data, parent) prevc = parent.child if prevc == parent @@ -80,9 +80,10 @@ show(io::IO, n::Node) = print(io, n.data) # for c in parent # # do something # end -start(n::Node) = n.child -done(n::Node, state::Node) = n == state -next(n::Node, state::Node) = state, state == state.sibling ? n : state.sibling +function iterate(n::Node, state = n.child) + n == state && return nothing + return state, state == state.sibling ? n : state.sibling +end function showedges(io::IO, parent::Node, printfunc = identity) str = printfunc(parent.data) diff --git a/test/test.jl b/test/test.jl index efc9b5a..8c9b9f4 100644 --- a/test/test.jl +++ b/test/test.jl @@ -4,10 +4,10 @@ function profile_test(n) for i = 1:n A = randn(100,100,20) m = maximum(A) - Afft = fft(A) - Am = mapslices(sum, A, 2) + # Afft = fft(A) + Am = mapslices(sum, A, dims = 2) B = A[:,:,5] - Bsort = mapslices(sort, B, 1) + Bsort = mapslices(sort, B, dims = 1) b = rand(100) C = B.*b end diff --git a/test/tree.jl b/test/tree.jl index c84f70e..5d1e40d 100644 --- a/test/tree.jl +++ b/test/tree.jl @@ -3,7 +3,7 @@ root = Tree.Node(0) @assert Tree.isleaf(root) nchildren = 0 for c in root - nchildren += 1 + global nchildren += 1 end @assert nchildren == 0 c1 = Tree.addchild(root, 1) @@ -17,7 +17,7 @@ c22 = Tree.addchild(c2, 5) nchildren = 0 for c in root @assert !Tree.isroot(c) - nchildren += 1 + global nchildren += 1 end @assert nchildren == 3 @assert Tree.isleaf(c1) @@ -30,5 +30,6 @@ end children2 = [c21,c22] i = 0 for c in c2 + global i @assert c == children2[i+=1] end From f67b9c1cacffbdaf7fec1d2e99f2c92f85d7fba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Fri, 10 Aug 2018 14:56:06 +0200 Subject: [PATCH 02/14] Attempt to fix conditional submodule import. --- src/ProfileView.jl | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ProfileView.jl b/src/ProfileView.jl index e654f29..356e639 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -41,15 +41,17 @@ function have_display() return haskey(ENV, "DISPLAY") end +include("ProfileViewSVG.jl") +include("ProfileViewGtk.jl") + function __init__() - push!(LOAD_PATH, splitdir(@__FILE__)[1]) if (isdefined(Main, :IJulia) && !isdefined(Main, :PROFILEVIEW_USEGTK)) || !have_display() - eval(Expr(:import, :ProfileViewSVG)) + eval(Expr(:import, Symbol(".ProfileViewSVG"))) @eval begin view(data = Profile.fetch(); C = false, lidict = nothing, colorgc = true, fontsize = 12, combine = true, pruned = []) = ProfileViewSVG.view(data; C=C, lidict=lidict, colorgc=colorgc, fontsize=fontsize, combine=combine, pruned=pruned) end else - eval(Expr(:import, :ProfileViewGtk)) + eval(Expr(:import, Symbol(".ProfileViewGtk"))) @eval begin view(data = Profile.fetch(); C = false, lidict = nothing, colorgc = true, fontsize = 12, combine = true, pruned = []) = ProfileViewGtk.view(data; C=C, lidict=lidict, colorgc=colorgc, fontsize=fontsize, combine=combine, pruned=pruned) From 8fe3b021a1e091489029765eaffdd5b6707ee0f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Sat, 11 Aug 2018 16:08:15 +0200 Subject: [PATCH 03/14] Simplify constructurs. Relax dispatch for addsibling and addchild. Remove commented out fft call during testing. --- src/tree.jl | 10 +++++----- test/test.jl | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/tree.jl b/src/tree.jl index 62e01e8..d1f23ae 100644 --- a/src/tree.jl +++ b/src/tree.jl @@ -23,7 +23,7 @@ mutable struct Node{T} sibling::Node{T} # Constructor for the root of the tree - function (::Type{Node{T}})(data::T) where T + function Node{T}(data) where T n = new{T}(data) n.parent = n n.child = n @@ -31,7 +31,7 @@ mutable struct Node{T} n end # Constructor for all others - function (::Type{Node{T}})(data::T, parent::Node) where T + function Node{T}(data, parent::Node) where T n = new{T}(data, parent) n.child = n n.sibling = n @@ -39,7 +39,7 @@ mutable struct Node{T} end end Node(data::T) where {T} = Node{T}(data) -Node(data::T, parent::Node{T}) where {T} = Node{T}(data, parent) +Node(data, parent::Node{T}) where {T} = Node{T}(data, parent) function lastsibling(sib::Node) newsib = sib.sibling @@ -50,7 +50,7 @@ function lastsibling(sib::Node) sib end -function addsibling(oldersib::Node{T}, data::T) where T +function addsibling(oldersib::Node{T}, data) where T if oldersib.sibling != oldersib error("Truncation of sibling list") end @@ -59,7 +59,7 @@ function addsibling(oldersib::Node{T}, data::T) where T youngersib end -function addchild(parent::Node{T}, data::T) where T +function addchild(parent::Node{T}, data) where T newc = Node(data, parent) prevc = parent.child if prevc == parent diff --git a/test/test.jl b/test/test.jl index 8c9b9f4..bb98eb1 100644 --- a/test/test.jl +++ b/test/test.jl @@ -4,7 +4,6 @@ function profile_test(n) for i = 1:n A = randn(100,100,20) m = maximum(A) - # Afft = fft(A) Am = mapslices(sum, A, dims = 2) B = A[:,:,5] Bsort = mapslices(sort, B, dims = 1) From 069910b49bc34ee2140e0c4a55e94562fee907c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Sun, 12 Aug 2018 10:19:33 +0200 Subject: [PATCH 04/14] Revert "Attempt to fix conditional submodule import." This reverts commit f67b9c1cacffbdaf7fec1d2e99f2c92f85d7fba5. --- src/ProfileView.jl | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/ProfileView.jl b/src/ProfileView.jl index 356e639..e654f29 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -41,17 +41,15 @@ function have_display() return haskey(ENV, "DISPLAY") end -include("ProfileViewSVG.jl") -include("ProfileViewGtk.jl") - function __init__() + push!(LOAD_PATH, splitdir(@__FILE__)[1]) if (isdefined(Main, :IJulia) && !isdefined(Main, :PROFILEVIEW_USEGTK)) || !have_display() - eval(Expr(:import, Symbol(".ProfileViewSVG"))) + eval(Expr(:import, :ProfileViewSVG)) @eval begin view(data = Profile.fetch(); C = false, lidict = nothing, colorgc = true, fontsize = 12, combine = true, pruned = []) = ProfileViewSVG.view(data; C=C, lidict=lidict, colorgc=colorgc, fontsize=fontsize, combine=combine, pruned=pruned) end else - eval(Expr(:import, Symbol(".ProfileViewGtk"))) + eval(Expr(:import, :ProfileViewGtk)) @eval begin view(data = Profile.fetch(); C = false, lidict = nothing, colorgc = true, fontsize = 12, combine = true, pruned = []) = ProfileViewGtk.view(data; C=C, lidict=lidict, colorgc=colorgc, fontsize=fontsize, combine=combine, pruned=pruned) From 6e0d29144b2f08ac5eb798d699e0746ef4df0188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Fri, 17 Aug 2018 10:49:05 +0200 Subject: [PATCH 05/14] Make eval of import robust to AST changes. --- src/ProfileView.jl | 4 ++-- src/ProfileViewGtk.jl | 2 +- src/ProfileViewSVG.jl | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ProfileView.jl b/src/ProfileView.jl index e654f29..4cedd7a 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -44,12 +44,12 @@ end function __init__() push!(LOAD_PATH, splitdir(@__FILE__)[1]) if (isdefined(Main, :IJulia) && !isdefined(Main, :PROFILEVIEW_USEGTK)) || !have_display() - eval(Expr(:import, :ProfileViewSVG)) + @eval import ProfileViewSVG @eval begin view(data = Profile.fetch(); C = false, lidict = nothing, colorgc = true, fontsize = 12, combine = true, pruned = []) = ProfileViewSVG.view(data; C=C, lidict=lidict, colorgc=colorgc, fontsize=fontsize, combine=combine, pruned=pruned) end else - eval(Expr(:import, :ProfileViewGtk)) + @eval import ProfileViewGtk @eval begin view(data = Profile.fetch(); C = false, lidict = nothing, colorgc = true, fontsize = 12, combine = true, pruned = []) = ProfileViewGtk.view(data; C=C, lidict=lidict, colorgc=colorgc, fontsize=fontsize, combine=combine, pruned=pruned) diff --git a/src/ProfileViewGtk.jl b/src/ProfileViewGtk.jl index e9190ec..606c1ef 100644 --- a/src/ProfileViewGtk.jl +++ b/src/ProfileViewGtk.jl @@ -14,7 +14,7 @@ mutable struct ZoomCanvas end function __init__() - eval(Expr(:import, :ProfileView)) + @eval import ProfileView end function closeall() diff --git a/src/ProfileViewSVG.jl b/src/ProfileViewSVG.jl index e8c8e07..cb9b498 100644 --- a/src/ProfileViewSVG.jl +++ b/src/ProfileViewSVG.jl @@ -3,7 +3,7 @@ __precompile__() module ProfileViewSVG function __init__() - eval(Expr(:import, :ProfileView)) + @eval import ProfileView end function view(data = Profile.fetch(); C = false, lidict = nothing, colorgc = true, fontsize = 12, combine = true, pruned = true) From bc0dcb9ae7caca2dd07145b22ebfe72005ddf68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Fri, 17 Aug 2018 10:50:19 +0200 Subject: [PATCH 06/14] Use __DIR__ macro directly instead of extracting dir name from __FILE__. --- src/ProfileView.jl | 2 +- src/svgwriter.jl | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ProfileView.jl b/src/ProfileView.jl index 4cedd7a..f44a6a0 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -42,7 +42,7 @@ function have_display() end function __init__() - push!(LOAD_PATH, splitdir(@__FILE__)[1]) + push!(LOAD_PATH, @__DIR__) if (isdefined(Main, :IJulia) && !isdefined(Main, :PROFILEVIEW_USEGTK)) || !have_display() @eval import ProfileViewSVG @eval begin diff --git a/src/svgwriter.jl b/src/svgwriter.jl index e6e115d..fed5f0d 100644 --- a/src/svgwriter.jl +++ b/src/svgwriter.jl @@ -1,5 +1,5 @@ -const snapsvgjs = joinpath(dirname(@__FILE__), "..", "templates", "snap.svg-min.js") -const viewerjs = joinpath(dirname(@__FILE__), "viewer.js") +const snapsvgjs = joinpath(@__DIR__, "..", "templates", "snap.svg-min.js") +const viewerjs = joinpath(@__DIR__, "viewer.js") function escape_script(js::AbstractString) return replace(js, "]]", "] ]") From 55a99afcf43edd705a92b8273b30574110268d71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Fri, 17 Aug 2018 13:23:31 +0200 Subject: [PATCH 07/14] Add using Profile. --- src/ProfileView.jl | 1 + test/test.jl | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ProfileView.jl b/src/ProfileView.jl index f44a6a0..39ced1b 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -2,6 +2,7 @@ __precompile__() module ProfileView +using Profile using Colors import Base: contains, isequal, show, mimewritable diff --git a/test/test.jl b/test/test.jl index bb98eb1..222d9fc 100644 --- a/test/test.jl +++ b/test/test.jl @@ -1,3 +1,4 @@ +using Profile using ProfileView function profile_test(n) From 754f61a0e7cc7323387e3436623229808baa0ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Fri, 17 Aug 2018 13:24:52 +0200 Subject: [PATCH 08/14] Fix more deprecations. --- src/ProfileView.jl | 12 ++++++------ src/pvtree.jl | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ProfileView.jl b/src/ProfileView.jl index 39ced1b..cc80aad 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -5,7 +5,7 @@ module ProfileView using Profile using Colors -import Base: contains, isequal, show, mimewritable +import Base: isequal, show include("tree.jl") include("pvtree.jl") @@ -98,7 +98,7 @@ function prepare_data(data, lidict) # Do code address lookups on all unique instruction pointers uip = unique(vcat(bt...)) if lidict == nothing - lkup = Vector{StackFrame}[Profile.lookup(ip) for ip in uip] + lkup = Vector{StackTraces.StackFrame}[Profile.lookup(ip) for ip in uip] lidict = Dict(zip(uip, lkup)) else lkup = [lidict[ip] for ip in uip] @@ -171,7 +171,7 @@ function svgwrite(filename::AbstractString; kwargs...) end -mimewritable(::MIME"image/svg+xml", pd::ProfileData) = true +Base.showable(::MIME"image/svg+xml", pd::ProfileData) = true function show(f::IO, ::MIME"image/svg+xml", pd::ProfileData) img = pd.img @@ -268,7 +268,7 @@ function buildtags!(rowtags, parent, level) end t = rowtags[level] for c in parent - t[c.data.hspan] = TagData(c.data.ip, c.data.status) + t[c.data.hspan] .= Ref(TagData(c.data.ip, c.data.status)) buildtags!(rowtags, c, level+1) end end @@ -319,10 +319,10 @@ end function fillrow!(img, j, rng::UnitRange{Int}, colorindex, colorlen, regcolor, gccolor, status) if status > 0 - img[rng,j] = gccolor + img[rng,j] .= gccolor return colorindex else - img[rng,j] = regcolor + img[rng,j] .= regcolor return mod1(colorindex+1, colorlen) end end diff --git a/src/pvtree.jl b/src/pvtree.jl index 28922f0..d48b257 100644 --- a/src/pvtree.jl +++ b/src/pvtree.jl @@ -37,9 +37,9 @@ function buildgraph!(parent::Node, bt::Vector{Vector{UInt}}, counts::Vector{Int} end end ngroups = length(dorder) - group = Vector{Vector{Int}}(ngroups) # indices in bt that have the same sortorder - n = Array{Int}(ngroups) # aggregated counts for this group - order = Array{Int}(ngroups) + group = Vector{Vector{Int}}(undef, ngroups) # indices in bt that have the same sortorder + n = Array{Int}(undef, ngroups) # aggregated counts for this group + order = Array{Int}(undef, ngroups) i = 1 for (key, v) in dorder order[i] = key From 52a45c58a225baedc352e01bd1bc4521e2423cba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Fri, 17 Aug 2018 13:25:53 +0200 Subject: [PATCH 09/14] tree_aggregate is gone from Profile. Revive a local copy. --- src/ProfileView.jl | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/ProfileView.jl b/src/ProfileView.jl index cc80aad..8946bfe 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -71,7 +71,7 @@ function prepare(data; C = false, lidict = nothing, colorgc = true, combine = tr end function prepare_data(data, lidict) - bt, counts = Profile.tree_aggregate(data) + bt, counts = tree_aggregate(data) if isempty(counts) Profile.warning_empty() error("Nothing to view") @@ -408,4 +408,27 @@ function pushpruned!(pruned_ips, pruned, lidict) end end +## A tree representation +# Identify and counts repetitions of all unique backtraces +function tree_aggregate(data::Vector{UInt64}) + iz = findall(iszero, data) # find the breaks between backtraces + treecount = Dict{Vector{UInt64},Int}() + istart = 1 + for iend in iz + tmp = data[iend - 1 : -1 : istart] + treecount[tmp] = get(treecount, tmp, 0) + 1 + istart = iend + 1 + end + bt = Vector{Vector{UInt64}}(undef, 0) + counts = Vector{Int}(undef, 0) + for (k, v) in treecount + if !isempty(k) + push!(bt, k) + push!(counts, v) + end + end + return (bt, counts) +end + + end From 837daa128c53a03a987ea4f0ec6401d719aa9b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnar=20Farneb=C3=A4ck?= Date: Mon, 27 Aug 2018 12:58:40 +0200 Subject: [PATCH 10/14] Fix ProfileViewGtk deprecations. --- src/ProfileViewGtk.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ProfileViewGtk.jl b/src/ProfileViewGtk.jl index 606c1ef..a90b31e 100644 --- a/src/ProfileViewGtk.jl +++ b/src/ProfileViewGtk.jl @@ -24,13 +24,13 @@ function closeall() nothing end -const window_wrefs = WeakKeyDict{Gtk.GtkWindowLeaf,Void}() +const window_wrefs = WeakKeyDict{Gtk.GtkWindowLeaf,Nothing}() function view(data = Profile.fetch(); lidict=nothing, kwargs...) bt, uip, counts, lidict, lkup = ProfileView.prepare_data(data, lidict) # Display in a window c = canvas(UserUnit) - setproperty!(widget(c), :expand, true) + set_gtk_property!(widget(c), :expand, true) f = Frame(c) tb = Toolbar() bx = Box(:v) @@ -40,8 +40,8 @@ function view(data = Profile.fetch(); lidict=nothing, kwargs...) tb_save_as = ToolButton("gtk-save-as") push!(tb, tb_open) push!(tb, tb_save_as) - signal_connect(open_cb, tb_open, "clicked", Void, (), false, (widget(c),kwargs)) - signal_connect(save_as_cb, tb_save_as, "clicked", Void, (), false, (widget(c),data,lidict,kwargs)) + 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),data,lidict,kwargs)) win = Window(bx, "Profile") GtkReactive.gc_preserve(win, c) # Register the window with closeall @@ -57,17 +57,17 @@ function view(data = Profile.fetch(); lidict=nothing, kwargs...) # Ctrl-w and Ctrl-q destroy the window signal_connect(win, "key-press-event") do w, evt if evt.state == CONTROL && (evt.keyval == UInt('q') || evt.keyval == UInt('w')) - @schedule destroy(w) + @async destroy(w) nothing end end - showall(win) + Gtk.showall(win) end function viewprof(c, bt, uip, counts, lidict, lkup; C = false, colorgc = true, fontsize = 12, combine = true, pruned=[]) img, lidict, imgtags = ProfileView.prepare_image(bt, uip, counts, lidict, lkup, C, colorgc, combine, pruned) - img24 = UInt32[reinterpret(UInt32, convert(RGB24, img[i,j])) for i = 1:size(img,1), j = size(img,2):-1:1]' + img24 = Matrix(UInt32[reinterpret(UInt32, convert(RGB24, img[i,j])) for i = 1:size(img,1), j = size(img,2):-1:1]') fv = XY(0.0..size(img24,2), 0.0..size(img24,1)) zr = Signal(ZoomRegion(fv, fv)) sigrb = init_zoom_rubberband(c, zr) From 8c6a055079df351d46ce810d23dfc5c14d0b81cc Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 30 Aug 2018 05:37:30 -0500 Subject: [PATCH 11/14] Update the README for the new version Also eliminates a warning about InteractiveUtils --- README.md | 104 +++++++++++++----------------------------- readme_images/pv1.jpg | Bin 39735 -> 24729 bytes readme_images/pv2.jpg | Bin 22398 -> 0 bytes src/ProfileViewGtk.jl | 3 +- 4 files changed, 33 insertions(+), 74 deletions(-) delete mode 100644 readme_images/pv2.jpg diff --git a/README.md b/README.md index 096690e..359b03c 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,8 @@ [![Build Status](https://travis-ci.org/timholy/ProfileView.jl.svg)](https://travis-ci.org/timholy/ProfileView.jl) -# NEWS - -Version 0.2 of ProfileView has several major changes: - -- Red highlighting has been restored; it now highlights - type-instabilities, not garbage collection, as a means to better - distinguish legitimate uses of allocation from ones that might be - avoidable. - -- For users of the Gtk version, ProfileView has switched from - [GtkUtilities](https://github.com/timholy/GtkUtilities.jl) to - [GtkReactive](https://github.com/JuliaGizmos/GtkReactive.jl). One - consequence is that you now need to hold down Ctrl for any zoom - operation. You can now shift the displayed region by click-dragging - on the image. - -- Also for users of the Gtk version, Ctrl-q and Ctrl-w close the - window. You can also use `ProfileView.closeall()` to close all - windows opened by ProfileView. +Note this README is for users of Julia 0.7 and higher; users of earlier versions +should see [this page](https://github.com/timholy/ProfileView.jl/tree/julia0.6). # Introduction @@ -51,16 +34,16 @@ function profile_test(n) for i = 1:n A = randn(100,100,20) m = maximum(A) - Afft = fft(A) - Am = mapslices(sum, A, 2) + Am = mapslices(sum, A; dims=2) B = A[:,:,5] - Bsort = mapslices(sort, B, 1) + Bsort = mapslices(sort, B; dims=1) b = rand(100) C = B.*b end end profile_test(1) # run once to trigger compilation +using Profile Profile.clear() # in case we have any previous profiling data @profile profile_test(10) ``` @@ -74,14 +57,21 @@ If you're following along, you should see something like this: ![ProfileView](readme_images/pv1.jpg) -This plot is a visual representation of the *call graph* of the code that you just profiled. The "root" of the tree is at the bottom; if you move your mouse over the long horizontal magenta bar at the bottom, you'll see it's an anonymous function in `REPL.jl`; the orangish one above that is `eval_user_input` in the same function. As is explained [elsewhere](http://docs.julialang.org/en/latest/stdlib/profile/), these are what run your code in the REPL. If you move your mouse upwards, you'll eventually get to the function(s) you ran with `@profile`. +(Note that collected profiles can vary from run-to-run, so don't be alarmed +if you get something different.) +This plot is a visual representation of the *call graph* of the code that you just profiled. +The "root" of the tree is at the bottom; if you move your mouse the long horizontal +bars near the bottom, you should fine one for `eval_user_input` in REPL.jl. +As is explained [elsewhere](http://docs.julialang.org/en/latest/stdlib/profile/), +these are what run your code in the REPL. +If you move your mouse upwards, you'll eventually get to the function(s) you ran with `@profile`. While the vertical axis therefore represents nesting depth, the horizontal axis represents the amount of time (more precisely, the number of backtraces) spent at each line. One sees on the 4th line from the bottom, there are several differently-colored bars, each corresponding to a different line of `profile_test`. The fact that -they are all positioned on top of the lower magenta bar means that all +they are all positioned on top of the lower peach-colored bar means that all of these lines are called by the same "parent" function. Within a block of code, they are sorted in order of increasing line number, to make it easier for you to compare to the source code. @@ -89,68 +79,38 @@ make it easier for you to compare to the source code. From this visual representation, we can very quickly learn several things about this function: -- The most deeply-nested line corresponds to `mapslices(sort, B, 1)`, - resulting in the tall "stack" of bars on the right edge. However, - this call does not take much time, because these bars are narrow - horizontally. +- The most deeply-nested call corresponds to the `mapslices(sort, B; dims=1)` call. + (If you hover over the top-most bars you will see they correspond to lines in `sort.jl`.) + In contrast, the call to `maximum` (the lowest blue bar) resolves to just two (non-inlined) calls. -- In contrast, the two most time-consuming operations are the calls to - `fft` and `mapslices(sum, A, 2)`. (This is more time-consuming than - the `mapslices(sort,...)` simply because it has to process more +- `mapslices(sum, A; dims=2)` is considerably more expensive than + `mapslices(sort, B; dims=1)`. (This is because it has to process more data.) -One thing we haven't yet discussed is the difference between the red -bars and the more pastel-colored bars. To explore this difference, -let's consider a different function: - -```julia -unstable(x) = x > 0.5 ? true : 0.0 - -function profile_unstable_test(m, n) - s = s2 = 0 - for i = 1:n - for k = 1:m - s += unstable(rand()) - end - x = collect(1:20) - s2 += sum(x) - end - s, s2 -end - -profile_unstable_test(1, 1) -Profile.clear() -@profile profile_unstable_test(10, 10^6) -ProfileView.view() -``` - -The main thing to note about this function is that the function -`unstable` does not have inferrable return type (a.k.a., it is -type-unstable); it can return either a `Bool` or a `Float64` depending -on the *value* (not type) of `x`. When we visualize the profiling -results for this function, we see something like the following: - -![ProfileView](readme_images/pv2.jpg) - -In this plot, red is a special color: it is reserved for function -calls that are deduced to be non-inferrable (by virtue of their +It is also worth noting that red is a special color: it is reserved for function +calls that have to be resolved at run-time (by virtue of their execution of the C functions `jl_invoke` or -`jl_apply_generic`). Because type-instability often has a significant +`jl_apply_generic`). Because run-time dispatch (aka, run-time method lookup or +a virtual call) often has a significant impact on performance, we highlight the problematic call in red. It's worth noting that some red is unavoidable; for example, the REPL can't predict in advance the return types from what users type at the -prompt. Red bars are problematic only when they account for a sizable +prompt, and so `eval_user_input` is red. +Red bars are problematic only when they account for a sizable fraction of the top "row," as only in such cases are they likely to be -the source of a significant performance bottleneck. In our first -example, we can see that `mapslices` is (internally) non-inferrable; +the source of a significant performance bottleneck. +We can see that `mapslices` relies on run-time dispatch; from the absence of pastel-colored bars above much of the red, we -might guess that this type-instability makes a substantial +might guess that this makes a substantial contribution to its total run time. ## GUI features ### Gtk Interface +- Ctrl-q and Ctrl-w close the window. You can also use + `ProfileView.closeall()` to close all windows opened by ProfileView. + - Left-clicking on a bar will cause information about this line to be printed in the REPL. This can be a convenient way to "mark" lines for later investigation. @@ -164,7 +124,7 @@ contribution to its total run time. with CTRL-scroll. You can also use your keyboard (arrow keys, plus SHIFT and CTRL modifiers). Double-click to restore the full view. -- To use the Gtk interface in IJulia, set `PROFILEVIEW_USEGTK = true` in +- To use the Gtk interface in Juno or IJulia, set `PROFILEVIEW_USEGTK = true` in the `Main` module before `using ProfileView`. - The toolbar at the top contains two icons to load and save profile diff --git a/readme_images/pv1.jpg b/readme_images/pv1.jpg index 2aa20d43cbe2bbfc4e5b561aa622e5989c72a5f6..7d4414914be19c7d1b2836a20fd5121c97b70d44 100644 GIT binary patch literal 24729 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 literal 39735 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 diff --git a/readme_images/pv2.jpg b/readme_images/pv2.jpg deleted file mode 100644 index 9c807a3afb4834762cefb26f69aea6299230cab3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22398 zcmeHv2|Sc*-|$^XmXZ(^rlM4mN+{d3*^(T}QkKb1l8`;_R?5>3y>5)pi~(rVSrv5^Xw8~6 z&=v3xV)Q_#Ar>ZPW@aW9@W#TzvW}H=Ju7%|ad5D4a&v9o%+1Bk&9g<2pNDrFA2+wa zPJwMgLMRl9hkuvoE@4qYVU+N~@S=X^{T)%#!FfTW+@PGKlcn5K>Un92mIn$b5 z&|2;_Ox$Z2A0QM2tziMsegpWo*P69V%mB*uZ0sChLhdGL?HVSgwE%vA0GRC!u0zb+ zESq;8KDmxZ+mv;eBkz$r!7tZ~oXUF7r_($yDrM$$myLZ3zrfaQyZ4Cg-M9a!w2bVr z<8r6ZD66Qdsh_=YQCCmj;L>Gt3rj0&8{4bSH(gxa+&w(+-GAWg_wbQ_Na)kB@Q7#6 zBje&}wEm9S4|s6{yw(D?FtdKcYt33WFqpWRS#};?xA~+ttEnT;t|NEW z^PUQRnf0DcL`r9#&&;WreT(SP!QBhrQ2Q0LzlPY|{}g6_N9-SXbwiv?YXI|@xFIw& zAhFB0qg&p~y<>0uFDWU9pp2x`^D$%DFB_6S_jMQgxl*0fLPer)R*57y$xp|>X&;$? zmQ%R-h_Z{rSfj^#mWKiq^QEKH`WHt9mLeuHG`DShWqB}|ZX7tH)G`G2DUzA@TO2%>{fGx8p0CFws_poBe5JXp zsAq=tWLa5Bc&GL~t^7BwBE@(^r%r9rM9ZM~M*+9K$OSKQ7X%jz`sXQad0P~rnTvFV z$TwWcYTae071mjAGkheE0rf^$h(Jp6(K-bNTJ~HOu3IG8O|Bj`(sWvp zJ9lK{?srRMY!xldIo zIV<|tYLSb3b=F}OhRjzMTlccBaM~24na#y}-}J2CCbn#a^>9nf!%BY%iOA1(dlGd^ zGD4=yC^}rSazH>*U?94G^MwAkT|JtjHKP1yFALS0MF@J6qBu)E2}xN;jSg4?_UZxd znjHWue)|Armjx{udT*at+endZww?8g604{5 zu`T`FO3$abvY4canU7sk``LPx)8aK=cNv+nwtQkbe1nY(nmR94s3ox7_s~i2#AnFv zdF6>t$(gy~j-5wXZu{ov>)>|;F3G#gV|N!0-xi69bP^fv+&`h+b|m=j`fBM%3}}6l zA3VYDz<_Rb#8PBCqMnDXbgV4rCZ-Qg8*jgeAv5%e(G^yB9 z26TGka|YzoPut6Ye4RV-6Q-@`xgG}e!2IF+lu6@Ptwrq{!^OMuEtUxIh`V=hM$Gc- zj1QqXS^3HV+x=cnUud|o=jq$K{I_2OUh&k=VL%nfr~8SACUGk>45%DSvR3p$e1)VC zI-aC^nxjru$)5Hp;h~Pd9_-A3L_PwoD$PW;HkmM>mwx9M5Z(ddg;y<+x9m_v?Ib^^ z>UPnaXjoprGbY^b z`d5*_T2V7g;b&eyarnuH2Y)V0?K6`41+D2z0z1{ZTsSaqYmGMRDtrhPzt?&Aap6GZ zQvY9v091rF2cnO8#!|^hk-Az!f=n zQ&W%qha=mzNDv0^MoUR;sQoM!sGqP}8CU7E_Sb%wmCQxdZ+b(ZqImne%APiVyjFG2 z*XHGy^hGv~@ZKh|@neL%*hmphz$s;(R*ON^-OQ`mKS2bV9a{2QlN+&N~Y z@_%XuR%?(@Tc%lYk<@8(d$aq{&isSgjtEuC$D%z*%($eL_WJF?v=GbZY1cWVO9GQb zoYqQFJoVH6IVSwSVX|m%{c(k#a=UFNr$8|EvE#FR{N z`3P4ltK*{4A$HyP)w1$~VEnq(0U$N?z$vA&Qku~=(D$nnNBr%SU7@f0!-kYyr z-O1-x(cRGZAmftVmIEvwU)Alme))dF`{4lHuJZ@^{Zp8TFzYR4l1HX-=EH+78s74H zGDBPZeu)}55J+8iti>Gq}`W8MjQad+!)961juY_wVXk*|SE&_-oQ!*NT2 zRnQhyEOp(AQ|cD>4<5&%uxr9pt(QseT2;bZ7A|6iAEa<38qI%F-}2SmwRcL^+-ZDk zh^!|hrPyB4B{up~8U2MTw|X$AzR!Q?$p8s*>NHWIZUnrbdmRn#U%bq)3y zqi3ACb}h1VyK4#v;m429n8X|9hE7FB1YL94N>hAo8=hC>@`ZE7sV8qdM8oQrxP3;% zM}+gNXQym7>7y4U5%sD^_^;e<3@FZ|h`nl|A*?ZBAl>c!+kl3mqSlNZ4`f-s%xvEu z7`-M$Rm|V~IBwHbN!CD@eZ+JfwK1whY0wB}KCrJp<%mZ}hUkN^lj2Tng3D>%V(%}* z6*%ajB<-|K`39$cZH>vHLmsDZ?K)$$MKs5D7jsUk+;#ltS?zxVl@4a`b+zZUOcl#_u`ekLXhy&DKqxH zaku}i#{T285(8B@1?}sTrSF=?wP70rxM?Y$PRA^~;5e%5FvGz`MnF*kpNbs)dF>}% ze#*p8efY1l1s4zX8lE5BS>Ovwpdxo2Nf+CoOK|md6lFPr0iAlBCoWuvoW8+;I&<(8 zUY9KOAQoP2HB)8YU)KB^4Au<`sfe1beR4 zjvv`)%YcmbFrZKIi5E8g897fg^J**ae88)H0+97THuyrh9v!`un;lo>RYa588Bpt3 zdspjN)d{bTTqfHjE3od`Kf1XzmYCC*!yEO?*^RP+G@4$8@>Aags0hP z1UWvRv-w#ZAS}` zoEG7(Tp{Y=;+4&u)>CEe=E!DEeguXIJfI$Gw~bFUQy{OD)yf&S4L_m>JbC5_5Dn4H z9hnBCY!Ca_q*Hx{#}(QPmvmf%o5B&iwu$a2K55z{gX>VasV!A*$$YD;lRCWt=ltZ9 z*1EK!?5*&KC3>7BUQD_KYN0cJ9T+d;-Y#F4 zPZ&782avt~8Y@%mEByyrN8YEos24S7C+zmIw4+|l=$W&MDLzWUMJZ$oakloH4ScG5 z>s-{R>*&qPI;H7WL@g_#YKitGeqqt(E3s<3OM67GX;(cRXBHH0Ya*S^L6I2{P!LJL zr%*&4QPk(T%4VRLtNVfhH8CLMB#i9InDk8dO&as|g=On1f!;-IZIrt+ zVVm;gdQGT;gpF$CFSedXW_Y#ru2^2}C-8#0a0U-Nq20QVl23aefu6qKPvN~;YsMi- z-cZ4SOj;Pwgm%YA^RDkr*4`_3{rA2)^qYQu)6u^=m}+if$PyuNXG0Yd;JK7SFy==z zEO1MUj=)g#m0nFmu}i1Z)IHzC`R@+|o2*VuN(_hiX}-Gwe;?tA`rBr2gYfEE9j+>e#$oew9e35f?!hYI8p*^$?3C8 zk&7f}dW~nc30Ty}=OM`7R9c6Gm9&Z6FnxvfUe<}J%^@q zNT#4#hN4;mU^_lzhT^{w-WgZ7sA{XstDkY}WLPR(t<#DWXoA*(>baz;Rbuw=0DeT4 zi|(cI?V=&yM^e%U)O=8bo%gatfs+JspddFk_yUd}Hu@E`ExM0fpk|poII6&$jfKM+oL4{2af0P?n75 zL#z|EO{^IZhavI;O+L&AU3)Yi&+pT4Z4oY4EWUn>Y9&8R z4Fi(HQH3PHG#tn;UB-vT-h}>=9-m7^R3AMz+cz4fD|lfvR6mb$wFr}~7~;QxCz+s^ zfL{YPi2>QgBm7wUSq4N#GobG2h-nf$M%y~hfUXaq$HxKM)yrqaf!+?fGoZFifD;3v zXJA*a@asJS@V|D)$2lc=@7P*997);DLpys0`BZsB3cKV|4_9czZ9xnu%mHaNKGs#m zw9P3qE^-WOnAEA!GvysM&jZ%K!mYOf)X~a3 z(-X0ztCEypJRuz*YUf8aWCMZ0Jz!2JfV)6B^3wswM&`l5#!qBMqX%E(r&JhFEl?WU zcw}1u^2&jh3NJ^ZbKl^aNN55V$?uCY13CdHr+_AlOi;wfxxU)~_@7mK&s-B*M7U`8 zkew^s@Fz5)#f7L*l>EsR-!Hvt&%Xn>g8@DC{(vEJ0qR!(m-kOlvq3t_Ve~i7q5t2Y zHM82Jk~h{3!(AoU`hZEhZ|-Utq9rmzqSoqfFDdIHX5@c||Ff}zGa=(N6q#SY0@?Yr z$G`{Q@XyGU@^@^p|DP~qwUc?iTFwgan`qWZM~NMlq(X~nk)NTu&XqW0m&^*K4%BVH zNT#bqXYO4!g+EZ&u>*OO>V zI5kx_b9yP5=*_q2N0a8KtM!vX^gMI)`&&)&m-Bc)M8S%v1~Z^d)Q9*96ay0TV?d(b zDCJv&3}^$;G#yz)7f;nh&9`BeMBq^n=;bui)9)kjdk~GoAC!h9DXhRWgV{AG)n?4G z?{`pLwY8lo8aEG*pcc3a0+2flh~N$rRGW$VjjPsPy=(Vpt#6XaN;^Hf3k0b!Efz)l z16I=UgPGlEsjxr<|I3pfl7^`(0NL{b85OLT6;Plw__~2|3>JzgM@CKDnTz z_aZd)R~Q}#>a8o8KCk^as4TUhb{~Q%6~>h6Q(D8|rhZyDX2kS&*Y*1~)bx2>284zQ zm*6L`wHX`%+}RO-IATr}{QToSRM%(RC{3Rn7>66BHiI7|XB4xz0%H0IGV8Ld7#YB6 z^?fvA^f(Bn$BZG<4U30q29;7XbC*%`!Eh@M0TMH_M$%U!sQFVp<;k`0wFcFBVmT{ z%YDf#*k#r(TGwy@+#_r5Wq$0loc^h3lP&JKv|~r3zK*n9c1X3e@Ns+4@a9>{LPc}T zxHzyt`d+Em9uNO%DyqqOy(^h?L8O6ObNuvhdqc?mD(ZxS0 zg7>{^V?jT2M-2=j-Ax(Si!|+!lOYTU1i?FvdT+7v!DXs6@kQ$)A5plv1xNS7P%eVV zJ~I-n^m{HVZigbUfDaa*#Qhl9V}6{3Gx4Zr6alNCubxjUhwDX*&c8@9y030rgM8QIRf`AmcBoJT1w!n zM(<~X^bgL5!w1z>KG&w^A1FCw)=Nr%B#}S0sTV}Q`kcYns*FEwbe_pxN)gd0^mQN2 zF7Z9QDNK9!?ShL=K{=*=r%Jp8T{CK(6KbD=XvL4`tsIiruOaaRB9vs@T{k9jQlULV|GVFj%pv3lHbI& zTZm7s$xI;DTiL+B!`#im#ZkfGfVTSfYk?_wW2c{R2%1~TMUUsoP+G>r)5-6k+~UH5{6LARp)EJk0ZNF%+sx98L2DT8%23utU2S~Vm&H~8)kM9Cf02@e9aQ$(*gNnTj1v-!nK%Ow~wU>leu!e3!`lS_~B< zL!sMWH_hA9$-lWiqdJRaqS!e^jcJZ+(LX>nza&4q)$~bRn-t`@8+2~xkXsQk>hiq~V!9mh zt|<;5pQXVXS0= zpWk>)yTRrHR$_$Gcb4+{R?e-CR%Ng2LC-AJU#iDlX|8_x^zlI8JzadxPO4+_b9~VrQQdkyog+jcnS=fBZoU?6$q*o`-%7oD@Ih0= z$Y}N$8~be2x$!)o+(6rpXfNf(&%~6W)LNOBw(5H#HEks%yh`)&I%-SqvFE(QC(U@G zoDb`M7Ce5=rIBqe&{-=$heu=oX=~^Gm5ott2RmFE^~0-2%thDCsonf)K#yum8SNpNVZzNDF)wU)ch0sT@d#?yecxbUuZ5 zbgQ}qe`ej>u>}80ophD^BZW!U4ld}qZtCj^qGs`&g{f`P)0J(~xg&W}tPVDif6Iweg*e&Gj4m@etT}cif`J(pzkl?d=cP0gB{nF%|ZrV*lZyC#i zGr5ET&Wj087PIcXHx2pX$%*qJv~P)t&7-+6pgRP)od+6u0-kzzN?h*<&e2a=G!|R9 znvq|aZlY;v6MFi3Yql1zrHY)?14Xr^cPkNyv)qXxx<@@%2wCp_1%YW=C^B5acE`Q={OD6-)w! zt)!-{_f5MX*-=fs#T1$E#4Z(qguSRr+_=!ZTc738cy;~`kO4z09ih# zkY3m{jH~ENj7qAT^FYGs=L0Pc$9BnWSJG`&o^zI$9V_)YA*~k3ImXtZEg3q(Cu~Kt z78JaBywTA=%kpM~b~bZ}tjgYng6+Wt$Bv2K+%fOfF5W9F{?c*m=yi)i!~2`h2wi;G ze&`~7@V2)c)hcaLPs99rGfTd4qrt-++ar12L8@BUdQCN&n_dxbi!ND$?bO1yxIoBFZ7k?I}uf5G|~-E zm;nu7TJpqEA8>>@2DBCl8(Ltc1*F`<6JNPUJ20SKAnRM6Ln3%~xMbuH`q6@9&`VVC z``@lVNQ>zBl^$@x$d6NKrn4Z=7!m&%MKcNL=)}>|X0*HG&J1MsCZNSQ;q2 zRQ1s8@LbWql%MWukS80`n)0eG!nr-OqvDFRzSg10AhW~flbY$z%^8padSRIXZR@97 zlrx|MWuu67ZsX(Y{*&nJ1Ms*XqSQaE`j1sXE#}f8e4S(={LDEX+kqe1DoF!*)+xS* z&esj`ldWgWL;9VBR_> zCV;A45NbYf0$v;j48YKAmN$bs)ZZ5;%$Or5^6^V^V#r;)`qasq67d%e92L8LCwkAm zS&X(2=33CRrinYSz@PX*o|y;=U(+2!oadbP8yM&`x84oFyV$3DD-AEB*pth+%7<)* zOUHGtYJ?hZ&MWy+L?3T#({D!4rle-Fe2(v;i_V*uvHZ%{SpLXeLC z)2(j8)@`%+%qMWhXRZ<47d%;H{Hpi_j-=Pe4)SwJL=$Ae9|3XMd){M+EI@jb@uVVj z_LLi@vl`ubjg_D^i=hQC=JFA>egFovaO?*%DQf}fZT&Q3)ZZ{dP3JL@#a1K{HFCE< zMQjz?H{tTUd42jCm~hnrIf(sAiRoM-a4q$$p@mW-Ko#TGjIuZA46O!bH9?lW®x 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 Date: Thu, 30 Aug 2018 05:55:46 -0500 Subject: [PATCH 12/14] Improve the conditional module-loading This circumvents a scary-seeming warning. --- src/ProfileView.jl | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ProfileView.jl b/src/ProfileView.jl index 8946bfe..4cb680e 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -43,14 +43,17 @@ function have_display() end function __init__() - push!(LOAD_PATH, @__DIR__) if (isdefined(Main, :IJulia) && !isdefined(Main, :PROFILEVIEW_USEGTK)) || !have_display() - @eval import ProfileViewSVG + # @eval import ProfileViewSVG + include(joinpath(@__DIR__, "ProfileViewSVG.jl")) + @eval import .ProfileViewSVG @eval begin view(data = Profile.fetch(); C = false, lidict = nothing, colorgc = true, fontsize = 12, combine = true, pruned = []) = ProfileViewSVG.view(data; C=C, lidict=lidict, colorgc=colorgc, fontsize=fontsize, combine=combine, pruned=pruned) end else - @eval import ProfileViewGtk + # @eval import ProfileViewGtk + include(joinpath(@__DIR__, "ProfileViewGtk.jl")) + @eval import .ProfileViewGtk @eval begin view(data = Profile.fetch(); C = false, lidict = nothing, colorgc = true, fontsize = 12, combine = true, pruned = []) = ProfileViewGtk.view(data; C=C, lidict=lidict, colorgc=colorgc, fontsize=fontsize, combine=combine, pruned=pruned) @@ -62,7 +65,6 @@ Closes all windows opened by ProfileView. closeall() = ProfileViewGtk.closeall() end end - pop!(LOAD_PATH) end function prepare(data; C = false, lidict = nothing, colorgc = true, combine = true, pruned = []) From 407a05301317a5418714f4f3093495a2a42f46b3 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 30 Aug 2018 06:16:30 -0500 Subject: [PATCH 13/14] Fix and test the svgwriter This will help ensure that IJulia continues to work --- src/ProfileView.jl | 23 ++++++++++++++--------- src/ProfileViewSVG.jl | 2 -- src/svgwriter.jl | 8 ++++---- test/test.jl | 4 ++++ 4 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/ProfileView.jl b/src/ProfileView.jl index 4cb680e..da41257 100644 --- a/src/ProfileView.jl +++ b/src/ProfileView.jl @@ -1,8 +1,6 @@ -__precompile__() - module ProfileView -using Profile +using Profile, UUIDs using Colors import Base: isequal, show @@ -159,14 +157,21 @@ function prepare_image(bt, uip, counts, lidict, lkup, C, colorgc, combine, img, lidict, imgtags end -function svgwrite(filename::AbstractString, data, lidict; C = false, colorgc = true, fontsize = 12, combine = true, pruned = []) +function svgwrite(io::IO, data, lidict; C = false, colorgc = true, fontsize = 12, combine = true, pruned = []) img, lidict, imgtags = prepare(data, C=C, lidict=lidict, colorgc=colorgc, combine=combine, pruned=pruned) pd = ProfileData(img, lidict, imgtags, fontsize) + show(io, "image/svg+xml", pd) +end +function svgwrite(filename::AbstractString, data, lidict; kwargs...) open(filename, "w") do file - show(file, "image/svg+xml", pd) + svgwrite(file, data, lidict; kwargs...) end nothing end +function svgwrite(io::IO; kwargs...) + data, lidict = Profile.retrieve() + svgwrite(io, data, lidict; kwargs...) +end function svgwrite(filename::AbstractString; kwargs...) data, lidict = Profile.retrieve() svgwrite(filename, data, lidict; kwargs...) @@ -192,9 +197,9 @@ function show(f::IO, ::MIME"image/svg+xml", pd::ProfileData) ystep = (height - (topmargin + botmargin)) / nrows avgcharwidth = 6 # for Verdana 12 pt font function eschtml(str) - s = replace(str, '<', "<") - s = replace(s, '>', ">") - s = replace(s, '&', "&") + s = replace(str, '<' => "<") + s = replace(s, '>' => ">") + s = replace(s, '&' => "&") s end function printrec(f, samples, xstart, xend, y, tag, rgb) @@ -219,7 +224,7 @@ function show(f::IO, ::MIME"image/svg+xml", pd::ProfileData) # end end - fig_id = string("fig-", replace(string(Base.Random.uuid4()), "-", "")) + fig_id = string("fig-", replace(string(uuid4()), "-" => "")) svgheader(f, fig_id, width=width, height=height) # rectangles are on a grid and split across multiple columns (must span similar adjacent ones together) for r in 1:nrows diff --git a/src/ProfileViewSVG.jl b/src/ProfileViewSVG.jl index cb9b498..8c0efc5 100644 --- a/src/ProfileViewSVG.jl +++ b/src/ProfileViewSVG.jl @@ -1,5 +1,3 @@ -__precompile__() - module ProfileViewSVG function __init__() diff --git a/src/svgwriter.jl b/src/svgwriter.jl index fed5f0d..e25338c 100644 --- a/src/svgwriter.jl +++ b/src/svgwriter.jl @@ -2,7 +2,7 @@ const snapsvgjs = joinpath(@__DIR__, "..", "templates", "snap.svg-min.js") const viewerjs = joinpath(@__DIR__, "viewer.js") function escape_script(js::AbstractString) - return replace(js, "]]", "] ]") + return replace(js, "]]" => "] ]") end function svgheader(f::IO, fig_id::AbstractString; width=1200, height=706, font="Verdana") @@ -37,9 +37,9 @@ end function svgfinish(f::IO, fig_id) print(f, """ - +