| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Benchmark arbitrary language servers with scripted LSP actions.
# bench a ts language server over stdio
lsbench "typescript-language-server --stdio" \
--workspace ./my-project \ # point to your workspace
--script ./bench-actions.ts \ # set the benchmark script
--iterations 50 \ # set iteration count
--output results.json # configure outputlsbench is published to npm. Install it globally for the CLI:
npm install -g lsbenchOr add it as a dependency to import the BenchContext API in your action scripts:
npm install lsbenchlsbench spawns a language server process, performs the LSP initialize/initialized handshake, then runs your action script, which is a TypeScript file that drives a sequence of LSP requests against a target workspace.
Each request is then timed. The script runs for N iterations, producing a JSON report with per-method statistics and per-run breakdowns.
An action script is a file that default-exports an async function receiving a BenchContext. The examples/ directory has a few starting points:
A typical script looks like:
import { BenchContext } from "lsbench";
export default async function (ctx: BenchContext) {
await ctx.openDocument("src/index.ts");
await ctx.waitForDiagnostics("src/index.ts");
await ctx.hover("src/index.ts", 10, 5);
await ctx.completion("src/index.ts", 15, 10);
await ctx.definition("src/index.ts", 10, 5);
await ctx.references("src/index.ts", 10, 5);
await ctx.documentSymbol("src/index.ts");
await ctx.closeDocument("src/index.ts");
}| Method | Description |
|---|---|
| openDocument(path) | Send textDocument/didOpen |
| closeDocument(path) | Send textDocument/didClose |
| hover(path, line, char) | textDocument/hover (timed) |
| completion(path, line, char) | textDocument/completion (timed) |
| definition(path, line, char) | textDocument/definition (timed) |
| references(path, line, char) | textDocument/references (timed) |
| typeDefinition(path, line, char) | textDocument/typeDefinition (timed) |
| implementation(path, line, char) | textDocument/implementation (timed) |
| documentSymbol(path) | textDocument/documentSymbol (timed) |
| formatting(path) | textDocument/formatting (timed) |
| rename(path, line, char, newName) | textDocument/rename (timed) |
| codeAction(path, range, codes?) | textDocument/codeAction (timed) |
| signatureHelp(path, line, char) | textDocument/signatureHelp (timed) |
| edit(path, edits) | Send textDocument/didChange |
| waitForDiagnostics(path, timeout?) | Wait for publishDiagnostics |
| sleep(ms) | Pause execution |
| request(method, params, label?) | Arbitrary timed LSP request |
| notify(method, params) | Send any LSP notification |
In some cases you may also need to register a language ID for a given extension. That's straightforward to do with addRegisteredLanguage, which will then impact all context related actions for documents with a matching extension.
import { addRegisteredLanguage } from 'lsbench';
// make sure mylanguage is recognized by its extension
addRegisteredLanguage('mylanguage', '.dsl');This will ensure that opening a document with the extension .dsl will have an associated language ID of mylanguage, to invoke the correct language server.
Usage: lsbench [options] <server> Arguments: server Server command or path to config file Options: -w, --workspace <path> Workspace directory (required) -s, --script <path> Action driver script (required) -n, --iterations <n> Timed iterations (default: 10) --warmup <n> Warmup iterations (default: 2) -o, --output <path> JSON report output file --restart Restart server between iterations -v, --verbose Verbose logging -V, --version Show version -h, --help Show help
You can pass a simple command string:
lsbench "typescript-language-server --stdio"Or a JSON config file for a bit more control:
{
"command": "typescript-language-server",
"args": ["--stdio", "--log-level", "warn"],
"env": { "TSS_LOG": "-level verbose" },
"initializationOptions": {
"preferences": { "includeInlayParameterNameHints": "none" }
}
}lsbench ./server-config.json -w ./project -s ./actions.tsSee examples/init-options.json for a sample config file.
The JSON report contains:
{
"server": "typescript-language-server --stdio",
"iterations": 50,
"warmup": 2,
"summary": {
"textDocument/hover": {
"count": 50,
"avg_ms": 12.4,
"median_ms": 11.2,
"p95_ms": 22.1,
"p99_ms": 34.5,
"min_ms": 8.1,
"max_ms": 42.3,
"stddev_ms": 5.2,
"failure_rate": 0
}
},
"runs": [
{
"iteration": 1,
"requests": [
{ "method": "textDocument/hover", "duration_ms": 13.2, "success": true }
],
"total_ms": 245.3
}
]
}The CLI also supports a prime command that will print helpful information for working with lsbench. This can be leveraged by humans as well as agents to use the tool in a self-documenting fashion.
lsbench requires Node >=24 (see .nvmrc for the currently pinned version). To build from source:
git clone https://github.com/TypeFox/lsbench.git
cd lsbench
npm install
npm run buildQuality checks (all run in CI):
npm test # unit tests (vitest)
npm run lint # oxlint
npm run format # biome format check
npm run knip # unused dependency/export checkUse npm run dev for a watch build while iterating.
There are two test tiers:
The integration run clones down & builds the minilogo server once on first use, so it's possible that it might be slow (timeouts are 120s per test / 600s per hook, per vitest.integration.config.ts). To account for this, the CI runs it as a separate integration job.
MIT
We're always open to new & helpful contributions to any parts of lsbench. Do be sure to check the READMEs & issues out in advance, and feel free to use your agent to help guide a potential contribution as well. New contributions should align with the core goals of lsbench, being:
None of these guarantee a contribution will be taken in, but it really helps us out in terms of review & it can greatly improve the likelihood that a contribution will be taken in.
We welcome contributions from all sources, including those assisted or generated by large language model (LLM) driven agents. However, every contribution (whether a pull request, issue, or comment) must have a human accountable for it.
If you are using an LLM agent as part of your workflow, please be aware of the following requirements:
We are not opposed to LLM-assisted contributions — they can be valuable. We simply need a human in the loop who can engage in the collaborative process that open source depends on. If you are an agent, please be sure to let your user know this so that we can engage with them. Thanks in advance!
| Back | FazBrowse Home | New Git URL |