From a9dffcaa65b294b3c3b6dd528f09b059aed1f015 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Thu, 1 Aug 2024 15:01:03 -0400 Subject: [PATCH 01/14] color packages etc. in Profile.print() --- stdlib/Profile/Project.toml | 6 +++ stdlib/Profile/src/Allocs.jl | 2 +- stdlib/Profile/src/Profile.jl | 71 ++++++++++++++++++++++++----------- 3 files changed, 57 insertions(+), 22 deletions(-) diff --git a/stdlib/Profile/Project.toml b/stdlib/Profile/Project.toml index ad0107ecf9404..13cd11f70d9b4 100644 --- a/stdlib/Profile/Project.toml +++ b/stdlib/Profile/Project.toml @@ -2,6 +2,12 @@ name = "Profile" uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" version = "1.11.0" +[deps] +StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b" + +[compat] +StyledStrings = "1.11.0" + [extras] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" diff --git a/stdlib/Profile/src/Allocs.jl b/stdlib/Profile/src/Allocs.jl index 31d703a151ad8..15dfaefef0d70 100644 --- a/stdlib/Profile/src/Allocs.jl +++ b/stdlib/Profile/src/Allocs.jl @@ -321,7 +321,7 @@ end function flat(io::IO, data::Vector{Alloc}, cols::Int, fmt::ProfileFormat) fmt.combine || error(ArgumentError("combine=false")) lilist, n, m, totalbytes = parse_flat(fmt.combine ? StackFrame : UInt64, data, fmt.C) - filenamemap = Dict{Symbol,String}() + filenamemap = Dict{Symbol,Tuple{String,String}}() if isempty(lilist) warning_empty() return true diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 799f23034b9ac..14e9b207cb8e3 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -38,6 +38,7 @@ public clear, Allocs import Base.StackTraces: lookup, UNKNOWN, show_spec_linfo, StackFrame +using StyledStrings: @styled_str const nmeta = 4 # number of metadata fields per block (threadid, taskid, cpu_cycle_clock, thread_sleeping) @@ -503,7 +504,7 @@ end # Take a file-system path and try to form a concise representation of it # based on the package ecosystem -function short_path(spath::Symbol, filenamecache::Dict{Symbol, String}) +function short_path(spath::Symbol, filenamecache::Dict{Symbol, Tuple{String,String}}) return get!(filenamecache, spath) do path = Base.fixup_stdlib_path(string(spath)) if isabspath(path) @@ -523,19 +524,19 @@ function short_path(spath::Symbol, filenamecache::Dict{Symbol, String}) isempty(pkgid.name) && return path # bad Project file # return the joined the module name prefix and path suffix path = path[nextind(path, sizeof(root)):end] - return string("@", pkgid.name, path) + return string("@", pkgid.name), path end end end end - return path + return "", path elseif isfile(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "base", path)) # do the same mechanic for Base (or Core/Compiler) files as above, # but they start from a relative path - return joinpath("@Base", normpath(path)) + return "@Base", normpath(path) else # for non-existent relative paths (such as "REPL[1]"), just consider simplifying them - return normpath(path) # drop leading "./" + return "", normpath(path) # drop leading "./" end end end @@ -766,7 +767,7 @@ function flat(io::IO, data::Vector{UInt64}, lidict::Union{LineInfoDict, LineInfo m = m[keep] end util_perc = (1 - (nsleeping / totalshots)) * 100 - filenamemap = Dict{Symbol,String}() + filenamemap = Dict{Symbol,Tuple{String,String}}() if isempty(lilist) if is_subsection Base.print(io, "Total snapshots: ") @@ -790,7 +791,7 @@ end function print_flat(io::IO, lilist::Vector{StackFrame}, n::Vector{Int}, m::Vector{Int}, - cols::Int, filenamemap::Dict{Symbol,String}, + cols::Int, filenamemap::Dict{Symbol,Tuple{String,String}}, fmt::ProfileFormat) if fmt.sortedby === :count p = sortperm(n) @@ -802,7 +803,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, lilist = lilist[p] n = n[p] m = m[p] - filenames = String[short_path(li.file, filenamemap) for li in lilist] + pkgnames_filenames = Tuple{String,String}[short_path(li.file, filenamemap) for li in lilist] funcnames = String[string(li.func) for li in lilist] wcounts = max(6, ndigits(maximum(n))) wself = max(9, ndigits(maximum(m))) @@ -813,7 +814,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, li = lilist[i] maxline = max(maxline, li.line) maxfunc = max(maxfunc, length(funcnames[i])) - maxfile = max(maxfile, length(filenames[i])) + maxfile = max(maxfile, sum(textwidth, pkgnames_filenames[i]) + 1) end wline = max(5, ndigits(maxline)) ntext = max(20, cols - wcounts - wself - wline - 3) @@ -841,9 +842,17 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, Base.print(io, "[any unknown stackframes]") end else - file = filenames[i] + pkgname, file = pkgnames_filenames[i] isempty(file) && (file = "[unknown file]") - Base.print(io, rpad(rtruncto(file, wfile), wfile, " "), " ") + pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) + Base.printstyled(io, pkgname, color=pkgcolor) + file_trunc = rtruncto(file, wfile) + wpad = wfile - textwidth(pkgname) + if !isempty(pkgname) && !startswith(file_trunc, "/") + Base.print(io, "/") + wpad -= 1 + end + Base.print(io, rpad(file_trunc, wpad, " "), " ") Base.print(io, lpad(li.line > 0 ? string(li.line) : "?", wline, " "), " ") fname = funcnames[i] if !li.from_c && li.linfo !== nothing @@ -889,14 +898,17 @@ function indent(depth::Int) return indent end -function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, maxes, filenamemap::Dict{Symbol,String}, showpointer::Bool) +# mimics Stacktraces +const PACKAGE_FIXEDCOLORS = Dict{String, Any}("@Base" => :light_black, "@Core" => :light_black) + +function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, maxes, filenamemap::Dict{Symbol,Tuple{String,String}}, showpointer::Bool) nindent = min(cols>>1, level) ndigoverhead = ndigits(maxes.overhead) ndigcounts = ndigits(maxes.count) ndigline = ndigits(maximum(frame.frame.line for frame in frames)) + 6 ntext = max(30, cols - ndigoverhead - nindent - ndigcounts - ndigline - 6) widthfile = 2*ntext÷5 # min 12 - strs = Vector{String}(undef, length(frames)) + strs = Vector{Base.AnnotatedString{String}}(undef, length(frames)) showextra = false if level > nindent nextra = level - nindent @@ -924,7 +936,7 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma else fname = string(li.func) end - filename = short_path(li.file, filenamemap) + pkgname, filename = short_path(li.file, filenamemap) if showpointer fname = string( "0x", @@ -932,8 +944,12 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma " ", fname) end - strs[i] = string(stroverhead, "╎", base, strcount, " ", - rtruncto(filename, widthfile), + pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) + remaining_path = rtruncto(filename, widthfile - length(pkgname) - 1) + strs[i] = Base.annotatedstring(stroverhead, "╎", base, strcount, " ", + styled"{$pkgcolor:$pkgname}", + !isempty(pkgname) && !startswith(remaining_path, "/") ? "/" : "", + remaining_path, ":", li.line == -1 ? "?" : string(li.line), "; ", @@ -1101,7 +1117,7 @@ end # avoid stack overflows. function print_tree(io::IO, bt::StackFrameTree{T}, cols::Int, fmt::ProfileFormat, is_subsection::Bool) where T maxes = maxstats(bt) - filenamemap = Dict{Symbol,String}() + filenamemap = Dict{Symbol,Tuple{String,String}}() worklist = [(bt, 0, 0, "")] if !is_subsection Base.print(io, "Overhead ╎ [+additional indent] Count File:Line; Function\n") @@ -1197,22 +1213,35 @@ function callersf(matchfunc::Function, bt::Vector, lidict::LineInfoFlatDict) end # Utilities -function rtruncto(str::String, w::Int) +function rtruncto(str::AbstractString, w::Int, replace_str::AbstractString = "…") if textwidth(str) <= w return str else - return string("…", str[prevind(str, end, w-2):end]) + i, acclen = firstindex(str), textwidth(replace_str) + while acclen + textwidth(str[i]) <= w && i < lastindex(str) + acclen += textwidth(str[i]) + i = nextind(str, i) + end + return str[firstindex(str):prevind(str, i)] * replace_str end end -function ltruncto(str::String, w::Int) + +function ltruncto(str::AbstractString, w::Int, replace_str::AbstractString = "…") if textwidth(str) <= w return str else - return string(str[1:nextind(str, 1, w-2)], "…") + i, acclen = lastindex(str), textwidth(replace_str) + while acclen + textwidth(str[i]) <= w && i > firstindex(str) + acclen += textwidth(str[i]) + i = prevind(str, i) + end + return replace_str * str[nextind(str, i):lastindex(str)] end end + + truncto(str::Symbol, w::Int) = truncto(string(str), w) # Order alphabetically (file, function) and then by line number From 538c7663e5a53375c06becc82223e58d22d57692 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sun, 11 Aug 2024 16:34:06 +0200 Subject: [PATCH 02/14] use new Base string truncation functions --- stdlib/Profile/src/Profile.jl | 40 +++++------------------------------ 1 file changed, 5 insertions(+), 35 deletions(-) diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 14e9b207cb8e3..8bfaa51726d48 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -846,7 +846,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, isempty(file) && (file = "[unknown file]") pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) Base.printstyled(io, pkgname, color=pkgcolor) - file_trunc = rtruncto(file, wfile) + file_trunc = ltruncate(file, wfile) wpad = wfile - textwidth(pkgname) if !isempty(pkgname) && !startswith(file_trunc, "/") Base.print(io, "/") @@ -859,7 +859,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, fname = sprint(show_spec_linfo, li) end isempty(fname) && (fname = "[unknown function]") - Base.print(io, ltruncto(fname, wfunc)) + Base.print(io, rtruncate(fname, wfunc)) end println(io) end @@ -945,7 +945,7 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma fname) end pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) - remaining_path = rtruncto(filename, widthfile - length(pkgname) - 1) + remaining_path = ltruncate(filename, widthfile - length(pkgname) - 1) strs[i] = Base.annotatedstring(stroverhead, "╎", base, strcount, " ", styled"{$pkgcolor:$pkgname}", !isempty(pkgname) && !startswith(remaining_path, "/") ? "/" : "", @@ -958,7 +958,7 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma else strs[i] = string(stroverhead, "╎", base, strcount, " [unknown stackframe]") end - strs[i] = ltruncto(strs[i], cols) + strs[i] = rtruncate(strs[i], cols) end return strs end @@ -1212,37 +1212,7 @@ function callersf(matchfunc::Function, bt::Vector, lidict::LineInfoFlatDict) return [(v[i], k[i]) for i in p] end -# Utilities -function rtruncto(str::AbstractString, w::Int, replace_str::AbstractString = "…") - if textwidth(str) <= w - return str - else - i, acclen = firstindex(str), textwidth(replace_str) - while acclen + textwidth(str[i]) <= w && i < lastindex(str) - acclen += textwidth(str[i]) - i = nextind(str, i) - end - return str[firstindex(str):prevind(str, i)] * replace_str - end -end - -function ltruncto(str::AbstractString, w::Int, replace_str::AbstractString = "…") - if textwidth(str) <= w - return str - else - i, acclen = lastindex(str), textwidth(replace_str) - while acclen + textwidth(str[i]) <= w && i > firstindex(str) - acclen += textwidth(str[i]) - i = prevind(str, i) - end - return replace_str * str[nextind(str, i):lastindex(str)] - end -end - - - - -truncto(str::Symbol, w::Int) = truncto(string(str), w) +## Utilities # Order alphabetically (file, function) and then by line number function liperm(lilist::Vector{StackFrame}) From a67732b403765628cfde5a852f2209752db7b33f Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sun, 11 Aug 2024 16:45:39 +0200 Subject: [PATCH 03/14] use eachindex --- stdlib/Profile/src/Profile.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 8bfaa51726d48..84e578b1a1512 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -679,7 +679,7 @@ function add_fake_meta(data; threadid = 1, taskid = 0xf0f0f0f0) !isempty(data) && has_meta(data) && error("input already has metadata") cpu_clock_cycle = UInt64(99) data_with_meta = similar(data, 0) - for i = 1:length(data) + for i in eachindex(data) val = data[i] if iszero(val) # META_OFFSET_THREADID, META_OFFSET_TASKID, META_OFFSET_CPUCYCLECLOCK, META_OFFSET_SLEEPSTATE @@ -810,7 +810,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, maxline = 1 maxfile = 6 maxfunc = 10 - for i in 1:length(lilist) + for i in eachindex(lilist) li = lilist[i] maxline = max(maxline, li.line) maxfunc = max(maxfunc, length(funcnames[i])) @@ -830,7 +830,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, rpad("File", wfile, " "), " ", lpad("Line", wline, " "), " Function") println(io, lpad("=====", wcounts, " "), " ", lpad("========", wself, " "), " ", rpad("====", wfile, " "), " ", lpad("====", wline, " "), " ========") - for i = 1:length(n) + for i in eachindex(n) n[i] < fmt.mincount && continue li = lilist[i] Base.print(io, lpad(string(n[i]), wcounts, " "), " ") @@ -915,7 +915,7 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma nindent -= ndigits(nextra) + 2 showextra = true end - for i = 1:length(frames) + for i in eachindex(frames) frame = frames[i] li = frame.frame stroverhead = lpad(frame.overhead > 0 ? string(frame.overhead) : "", ndigoverhead, " ") From 024866f51e50ee647f8992d5161c11d2b9152d29 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sun, 11 Aug 2024 17:12:40 +0200 Subject: [PATCH 04/14] fix some length -> textwidth --- stdlib/Profile/src/Profile.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 84e578b1a1512..660506e83b5af 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -813,7 +813,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, for i in eachindex(lilist) li = lilist[i] maxline = max(maxline, li.line) - maxfunc = max(maxfunc, length(funcnames[i])) + maxfunc = max(maxfunc, textwidth(funcnames[i])) maxfile = max(maxfile, sum(textwidth, pkgnames_filenames[i]) + 1) end wline = max(5, ndigits(maxline)) @@ -945,7 +945,7 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma fname) end pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) - remaining_path = ltruncate(filename, widthfile - length(pkgname) - 1) + remaining_path = ltruncate(filename, widthfile - textwidth(pkgname) - 1) strs[i] = Base.annotatedstring(stroverhead, "╎", base, strcount, " ", styled"{$pkgcolor:$pkgname}", !isempty(pkgname) && !startswith(remaining_path, "/") ? "/" : "", From 8e53b1fbcdd615b7224790f7a7d5fead938db4c6 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sun, 11 Aug 2024 18:30:36 +0200 Subject: [PATCH 05/14] StyledStrings fixes --- stdlib/Profile/src/Profile.jl | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 660506e83b5af..3abaf1778e113 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -38,6 +38,7 @@ public clear, Allocs import Base.StackTraces: lookup, UNKNOWN, show_spec_linfo, StackFrame +import Base: AnnotatedString using StyledStrings: @styled_str const nmeta = 4 # number of metadata fields per block (threadid, taskid, cpu_cycle_clock, thread_sleeping) @@ -67,7 +68,7 @@ function _peek_report() iob = IOBuffer() ioc = IOContext(IOContext(iob, stderr), :displaysize=>displaysize(stderr)) print(ioc, groupby = [:thread, :task]) - Base.print(stderr, String(take!(iob))) + Base.print(stderr, read(seekstart(iob), AnnotatedString)) end # This is a ref so that it can be overridden by other profile info consumers. const peek_report = Ref{Function}(_peek_report) @@ -899,7 +900,7 @@ function indent(depth::Int) end # mimics Stacktraces -const PACKAGE_FIXEDCOLORS = Dict{String, Any}("@Base" => :light_black, "@Core" => :light_black) +const PACKAGE_FIXEDCOLORS = Dict{String, Any}("@Base" => :gray, "@Core" => :gray) function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, maxes, filenamemap::Dict{Symbol,Tuple{String,String}}, showpointer::Bool) nindent = min(cols>>1, level) @@ -908,7 +909,7 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma ndigline = ndigits(maximum(frame.frame.line for frame in frames)) + 6 ntext = max(30, cols - ndigoverhead - nindent - ndigcounts - ndigline - 6) widthfile = 2*ntext÷5 # min 12 - strs = Vector{Base.AnnotatedString{String}}(undef, length(frames)) + strs = Vector{AnnotatedString{String}}(undef, length(frames)) showextra = false if level > nindent nextra = level - nindent @@ -1118,7 +1119,7 @@ end function print_tree(io::IO, bt::StackFrameTree{T}, cols::Int, fmt::ProfileFormat, is_subsection::Bool) where T maxes = maxstats(bt) filenamemap = Dict{Symbol,Tuple{String,String}}() - worklist = [(bt, 0, 0, "")] + worklist = [(bt, 0, 0, AnnotatedString(""))] if !is_subsection Base.print(io, "Overhead ╎ [+additional indent] Count File:Line; Function\n") Base.print(io, "=========================================================\n") @@ -1151,7 +1152,7 @@ function print_tree(io::IO, bt::StackFrameTree{T}, cols::Int, fmt::ProfileFormat count = down.count count < fmt.mincount && continue count < noisefloor && continue - str = strs[i] + str = strs[i]::AnnotatedString noisefloor_down = fmt.noisefloor > 0 ? floor(Int, fmt.noisefloor * sqrt(count)) : 0 pushfirst!(worklist, (down, level + 1, noisefloor_down, str)) end From 993bb8501a50e2e146f9e2ada68a19a040ba744b Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Sun, 1 Sep 2024 23:40:37 -0400 Subject: [PATCH 06/14] make file paths clickable in URI-recognizing terminal --- stdlib/Profile/src/Allocs.jl | 2 +- stdlib/Profile/src/Profile.jl | 84 +++++++++++++++++++++++++---------- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/stdlib/Profile/src/Allocs.jl b/stdlib/Profile/src/Allocs.jl index 15dfaefef0d70..d48a10bd097fc 100644 --- a/stdlib/Profile/src/Allocs.jl +++ b/stdlib/Profile/src/Allocs.jl @@ -321,7 +321,7 @@ end function flat(io::IO, data::Vector{Alloc}, cols::Int, fmt::ProfileFormat) fmt.combine || error(ArgumentError("combine=false")) lilist, n, m, totalbytes = parse_flat(fmt.combine ? StackFrame : UInt64, data, fmt.C) - filenamemap = Dict{Symbol,Tuple{String,String}}() + filenamemap = FileNameMap() if isempty(lilist) warning_empty() return true diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index 3abaf1778e113..bbdf4c3afa693 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -505,9 +505,10 @@ end # Take a file-system path and try to form a concise representation of it # based on the package ecosystem -function short_path(spath::Symbol, filenamecache::Dict{Symbol, Tuple{String,String}}) +function short_path(spath::Symbol, filenamecache::Dict{Symbol, Tuple{String,String,String}}) return get!(filenamecache, spath) do path = Base.fixup_stdlib_path(string(spath)) + possible_base_path = normpath(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "base", path)) if isabspath(path) if ispath(path) # try to replace the file-system prefix with a short "@Module" one, @@ -524,20 +525,21 @@ function short_path(spath::Symbol, filenamecache::Dict{Symbol, Tuple{String,Stri pkgid = Base.project_file_name_uuid(project_file, "") isempty(pkgid.name) && return path # bad Project file # return the joined the module name prefix and path suffix - path = path[nextind(path, sizeof(root)):end] - return string("@", pkgid.name), path + _short_path = path[nextind(path, sizeof(root)):end] + return path, string("@", pkgid.name), _short_path end end end end - return "", path - elseif isfile(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "base", path)) + return path, "", path + elseif isfile(possible_base_path) # do the same mechanic for Base (or Core/Compiler) files as above, # but they start from a relative path - return "@Base", normpath(path) + return possible_base_path, "@Base", normpath(path) else # for non-existent relative paths (such as "REPL[1]"), just consider simplifying them - return "", normpath(path) # drop leading "./" + path = normpath(path) + return "", "", path # drop leading "./" end end end @@ -758,6 +760,8 @@ function parse_flat(::Type{T}, data::Vector{UInt64}, lidict::Union{LineInfoDict, return (lilist, n, m, totalshots, nsleeping) end +const FileNameMap = Dict{Symbol,Tuple{String,String,String}} + function flat(io::IO, data::Vector{UInt64}, lidict::Union{LineInfoDict, LineInfoFlatDict}, cols::Int, fmt::ProfileFormat, threads::Union{Int,AbstractVector{Int}}, tasks::Union{UInt,AbstractVector{UInt}}, is_subsection::Bool) lilist, n, m, totalshots, nsleeping = parse_flat(fmt.combine ? StackFrame : UInt64, data, lidict, fmt.C, threads, tasks) @@ -768,7 +772,7 @@ function flat(io::IO, data::Vector{UInt64}, lidict::Union{LineInfoDict, LineInfo m = m[keep] end util_perc = (1 - (nsleeping / totalshots)) * 100 - filenamemap = Dict{Symbol,Tuple{String,String}}() + filenamemap = FileNameMap() if isempty(lilist) if is_subsection Base.print(io, "Total snapshots: ") @@ -790,9 +794,34 @@ function flat(io::IO, data::Vector{UInt64}, lidict::Union{LineInfoDict, LineInfo return false end +# make a terminal-clickable link to the file and linenum. +# Similar to `define_default_editors` in `Base.Filesystem` but for creating URIs not commands +function editor_link(path::String, linenum::Int) + editor = get(ENV, "JULIA_EDITOR", "") + + if editor == "code" + return "vscode://file/$path:$linenum" + elseif editor == "subl" || editor == "sublime_text" + return "subl://$path:$linenum" + elseif editor == "idea" || occursin("idea", editor) + return "idea://open?file=$path&line=$linenum" + elseif editor == "pycharm" + return "pycharm://open?file=$path&line=$linenum" + elseif editor == "atom" + return "atom://core/open/file?filename=$path&line=$linenum" + elseif editor == "emacsclient" + return "emacs://open?file=$path&line=$linenum" + elseif editor == "vim" || editor == "nvim" + return "vim://open?file=$path&line=$linenum" + else + # TODO: convert the path to a generic URI (line numbers are not supported by generic URI) + return path + end +end + function print_flat(io::IO, lilist::Vector{StackFrame}, n::Vector{Int}, m::Vector{Int}, - cols::Int, filenamemap::Dict{Symbol,Tuple{String,String}}, + cols::Int, filenamemap::FileNameMap, fmt::ProfileFormat) if fmt.sortedby === :count p = sortperm(n) @@ -804,7 +833,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, lilist = lilist[p] n = n[p] m = m[p] - pkgnames_filenames = Tuple{String,String}[short_path(li.file, filenamemap) for li in lilist] + pkgnames_filenames = Tuple{String,String,String}[short_path(li.file, filenamemap) for li in lilist] funcnames = String[string(li.func) for li in lilist] wcounts = max(6, ndigits(maximum(n))) wself = max(9, ndigits(maximum(m))) @@ -815,7 +844,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, li = lilist[i] maxline = max(maxline, li.line) maxfunc = max(maxfunc, textwidth(funcnames[i])) - maxfile = max(maxfile, sum(textwidth, pkgnames_filenames[i]) + 1) + maxfile = max(maxfile, sum(textwidth, pkgnames_filenames[i][2:3]) + 1) end wline = max(5, ndigits(maxline)) ntext = max(20, cols - wcounts - wself - wline - 3) @@ -843,7 +872,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, Base.print(io, "[any unknown stackframes]") end else - pkgname, file = pkgnames_filenames[i] + path, pkgname, file = pkgnames_filenames[i] isempty(file) && (file = "[unknown file]") pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) Base.printstyled(io, pkgname, color=pkgcolor) @@ -853,7 +882,12 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, Base.print(io, "/") wpad -= 1 end - Base.print(io, rpad(file_trunc, wpad, " "), " ") + if isempty(path) + Base.print(io, rpad(file_trunc, wpad, " ")) + else + link = editor_link(path, li.line) + Base.print(io, rpad(styled"{link=$link:$file_trunc}", wpad, " ")) + end Base.print(io, lpad(li.line > 0 ? string(li.line) : "?", wline, " "), " ") fname = funcnames[i] if !li.from_c && li.linfo !== nothing @@ -902,7 +936,7 @@ end # mimics Stacktraces const PACKAGE_FIXEDCOLORS = Dict{String, Any}("@Base" => :gray, "@Core" => :gray) -function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, maxes, filenamemap::Dict{Symbol,Tuple{String,String}}, showpointer::Bool) +function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, maxes, filenamemap::FileNameMap, showpointer::Bool) nindent = min(cols>>1, level) ndigoverhead = ndigits(maxes.overhead) ndigcounts = ndigits(maxes.count) @@ -937,7 +971,7 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma else fname = string(li.func) end - pkgname, filename = short_path(li.file, filenamemap) + path, pkgname, filename = short_path(li.file, filenamemap) if showpointer fname = string( "0x", @@ -947,14 +981,16 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma end pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) remaining_path = ltruncate(filename, widthfile - textwidth(pkgname) - 1) - strs[i] = Base.annotatedstring(stroverhead, "╎", base, strcount, " ", - styled"{$pkgcolor:$pkgname}", - !isempty(pkgname) && !startswith(remaining_path, "/") ? "/" : "", - remaining_path, - ":", - li.line == -1 ? "?" : string(li.line), - "; ", - fname) + linenum = li.line == -1 ? "?" : string(li.line) + slash = (!isempty(pkgname) && !startswith(remaining_path, "/")) ? "/" : "" + styled_path = styled"{$pkgcolor:$pkgname}$slash$remaining_path:$linenum" + rich_file = if isempty(path) + styled_path + else + link = editor_link(path, li.line) + styled"{link=$link:$styled_path}" + end + strs[i] = Base.annotatedstring(stroverhead, "╎", base, strcount, " ", rich_file, "; $fname") end else strs[i] = string(stroverhead, "╎", base, strcount, " [unknown stackframe]") @@ -1118,7 +1154,7 @@ end # avoid stack overflows. function print_tree(io::IO, bt::StackFrameTree{T}, cols::Int, fmt::ProfileFormat, is_subsection::Bool) where T maxes = maxstats(bt) - filenamemap = Dict{Symbol,Tuple{String,String}}() + filenamemap = FileNameMap() worklist = [(bt, 0, 0, AnnotatedString(""))] if !is_subsection Base.print(io, "Overhead ╎ [+additional indent] Count File:Line; Function\n") From 6ae0b18995122633e0693986d0805279d3138f3d Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 2 Sep 2024 00:07:58 -0400 Subject: [PATCH 07/14] add news --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index b5caaf5376fb5..95a8a51c67ac8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -138,6 +138,9 @@ Standard library changes * `Profile.take_heap_snapshot` takes a new keyword argument, `redact_data::Bool`, that is `true` by default. When set, the contents of Julia objects are not emitted in the heap snapshot. This currently only applies to strings. ([#55326]) +* `Profile.print()` now colors Base/Core/Package modules similarly to how they are in stacktraces. + Also paths, even if truncated, are now clickable in terminals that support URI links + to take you to the specified `JULIA_EDITOR` for the given file & line number. ([#55335]) #### Random From bbc1a4049b16be98895404506db4c03805a4e0db Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 2 Sep 2024 13:28:51 -0400 Subject: [PATCH 08/14] bold lines that have overhead --- stdlib/Profile/src/Profile.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index bbdf4c3afa693..aee07489af9e9 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -990,7 +990,10 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma link = editor_link(path, li.line) styled"{link=$link:$styled_path}" end - strs[i] = Base.annotatedstring(stroverhead, "╎", base, strcount, " ", rich_file, "; $fname") + strs[i] = Base.annotatedstring(stroverhead, "╎", base, strcount, " ", rich_file, "; ", fname) + if frame.overhead > 0 + strs[i] = styled"{bold:$(strs[i])}" + end end else strs[i] = string(stroverhead, "╎", base, strcount, " [unknown stackframe]") From d08de7848dc989ecac364e337c109034570096bc Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 2 Sep 2024 13:38:58 -0400 Subject: [PATCH 09/14] fix --- stdlib/Profile/src/Profile.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index aee07489af9e9..b048c6a150aa8 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -65,7 +65,7 @@ end # An internal function called to show the report after an information request (SIGINFO or SIGUSR1). function _peek_report() - iob = IOBuffer() + iob = Base.AnnotatedIOBuffer() ioc = IOContext(IOContext(iob, stderr), :displaysize=>displaysize(stderr)) print(ioc, groupby = [:thread, :task]) Base.print(stderr, read(seekstart(iob), AnnotatedString)) From d041e9f5577513b44f12b8e309ee0ea507df8438 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 2 Sep 2024 13:48:19 -0400 Subject: [PATCH 10/14] remove ; --- stdlib/Profile/src/Profile.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index b048c6a150aa8..fa05d5a5288f8 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -268,7 +268,7 @@ function print(io::IO, end any_nosamples = true if format === :tree - Base.print(io, "Overhead ╎ [+additional indent] Count File:Line; Function\n") + Base.print(io, "Overhead ╎ [+additional indent] Count File:Line Function\n") Base.print(io, "=========================================================\n") end if groupby == [:task, :thread] @@ -990,7 +990,7 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma link = editor_link(path, li.line) styled"{link=$link:$styled_path}" end - strs[i] = Base.annotatedstring(stroverhead, "╎", base, strcount, " ", rich_file, "; ", fname) + strs[i] = Base.annotatedstring(stroverhead, "╎", base, strcount, " ", rich_file, " ", fname) if frame.overhead > 0 strs[i] = styled"{bold:$(strs[i])}" end @@ -1160,7 +1160,7 @@ function print_tree(io::IO, bt::StackFrameTree{T}, cols::Int, fmt::ProfileFormat filenamemap = FileNameMap() worklist = [(bt, 0, 0, AnnotatedString(""))] if !is_subsection - Base.print(io, "Overhead ╎ [+additional indent] Count File:Line; Function\n") + Base.print(io, "Overhead ╎ [+additional indent] Count File:Line Function\n") Base.print(io, "=========================================================\n") end while !isempty(worklist) From 7c16e91570ce1911fa3321e618e5ebaa7f32e93d Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Mon, 2 Sep 2024 15:54:51 -0400 Subject: [PATCH 11/14] Update Allocs.jl --- stdlib/Profile/src/Allocs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/Profile/src/Allocs.jl b/stdlib/Profile/src/Allocs.jl index d48a10bd097fc..9d0b18cb468ca 100644 --- a/stdlib/Profile/src/Allocs.jl +++ b/stdlib/Profile/src/Allocs.jl @@ -321,7 +321,7 @@ end function flat(io::IO, data::Vector{Alloc}, cols::Int, fmt::ProfileFormat) fmt.combine || error(ArgumentError("combine=false")) lilist, n, m, totalbytes = parse_flat(fmt.combine ? StackFrame : UInt64, data, fmt.C) - filenamemap = FileNameMap() + filenamemap = Profile.FileNameMap() if isempty(lilist) warning_empty() return true From 15b24a8d89211d3188aec0a9a568a14d03c34044 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 3 Sep 2024 12:15:16 -0400 Subject: [PATCH 12/14] NFC: update manifest stdlib versions --- stdlib/Manifest.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/stdlib/Manifest.toml b/stdlib/Manifest.toml index c9d2086432a85..19ad5d2550c59 100644 --- a/stdlib/Manifest.toml +++ b/stdlib/Manifest.toml @@ -68,12 +68,12 @@ version = "1.11.0" [[deps.JuliaSyntaxHighlighting]] deps = ["StyledStrings"] uuid = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011" -version = "1.11.0" +version = "1.12.0" [[deps.LLD_jll]] deps = ["Artifacts", "Libdl", "Zlib_jll", "libLLVM_jll"] uuid = "d55e3150-da41-5e91-b323-ecfd1eec6109" -version = "16.0.6+4" +version = "18.1.7+2" [[deps.LLVMLibUnwind_jll]] deps = ["Artifacts", "Libdl"] @@ -113,12 +113,12 @@ version = "1.11.0+1" [[deps.LibUV_jll]] deps = ["Artifacts", "Libdl"] uuid = "183b4373-6708-53ba-ad28-60e28bb38547" -version = "2.0.1+16" +version = "2.0.1+17" [[deps.LibUnwind_jll]] deps = ["Artifacts", "Libdl"] uuid = "745a5e78-f969-53e9-954f-d19f2f74f4e3" -version = "1.8.1+0" +version = "1.8.1+1" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" @@ -163,7 +163,7 @@ version = "1.2.0" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.26+2" +version = "0.3.28+2" [[deps.OpenLibm_jll]] deps = ["Artifacts", "Libdl"] @@ -223,7 +223,7 @@ version = "1.11.0" [[deps.SparseArrays]] deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" -version = "1.11.0" +version = "1.12.0" [[deps.Statistics]] deps = ["LinearAlgebra"] @@ -242,7 +242,7 @@ version = "1.11.0" [[deps.SuiteSparse_jll]] deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "7.7.0+0" +version = "7.8.0+0" [[deps.TOML]] deps = ["Dates"] @@ -281,12 +281,12 @@ version = "2.2.5+0" [[deps.libLLVM_jll]] deps = ["Artifacts", "Libdl"] uuid = "8f36deef-c2a5-5394-99ed-8e07531fb29a" -version = "16.0.6+4" +version = "18.1.7+2" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+1" +version = "5.11.0+0" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] From 406b1a8051c65768d24e08fd902dfa1d7e1f1188 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 3 Sep 2024 12:15:39 -0400 Subject: [PATCH 13/14] add styledstrings dep to stdlib manifest --- stdlib/Manifest.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/Manifest.toml b/stdlib/Manifest.toml index 19ad5d2550c59..f9fb307190838 100644 --- a/stdlib/Manifest.toml +++ b/stdlib/Manifest.toml @@ -190,6 +190,7 @@ uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" version = "1.11.0" [[deps.Profile]] +deps = ["StyledStrings"] uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" version = "1.11.0" From cf8fa76e3520f86a1113eb5a574ea8d889225d7d Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Tue, 3 Sep 2024 15:24:00 -0400 Subject: [PATCH 14/14] avoid erroring in very narrow terminals --- stdlib/Profile/src/Profile.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/Profile/src/Profile.jl b/stdlib/Profile/src/Profile.jl index fa05d5a5288f8..a80e6c71e5aef 100644 --- a/stdlib/Profile/src/Profile.jl +++ b/stdlib/Profile/src/Profile.jl @@ -876,7 +876,7 @@ function print_flat(io::IO, lilist::Vector{StackFrame}, isempty(file) && (file = "[unknown file]") pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) Base.printstyled(io, pkgname, color=pkgcolor) - file_trunc = ltruncate(file, wfile) + file_trunc = ltruncate(file, max(1, wfile)) wpad = wfile - textwidth(pkgname) if !isempty(pkgname) && !startswith(file_trunc, "/") Base.print(io, "/") @@ -980,7 +980,7 @@ function tree_format(frames::Vector{<:StackFrameTree}, level::Int, cols::Int, ma fname) end pkgcolor = get!(() -> popfirst!(Base.STACKTRACE_MODULECOLORS), PACKAGE_FIXEDCOLORS, pkgname) - remaining_path = ltruncate(filename, widthfile - textwidth(pkgname) - 1) + remaining_path = ltruncate(filename, max(1, widthfile - textwidth(pkgname) - 1)) linenum = li.line == -1 ? "?" : string(li.line) slash = (!isempty(pkgname) && !startswith(remaining_path, "/")) ? "/" : "" styled_path = styled"{$pkgcolor:$pkgname}$slash$remaining_path:$linenum"