Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gtk4 port #223

Merged
merged 24 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
627c48b
use Gtk4.jl
jwahlstrand Jan 18, 2023
36e390f
Merge 'origin/master' into gtk4
jwahlstrand Mar 5, 2023
37b9ce7
mask out private values in Gdk.ModifierType
jwahlstrand Mar 22, 2023
b5d10fd
fix open, save, and info dialogs
jwahlstrand Mar 23, 2023
b71a404
fixes for Gtk4 0.4
jwahlstrand May 29, 2023
327c01d
depend on GtkObservables (gtk4 branch)
jwahlstrand Jul 1, 2023
8ce9d26
Merge branch 'master' into gtk4
jwahlstrand Jul 1, 2023
4b9c313
get tests passing again
jwahlstrand Jul 1, 2023
0930b10
suppress warning in info_cb
jwahlstrand Jul 2, 2023
9c1113b
Merge branch 'master' into gtk4
jwahlstrand Jul 24, 2023
bccc867
pause GLib main loop during profiling and retrieval
jwahlstrand Jul 30, 2023
d34dcbe
bump Gtk4 compat
jwahlstrand Aug 6, 2023
e97178f
bump GtkObservables compat
jwahlstrand Sep 17, 2023
198582b
Update src/ProfileView.jl
jwahlstrand Jan 19, 2024
f696b4a
Apply suggestions from code review
jwahlstrand Jan 19, 2024
970451d
bump Gtk4 compat while keeping 0.5
jwahlstrand Jan 19, 2024
0f4e77c
Merge branch 'master' into pr/223
IanButterworth Aug 5, 2024
03521e6
Merge branch 'master' into pr/223
IanButterworth Aug 5, 2024
f3754d0
remove Gtk fix from master
IanButterworth Aug 5, 2024
68c258f
add additional sleep to get tests working locally on MacOS
Aug 5, 2024
a17f15b
a bit of polish
jwahlstrand Aug 10, 2024
aae3d94
bump runtime of profile function
jwahlstrand Aug 11, 2024
0593624
that seemed to work? remove println
jwahlstrand Aug 11, 2024
896e0fc
comment out precompile code that causes problems on MacOS
Aug 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
FlameGraphs = "08572546-2f56-4bcf-ba4e-bab62c3a3f89"
Graphics = "a2bd30eb-e257-5431-a919-1863eab51364"
Gtk = "4c0ca9eb-093a-5379-98c5-f87ac0bbbf44"
Gtk4 = "9db2cae5-386f-4011-9d63-a5602296539b"
GtkObservables = "8710efd8-4ad6-11eb-33ea-2d5ceb25a41c"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953"
Expand All @@ -27,8 +27,8 @@ Colors = "0.9, 0.10, 0.11, 0.12"
FileIO = "1.6"
FlameGraphs = "0.2.10, 1"
Graphics = "0.4, 1"
Gtk = "1.2"
GtkObservables = "1"
Gtk4 = "0.5, 0.6"
GtkObservables = "2"
IntervalSets = "0.2, 0.3, 0.4, 0.5, 0.6, 0.7"
MethodAnalysis = "0.4"
Preferences = "1.2"
Expand Down
160 changes: 90 additions & 70 deletions src/ProfileView.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@
using Base.StackTraces: StackFrame
using MethodAnalysis
using InteractiveUtils
using Gtk.ShortNames, GtkObservables, Colors, FileIO, IntervalSets
using Gtk4, GtkObservables, Colors, FileIO, IntervalSets
import GtkObservables: Canvas
import Cairo
using Graphics
using Preferences
using Requires

using FlameGraphs: Node, NodeData
using Gtk.GConstants.GdkModifierType: SHIFT, CONTROL, MOD1
const CONTROL = Gtk4.ModifierType_CONTROL_MASK

