|
1 | 1 | // SPDX-License-Identifier: EUPL-1.2 |
2 | 2 |
|
3 | | -// Benchmarks for the LSP diagnostic pipeline in lsp.go. The four |
4 | | -// built-in sources (ax-7 naming, result-shape, spor, test-imports) |
5 | | -// are registered in init(), so LSPComputeDiagnostics exercises the |
6 | | -// whole scan + helper chain on each call. Content is crafted to enter |
7 | | -// the result-shape and import-extraction paths so their helpers run. |
| 3 | +// Benchmarks for lsp.go. White-box (package core) so the JSON-RPC server |
| 4 | +// loop — dispatch, the document handlers, readMessage/writeMessage, |
| 5 | +// respond/notify, publishDiagnostics — can be driven directly. These are |
| 6 | +// unexported and editor-rate (fire per keystroke), not reachable from a |
| 7 | +// black-box bench. Output goes to Discard so the numbers are the protocol |
| 8 | +// machinery, not the terminal. |
8 | 9 | // |
9 | | -// The JSON-RPC server loop (run/readMessage/dispatch/handle*) is driven |
10 | | -// by stdin and is exercised by the functional tests, not benchmarked |
11 | | -// here — it is request-rate bound, not a per-token hot path. |
12 | | -// |
13 | | -// Run: go test -bench='BenchmarkLSP' -benchmem -run='^$' . |
14 | | - |
15 | | -package core_test |
| 10 | +// Run: go test -bench='BenchmarkLSP|BenchmarkLsp' -benchmem -run='^$' . |
16 | 11 |
|
17 | | -import ( |
18 | | - . "dappco.re/go" |
19 | | -) |
| 12 | +package core |
20 | 13 |
|
21 | 14 | var ( |
22 | 15 | lspSink []LSPDiagnostic |
23 | 16 | lspSinkStr []string |
| 17 | + lspSinkRes Result |
24 | 18 | ) |
25 | 19 |
|
| 20 | +func lspBenchServer() *lspServer { |
| 21 | + return &lspServer{out: Discard, documents: NewRegistry[[]byte]()} |
| 22 | +} |
| 23 | + |
| 24 | +// --- diagnostic pipeline (exported) --- |
| 25 | + |
26 | 26 | func BenchmarkLSPComputeDiagnostics(b *B) { |
27 | 27 | uri := "file:///agent/worker.go" |
28 | 28 | content := []byte(`package worker |
@@ -63,3 +63,99 @@ func BenchmarkLSPDiagnosticSources(b *B) { |
63 | 63 | lspSinkStr = LSPDiagnosticSources() |
64 | 64 | } |
65 | 65 | } |
| 66 | + |
| 67 | +// --- JSON-RPC server loop (white-box) --- |
| 68 | + |
| 69 | +func BenchmarkLspServer_dispatch_Initialize(b *B) { |
| 70 | + srv := lspBenchServer() |
| 71 | + raw := []byte(`{"jsonrpc":"2.0","id":1,"method":"initialize"}`) |
| 72 | + b.ReportAllocs() |
| 73 | + for i := 0; i < b.N; i++ { |
| 74 | + srv.dispatch(raw) |
| 75 | + } |
| 76 | +} |
| 77 | + |
| 78 | +func BenchmarkLspServer_dispatch_DidOpen(b *B) { |
| 79 | + srv := lspBenchServer() |
| 80 | + raw := []byte(`{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///x.go","text":"package x\n"}}}`) |
| 81 | + b.ReportAllocs() |
| 82 | + for i := 0; i < b.N; i++ { |
| 83 | + srv.dispatch(raw) // → handleDocumentSync → lspExtractDocument → publishDiagnostics → notify → writeMessage |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +func BenchmarkLspServer_dispatch_DidChange(b *B) { |
| 88 | + srv := lspBenchServer() |
| 89 | + raw := []byte(`{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///x.go"},"contentChanges":[{"text":"package x\n"}]}}`) |
| 90 | + b.ReportAllocs() |
| 91 | + for i := 0; i < b.N; i++ { |
| 92 | + srv.dispatch(raw) // → handleDocumentChange → lspExtractDocumentChange |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +func BenchmarkLspServer_dispatch_DidClose(b *B) { |
| 97 | + srv := lspBenchServer() |
| 98 | + srv.dispatch([]byte(`{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///x.go","text":"package x\n"}}}`)) |
| 99 | + raw := []byte(`{"jsonrpc":"2.0","method":"textDocument/didClose","params":{"textDocument":{"uri":"file:///x.go"}}}`) |
| 100 | + b.ReportAllocs() |
| 101 | + for i := 0; i < b.N; i++ { |
| 102 | + srv.dispatch(raw) // → handleDocumentClose |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +func BenchmarkLspServer_readMessage(b *B) { |
| 107 | + frame := "Content-Length: 17\r\n\r\n{\"jsonrpc\":\"2.0\"}" |
| 108 | + b.ReportAllocs() |
| 109 | + for i := 0; i < b.N; i++ { |
| 110 | + srv := lspBenchServer() |
| 111 | + srv.in = NewBufReader(NewReader(frame)) |
| 112 | + lspSinkRes = srv.readMessage() |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +func BenchmarkLspServer_writeMessage(b *B) { |
| 117 | + srv := lspBenchServer() |
| 118 | + payload := lspMessage{JSONRPC: "2.0", Method: "textDocument/publishDiagnostics"} |
| 119 | + b.ReportAllocs() |
| 120 | + for i := 0; i < b.N; i++ { |
| 121 | + lspSinkRes = srv.writeMessage(payload) |
| 122 | + } |
| 123 | +} |
| 124 | + |
| 125 | +func BenchmarkLspServer_respond(b *B) { |
| 126 | + srv := lspBenchServer() |
| 127 | + id := 1 |
| 128 | + b.ReportAllocs() |
| 129 | + for i := 0; i < b.N; i++ { |
| 130 | + srv.respond(&id, nil, nil) |
| 131 | + } |
| 132 | +} |
| 133 | + |
| 134 | +func BenchmarkLspServer_notify(b *B) { |
| 135 | + srv := lspBenchServer() |
| 136 | + b.ReportAllocs() |
| 137 | + for i := 0; i < b.N; i++ { |
| 138 | + srv.notify("textDocument/publishDiagnostics", nil) |
| 139 | + } |
| 140 | +} |
| 141 | + |
| 142 | +func BenchmarkLspServer_publishDiagnostics(b *B) { |
| 143 | + srv := lspBenchServer() |
| 144 | + content := []byte("package x\n") |
| 145 | + b.ReportAllocs() |
| 146 | + for i := 0; i < b.N; i++ { |
| 147 | + srv.publishDiagnostics("file:///x.go", content) |
| 148 | + } |
| 149 | +} |
| 150 | + |
| 151 | +// LSPServe with an already-cancelled context: the run loop checks ctx |
| 152 | +// before reading stdin, so it returns immediately — covers the entry + |
| 153 | +// shutdown path without blocking on input. |
| 154 | +func BenchmarkLSPServe(b *B) { |
| 155 | + b.ReportAllocs() |
| 156 | + for i := 0; i < b.N; i++ { |
| 157 | + ctx, cancel := WithCancel(Background()) |
| 158 | + cancel() |
| 159 | + lspSinkRes = LSPServe(ctx) |
| 160 | + } |
| 161 | +} |
0 commit comments