From efe9cfbc969bd49b56b02caf12d3d90a79f7c8ee Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Tue, 30 Sep 2025 17:25:56 +0200 Subject: [PATCH 1/3] Linearize function ids --- src/PProf.jl | 88 +++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/src/PProf.jl b/src/PProf.jl index 4d5c70a..4a27ea4 100644 --- a/src/PProf.jl +++ b/src/PProf.jl @@ -131,9 +131,8 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, # Setup: enter!("") # NOTE: pprof requires first entry to be "" - # Functions need a uid, we'll use the pointer for the method instance - seen_funcs = Set{UInt64}() - funcs = Dict{UInt64, Function}() + funcaddr_to_id = Dict{UInt64, Int64}() + functions = Vector{Function}() seen_locs = Set{UInt64}() locs = Dict{UInt64, Location}() @@ -242,47 +241,50 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, location_from_c &= frame.from_c # Use a unique function id for the frame: - func_id = method_instance_id(frame) - push!(location.line, Line(function_id = func_id, line = frame.line)) - - # Known function - func_id in seen_funcs && continue - push!(seen_funcs, func_id) - - # Store the function in our functions dict - file = nothing - simple_name = _escape_name_for_pprof(frame.func) - local full_name_with_args - if frame.linfo !== nothing && frame.linfo isa Core.MethodInstance - linfo = frame.linfo::Core.MethodInstance - meth = linfo.def - file = string(meth.file) - io = IOBuffer() - Base.show_tuple_as_call(io, meth.name, linfo.specTypes) - full_name_with_args = _escape_name_for_pprof(String(take!(io))) - start_line = convert(Int64, meth.line) - else - # frame.linfo either nothing or CodeInfo, either way fallback - file = string(frame.file) - full_name_with_args = _escape_name_for_pprof(string(frame.func)) - start_line = convert(Int64, frame.line) # TODO: Get start_line properly + func_addr = method_instance_id(frame) + func_id = get(funcaddr_to_id, func_addr, 0) + resolved = func_id != 0 + if !resolved + func_id = length(functions) + 1 + funcaddr_to_id[func_addr] = func_id end - isempty(simple_name) && (simple_name = "[unknown function]") - isempty(full_name_with_args) && (full_name_with_args = "[unknown function]") - # WEIRD TRICK: By entering a separate copy of the string (with a - # different string id) for the name and system_name, pprof will use - # the supplied `name` *verbatim*, without pruning off the arguments. - # So even when full_signatures == false, we want to generate two `enter!` ids. - system_name = enter!(simple_name) - if full_signatures - name = enter!(full_name_with_args) - else - name = enter!(simple_name) + push!(location.line, Line(function_id = funcaddr_to_id[func_addr], line = frame.line)) + + if !resolved + file = nothing + simple_name = _escape_name_for_pprof(frame.func) + local full_name_with_args + if frame.linfo !== nothing && frame.linfo isa Core.MethodInstance + linfo = frame.linfo::Core.MethodInstance + meth = linfo.def + file = string(meth.file) + io = IOBuffer() + Base.show_tuple_as_call(io, meth.name, linfo.specTypes) + full_name_with_args = _escape_name_for_pprof(String(take!(io))) + start_line = convert(Int64, meth.line) + else + # frame.linfo either nothing or CodeInfo, either way fallback + file = string(frame.file) + full_name_with_args = _escape_name_for_pprof(string(frame.func)) + start_line = convert(Int64, frame.line) # TODO: Get start_line properly + end + isempty(simple_name) && (simple_name = "[unknown function]") + isempty(full_name_with_args) && (full_name_with_args = "[unknown function]") + # WEIRD TRICK: By entering a separate copy of the string (with a + # different string id) for the name and system_name, pprof will use + # the supplied `name` *verbatim*, without pruning off the arguments. + # So even when full_signatures == false, we want to generate two `enter!` ids. + system_name = enter!(simple_name) + if full_signatures + name = enter!(full_name_with_args) + else + name = enter!(simple_name) + end + file = Base.find_source_file(file) + filename = enter!(file) + # Decode C functions always + push!(functions, Function(func_id, name, system_name, filename, start_line)) end - file = Base.find_source_file(file) - filename = enter!(file) - # Decode C functions always - funcs[func_id] = Function(func_id, name, system_name, filename, start_line) end locs_from_c[ip] = location_from_c # Only keep C frames if from_c=true @@ -306,7 +308,7 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, sample_type = sample_type, sample = samples, location = collect(values(locs)), - var"#function" = collect(values(funcs)), + var"#function" = functions, string_table = collect(keys(string_table)), drop_frames = drop_frames, keep_frames = keep_frames, From 160d9b541399fc9714516bb1274979ca65785b68 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Tue, 30 Sep 2025 17:42:41 +0200 Subject: [PATCH 2/3] Linearize location ids --- src/PProf.jl | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/PProf.jl b/src/PProf.jl index 4a27ea4..efabec5 100644 --- a/src/PProf.jl +++ b/src/PProf.jl @@ -134,9 +134,10 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, funcaddr_to_id = Dict{UInt64, Int64}() functions = Vector{Function}() - seen_locs = Set{UInt64}() - locs = Dict{UInt64, Location}() + locaddr_to_id = Dict{UInt64, Int64}() + locations = Vector{Location}() locs_from_c = Dict{UInt64, Bool}() + samples = Vector{Sample}() sample_type = [ @@ -147,7 +148,7 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, drop_frames = isnothing(drop_frames) ? 0 : enter!(drop_frames) keep_frames = isnothing(keep_frames) ? 0 : enter!(keep_frames) # start decoding backtraces - location_id = Vector{eltype(data)}() + location_id = Vector{Int64}() # All samples get the same value for CPU profiles. value = [ @@ -171,7 +172,7 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, if meta !== nothing # Finish last block push!(samples, Sample(;location_id = reverse!(location_id), value = value, label = meta)) - location_id = Vector{eltype(data)}() + location_id = Vector{Int64}() end # Consume all of the metadata entries in the buffer, and then position the IP @@ -204,7 +205,7 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, else # Finish last block push!(samples, Sample(;location_id = reverse!(location_id), value = value)) - location_id = Vector{eltype(data)}() + location_id = Vector{Int}() lastwaszero = true end idx -= 1 @@ -219,17 +220,22 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, # that IP to a specific frame (or set of frames, if inlining occured). # if we have already seen this IP avoid decoding it again - if ip in seen_locs + locid = get(locaddr_to_id, ip, 0) + seen = locid != 0 + if !seen + locid = length(locations) + 1 + locaddr_to_id[ip] = locid + end + if seen # Only keep C frames if from_c=true if (from_c || !locs_from_c[ip]) - push!(location_id, ip) + push!(location_id, locid) end continue end - push!(seen_locs, ip) # Decode the IP into information about this stack frame (or frames given inlining) - location = Location(;id = ip, address = ip) + location = Location(;id = locid, address = ip) location_from_c = true # Will have multiple frames if frames were inlined (the last frame is the "real # function", the inlinee) @@ -289,8 +295,9 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, locs_from_c[ip] = location_from_c # Only keep C frames if from_c=true if (from_c || !location_from_c) - locs[ip] = location - push!(location_id, ip) + push!(locations, location) + @assert length(locations) == locid + push!(location_id, locid) end end if length(data) > 0 @@ -307,7 +314,7 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, prof = PProfile( sample_type = sample_type, sample = samples, - location = collect(values(locs)), + location = locations, var"#function" = functions, string_table = collect(keys(string_table)), drop_frames = drop_frames, From 27aa78437747cbf03e265c6717b3d53eb1733fa2 Mon Sep 17 00:00:00 2001 From: Valentin Churavy Date: Tue, 30 Sep 2025 21:00:14 +0200 Subject: [PATCH 3/3] cleanup --- src/PProf.jl | 69 ++++++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 35 deletions(-) diff --git a/src/PProf.jl b/src/PProf.jl index efabec5..f57e812 100644 --- a/src/PProf.jl +++ b/src/PProf.jl @@ -255,42 +255,41 @@ function pprof(data::Union{Nothing, Vector{UInt}} = nothing, funcaddr_to_id[func_addr] = func_id end push!(location.line, Line(function_id = funcaddr_to_id[func_addr], line = frame.line)) - - if !resolved - file = nothing - simple_name = _escape_name_for_pprof(frame.func) - local full_name_with_args - if frame.linfo !== nothing && frame.linfo isa Core.MethodInstance - linfo = frame.linfo::Core.MethodInstance - meth = linfo.def - file = string(meth.file) - io = IOBuffer() - Base.show_tuple_as_call(io, meth.name, linfo.specTypes) - full_name_with_args = _escape_name_for_pprof(String(take!(io))) - start_line = convert(Int64, meth.line) - else - # frame.linfo either nothing or CodeInfo, either way fallback - file = string(frame.file) - full_name_with_args = _escape_name_for_pprof(string(frame.func)) - start_line = convert(Int64, frame.line) # TODO: Get start_line properly - end - isempty(simple_name) && (simple_name = "[unknown function]") - isempty(full_name_with_args) && (full_name_with_args = "[unknown function]") - # WEIRD TRICK: By entering a separate copy of the string (with a - # different string id) for the name and system_name, pprof will use - # the supplied `name` *verbatim*, without pruning off the arguments. - # So even when full_signatures == false, we want to generate two `enter!` ids. - system_name = enter!(simple_name) - if full_signatures - name = enter!(full_name_with_args) - else - name = enter!(simple_name) - end - file = Base.find_source_file(file) - filename = enter!(file) - # Decode C functions always - push!(functions, Function(func_id, name, system_name, filename, start_line)) + resolved && continue + + file = nothing + simple_name = _escape_name_for_pprof(frame.func) + local full_name_with_args + if frame.linfo !== nothing && frame.linfo isa Core.MethodInstance + linfo = frame.linfo::Core.MethodInstance + meth = linfo.def + file = string(meth.file) + io = IOBuffer() + Base.show_tuple_as_call(io, meth.name, linfo.specTypes) + full_name_with_args = _escape_name_for_pprof(String(take!(io))) + start_line = convert(Int64, meth.line) + else + # frame.linfo either nothing or CodeInfo, either way fallback + file = string(frame.file) + full_name_with_args = _escape_name_for_pprof(string(frame.func)) + start_line = convert(Int64, frame.line) # TODO: Get start_line properly + end + isempty(simple_name) && (simple_name = "[unknown function]") + isempty(full_name_with_args) && (full_name_with_args = "[unknown function]") + # WEIRD TRICK: By entering a separate copy of the string (with a + # different string id) for the name and system_name, pprof will use + # the supplied `name` *verbatim*, without pruning off the arguments. + # So even when full_signatures == false, we want to generate two `enter!` ids. + system_name = enter!(simple_name) + if full_signatures + name = enter!(full_name_with_args) + else + name = enter!(simple_name) end + file = Base.find_source_file(file) + filename = enter!(file) + # Decode C functions always + push!(functions, Function(func_id, name, system_name, filename, start_line)) end locs_from_c[ip] = location_from_c # Only keep C frames if from_c=true