Skip to content

Commit

Permalink
Merge pull request #2890 from julia-vscode/sp/allocation-profiler
Browse files Browse the repository at this point in the history
Allocation profiler and some profiler tweaks
  • Loading branch information
pfitzseb committed May 24, 2022
2 parents dddff57 + d20ee2c commit d30fc70
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 89 deletions.
2 changes: 1 addition & 1 deletion libs/jl-profile
6 changes: 3 additions & 3 deletions scripts/packages/VSCodeServer/src/VSCodeServer.jl
@@ -1,7 +1,7 @@
module VSCodeServer

export vscodedisplay, @vscodedisplay, @enter, @run
export view_profile, @profview
export view_profile, view_profile_allocs, @profview, @profview_allocs

using REPL, Sockets, Base64, Pkg, UUIDs, Dates, Profile
import Base: display, redisplay
Expand All @@ -28,7 +28,7 @@ function __init__()
if VERSION >= v"1.4" && isdefined(InteractiveUtils, :EDITOR_CALLBACKS)
pushfirst!(InteractiveUtils.EDITOR_CALLBACKS, function (cmd::Cmd, path::AbstractString, line::Integer)
cmd == `code` || return false
JSONRPC.send(conn_endpoint[], repl_open_file_notification_type, (; path = String(path), line = Int(line)))
JSONRPC.send(conn_endpoint[], repl_open_file_notification_type, (; path=String(path), line=Int(line)))
return true
end)
end
Expand Down Expand Up @@ -101,7 +101,7 @@ function dispatch_msg(conn_endpoint, msg_dispatcher, msg, is_dev)
end
end

function serve(args...; is_dev = false, crashreporting_pipename::Union{AbstractString,Nothing} = nothing)
function serve(args...; is_dev=false, crashreporting_pipename::Union{AbstractString,Nothing}=nothing)
if !HAS_REPL_TRANSFORM[] && isdefined(Base, :active_repl)
hook_repl(Base.active_repl)
end
Expand Down
201 changes: 151 additions & 50 deletions scripts/packages/VSCodeServer/src/profiler.jl
@@ -1,12 +1,22 @@
function view_profile(; C = false, kwargs...)
d = Dict()
using Profile

# https://github.com/timholy/FlameGraphs.jl/blob/master/src/graph.jl
const ProfileFrameFlag = (
RuntimeDispatch = UInt8(2^0),
GCEvent = UInt8(2^1),
REPL = UInt8(2^2),
Compilation = UInt8(2^3),
TaskEvent = UInt8(2^4)
)

function view_profile(data = Profile.fetch(); C=false, kwargs...)
d = Dict{String,ProfileFrame}()

if VERSION >= v"1.8.0-DEV.460"
threads = ["all", 1:Threads.nthreads()...]
else
threads = ["all"]
end
data = Profile.fetch()

if isempty(data)
Profile.warning_empty()
Expand All @@ -16,23 +26,18 @@ function view_profile(; C = false, kwargs...)
lidict = Profile.getdict(unique(data))
data_u64 = convert(Vector{UInt64}, data)
for thread in threads
graph = stackframetree(data_u64, lidict; thread = thread, kwargs...)
d[thread] = dicttree(Dict(
:func => "root",
:file => "",
:path => "",
:line => 0,
:count => graph.count,
:flags => 0x0,
:children => []
), graph; C = C, kwargs...)
end

JSONRPC.send(conn_endpoint[], repl_showprofileresult_notification_type, (; trace = d))
graph = stackframetree(data_u64, lidict; thread=thread, kwargs...)
d[string(thread)] = make_tree(
ProfileFrame(
"root", "", "", 0, graph.count, missing, 0x0, missing, ProfileFrame[]
), graph; C=C, kwargs...)
end

JSONRPC.send(conn_endpoint[], repl_showprofileresult_notification_type, (; trace=d, typ="Thread"))
end

