Skip to content

Commit 5ffee12

Browse files
Profile: metadata handling tweaks (#42868)
As was discussed in #42482 after merge, there's some things that could be done better: - Reverts the is_block_end logic, given it should only be run on data that includes metadata, so can afford to be more efficient - Adds has_meta for identifying when profile data has metadata - Adds metadata checks to functions that expect metadata to be present - Changes Profile.fetch to by default include metadata. The reason for this is that it was previously made to strip metadata to avoid breaking existing downstream Profile data consumers, but it turns out that they use Profile internals like tree! that require the metadata to be present, so this ended up causing an unintended breakage anyway - Adds consts for the metadata field offsets, for consumers to use
1 parent 52ff195 commit 5ffee12

File tree

2 files changed

+42
-27
lines changed

2 files changed

+42
-27
lines changed

stdlib/Profile/src/Profile.jl

+39-24
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ struct ProfileFormat
122122
end
123123
end
124124

125+
# offsets of the metadata in the data stream
126+
const META_OFFSET_SLEEPSTATE = 2
127+
const META_OFFSET_CPUCYCLECLOCK = 3
128+
const META_OFFSET_TASKID = 4
129+
const META_OFFSET_THREADID = 5
130+
125131
"""
126132
print([io::IO = stdout,] [data::Vector]; kwargs...)
127133
@@ -267,8 +273,8 @@ function get_task_ids(data::Vector{<:Unsigned}, threadid = nothing)
267273
taskids = UInt[]
268274
for i in length(data):-1:1
269275
if is_block_end(data, i)
270-
if isnothing(threadid) || data[i - 5] == threadid
271-
taskid = data[i - 4]
276+
if isnothing(threadid) || data[i - META_OFFSET_THREADID] == threadid
277+
taskid = data[i - META_OFFSET_TASKID]
272278
!in(taskid, taskids) && push!(taskids, taskid)
273279
end
274280
end
@@ -280,8 +286,8 @@ function get_thread_ids(data::Vector{<:Unsigned}, taskid = nothing)
280286
threadids = Int[]
281287
for i in length(data):-1:1
282288
if is_block_end(data, i)
283-
if isnothing(taskid) || data[i - 4] == taskid
284-
threadid = data[i - 5]
289+
if isnothing(taskid) || data[i - META_OFFSET_TASKID] == taskid
290+
threadid = data[i - META_OFFSET_THREADID]
285291
!in(threadid, threadids) && push!(threadids, threadid)
286292
end
287293
end
@@ -296,13 +302,20 @@ function is_block_end(data, i)
296302
# and we could have (though very unlikely):
297303
# 1:<stack><metadata><null><null><NULL><metadata><null><null>:end
298304
# and we want to ignore the triple NULL (which is an ip).
299-
data[i] == 0 || return false # first block end null
300-
data[i - 1] == 0 || return false # second block end null
301-
data[i - 2] in 1:2 || return false # sleep state
302-
data[i - 3] != 0 || return false # cpu_cycle_clock
303-
data[i - 4] != 0 || return false # taskid
304-
data[i - 5] != 0 || return false # threadid
305-
return true
305+
return data[i] == 0 && data[i - 1] == 0 && data[i - META_OFFSET_SLEEPSTATE] != 0
306+
end
307+
308+
function has_meta(data)
309+
for i in 6:length(data)
310+
data[i] == 0 || continue # first block end null
311+
data[i - 1] == 0 || continue # second block end null
312+
data[i - META_OFFSET_SLEEPSTATE] in 1:2 || continue
313+
data[i - META_OFFSET_CPUCYCLECLOCK] != 0 || continue
314+
data[i - META_OFFSET_TASKID] != 0 || continue
315+
data[i - META_OFFSET_THREADID] != 0 || continue
316+
return true
317+
end
318+
return false
306319
end
307320

308321
"""
@@ -505,15 +518,15 @@ error_codes = Dict(
505518

506519

507520
"""
508-
fetch(;include_meta = false) -> data
521+
fetch(;include_meta = true) -> data
509522
510523
Returns a copy of the buffer of profile backtraces. Note that the
511524
values in `data` have meaning only on this machine in the current session, because it
512525
depends on the exact memory addresses used in JIT-compiling. This function is primarily for
513526
internal use; [`retrieve`](@ref) may be a better choice for most users.
514-
By default metadata such as threadid and taskid will be stripped. Set `include_meta` to `true` to include metadata.
527+
By default metadata such as threadid and taskid is included. Set `include_meta` to `false` to strip metadata.
515528
"""
516-
function fetch(;include_meta = false)
529+
function fetch(;include_meta = true)
517530
maxlen = maxlen_data()
518531
len = len_data()
519532
if is_buffer_full()
@@ -542,7 +555,7 @@ function strip_meta(data)
542555
i -= 1
543556
j -= 1
544557
end
545-
@assert i == j == 0 "metadata stripping failed i=$i j=$j data[1:i]=$(data[1:i])"
558+
@assert i == j == 0 "metadata stripping failed"
546559
return data_stripped
547560
end
548561

@@ -556,7 +569,7 @@ details of the metadata format.
556569
function add_fake_meta(data; threadid = 1, taskid = 0xf0f0f0f0)
557570
threadid == 0 && error("Fake threadid cannot be 0")
558571
taskid == 0 && error("Fake taskid cannot be 0")
559-
any(Base.Fix1(is_block_end, data), eachindex(data)) && error("input already has metadata")
572+
!isempty(data) && has_meta(data) && error("input already has metadata")
560573
cpu_clock_cycle = UInt64(99)
561574
data_with_meta = similar(data, 0)
562575
for i = 1:length(data)
@@ -576,6 +589,7 @@ end
576589
# Merging multiple equivalent entries and recursive calls
577590
function parse_flat(::Type{T}, data::Vector{UInt64}, lidict::Union{LineInfoDict, LineInfoFlatDict}, C::Bool,
578591
threads::Union{Int,AbstractVector{Int}}, tasks::Union{UInt,AbstractVector{UInt}}) where {T}
592+
!isempty(data) && !has_meta(data) && error("Profile data is missing required metadata")
579593
lilist = StackFrame[]
580594
n = Int[]
581595
m = Int[]
@@ -591,10 +605,10 @@ function parse_flat(::Type{T}, data::Vector{UInt64}, lidict::Union{LineInfoDict,
591605
ip = data[i]
592606
if is_block_end(data, i)
593607
# read metadata
594-
thread_sleeping = data[i - 2] - 1 # subtract 1 as state is incremented to avoid being equal to 0
595-
# cpu_cycle_clock = data[i - 3]
596-
taskid = data[i - 4]
597-
threadid = data[i - 5]
608+
thread_sleeping = data[i - META_OFFSET_SLEEPSTATE] - 1 # subtract 1 as state is incremented to avoid being equal to 0
609+
# cpu_cycle_clock = data[i - META_OFFSET_CPUCYCLECLOCK]
610+
taskid = data[i - META_OFFSET_TASKID]
611+
threadid = data[i - META_OFFSET_THREADID]
598612
if !in(threadid, threads) || !in(taskid, tasks)
599613
skip = true
600614
continue
@@ -828,6 +842,7 @@ end
828842
# turn a list of backtraces into a tree (implicitly separated by NULL markers)
829843
function tree!(root::StackFrameTree{T}, all::Vector{UInt64}, lidict::Union{LineInfoFlatDict, LineInfoDict}, C::Bool, recur::Symbol,
830844
threads::Union{Int,AbstractVector{Int},Nothing}=nothing, tasks::Union{UInt,AbstractVector{UInt},Nothing}=nothing) where {T}
845+
!isempty(all) && !has_meta(all) && error("Profile data is missing required metadata")
831846
parent = root
832847
tops = Vector{StackFrameTree{T}}()
833848
build = Vector{StackFrameTree{T}}()
@@ -839,10 +854,10 @@ function tree!(root::StackFrameTree{T}, all::Vector{UInt64}, lidict::Union{LineI
839854
ip = all[i]
840855
if is_block_end(all, i)
841856
# read metadata
842-
thread_sleeping = all[i - 2] - 1 # subtract 1 as state is incremented to avoid being equal to 0
843-
# cpu_cycle_clock = all[i - 3]
844-
taskid = all[i - 4]
845-
threadid = all[i - 5]
857+
thread_sleeping = all[i - META_OFFSET_SLEEPSTATE] - 1 # subtract 1 as state is incremented to avoid being equal to 0
858+
# cpu_cycle_clock = all[i - META_OFFSET_CPUCYCLECLOCK]
859+
taskid = all[i - META_OFFSET_TASKID]
860+
threadid = all[i - META_OFFSET_THREADID]
846861
if (threads !== nothing && !in(threadid, threads)) ||
847862
(tasks !== nothing && !in(taskid, tasks))
848863
skip = true

stdlib/Profile/test/runtests.jl

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Profile.init()
99
let iobuf = IOBuffer()
1010
for fmt in (:tree, :flat)
1111
Test.@test_logs (:warn, r"^There were no samples collected\.") Profile.print(iobuf, format=fmt, C=true)
12-
Test.@test_logs (:warn, r"^There were no samples collected\.") Profile.print(iobuf, [0x0000000000000001], Dict(0x0000000000000001 => [Base.StackTraces.UNKNOWN]), format=fmt, C=false)
12+
Test.@test_logs (:warn, r"^There were no samples collected\.") Profile.print(iobuf, Profile.add_fake_meta([0x0000000000000001, 0x0000000000000000]), Dict(0x0000000000000001 => [Base.StackTraces.UNKNOWN]), format=fmt, C=false)
1313
end
1414
end
1515

@@ -75,8 +75,8 @@ end
7575
end
7676

7777
@testset "Profile.fetch() with and without meta" begin
78-
data_without = Profile.fetch()
79-
data_with = Profile.fetch(include_meta = true)
78+
data_without = Profile.fetch(include_meta = false)
79+
data_with = Profile.fetch()
8080
@test data_without[1] == data_with[1]
8181
@test data_without[end] == data_with[end]
8282
nblocks = count(Base.Fix1(Profile.is_block_end, data_with), eachindex(data_with))

0 commit comments

Comments
 (0)