export @profview, warntype_clicked, descend_clicked, ascend_clicked
@deprecate warntype_last warntype_clicked
Expand Down Expand Up @@ -114,13 +115,13 @@
return quote
Profile.clear()
# pause the eventloop while profiling
before = Gtk.is_eventloop_running()
before = Gtk4.GLib.is_loop_running()
dt = Dates.now()
try
Gtk.enable_eventloop(false, wait_stopped = true)
Gtk4.GLib.stop_main_loop(true)
@profile $(esc(ex))
finally
Gtk.enable_eventloop(before, wait_stopped = true)
before && Gtk4.GLib.start_main_loop()
end
view(;windowname = "Profile - $(Time(round(dt, Second)))")
end
Expand All @@ -139,7 +140,7 @@
return nothing
end

const window_wrefs = WeakKeyDict{Gtk.GtkWindowLeaf,Nothing}()
const window_wrefs = WeakKeyDict{Gtk4.GtkWindowLeaf,Nothing}()
const tabname_allthreads = Symbol("All Threads")
const tabname_alltasks = Symbol("All Tasks")

Expand All @@ -153,7 +154,7 @@
- `fcolor`: an optional coloration function. The main options are `FlameGraphs.FlameColors`
and `FlameGraphs.StackFrameCategory`.
- `C::Bool = false`: if true, the graph will include stackframes from C code called by Julia.
- `recur`: on Julia 1.4+, collapse recursive calls (see `Profile.print` for more detail)
- `recur`: collapse recursive calls (see `Profile.print` for more detail)
- `expand_threads::Bool = true`: Break down profiling by thread (true by default)
- `expand_tasks::Bool = false`: Break down profiling of each thread by task (false by default)
- `graphtype::Symbol = :default`: Control how the graph is shown. `:flame` displays from the bottom up, `:icicle` from
Expand Down Expand Up @@ -198,7 +199,7 @@
end
function view(; kwargs...)
# pausing the event loop here to facilitate a fast retrieve
data, lidict = Gtk.pause_eventloop() do
data, lidict = Gtk4.GLib.pause_main_loop() do
Profile.retrieve()
end
view(_theme_colors[_theme[]], data; lidict=lidict, kwargs...)
Expand All @@ -213,14 +214,14 @@
end
function view(fcolor, g::Node{NodeData}; data=nothing, lidict=nothing, kwargs...)
win, _ = viewgui(fcolor, g; data=data, lidict=lidict, kwargs...)
Gtk.showall(win)
win
end
function view(g_or_gdict::Union{Node{NodeData},NestedGraphDict}; kwargs...)
view(_theme_colors[_theme[]], g_or_gdict; kwargs...)
end
function view(fcolor, g_or_gdict::Union{Node{NodeData},NestedGraphDict}; data=nothing, lidict=nothing, kwargs...)
win, _ = viewgui(fcolor, g_or_gdict; data=data, lidict=lidict, kwargs...)
Gtk.showall(win)
win
end

function viewgui(fcolor, g::Node{NodeData}; kwargs...)
Expand All @@ -233,67 +234,66 @@
graphtype = _graphtype[]
end
thread_tabs = collect(keys(gdict))
nb_threads = Notebook() # for holding the per-thread pages
Gtk.GAccessor.scrollable(nb_threads, true)
Gtk.GAccessor.show_tabs(nb_threads, length(thread_tabs) > 1)
nb_threads = GtkNotebook() # for holding the per-thread pages
Gtk4.scrollable(nb_threads, true)
Gtk4.show_tabs(nb_threads, length(thread_tabs) > 1)
sort!(thread_tabs, by = s -> something(tryparse(Int, string(s)), 0)) # sorts thread_tabs as [all threads, 1, 2, 3 ....]

for thread_tab in thread_tabs
gdict_thread = gdict[thread_tab]
task_tabs = collect(keys(gdict_thread))
sort!(task_tabs, by = s -> s == tabname_alltasks ? "" : string(s)) # sorts thread_tabs as [all threads, 0xds ....]

nb_tasks = Notebook() # for holding the per-task pages
Gtk.GAccessor.scrollable(nb_tasks, true)
Gtk.GAccessor.show_tabs(nb_tasks, length(task_tabs) > 1)
nb_tasks = GtkNotebook() # for holding the per-task pages
Gtk4.scrollable(nb_tasks, true)
Gtk4.show_tabs(nb_tasks, length(task_tabs) > 1)
task_tab_num = 1
for task_tab in task_tabs
g = gdict_thread[task_tab]
gsig = Observable(g) # allow substitution by the open dialog
c = canvas(UserUnit)
set_gtk_property!(widget(c), :expand, true)

