返回 DeepSeek-Reasonix
benchmark_test.go
根目录 / internal / extension / sidecar / benchmark_test.go
1 package sidecar
2
3 import (
4 "context"
5 "sort"
6 "strconv"
7 "testing"
8 "time"
9
10 "reasonix/internal/extension"
11 "reasonix/internal/extension/dispatch"
12 "reasonix/internal/pluginpkg"
13 )
14
15 // BenchmarkExtensionSidecarStartup measures a real no-op sidecar process
16 // spawn plus Extension Protocol handshake. Shutdown is kept outside the
17 // benchmark timer; sampled p50/p95 cover StartClient only.
18 func BenchmarkExtensionSidecarStartup(b *testing.B) {
19 pkg, installed := fakeSidecarPackage(b, "benchmark", nil)
20 opts := ClientOptions{Package: pkg, Installed: installed, Session: testSessionContext()}
21 b.ReportAllocs()
22 const maxSamples = 100_000
23 samples := make([]int64, 0, maxSamples)
24 for b.Loop() {
25 start := time.Now()
26 client, err := StartClient(context.Background(), opts)
27 if err != nil {
28 b.Fatal(err)
29 }
30 elapsed := time.Since(start).Nanoseconds()
31 b.StopTimer()
32 if err := client.Close(); err != nil {
33 b.Fatal(err)
34 }
35 b.StartTimer()
36 if len(samples) < maxSamples {
37 samples = append(samples, elapsed)
38 }
39 }
40 b.StopTimer()
41 reportSidecarPercentiles(b, samples)
42 }
43
44 // BenchmarkExtensionSidecarDispatchLatency measures actual NDJSON RPC through
45 // one and four real no-op sidecar processes on turn and tool hot paths. Calls
46 // are serial by protocol contract, so the four-sidecar case exposes additive
47 // latency instead of only the dispatcher's in-process overhead.
48 func BenchmarkExtensionSidecarDispatchLatency(b *testing.B) {
49 for _, count := range []int{1, 4} {
50 count := count
51 b.Run("Turn/NoopSidecars"+strconv.Itoa(count), func(b *testing.B) {
52 d := benchmarkSidecarDispatcher(b, extension.PointInputReceive, count)
53 payload := dispatch.InputPayload{Text: "hello"}
54 benchmarkSidecarLatency(b, func() error {
55 _, err := d.Intercept(context.Background(), extension.PointInputReceive, &payload)
56 return err
57 })
58 })
59 b.Run("Tool/NoopSidecars"+strconv.Itoa(count), func(b *testing.B) {
60 d := benchmarkSidecarDispatcher(b, extension.PointToolBefore, count)
61 payload := dispatch.ToolBeforePayload{Name: "bash", Arguments: `{"cmd":"pwd"}`}
62 benchmarkSidecarLatency(b, func() error {
63 _, err := d.Intercept(context.Background(), extension.PointToolBefore, &payload)
64 return err
65 })
66 })
67 }
68 }
69
70 func benchmarkSidecarDispatcher(b *testing.B, point extension.InterceptorPoint, count int) *dispatch.Dispatcher {
71 b.Helper()
72 chain := make([]extension.Contribution, 0, count)
73 clients := make(map[string]*Client, count)
74 for i := 0; i < count; i++ {
75 pluginID := "benchmark-" + strconv.Itoa(i)
76 client := startFakeClient(b, func(rt *pluginpkg.RuntimeSpec) {
77 rt.Intercepts = []string{string(point)}
78 }, nil)
79 clients[pluginID] = client
80 chain = append(chain, extension.Contribution{
81 Kind: extension.KindInterceptor,
82 ID: string(point),
83 Source: extension.ContributionSource{
84 Scope: extension.ScopePlugin,
85 PluginID: pluginID,
86 },
87 })
88 }
89 return dispatch.New(map[extension.InterceptorPoint][]extension.Contribution{point: chain}, nil,
90 func(pluginID string) dispatch.Client { return clients[pluginID] }, nil, dispatch.Options{})
91 }
92
93 func benchmarkSidecarLatency(b *testing.B, fn func() error) {
94 b.Helper()
95 b.ReportAllocs()
96 const maxSamples = 100_000
97 samples := make([]int64, 0, maxSamples)
98 for b.Loop() {
99 start := time.Now()
100 if err := fn(); err != nil {
101 b.Fatal(err)
102 }
103 if len(samples) < maxSamples {
104 samples = append(samples, time.Since(start).Nanoseconds())
105 }
106 }
107 b.StopTimer()
108 reportSidecarPercentiles(b, samples)
109 }
110
111 func reportSidecarPercentiles(b *testing.B, samples []int64) {
112 b.Helper()
113 sort.Slice(samples, func(i, j int) bool { return samples[i] < samples[j] })
114 if len(samples) == 0 {
115 return
116 }
117 b.ReportMetric(float64(samples[(len(samples)-1)*50/100]), "p50-ns/op")
118 b.ReportMetric(float64(samples[(len(samples)-1)*95/100]), "p95-ns/op")
119 }
120
121 var _ dispatch.Client = (*Client)(nil)
122
122 lines GO