Skip to content

Commit d12a309

Browse files
authored
chore(lsp): introducing benchmark (#4601)
1 parent c200c71 commit d12a309

4 files changed

Lines changed: 125 additions & 0 deletions

File tree

.circleci/continue_config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ jobs:
9393
- run:
9494
name: Run linters and code style checks
9595
command: make py-style
96+
- run:
97+
name: Exercise the benchmarks
98+
command: make benchmark-ci
9699
- run:
97100
name: Run cicd tests
98101
command: make cicd-test

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,6 @@ vscode-generate-openapi:
181181
python3 web/server/openapi.py --output vscode/openapi.json
182182
pnpm run fmt
183183
cd vscode/react && pnpm run generate:api
184+
185+
benchmark-ci:
186+
python benchmarks/lsp_render_model_bench.py --debug-single-value
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/usr/bin/env python
2+
3+
import asyncio
4+
import pyperf
5+
import os
6+
import logging
7+
from pathlib import Path
8+
from lsprotocol import types
9+
10+
from sqlmesh.lsp.custom import RenderModelRequest, RENDER_MODEL_FEATURE
11+
from sqlmesh.lsp.uri import URI
12+
from pygls.client import JsonRPCClient
13+
14+
# Suppress debug logging during benchmark
15+
logging.getLogger().setLevel(logging.WARNING)
16+
17+
18+
class LSPClient(JsonRPCClient):
19+
"""A custom LSP client for benchmarking."""
20+
21+
def __init__(self):
22+
super().__init__()
23+
self.render_model_result = None
24+
self.initialized = asyncio.Event()
25+
26+
# Register handlers for notifications we expect from the server
27+
@self.feature(types.WINDOW_SHOW_MESSAGE)
28+
def handle_show_message(_):
29+
# Silently ignore show message notifications during benchmark
30+
pass
31+
32+
@self.feature(types.WINDOW_LOG_MESSAGE)
33+
def handle_log_message(_):
34+
# Silently ignore log message notifications during benchmark
35+
pass
36+
37+
async def initialize_server(self):
38+
"""Send initialization request to server."""
39+
# Get the sushi example directory
40+
sushi_dir = Path(__file__).parent.parent / "examples" / "sushi"
41+
42+
response = await self.protocol.send_request_async(
43+
types.INITIALIZE,
44+
types.InitializeParams(
45+
process_id=os.getpid(),
46+
root_uri=URI.from_path(sushi_dir).value,
47+
capabilities=types.ClientCapabilities(),
48+
workspace_folders=[
49+
types.WorkspaceFolder(
50+
uri=URI.from_path(sushi_dir).value,
51+
name="sushi"
52+
)
53+
]
54+
)
55+
)
56+
57+
# Send initialized notification
58+
self.protocol.notify(types.INITIALIZED, types.InitializedParams())
59+
self.initialized.set()
60+
return response
61+
62+
63+
async def benchmark_render_model_async(client: LSPClient, model_path: Path):
64+
"""Benchmark the render_model request."""
65+
uri = URI.from_path(model_path).value
66+
67+
# Send render_model request
68+
result = await client.protocol.send_request_async(
69+
RENDER_MODEL_FEATURE,
70+
RenderModelRequest(textDocumentUri=uri)
71+
)
72+
73+
return result
74+
75+
76+
def benchmark_render_model(loops):
77+
"""Synchronous wrapper for the benchmark."""
78+
async def run():
79+
# Create client
80+
client = LSPClient()
81+
82+
# Start the SQLMesh LSP server as a subprocess
83+
await client.start_io("python", "-m", "sqlmesh.lsp.main")
84+
85+
# Initialize the server
86+
await client.initialize_server()
87+
88+
# Get a model file to test with
89+
sushi_dir = Path(__file__).parent.parent / "examples" / "sushi"
90+
model_path = sushi_dir / "models" / "customers.sql"
91+
92+
# Warm up
93+
await benchmark_render_model_async(client, model_path)
94+
95+
# Run benchmark
96+
t0 = pyperf.perf_counter()
97+
for _ in range(loops):
98+
await benchmark_render_model_async(client, model_path)
99+
dt = pyperf.perf_counter() - t0
100+
101+
# Clean up
102+
await client.stop()
103+
104+
return dt
105+
106+
return asyncio.run(run())
107+
108+
109+
def main():
110+
runner = pyperf.Runner()
111+
runner.bench_time_func(
112+
"lsp_render_model",
113+
benchmark_render_model
114+
)
115+
116+
117+
if __name__ == "__main__":
118+
main()

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ dev = [
7777
"pydantic",
7878
"PyAthena[Pandas]",
7979
"PyGithub~=2.5.0",
80+
"pyperf",
8081
"pyspark~=3.5.0",
8182
"pytest",
8283
"pytest-asyncio",

0 commit comments

Comments
 (0)