f = Frame(c)
tb = Toolbar()
tb_open = ToolButton("gtk-open")
Gtk.GAccessor.tooltip_text(tb_open, "open")
tb_save_as = ToolButton("gtk-save-as")
Gtk.GAccessor.tooltip_text(tb_save_as, "save")
tb_zoom_fit = ToolButton("gtk-zoom-fit")
Gtk.GAccessor.tooltip_text(tb_zoom_fit, "zoom to fit")
tb_zoom_in = ToolButton("gtk-zoom-in")
Gtk.GAccessor.tooltip_text(tb_zoom_in, "zoom in")
tb_zoom_out = ToolButton("gtk-zoom-out")
Gtk.GAccessor.tooltip_text(tb_zoom_out, "zoom out")
tb_info = ToolButton("gtk-info")
Gtk.GAccessor.tooltip_text(tb_info, "ProfileView tips")
tb_text_item = ToolItem()
Gtk.GAccessor.expand(tb_text_item, true)
tb_text = Entry()
Gtk.GAccessor.has_frame(tb_text, false)
Gtk.GAccessor.sensitive(tb_text, false)
push!(tb_text_item, tb_text)
set_gtk_property!(widget(c), :vexpand, true)

f = GtkFrame(c)
Gtk4.css_classes(f, ["squared"])
tb = GtkBox(:h)
tb_open = GtkButton(:icon_name,"document-open-symbolic")
Gtk4.tooltip_text(tb_open, "open")
tb_save_as = GtkButton(:icon_name,"document-save-as-symbolic")
Gtk4.tooltip_text(tb_save_as, "save")
tb_zoom_fit = GtkButton(:icon_name,"zoom-fit-best-symbolic")
Gtk4.tooltip_text(tb_zoom_fit, "zoom to fit")
tb_zoom_in = GtkButton(:icon_name,"zoom-in-symbolic")
Gtk4.tooltip_text(tb_zoom_in, "zoom in")
tb_zoom_out = GtkButton(:icon_name, "zoom-out-symbolic")
Gtk4.tooltip_text(tb_zoom_out, "zoom out")
tb_info = GtkButton(:icon_name, "dialog-information-symbolic")
Gtk4.tooltip_text(tb_info, "ProfileView tips")
tb_text = GtkEntry()
Gtk4.has_frame(tb_text, false)
Gtk4.sensitive(tb_text, false)
tb_text.hexpand = true

push!(tb, tb_open)
push!(tb, tb_save_as)
push!(tb, SeparatorToolItem())
push!(tb, GtkSeparator(:h))
push!(tb, tb_zoom_fit)
push!(tb, tb_zoom_out)
push!(tb, tb_zoom_in)
push!(tb, SeparatorToolItem())
push!(tb, GtkSeparator(:h))
push!(tb, tb_info)
push!(tb, SeparatorToolItem())
push!(tb, tb_text_item)
push!(tb, GtkSeparator(:h))
jwahlstrand marked this conversation as resolved.
Show resolved Hide resolved
push!(tb, tb_text)
# FIXME: likely have to do `allkwargs` in the open/save below (add in C, combine, recur)
signal_connect(open_cb, tb_open, "clicked", Nothing, (), false, (widget(c),gsig,kwargs))
signal_connect(save_as_cb, tb_save_as, "clicked", Nothing, (), false, (widget(c),data,lidict,g))
signal_connect(info_cb, tb_info, "clicked", Nothing, (), false, ())
signal_connect(info_cb, tb_info, "clicked", Nothing, (), false, (widget(c),))

bx = Box(:v)
bx = GtkBox(:v)
push!(bx, tb)
push!(bx, f)
# don't use the actual taskid as the tab as it's very long
push!(nb_tasks, bx, task_tab_num == 1 ? task_tab : Symbol(task_tab_num - 1))
push!(nb_tasks, bx, task_tab_num == 1 ? string(task_tab) : string(task_tab_num - 1))
fdraw = viewprof(fcolor, c, gsig, (tb_zoom_fit, tb_zoom_out, tb_zoom_in, tb_text), graphtype; kwargs...)
GtkObservables.gc_preserve(nb_threads, c)
GtkObservables.gc_preserve(nb_threads, fdraw)
Expand All @@ -303,12 +303,12 @@
push!(nb_threads, nb_tasks, string(thread_tab))
end

