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 Int32("surface_width", width).
106 Int32("surface_height", height).
107 Float32("surface_scale", scale).
108 Bool("external_begin_frame", externalBeginFrameEnabled()).
109 Bool("profile_snapshot_available", hasProfile).
110 Int64("profile_snapshot_age_ms", durationMillis(profileAge)).
111 Uint64("profile_frames_received", profile.FramesReceived).
112 Uint64("profile_frames_queued", profile.FramesQueued).
113 Uint64("profile_frames_rendered", profile.FramesRendered).
114 Uint64("profile_import_failures", profile.ImportFailures).
115 Uint64("profile_render_failures", profile.RenderFailures).
116 Uint32("profile_gc_delta", profile.GC.NumGCDelta).
117 Int64("cef_ui_heartbeat_ack_age_ms", durationMillis(uiHeartbeat.LastAckAge)).
118 Int64("cef_ui_heartbeat_latency_ms", durationMillis(uiHeartbeat.Latency)).
119 Int32("cef_ui_heartbeat_post_result", uiHeartbeat.PostResult).
120 Bool("cef_ui_heartbeat_in_flight", uiHeartbeat.InFlight).
121 Int64("cef_io_heartbeat_ack_age_ms", durationMillis(ioHeartbeat.LastAckAge)).
122 Int64("cef_io_heartbeat_latency_ms", durationMillis(ioHeartbeat.Latency)).
123 Int32("cef_io_heartbeat_post_result", ioHeartbeat.PostResult).
124 Bool("cef_io_heartbeat_in_flight", ioHeartbeat.InFlight).
125 Msg("cef: render diagnostic snapshot")
126}
127
128func (wv *WebView) maybeRunRenderStallRecovery(now time.Time, _ *Cef2gtkAdapter, paintsBefore int) {
129 if wv == nil || wv.ctx == nil || wv.destroyed.Load() || !renderStallRecoveryEnabled() {
130 return
131 }
132 wv.renderStallMu.Lock()
133 last := wv.renderStallRecoveryLastAt
134 if !last.IsZero() && now.Sub(last) < renderStallRecoveryCooldown {
135 wv.renderStallMu.Unlock()
136 return
137 }
138 wv.renderStallMu.Unlock()
139
140 wv.mu.RLock()
141 host := wv.host
142 browser := wv.browser
143 wv.mu.RUnlock()
144 if host == nil {
145 return
146 }
147 browserID := int32(0)
148 if browser != nil {
149 browserID = browser.GetIdentifier()
150 }
151 logging.FromContext(wv.ctx).Warn().
152 Uint64("webview_id", uint64(wv.id)).
153 Int32("browser_id", browserID).
154 Int("accelerated_paints_before", paintsBefore).
155 Msg("cef: render stall recovery pulse scheduled")
156 task := cefNewTask(cefTaskFunc(func() {
157 if wv == nil || wv.viewBridge == nil || wv.destroyed.Load() || wv.ctx == nil || host == nil {
158 return
159 }
160 host.Invalidate(purecef.PaintElementTypePetView)
161 host.WasHidden(1)
162 host.WasHidden(0)
163 host.NotifyScreenInfoChanged()
164 host.WasResized()
165 host.Invalidate(purecef.PaintElementTypePetView)
166 logging.FromContext(wv.ctx).Warn().
167 Uint64("webview_id", uint64(wv.id)).
168 Int32("browser_id", browserID).
169 Msg("cef: render stall recovery pulse executed")
170 }))
171 if task == nil {
172 return
173 }
174 result := cefPostTask(purecef.ThreadIDTidUi, task)
175 logging.FromContext(wv.ctx).Warn().
176 Uint64("webview_id", uint64(wv.id)).
177 Int32("browser_id", browserID).
178 Int32("post_result", result).
179 Msg("cef: render stall recovery pulse posted")
180 if result != 1 {
181 return
182 }
183 wv.renderStallMu.Lock()
184 wv.renderStallRecoveryLastAt = now
185 wv.renderStallMu.Unlock()
186 cefScheduleAfter(2*time.Second, func() {
187 if wv == nil || wv.viewBridge == nil || wv.destroyed.Load() || wv.ctx == nil {
188 return
189 }
190 after := wv.viewBridge.Diagnostics().AcceleratedPaints
191 logging.FromContext(wv.ctx).Warn().
192 Uint64("webview_id", uint64(wv.id)).
193 Int32("browser_id", browserID).
194 Int("accelerated_paints_before", paintsBefore).
195 Int("accelerated_paints_after", after).
196 Bool("recovered", after > paintsBefore).
197 Msg("cef: render stall recovery pulse result")
198 })
199}