perf(daemon): report invalidations per coordinator wake
- commit
- bda2cc46736cc8acfbb335a9f0ce93c2f28d1b24
- author
- brice · · 2026-07-12 04:39 UTC
- parent
- 3576752
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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; | |||
| 49 | 49 | serialized bytes. `coordinatorinvalidations/op`, `coordinatorwakes/op`, and | |
| 50 | 50 | `coordinatorcoalesced/op` respectively count scheduling requests, delivered | |
| 51 | 51 | 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 | |
| 53 | 54 | throughput or latency. | |
| 54 | 55 | ||
| 55 | 56 | 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`, and | 49 | serialized bytes. `coordinatorinvalidations/op`, `coordinatorwakes/op`, and | ||
| 50 | `coordinatorcoalesced/op` respectively count scheduling requests, delivered | 50 | `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 network | 52 | + | 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 kernel | 56 | 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) { | |||
| 185 | 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 | 195 | func benchmarkReportMetrics(b *testing.B, metrics performanceMetrics, operations, historyRows int) { | |
| 189 | 196 | b.Helper() | |
| 190 | 197 | 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) { | |||
| 900 | 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 | 903 | func TestRenderCoordinatorLifecycleDropsStaleWakes(t *testing.T) { | |
| 937 | 904 | cases := []struct { | |
| 938 | 905 | 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 string | 905 | name string | ||