|
1 | 1 | module Allocs
|
2 | 2 |
|
| 3 | +global print # Allocs.print is separate from both Base.print and Profile.print |
| 4 | +public @profile, |
| 5 | + clear, |
| 6 | + print, |
| 7 | + fetch |
| 8 | + |
| 9 | +using ..Profile: Profile, ProfileFormat, StackFrameTree, print_flat, print_tree |
3 | 10 | using Base.StackTraces: StackTrace, StackFrame, lookup
|
4 | 11 | using Base: InterpreterIP
|
5 | 12 |
|
|
138 | 145 | # Without this, the Alloc's stacktrace prints for lines and lines and lines...
|
139 | 146 | function Base.show(io::IO, a::Alloc)
|
140 | 147 | stacktrace_sample = length(a.stacktrace) >= 1 ? "$(a.stacktrace[1]), ..." : ""
|
141 |
| - print(io, "$Alloc($(a.type), $StackFrame[$stacktrace_sample], $(a.size))") |
| 148 | + Base.print(io, "$Alloc($(a.type), $StackFrame[$stacktrace_sample], $(a.size))") |
142 | 149 | end
|
143 | 150 |
|
144 | 151 | const BacktraceCache = Dict{BTElement,Vector{StackFrame}}
|
@@ -216,4 +223,201 @@ function stacktrace_memoized(
|
216 | 223 | return stack
|
217 | 224 | end
|
218 | 225 |
|
| 226 | +function warning_empty() |
| 227 | + @warn """ |
| 228 | + There were no samples collected. |
| 229 | + Run your program longer (perhaps by running it multiple times), |
| 230 | + or adjust the frequency of samples to record every event with |
| 231 | + the `sample_rate=1.0` kwarg.""" |
| 232 | +end |
| 233 | + |
| 234 | + |
| 235 | +""" |
| 236 | + Profile.Allocs.print([io::IO = stdout,] [data::AllocResults = fetch()]; kwargs...) |
| 237 | +
|
| 238 | +Prints profiling results to `io` (by default, `stdout`). If you do not |
| 239 | +supply a `data` vector, the internal buffer of accumulated backtraces |
| 240 | +will be used. |
| 241 | +
|
| 242 | +See `Profile.print` for an explanation of the valid keyword arguments. |
| 243 | +""" |
| 244 | +print(; kwargs...) = |
| 245 | + Profile.print(stdout, fetch(); kwargs...) |
| 246 | +print(io::IO; kwargs...) = |
| 247 | + Profile.print(io, fetch(); kwargs...) |
| 248 | +print(io::IO, data::AllocResults; kwargs...) = |
| 249 | + Profile.print(io, data; kwargs...) |
| 250 | +Profile.print(data::AllocResults; kwargs...) = |
| 251 | + Profile.print(stdout, data; kwargs...) |
| 252 | + |
| 253 | +function Profile.print(io::IO, |
| 254 | + data::AllocResults, |
| 255 | + ; |
| 256 | + format = :tree, |
| 257 | + C = false, |
| 258 | + #combine = true, |
| 259 | + maxdepth::Int = typemax(Int), |
| 260 | + mincount::Int = 0, |
| 261 | + noisefloor = 0, |
| 262 | + sortedby::Symbol = :filefuncline, |
| 263 | + groupby::Union{Symbol,AbstractVector{Symbol}} = :none, |
| 264 | + recur::Symbol = :off, |
| 265 | + ) |
| 266 | + pf = ProfileFormat(;C, maxdepth, mincount, noisefloor, sortedby, recur) |
| 267 | + Profile.print(io, data, pf, format) |
| 268 | + return |
| 269 | +end |
| 270 | + |
| 271 | +function Profile.print(io::IO, data::AllocResults, fmt::ProfileFormat, format::Symbol) |
| 272 | + cols::Int = Base.displaysize(io)[2] |
| 273 | + fmt.recur ∈ (:off, :flat, :flatc) || throw(ArgumentError("recur value not recognized")) |
| 274 | + data = data.allocs |
| 275 | + if format === :tree |
| 276 | + tree(io, data, cols, fmt) |
| 277 | + elseif format === :flat |
| 278 | + fmt.recur === :off || throw(ArgumentError("format flat only implements recur=:off")) |
| 279 | + flat(io, data, cols, fmt) |
| 280 | + else |
| 281 | + throw(ArgumentError("output format $(repr(format)) not recognized")) |
| 282 | + end |
| 283 | + nothing |
| 284 | +end |
| 285 | + |
| 286 | + |
| 287 | +function parse_flat(::Type{T}, data::Vector{Alloc}, C::Bool) where T |
| 288 | + lilist = StackFrame[] |
| 289 | + n = Int[] |
| 290 | + m = Int[] |
| 291 | + lilist_idx = Dict{T, Int}() |
| 292 | + recursive = Set{T}() |
| 293 | + totalbytes = 0 |
| 294 | + for r in data |
| 295 | + first = true |
| 296 | + empty!(recursive) |
| 297 | + nb = r.size # or 1 for counting |
| 298 | + totalbytes += nb |
| 299 | + for frame in r.stacktrace |
| 300 | + !C && frame.from_c && continue |
| 301 | + key = (T === UInt64 ? ip : frame) |
| 302 | + idx = get!(lilist_idx, key, length(lilist) + 1) |
| 303 | + if idx > length(lilist) |
| 304 | + push!(recursive, key) |
| 305 | + push!(lilist, frame) |
| 306 | + push!(n, nb) |
| 307 | + push!(m, 0) |
| 308 | + elseif !(key in recursive) |
| 309 | + push!(recursive, key) |
| 310 | + n[idx] += nb |
| 311 | + end |
| 312 | + if first |
| 313 | + m[idx] += nb |
| 314 | + first = false |
| 315 | + end |
| 316 | + end |
| 317 | + end |
| 318 | + @assert length(lilist) == length(n) == length(m) == length(lilist_idx) |
| 319 | + return (lilist, n, m, totalbytes) |
| 320 | +end |
| 321 | + |
| 322 | +function flat(io::IO, data::Vector{Alloc}, cols::Int, fmt::ProfileFormat) |
| 323 | + fmt.combine || error(ArgumentError("combine=false")) |
| 324 | + lilist, n, m, totalbytes = parse_flat(fmt.combine ? StackFrame : UInt64, data, fmt.C) |
| 325 | + filenamemap = Dict{Symbol,String}() |
| 326 | + if isempty(lilist) |
| 327 | + warning_empty() |
| 328 | + return true |
| 329 | + end |
| 330 | + print_flat(io, lilist, n, m, cols, filenamemap, fmt) |
| 331 | + Base.println(io, "Total snapshots: ", length(data)) |
| 332 | + Base.println(io, "Total bytes: ", totalbytes) |
| 333 | + return false |
| 334 | +end |
| 335 | + |
| 336 | +function tree!(root::StackFrameTree{T}, all::Vector{Alloc}, C::Bool, recur::Symbol) where {T} |
| 337 | + tops = Vector{StackFrameTree{T}}() |
| 338 | + build = Dict{T, StackFrameTree{T}}() |
| 339 | + for r in all |
| 340 | + first = true |
| 341 | + nb = r.size # or 1 for counting |
| 342 | + root.recur = 0 |
| 343 | + root.count += nb |
| 344 | + parent = root |
| 345 | + for i in reverse(eachindex(r.stacktrace)) |
| 346 | + frame = r.stacktrace[i] |
| 347 | + key = (T === UInt64 ? ip : frame) |
| 348 | + if (recur === :flat && !frame.from_c) || recur === :flatc |
| 349 | + # see if this frame already has a parent |
| 350 | + this = get!(build, frame, parent) |
| 351 | + if this !== parent |
| 352 | + # Rewind the `parent` tree back, if this exact ip (FIXME) was already present *higher* in the current tree |
| 353 | + push!(tops, parent) |
| 354 | + parent = this |
| 355 | + end |
| 356 | + end |
| 357 | + !C && frame.from_c && continue |
| 358 | + this = get!(StackFrameTree{T}, parent.down, key) |
| 359 | + if recur === :off || this.recur == 0 |
| 360 | + this.frame = frame |
| 361 | + this.up = parent |
| 362 | + this.count += nb |
| 363 | + this.recur = 1 |
| 364 | + else |
| 365 | + this.count_recur += 1 |
| 366 | + end |
| 367 | + parent = this |
| 368 | + end |
| 369 | + parent.overhead += nb |
| 370 | + if recur !== :off |
| 371 | + # We mark all visited nodes to so we'll only count those branches |
| 372 | + # once for each backtrace. Reset that now for the next backtrace. |
| 373 | + empty!(build) |
| 374 | + push!(tops, parent) |
| 375 | + for top in tops |
| 376 | + while top.recur != 0 |
| 377 | + top.max_recur < top.recur && (top.max_recur = top.recur) |
| 378 | + top.recur = 0 |
| 379 | + top = top.up |
| 380 | + end |
| 381 | + end |
| 382 | + empty!(tops) |
| 383 | + end |
| 384 | + let this = parent |
| 385 | + while this !== root |
| 386 | + this.flat_count += nb |
| 387 | + this = this.up |
| 388 | + end |
| 389 | + end |
| 390 | + end |
| 391 | + function cleanup!(node::StackFrameTree) |
| 392 | + stack = [node] |
| 393 | + while !isempty(stack) |
| 394 | + node = pop!(stack) |
| 395 | + node.recur = 0 |
| 396 | + empty!(node.builder_key) |
| 397 | + empty!(node.builder_value) |
| 398 | + append!(stack, values(node.down)) |
| 399 | + end |
| 400 | + nothing |
| 401 | + end |
| 402 | + cleanup!(root) |
| 403 | + return root |
| 404 | +end |
| 405 | + |
| 406 | +function tree(io::IO, data::Vector{Alloc}, cols::Int, fmt::ProfileFormat) |
| 407 | + fmt.combine || error(ArgumentError("combine=false")) |
| 408 | + if fmt.combine |
| 409 | + root = tree!(StackFrameTree{StackFrame}(), data, fmt.C, fmt.recur) |
| 410 | + else |
| 411 | + root = tree!(StackFrameTree{UInt64}(), data, fmt.C, fmt.recur) |
| 412 | + end |
| 413 | + print_tree(io, root, cols, fmt, false) |
| 414 | + if isempty(root.down) |
| 415 | + warning_empty() |
| 416 | + return true |
| 417 | + end |
| 418 | + Base.println(io, "Total snapshots: ", length(data)) |
| 419 | + Base.println(io, "Total bytes: ", root.count) |
| 420 | + return false |
| 421 | +end |
| 422 | + |
219 | 423 | end
|
0 commit comments