render_diagnostics.go RawHistoryFolder

1package cef
2
3import (
4	"time"
5
6	purecef "github.com/bnema/purego-cef/cef"
7	cef2gtk "github.com/bnema/purego-cef2gtk"
8
9	"github.com/bnema/dumber/internal/application/port"
10	"github.com/bnema/dumber/internal/logging"
11)
12
13const renderStallRecoveryCooldown = 10 * time.Second
14
15func (e *Engine) logAllWebViewRenderSnapshots(now time.Time, _ port.WebViewID, reason string, classification renderStallClassification) {
16	if e == nil {
17		return
18	}
19	e.activeWebViews.Range(func(_, value any) bool {
20		wv, ok := value.(*WebView)
21		if !ok || wv == nil || wv.viewBridge == nil || wv.destroyed.Load() {
22			return true
23		}
24		diag := wv.viewBridge.Diagnostics()
25		lastPaintAge := time.Duration(0)
26		if lastPaint := latestDiagnosticEventTime(diag, "accelerated-paint"); !lastPaint.IsZero() {
27			lastPaintAge = now.Sub(lastPaint)
28		}
29		wv.mu.RLock()
30		browser := wv.browser
31		uri := wv.uri
32		title := wv.title
33		isLoading := wv.isLoading
34		pendingURI := wv.pendingURI
35		wv.mu.RUnlock()
36		wv.logRenderDiagnosticSnapshot(
37			now, reason, classification, wv.viewBridge, diag, lastPaintAge,
38			browser, uri, title, isLoading, pendingURI, wv.audioPlaying.Load(),
39		)
40		return true
41	})
42}
43
44func (wv *WebView) logRenderDiagnosticSnapshot(
45	now time.Time,
46	reason string,
47	classification renderStallClassification,
48	bridge *Cef2gtkAdapter,
49	diag cef2gtk.Diagnostics,
50	lastPaintAge time.Duration,
51	browser purecef.Browser,
52	uri, title string,
53	isLoading bool,
54	pendingURI string,
55	audioPlaying bool,
56) {
57	if wv == nil || wv.ctx == nil {
58		return
59	}
60	var browserID int32
61	if browser != nil {
62		browserID = browser.GetIdentifier()
63	}
64	width, height := int32(0), int32(0)
65	scale := float32(0)
66	if bridge != nil {
67		width, height = bridge.Size()
68		scale = bridge.DeviceScaleFactor()
69	}
70	uiHeartbeat, ioHeartbeat := wv.cefHeartbeatSnapshots(now)
71	profile, profileAge, hasProfile := wv.latestCEF2GTKProfileSnapshot(now)
72	logging.FromContext(wv.ctx).Warn().
73		Str("reason", reason).
74		Str("classification", classification.Category).
75		Bool("cef_ui_blocked", classification.CEFUIBlocked).
76		Bool("cef_io_alive", classification.CEFIOAlive).
77		Uint64("webview_id", uint64(wv.id)).
78		Int32("browser_id", browserID).
79		Str("uri", logging.TruncateURL(uri, logging.PermissionLogURLMaxLen)).
80		Str("title", title).
81		Bool("is_loading", isLoading).
82		Str("pending_uri", logging.TruncateURL(pendingURI, logging.PermissionLogURLMaxLen)).
83		Bool("audio_playing", audioPlaying).
84		Uint64("audio_packets", wv.audioPacketCount.Load()).
85		Uint64("audio_writes", wv.audioWriteCount.Load()).
86		Int64("last_accelerated_paint_age_ms", durationMillis(lastPaintAge)).
87		Str("render_backend", diag.Backend).
88		Int("accelerated_paints", diag.AcceleratedPaints).
89		Int("unsupported_paints", diag.UnsupportedPaints).
90		Int("accelerated_paint_errors", diag.AcceleratedPaintErrors).
91		Int("import_failures", diag.ImportFailures).
92		Int("render_failures", diag.RenderFailures).
93		Int("textures_built", diag.TexturesBuilt).
94		Int("texture_build_failures", diag.TextureBuildFailures).
95		Int("fd_dup_failures", diag.FDDupFailures).
96		Int("unsupported_formats", diag.UnsupportedFormats).
97		Int("paintable_swaps", diag.PaintableSwaps).
98		Bool("pending_frame", diag.PendingFrame).
99		Bool("pending_scheduled", diag.PendingScheduled).
100		Int64("pending_age_ms", durationMillis(diag.PendingAge)).
101		Uint("pending_source_id", diag.PendingSourceID).
102		Int("pending_reschedules", diag.PendingReschedules).
103		Int("pending_schedule_failures", diag.PendingScheduleFailures).
104		Int("pending_idle_callbacks", diag.PendingIdleCallbacks).
105		Bool("gdk_offload_requested", diag.OffloadRequested).
106		Bool("gdk_offload_installed", diag.OffloadInstalled).
107		Int("gdk_import_priority", diag.ImportPriority).
108		Int("gdk_retire_limit", diag.RetireLimit).
109		Int32("surface_width", width).
110		Int32("surface_height", height).
111		Float32("surface_scale", scale).
112		Bool("external_begin_frame", externalBeginFrameEnabled()).
113		Bool("profile_snapshot_available", hasProfile).
114		Int64("profile_snapshot_age_ms", durationMillis(profileAge)).
115		Uint64("profile_frames_received", profile.FramesReceived).
116		Uint64("profile_frames_queued", profile.FramesQueued).
117		Uint64("profile_frames_rendered", profile.FramesRendered).
118		Uint64("profile_import_failures", profile.ImportFailures).
119		Uint64("profile_render_failures", profile.RenderFailures).
120		Uint32("profile_gc_delta", profile.GC.NumGCDelta).
121		Int64("cef_ui_heartbeat_ack_age_ms", durationMillis(uiHeartbeat.LastAckAge)).
122		Int64("cef_ui_heartbeat_latency_ms", durationMillis(uiHeartbeat.Latency)).
123		Int32("cef_ui_heartbeat_post_result", uiHeartbeat.PostResult).
124		Bool("cef_ui_heartbeat_in_flight", uiHeartbeat.InFlight).
125		Int64("cef_io_heartbeat_ack_age_ms", durationMillis(ioHeartbeat.LastAckAge)).
126		Int64("cef_io_heartbeat_latency_ms", durationMillis(ioHeartbeat.Latency)).
127		Int32("cef_io_heartbeat_post_result", ioHeartbeat.PostResult).
128		Bool("cef_io_heartbeat_in_flight", ioHeartbeat.InFlight).
129		Msg("cef: render diagnostic snapshot")
130}
131
132func (wv *WebView) maybeRunRenderStallRecovery(now time.Time, _ *Cef2gtkAdapter, paintsBefore int) {
133	if wv == nil || wv.ctx == nil || wv.destroyed.Load() || !renderStallRecoveryEnabled() {
134		return
135	}
136	wv.renderStallMu.Lock()
137	last := wv.renderStallRecoveryLastAt
138	if !last.IsZero() && now.Sub(last) < renderStallRecoveryCooldown {
139		wv.renderStallMu.Unlock()
140		return
141	}
142	wv.renderStallMu.Unlock()
143
144	wv.mu.RLock()
145	host := wv.host
146	browser := wv.browser
147	wv.mu.RUnlock()
148	if host == nil {
149		return
150	}
151	browserID := int32(0)
152	if browser != nil {
153		browserID = browser.GetIdentifier()
154	}
155	logging.FromContext(wv.ctx).Warn().
156		Uint64("webview_id", uint64(wv.id)).
157		Int32("browser_id", browserID).
158		Int("accelerated_paints_before", paintsBefore).
159		Msg("cef: render stall recovery pulse scheduled")
160	task := cefNewTask(cefTaskFunc(func() {
161		if wv == nil || wv.viewBridge == nil || wv.destroyed.Load() || wv.ctx == nil || host == nil {
162			return
163		}
164		host.Invalidate(purecef.PaintElementTypePetView)
165		host.WasHidden(1)
166		host.WasHidden(0)
167		host.NotifyScreenInfoChanged()
168		host.WasResized()
169		host.Invalidate(purecef.PaintElementTypePetView)
170		logging.FromContext(wv.ctx).Warn().
171			Uint64("webview_id", uint64(wv.id)).
172			Int32("browser_id", browserID).
173			Msg("cef: render stall recovery pulse executed")
174	}))
175	if task == nil {
176		return
177	}
178	result := cefPostTask(purecef.ThreadIDTidUi, task)
179	logging.FromContext(wv.ctx).Warn().
180		Uint64("webview_id", uint64(wv.id)).
181		Int32("browser_id", browserID).
182		Int32("post_result", result).
183		Msg("cef: render stall recovery pulse posted")
184	if result != 1 {
185		return
186	}
187	wv.renderStallMu.Lock()
188	wv.renderStallRecoveryLastAt = now
189	wv.renderStallMu.Unlock()
190	cefScheduleAfter(2*time.Second, func() {
191		if wv == nil || wv.viewBridge == nil || wv.destroyed.Load() || wv.ctx == nil {
192			return
193		}
194		after := wv.viewBridge.Diagnostics().AcceleratedPaints
195		logging.FromContext(wv.ctx).Warn().
196			Uint64("webview_id", uint64(wv.id)).
197			Int32("browser_id", browserID).
198			Int("accelerated_paints_before", paintsBefore).
199			Int("accelerated_paints_after", after).
200			Bool("recovered", after > paintsBefore).
201			Msg("cef: render stall recovery pulse result")
202	})
203}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close