detached 0e2e6e9

perf(daemon): record render coordinator burst metrics

commit
0e2e6e99a42af3c45862b0b14e1377253349bf05
author
brice · · 2026-07-11 15:09 UTC
parent
df0fe5f
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +22 -3
M internal/usecase/daemon/render_coordinator.go +20 -1
@@ -3,6 +3,7 @@ package daemon
33 import (
44 "runtime"
55 "sync"
6+ "sync/atomic"
67 "time"
78
89 "github.com/bnema/vev/internal/domain"
@@ -82,6 +83,15 @@ type renderCoordinatorOptions struct {
8283 }
8384
8485 // renderCoordinator fans in producer invalidations for one attached session.
86+// renderCoordinatorBurstMetrics is deliberately observational. It permits
87+// benchmark/pprof callers to measure producer bursts without influencing
88+// scheduling or transport policy.
89+type renderCoordinatorBurstMetrics struct {
90+ invalidations atomic.Uint64
91+ wakes atomic.Uint64
92+ coalesced atomic.Uint64
93+}
94+
8595 type renderCoordinator struct {
8696 mu sync.Mutex
8797 opts renderCoordinatorOptions
@@ -102,7 +112,8 @@ type renderCoordinator struct {
102112 attachment *attachedClient
103113 torndown bool
104114
105- resize resizeRequestMetadata
115+ resize resizeRequestMetadata
116+ metrics renderCoordinatorBurstMetrics
106117 // generation invalidates callbacks from superseded deadline/watchdog timers.
107118 generation uint64
108119 armed bool
@@ -127,6 +138,7 @@ func (c *renderCoordinator) invalidate(inv renderInvalidation) {
127138 if c.opts.onInvalidate != nil {
128139 c.opts.onInvalidate(inv)
129140 }
141+ c.metrics.invalidations.Add(1)
130142 wasPending, wasUrgent := c.pending, c.pendingUrgent
131143 c.pending = true
132144 c.pendingReset = c.pendingReset || inv.reset
@@ -281,6 +293,11 @@ func (c *renderCoordinator) resizeSnapshot() resizeRequestMetadata {
281293 return c.resize
282294 }
283295
296+// burstMetrics returns an observational snapshot for benchmark reporting.
297+func (c *renderCoordinator) burstMetrics() (invalidations, wakes, coalesced uint64) {
298+ return c.metrics.invalidations.Load(), c.metrics.wakes.Load(), c.metrics.coalesced.Load()
299+}
300+
284301 // renderCoordinator returns the coordinator installed for this session.
285302 func (s *session) renderCoordinator() *renderCoordinator { return s.coordinator.Load() }
286303
@@ -322,6 +339,8 @@ func (c *renderCoordinator) fire(gen uint64, watchdog bool) {
322339 return
323340 }
324341 w := renderWake{reset: c.pendingReset, urgent: c.pendingUrgent, coalesced: c.coalesced, watchdog: watchdog}
342+ c.metrics.wakes.Add(1)
343+ c.metrics.coalesced.Add(uint64(w.coalesced))
325344 c.pending, c.pendingReset, c.pendingUrgent, c.coalesced, c.armed = false, false, false, 0, false
326345 wake, preview := c.opts.wake, c.previewWake
327346 c.mu.Unlock()
@@ -3,6 +3,7 @@ package daemon
3 import (3 import (
4 "runtime"4 "runtime"
5 "sync"5 "sync"
6+ "sync/atomic"
6 "time"7 "time"
7 8
8 "github.com/bnema/vev/internal/domain"9 "github.com/bnema/vev/internal/domain"
@@ -82,6 +83,15 @@ type renderCoordinatorOptions struct {
82 }83 }
83 84
84 // renderCoordinator fans in producer invalidations for one attached session.85 // renderCoordinator fans in producer invalidations for one attached session.
86+// renderCoordinatorBurstMetrics is deliberately observational. It permits
87+// benchmark/pprof callers to measure producer bursts without influencing
88+// scheduling or transport policy.
89+type renderCoordinatorBurstMetrics struct {
90+ invalidations atomic.Uint64
91+ wakes atomic.Uint64
92+ coalesced atomic.Uint64
93+}
94+
85 type renderCoordinator struct {95 type renderCoordinator struct {
86 mu sync.Mutex96 mu sync.Mutex
87 opts renderCoordinatorOptions97 opts renderCoordinatorOptions
@@ -102,7 +112,8 @@ type renderCoordinator struct {
102 attachment *attachedClient112 attachment *attachedClient
103 torndown bool113 torndown bool
104 114
105- resize resizeRequestMetadata115+ resize resizeRequestMetadata
116+ metrics renderCoordinatorBurstMetrics
106 // generation invalidates callbacks from superseded deadline/watchdog timers.117 // generation invalidates callbacks from superseded deadline/watchdog timers.
107 generation uint64118 generation uint64
108 armed bool119 armed bool
@@ -127,6 +138,7 @@ func (c *renderCoordinator) invalidate(inv renderInvalidation) {
127 if c.opts.onInvalidate != nil {138 if c.opts.onInvalidate != nil {
128 c.opts.onInvalidate(inv)139 c.opts.onInvalidate(inv)
129 }140 }
141+ c.metrics.invalidations.Add(1)
130 wasPending, wasUrgent := c.pending, c.pendingUrgent142 wasPending, wasUrgent := c.pending, c.pendingUrgent
131 c.pending = true143 c.pending = true
132 c.pendingReset = c.pendingReset || inv.reset144 c.pendingReset = c.pendingReset || inv.reset
@@ -281,6 +293,11 @@ func (c *renderCoordinator) resizeSnapshot() resizeRequestMetadata {
281 return c.resize293 return c.resize
282 }294 }
283 295
296+// burstMetrics returns an observational snapshot for benchmark reporting.
297+func (c *renderCoordinator) burstMetrics() (invalidations, wakes, coalesced uint64) {
298+ return c.metrics.invalidations.Load(), c.metrics.wakes.Load(), c.metrics.coalesced.Load()
299+}
300+
284 // renderCoordinator returns the coordinator installed for this session.301 // renderCoordinator returns the coordinator installed for this session.
285 func (s *session) renderCoordinator() *renderCoordinator { return s.coordinator.Load() }302 func (s *session) renderCoordinator() *renderCoordinator { return s.coordinator.Load() }
286 303
@@ -322,6 +339,8 @@ func (c *renderCoordinator) fire(gen uint64, watchdog bool) {
322 return339 return
323 }340 }
324 w := renderWake{reset: c.pendingReset, urgent: c.pendingUrgent, coalesced: c.coalesced, watchdog: watchdog}341 w := renderWake{reset: c.pendingReset, urgent: c.pendingUrgent, coalesced: c.coalesced, watchdog: watchdog}
342+ c.metrics.wakes.Add(1)
343+ c.metrics.coalesced.Add(uint64(w.coalesced))
325 c.pending, c.pendingReset, c.pendingUrgent, c.coalesced, c.armed = false, false, false, 0, false344 c.pending, c.pendingReset, c.pendingUrgent, c.coalesced, c.armed = false, false, false, 0, false
326 wake, preview := c.opts.wake, c.previewWake345 wake, preview := c.opts.wake, c.previewWake
327 c.mu.Unlock()346 c.mu.Unlock()
M internal/usecase/daemon/render_coordinator_test.go +2 -2
@@ -67,7 +67,7 @@ func (h *coordinatorHarness) armedTimers(t *testing.T) []*signalTimer {
6767 // deterministic contract failure, not a slow behavior to poll for.
6868 func awaitWake(t *testing.T, ch chan renderWake) renderWake {
6969 t.Helper()
70- for range 64 {
70+ for range 4096 {
7171 select {
7272 case w := <-ch:
7373 return w
@@ -90,7 +90,7 @@ func requireNoWake(t *testing.T, ch chan renderWake) {
9090
9191 func awaitInvalidation(t *testing.T, ch chan renderInvalidation) renderInvalidation {
9292 t.Helper()
93- for range 64 {
93+ for range 4096 {
9494 select {
9595 case inv := <-ch:
9696 return inv
@@ -67,7 +67,7 @@ func (h *coordinatorHarness) armedTimers(t *testing.T) []*signalTimer {
67 // deterministic contract failure, not a slow behavior to poll for.67 // deterministic contract failure, not a slow behavior to poll for.
68 func awaitWake(t *testing.T, ch chan renderWake) renderWake {68 func awaitWake(t *testing.T, ch chan renderWake) renderWake {
69 t.Helper()69 t.Helper()
70- for range 64 {70+ for range 4096 {
71 select {71 select {
72 case w := <-ch:72 case w := <-ch:
73 return w73 return w
@@ -90,7 +90,7 @@ func requireNoWake(t *testing.T, ch chan renderWake) {
90 90
91 func awaitInvalidation(t *testing.T, ch chan renderInvalidation) renderInvalidation {91 func awaitInvalidation(t *testing.T, ch chan renderInvalidation) renderInvalidation {
92 t.Helper()92 t.Helper()
93- for range 64 {93+ for range 4096 {
94 select {94 select {
95 case inv := <-ch:95 case inv := <-ch:
96 return inv96 return inv

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 12

Tags 11

↑↓ moveenter openesc close