bx = Box(:v)
bx = GtkBox(:v)
push!(bx, nb_threads)

# Defer creating the window until here because Window includes a `show` that will unpause the Gtk eventloop
win = Window(windowname, 800, 600)
push!(win, bx)
win = GtkWindow(windowname, 800, 600)
win[] = bx

# Register the window with closeall
window_wrefs[win] = nothing
Expand All @@ -317,12 +317,8 @@
end

# Ctrl-w and Ctrl-q destroy the window
signal_connect(win, "key-press-event") do w, evt
if evt.state == CONTROL && (evt.keyval == UInt('q') || evt.keyval == UInt('w'))
@async destroy(w)
nothing
end
end
kc = GtkEventControllerKey(win)
signal_connect(close_cb, kc, "key-pressed", Cint, (UInt32, UInt32, UInt32), false, (win))

return win, _c, _fdraw, (_tb_open, _tb_save_as)
end
Expand Down Expand Up @@ -369,7 +365,7 @@
img, tagimg = img[:,2:end], discardfirstcol(tagimg)
img24 = RGB24.(img)
img24 = img24[:,end:-1:1]
fv = XY(0.0..size(img24,1), 0.0..size(img24,2))
fv = XY(0.5..size(img24,1)-0.5, 0.5..size(img24,2)-0.5)
zr = Observable(ZoomRegion(fv, fv))
signal_connect(zoom_fit_cb, tb_zoom_fit, "clicked", Nothing, (), false, (zr))
signal_connect(zoom_out_cb, tb_zoom_out, "clicked", Nothing, (), false, (zr))
Expand All @@ -393,7 +389,7 @@
lasttextbb = Ref(BoundingBox(1,0,1,0))
sigmotion = on(c.mouse.motion) do btn
# Repair image from ovewritten text
if c.widget.is_realized && c.widget.is_sized
if c.widget.is_sized
ctx = getgc(c)
if Graphics.width(lasttextbb[]) > 0
r = zr[]
Expand All @@ -410,17 +406,18 @@
# Write the info
xu, yu = btn.position.x, btn.position.y
sf = gettag(tagimg, xu, yu)
b = Gtk4.buffer(tb_text)
if sf != StackTraces.UNKNOWN
str_long = long_info_str(sf)
Gtk.GAccessor.text(tb_text, str_long)
b[String] = str_long
str = string(basename(string(sf.file)), ", ", sf.func, ": line ", sf.line)
set_source(ctx, fcolor(:font))
Cairo.set_font_face(ctx, "sans-serif $(fontsize)px")
xi = zr[].currentview.x
xmin, xmax = minimum(xi), maximum(xi)
lasttextbb[] = deform(Cairo.text(ctx, xu, yu, str, halign = xu < (2xmin+xmax)/3 ? "left" : xu < (xmin+2xmax)/3 ? "center" : "right"), -2, 2, -2, 2)
else
Gtk.GAccessor.text(tb_text, "")
b[String]=""
end
reveal(c)
end
Expand Down Expand Up @@ -466,9 +463,11 @@

@guarded function open_cb(::Ptr, settings::Tuple)
c, gsig, kwargs = settings
selection = open_dialog("Load profile data", toplevel(c), ("*.jlprof","*"))
isempty(selection) && return nothing
return _open(gsig, selection; kwargs...)
open_dialog("Load profile data", toplevel(c), ("*.jlprof","*")) do selection

Check warning on line 466 in src/ProfileView.jl

View check run for this annotation

Codecov / codecov/patch

src/ProfileView.jl#L466

Added line #L466 was not covered by tests
isempty(selection) && return nothing
_open(gsig, selection; kwargs...)
end
return nothing

Check warning on line 470 in src/ProfileView.jl

View check run for this annotation

Codecov / codecov/patch

src/ProfileView.jl#L470