function stackframetree(data_u64, lidict; thread = nothing, combine = true, recur = :off)
root = combine ? Profile.StackFrameTree{Profile.StackFrame}() : Profile.StackFrameTree{UInt64}()
function stackframetree(data_u64, lidict; thread=nothing, combine=true, recur=:off)
root = combine ? Profile.StackFrameTree{StackTraces.StackFrame}() : Profile.StackFrameTree{UInt64}()
if VERSION >= v"1.8.0-DEV.460"
thread = thread == "all" ? (1:Threads.nthreads()) : thread
root, _ = Profile.tree!(root, data_u64, lidict, true, recur, thread)
Expand All @@ -46,33 +51,22 @@ function stackframetree(data_u64, lidict; thread = nothing, combine = true, recu
return root
end

# https://github.com/timholy/FlameGraphs.jl/blob/master/src/graph.jl
const runtime_dispatch = UInt8(2^0)
const gc_event = UInt8(2^1)
const repl = UInt8(2^2)
const compilation = UInt8(2^3)
const task_event = UInt8(2^4)
# const = UInt8(2^5)
# const = UInt8(2^6)
# const = UInt8(2^7)
# const = UInt8(2^8)

function status(sf::Profile.StackFrame)
function status(sf::StackTraces.StackFrame)
st = UInt8(0)
if sf.from_c && (sf.func === :jl_invoke || sf.func === :jl_apply_generic || sf.func === :ijl_apply_generic)
st |= runtime_dispatch
st |= ProfileFrameFlag.RuntimeDispatch
end
if sf.from_c && startswith(String(sf.func), "jl_gc_")
st |= gc_event
st |= ProfileFrameFlag.GCEvent
end
if !sf.from_c && sf.func === :eval_user_input && endswith(String(sf.file), "REPL.jl")
st |= repl
st |= ProfileFrameFlag.REPL
end
if !sf.from_c && occursin("./compiler/", String(sf.file))
st |= compilation
st |= ProfileFrameFlag.Compilation
end
if !sf.from_c && occursin("task.jl", String(sf.file))
st |= task_event
st |= ProfileFrameFlag.TaskEvent
end
return st
end
Expand All @@ -88,36 +82,39 @@ function status(node::Profile.StackFrameTree, C::Bool)
return st
end

function add_child(graph, node, C::Bool)
function add_child(graph::ProfileFrame, node, C::Bool)
name = string(node.frame.file)
func = String(node.frame.func)

if func == ""
func = "unknown"
end

d = Dict(
:func => func,
:file => basename(name),
:path => fullpath(name),
:line => node.frame.line,
:count => node.count,
:flags => status(node, C),
:children => []
frame = ProfileFrame(
func,
basename(name),
fullpath(name),
node.frame.line,
node.count,
missing,
status(node, C),
missing,
ProfileFrame[]
)
push!(graph[:children], d)

return d
push!(graph.children, frame)

return frame
end

function dicttree(graph, node::Profile.StackFrameTree; C = false)
for child_node in sort!(collect(values(node.down)); rev = true, by = node -> node.count)
function make_tree(graph, node::Profile.StackFrameTree; C=false)
for child_node in sort!(collect(values(node.down)); rev=true, by=node -> node.count)
# child not a hidden frame
if C || !child_node.frame.from_c
child = add_child(graph, child_node, C)
dicttree(child, child_node; C = C)
make_tree(child, child_node; C=C)
else
dicttree(graph, child_node)
make_tree(graph, child_node)
end
end

Expand All @@ -138,3 +135,107 @@ macro profview(ex, args...)
view_profile(; $(esc.(args)...))
end
end

## Allocs

"""
@profview_allocs f(args...) [sample_rate=0.0001] [C=false]
Clear the Profile buffer, profile `f(args...)`, and view the result graphically.
"""
macro profview_allocs(ex, args...)
sample_rate_expr = :(sample_rate=0.0001)
for arg in args
if Meta.isexpr(arg, :(=)) && length(arg.args) > 0 && arg.args[1] === :sample_rate
sample_rate_expr = arg
end
end
if isdefined(Profile, :Allocs)
return quote
Profile.Allocs.clear()
Profile.Allocs.@profile $(esc(sample_rate_expr)) $(esc(ex))
view_profile_allocs()
end
else
return :(@error "This version of Julia does not support the allocation profiler.")
end
end

function view_profile_allocs(_results=Profile.Allocs.fetch(); C=false)
results = _results::Profile.Allocs.AllocResults
allocs = results.allocs

allocs_root = ProfileFrame("root", "", "", 0, 0, missing, 0x0, missing, ProfileFrame[])
counts_root = ProfileFrame("root", "", "", 0, 0, missing, 0x0, missing, ProfileFrame[])
for alloc in allocs
this_allocs = allocs_root
this_counts = counts_root

for sf in Iterators.reverse(alloc.stacktrace)
if !C && sf.from_c
continue
end
file = string(sf.file)
this_counts′ = ProfileFrame(
string(sf.func), basename(file), fullpath(file),
sf.line, 0, missing, 0x0, missing, ProfileFrame[]
)
ind = findfirst(c -> (
c.func == this_counts′.func &&
c.path == this_counts′.path &&
c.line == this_counts′.line
), this_allocs.children)

this_counts, this_allocs = if ind === nothing
push!(this_counts.children, this_counts′)
this_allocs′ = deepcopy(this_counts′)
push!(this_allocs.children, this_allocs′)

(this_counts′, this_allocs′)
else
(this_counts.children[ind], this_allocs.children[ind])
end
this_allocs.count += alloc.size
this_allocs.countLabel = memory_size(this_allocs.count)
this_counts.count += 1
end

alloc_type = replace(string(alloc.type), "Profile.Allocs." => "")
ind = findfirst(c -> (c.func == alloc_type), this_allocs.children)
if ind === nothing
push!(this_allocs.children, ProfileFrame(
alloc_type, "", "",
0, this_allocs.count, memory_size(this_allocs.count), ProfileFrameFlag.GCEvent, missing, ProfileFrame[]
))
push!(this_counts.children, ProfileFrame(
alloc_type, "", "",
0, 1, missing, ProfileFrameFlag.GCEvent, missing, ProfileFrame[]
))
else
this_counts.children[ind].count += 1
this_allocs.children[ind].count += alloc.size
this_allocs.children[ind].countLabel = memory_size(this_allocs.count)
end

counts_root.count += 1
allocs_root.count += alloc.size
allocs_root.countLabel = memory_size(allocs_root.count)
end

d = Dict{String, ProfileFrame}(
"size" => allocs_root,
"count" => counts_root
)

JSONRPC.send(conn_endpoint[], repl_showprofileresult_notification_type, (; trace=d, typ="Allocation"))
end

const prefixes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
function memory_size(size)
i = 1
while size > 1000 && i + 1 < length(prefixes)
size /= 1000
i += 1
end
return string(round(Int, size), " ", prefixes[i])
end
14 changes: 13 additions & 1 deletion scripts/packages/VSCodeServer/src/repl_protocol.jl
Expand Up @@ -96,6 +96,18 @@ JSONRPC.@dict_readable mutable struct GetTableDataRequest <: JSONRPC.Outbound
sortModel::Any
end

JSONRPC.@dict_readable mutable struct ProfileFrame <: JSONRPC.Outbound
func::String
file::String # human readable file name
path::String # absolute path
line::Int # 1-based line number
count::Int # number of samples in this frame
countLabel::Union{Missing,String} # defaults to `$count samples`
flags::UInt8 # any or all of ProfileFrameFlag
taskId::Union{Missing,UInt}
children::Vector{ProfileFrame}
end

const repl_runcode_request_type = JSONRPC.RequestType("repl/runcode", ReplRunCodeRequestParams, ReplRunCodeRequestReturn)
const repl_interrupt_notification_type = JSONRPC.NotificationType("repl/interrupt", Nothing)
const repl_getvariables_request_type = JSONRPC.RequestType("repl/getvariables", NamedTuple{(:modules,),Tuple{Bool}}, Vector{ReplWorkspaceItem})
Expand All @@ -104,7 +116,7 @@ const repl_showingrid_notification_type = JSONRPC.NotificationType("repl/showing
const repl_loadedModules_request_type = JSONRPC.RequestType("repl/loadedModules", Nothing, Vector{String})
const repl_isModuleLoaded_request_type = JSONRPC.RequestType("repl/isModuleLoaded", NamedTuple{(:mod,),Tuple{String}}, Bool)
const repl_startdebugger_notification_type = JSONRPC.NotificationType("repl/startdebugger", NamedTuple{(:debugPipename,),Tuple{String}})
const repl_showprofileresult_notification_type = JSONRPC.NotificationType("repl/showprofileresult", NamedTuple{(:trace,),Tuple{Dict{Any, Any}}})
const repl_showprofileresult_notification_type = JSONRPC.NotificationType("repl/showprofileresult", NamedTuple{(:trace,:typ),Tuple{Dict{String,ProfileFrame}, String}})
const repl_open_file_notification_type = JSONRPC.NotificationType("repl/openFile", NamedTuple{(:path, :line), Tuple{String, Int}})
const repl_toggle_plot_pane_notification_type = JSONRPC.NotificationType("repl/togglePlotPane", NamedTuple{(:enable,),Tuple{Bool}})
const repl_toggle_diagnostics_notification_type = JSONRPC.NotificationType("repl/toggleDiagnostics", NamedTuple{(:enable,),Tuple{Bool}})
Expand Down

0 comments on commit d30fc70

Please sign in to comment.