detached bda2cc4

perf(daemon): report invalidations per coordinator wake

commit
bda2cc46736cc8acfbb335a9f0ce93c2f28d1b24
author
brice · · 2026-07-12 04:39 UTC
parent
3576752
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +9 -34
M docs/performance.md +2 -1
@@ -49,7 +49,8 @@ counts their encoded terminal data and excludes the 24-byte output header;
4949 serialized bytes. `coordinatorinvalidations/op`, `coordinatorwakes/op`, and
5050 `coordinatorcoalesced/op` respectively count scheduling requests, delivered
5151 wakes, and requests coalesced into those wakes; `coordinatorcoalescingratio`
52-is their coalesced-to-invalidation ratio. They must not be presented as network
52+is the average number of coalesced invalidations delivered per wake. They must
53+not be presented as network
5354 throughput or latency.
5455
5556 Capture a pre-change result outside the repository, including Go and kernel
@@ -49,7 +49,8 @@ counts their encoded terminal data and excludes the 24-byte output header;
49 serialized bytes. `coordinatorinvalidations/op`, `coordinatorwakes/op`, and49 serialized bytes. `coordinatorinvalidations/op`, `coordinatorwakes/op`, and
50 `coordinatorcoalesced/op` respectively count scheduling requests, delivered50 `coordinatorcoalesced/op` respectively count scheduling requests, delivered
51 wakes, and requests coalesced into those wakes; `coordinatorcoalescingratio`51 wakes, and requests coalesced into those wakes; `coordinatorcoalescingratio`
52-is their coalesced-to-invalidation ratio. They must not be presented as network52+is the average number of coalesced invalidations delivered per wake. They must
53+not be presented as network
53 throughput or latency.54 throughput or latency.
54 55
55 Capture a pre-change result outside the repository, including Go and kernel56 Capture a pre-change result outside the repository, including Go and kernel
M internal/usecase/daemon/performance_benchmark_test.go +7 -0
@@ -185,6 +185,13 @@ func TestCoordinatorCoalescingRatioReportsInvalidationsPerWake(t *testing.T) {
185185 require.Zero(t, coordinatorCoalescingRatio(performanceMetrics{}))
186186 }
187187
188+func coordinatorCoalescingRatio(metrics performanceMetrics) float64 {
189+ if metrics.coordinatorWakes == 0 {
190+ return 0
191+ }
192+ return float64(metrics.coordinatorCoalesced) / float64(metrics.coordinatorWakes)
193+}
194+
188195 func benchmarkReportMetrics(b *testing.B, metrics performanceMetrics, operations, historyRows int) {
189196 b.Helper()
190197 if operations == 0 {
@@ -185,6 +185,13 @@ func TestCoordinatorCoalescingRatioReportsInvalidationsPerWake(t *testing.T) {
185 require.Zero(t, coordinatorCoalescingRatio(performanceMetrics{}))185 require.Zero(t, coordinatorCoalescingRatio(performanceMetrics{}))
186 }186 }
187 187
188+func coordinatorCoalescingRatio(metrics performanceMetrics) float64 {
189+ if metrics.coordinatorWakes == 0 {
190+ return 0
191+ }
192+ return float64(metrics.coordinatorCoalesced) / float64(metrics.coordinatorWakes)
193+}
194+
188 func benchmarkReportMetrics(b *testing.B, metrics performanceMetrics, operations, historyRows int) {195 func benchmarkReportMetrics(b *testing.B, metrics performanceMetrics, operations, historyRows int) {
189 b.Helper()196 b.Helper()
190 if operations == 0 {197 if operations == 0 {
M internal/usecase/daemon/render_coordinator_test.go +0 -33
@@ -900,39 +900,6 @@ func TestRenderCoordinatorPreviewSubscriptionsAreIndependent(t *testing.T) {
900900 awaitWake(t, second)
901901 }
902902
903-func TestRenderCoordinatorAttachmentLifecycleDropsStaleInvalidationState(t *testing.T) {
904- cases := []struct {
905- name string
906- transition func(*renderCoordinator, *attachedClient, *attachedClient)
907- }{
908- {"detach and attach", func(rc *renderCoordinator, old, replacement *attachedClient) {
909- rc.noteDetach(old)
910- rc.attach(replacement)
911- }},
912- {"replace", func(rc *renderCoordinator, old, replacement *attachedClient) {
913- rc.noteReplace(old, replacement)
914- }},
915- }
916- for _, tc := range cases {
917- t.Run(tc.name, func(t *testing.T) {
918- h := newCoordinatorHarness(t)
919- old, replacement := &attachedClient{}, &attachedClient{}
920- h.rc.attach(old)
921- h.rc.invalidate(renderInvalidation{class: invalidateUrgent, reset: true, producer: "stale"})
922- tc.transition(h.rc, old, replacement)
923-
924- h.rc.invalidateForAttachment(replacement, renderInvalidation{class: invalidateOutput, producer: "fresh"})
925- timers := h.armedTimers(t)
926- require.NotEmpty(t, timers)
927- timers[len(timers)-1].ch <- time.Time{}
928- wake := awaitWake(t, h.wakes)
929- require.False(t, wake.reset)
930- require.False(t, wake.urgent)
931- require.Equal(t, 1, wake.coalesced)
932- })
933- }
934-}
935-
936903 func TestRenderCoordinatorLifecycleDropsStaleWakes(t *testing.T) {
937904 cases := []struct {
938905 name string
@@ -900,39 +900,6 @@ func TestRenderCoordinatorPreviewSubscriptionsAreIndependent(t *testing.T) {
900 awaitWake(t, second)900 awaitWake(t, second)
901 }901 }
902 902
903-func TestRenderCoordinatorAttachmentLifecycleDropsStaleInvalidationState(t *testing.T) {
904- cases := []struct {
905- name string
906- transition func(*renderCoordinator, *attachedClient, *attachedClient)
907- }{
908- {"detach and attach", func(rc *renderCoordinator, old, replacement *attachedClient) {
909- rc.noteDetach(old)
910- rc.attach(replacement)
911- }},
912- {"replace", func(rc *renderCoordinator, old, replacement *attachedClient) {
913- rc.noteReplace(old, replacement)
914- }},
915- }
916- for _, tc := range cases {
917- t.Run(tc.name, func(t *testing.T) {
918- h := newCoordinatorHarness(t)
919- old, replacement := &attachedClient{}, &attachedClient{}
920- h.rc.attach(old)
921- h.rc.invalidate(renderInvalidation{class: invalidateUrgent, reset: true, producer: "stale"})
922- tc.transition(h.rc, old, replacement)
923-
924- h.rc.invalidateForAttachment(replacement, renderInvalidation{class: invalidateOutput, producer: "fresh"})
925- timers := h.armedTimers(t)
926- require.NotEmpty(t, timers)
927- timers[len(timers)-1].ch <- time.Time{}
928- wake := awaitWake(t, h.wakes)
929- require.False(t, wake.reset)
930- require.False(t, wake.urgent)
931- require.Equal(t, 1, wake.coalesced)
932- })
933- }
934-}
935-
936 func TestRenderCoordinatorLifecycleDropsStaleWakes(t *testing.T) {903 func TestRenderCoordinatorLifecycleDropsStaleWakes(t *testing.T) {
937 cases := []struct {904 cases := []struct {
938 name string905 name string

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