Added line #L470 was not covered by tests
end

function _open(gsig, selection; kwargs...)
Expand All @@ -484,12 +483,15 @@

@guarded function save_as_cb(::Ptr, profdata::Tuple)
c, data, lidict, g = profdata
selection = save_dialog("Save profile data as *.jlprof file", toplevel(c), ("*.jlprof",))
isempty(selection) && return nothing
if data === nothing && lidict === nothing
return _save(selection, g)
save_dialog("Save profile data as *.jlprof file", toplevel(c), ("*.jlprof",)) do selection

Check warning on line 486 in src/ProfileView.jl

View check run for this annotation

Codecov / codecov/patch

src/ProfileView.jl#L486

Added line #L486 was not covered by tests
isempty(selection) && return nothing
if data === nothing && lidict === nothing
_save(selection, g)
else
_save(selection, data, lidict)
end
end
return _save(selection, data, lidict)
return nothing

Check warning on line 494 in src/ProfileView.jl

View check run for this annotation

Codecov / codecov/patch

src/ProfileView.jl#L494

Added line #L494 was not covered by tests
end

function _save(selection, args...)
Expand All @@ -512,7 +514,7 @@
return nothing
end

@guarded function info_cb(::Ptr, ::Tuple)
@guarded function info_cb(::Ptr, win::Tuple)

Check warning on line 517 in src/ProfileView.jl

View check run for this annotation

Codecov / codecov/patch

src/ProfileView.jl#L517

Added line #L517 was not covered by tests
# Note: Keep this updated with the readme
info = """
ProfileView.jl Interface Tips
Expand Down Expand Up @@ -542,10 +544,22 @@

Color theme: The color theme used for the graph is `:light`, which can be changed to `:dark` via `ProfileView.set_theme!(:dark)`
"""
info_dialog(info)
info_dialog(info, toplevel(win[1])) do

Check warning on line 547 in src/ProfileView.jl

View check run for this annotation

Codecov / codecov/patch

src/ProfileView.jl#L547

Added line #L547 was not covered by tests
nothing
end
return nothing
end

unhandled = convert(Cint, false)

@guarded unhandled function close_cb(::Ptr, keyval::UInt32, keycode::UInt32, state::UInt32, win::GtkWindow)
if (ModifierType(state & Gtk4.MODIFIER_MASK) & CONTROL == CONTROL) && (keyval == UInt('q') || keyval == UInt('w'))
@async Gtk4.destroy(win)
return Cint(1)

Check warning on line 558 in src/ProfileView.jl

View check run for this annotation

Codecov / codecov/patch

src/ProfileView.jl#L555-L558

Added lines #L555 - L558 were not covered by tests
end
return Cint(0)

Check warning on line 560 in src/ProfileView.jl

View check run for this annotation

Codecov / codecov/patch

src/ProfileView.jl#L560

Added line #L560 was not covered by tests
end

discardfirstcol(A) = A[:,2:end]
discardfirstcol(A::IndirectArray) = IndirectArray(A.index[:,2:end], A.values)

Expand Down Expand Up @@ -577,6 +591,12 @@
printstyled(io, "\n`using Cthulhu` is required for `$(exc.f)`"; color=:yellow)
end
end
# by default GtkFrame uses rounded corners
css="""
.squared {border-radius: 0;}
"""
cssprov=GtkCssProvider(css)
push!(GdkDisplay(), cssprov, Gtk4.STYLE_PROVIDER_PRIORITY_APPLICATION)
end

using PrecompileTools
Expand Down Expand Up @@ -607,12 +627,12 @@
win, c, fdraw = viewgui(FlameGraphs.default_colors, gdict)
for obs in c.preserved
if isa(obs, Observable) || isa(obs, Observables.ObserverFunction)
precompile(obs)
# FIXME: on MacOS, the following seems to prevent ProfileView from working in the same session where precompiling was done
# precompile(obs)
end
end
precompile(fdraw)
closeall() # necessary to prevent serialization of stale references (including the internal `empty!`)
Gtk.enable_eventloop(false, wait_stopped = true) # to avoid trailing task warning
end
end
end
Expand Down
Loading
Loading