summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 22:58:53 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commitb1485c97b493f7eaf65c6afdfb82e3065306b8fe (patch)
tree890d411820859ef33f1adf3164176fe29e007961 /tools
parentf0e162d287f44a562ff79e7c1d2cf81ace00c836 (diff)
downloadpardes-b1485c97b493f7eaf65c6afdfb82e3065306b8fe.tar.gz
pardes-b1485c97b493f7eaf65c6afdfb82e3065306b8fe.zip
The PDF benches share their clock, fatal exit, hash, count parser and effect drain, and lspprobe is lspbench's probe subcommand
B4: test/bench_util.zig holds what pdf_bench, pdf_pointer_bench, pdf_scroll_bench and pdf_sections_bench each copied (nowNs x4, fatal x3 named per bench, mix x3, parsePositive/parseCount x3, drainEffects x2). They stay four entry points: pdf_bench links its own mupdf module (with its C source and c_heap) where the others link the core's, which one executable cannot import under one name, and their reports and identities are each bench's own. pdf_sections_bench had rotted since the pane's cursor and selections moved to its body and the render revision became next_raster_revision; it builds and runs again. C6: tools/lspprobe.zig is lspbench's `probe` subcommand (zig build lspbench -- probe <kind> <file> <line>:<col>), and the lspprobe build step goes; docs/lsp.md names the new command. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'tools')
-rw-r--r--tools/lspprobe.zig184
1 files changed, 0 insertions, 184 deletions
diff --git a/tools/lspprobe.zig b/tools/lspprobe.zig
deleted file mode 100644
index 0b7d772c..00000000
--- a/tools/lspprobe.zig
+++ /dev/null
@@ -1,184 +0,0 @@
-//! One language query against the REAL seam, from the command line.
-//!
-//! pardes-lspprobe <kind> <file> <line>:<col> [arg] [--reps N]
-//!
-//! `kind` is an `lsp.Kind` tag (`definition`, `references`, ...) or its helix
-//! key (`gd`, `gr`, `gy`, `gi`, `gD`). Line and column are 1-based, the way
-//! the rows print them. Rows go to stdout; the timing line goes to stderr so
-//! `lspprobe gd f.rs 10:5 | head` still composes.
-//!
-//! This is the development probe for the PROTOCOL CLIENT: point it at a file
-//! in any workspace (a rust crate, a C tree with compile_commands.json) and it
-//! exercises exactly what `gd` in the editor would — same seam, same client,
-//! same child server. The server is spawned fresh per invocation and kept for
-//! the process, so rep 0 is the true cold cost (spawn + handshake + whatever
-//! indexing the deadline allows) and later reps are the warm per-press cost.
-//! Generic on purpose: no project names anywhere — the workspace is whatever
-//! the file's root markers say.
-const std = @import("std");
-const lsp = @import("pardes").lsp;
-
-extern "c" fn realpath(path: [*:0]const u8, resolved: [*]u8) ?[*:0]u8;
-
-pub const std_options: std.Options = .{ .log_level = .err };
-
-fn nowUs() u64 {
- var ts: std.c.timespec = undefined;
- _ = std.c.clock_gettime(.MONOTONIC, &ts);
- return @as(u64, @intCast(ts.sec)) *| 1_000_000 +| @as(u64, @intCast(ts.nsec)) / 1000;
-}
-
-const aliases = .{
- .{ "gd", lsp.Kind.definition },
- .{ "gD", lsp.Kind.declaration },
- .{ "gy", lsp.Kind.type_definition },
- .{ "gi", lsp.Kind.implementation },
- .{ "gr", lsp.Kind.references },
-};
-
-fn usage() noreturn {
- std.debug.print(
- \\usage: pardes-lspprobe <kind> <file> <line>:<col> [arg] [--reps N] [--warm S]
- \\ kind: gd gD gy gi gr, or any lsp.Kind tag (hover, references,
- \\ document_symbols, workspace_symbols, incoming_calls, ...)
- \\ arg: the new name for rename, the query for workspace_symbols
- \\ --warm sleep S seconds after the first (cold) query — long enough
- \\ for an indexing server to reach ready, narrated on stderr
- \\
- , .{});
- std.process.exit(2);
-}
-
-/// The editor's message row, here: the client's reader threads narrate server
-/// state through the same sink the shells register, printed to stderr so the
-/// rows on stdout stay clean.
-fn statusSink(ctx: ?*anyopaque, text: []const u8) void {
- _ = ctx;
- std.debug.print("# status: {s}\n", .{text});
-}
-
-extern "c" fn usleep(usec: c_uint) c_int;
-
-pub fn main(init: std.process.Init) !void {
- const gpa = std.heap.page_allocator;
- const arena = init.arena.allocator();
- const io = std.Io.Threaded.global_single_threaded.io();
-
- const args = try init.minimal.args.toSlice(arena);
- if (args.len < 4) usage();
-
- // comma-separated kinds share ONE server instance and its index — the
- // whole battery against a big workspace pays the cold cost once
- var kinds: [16]lsp.Kind = undefined;
- var nkinds: usize = 0;
- var kit = std.mem.tokenizeScalar(u8, args[1], ',');
- while (kit.next()) |word| {
- if (nkinds == kinds.len) usage();
- kinds[nkinds] = blk: {
- inline for (aliases) |a| if (std.mem.eql(u8, word, a[0])) break :blk a[1];
- break :blk std.meta.stringToEnum(lsp.Kind, word) orelse usage();
- };
- nkinds += 1;
- }
- if (nkinds == 0) usage();
-
- var pz: [4096:0]u8 = undefined;
- const pz_s = std.fmt.bufPrintSentinel(&pz, "{s}", .{args[2]}, 0) catch usage();
- var realbuf: [4096]u8 = undefined;
- const path = std.mem.span(realpath(pz_s.ptr, &realbuf) orelse {
- std.debug.print("cannot resolve {s}\n", .{args[2]});
- std.process.exit(1);
- });
- const source = std.Io.Dir.cwd().readFileAllocOptions(
- io,
- path,
- arena,
- .limited(64 << 20),
- .of(u8),
- 0,
- ) catch {
- std.debug.print("cannot read {s}\n", .{path});
- std.process.exit(1);
- };
-
- const colon = std.mem.indexOfScalar(u8, args[3], ':') orelse usage();
- const line = std.fmt.parseInt(u32, args[3][0..colon], 10) catch usage();
- const col = std.fmt.parseInt(u32, args[3][colon + 1 ..], 10) catch usage();
- if (line == 0 or col == 0) usage();
-
- var arg: []const u8 = "";
- var reps: u32 = 1;
- var warm_s: u32 = 0;
- var i: usize = 4;
- while (i < args.len) : (i += 1) {
- if (std.mem.eql(u8, args[i], "--reps")) {
- i += 1;
- if (i >= args.len) usage();
- reps = @max(1, std.fmt.parseInt(u32, args[i], 10) catch usage());
- } else if (std.mem.eql(u8, args[i], "--warm")) {
- i += 1;
- if (i >= args.len) usage();
- warm_s = std.fmt.parseInt(u32, args[i], 10) catch usage();
- } else arg = args[i];
- }
-
- lsp.setStatusSink(null, statusSink);
-
- // 1-based line:col -> byte offset
- var off: usize = 0;
- var ln: u32 = 1;
- while (ln < line and off < source.len) : (off += 1) {
- if (source[off] == '\n') ln += 1;
- }
- off = @min(off + col - 1, source.len);
-
- const root = std.fs.path.dirname(path) orelse "/";
- const timings = try arena.alloc(u64, reps);
- var stdout_buf: [64 * 1024]u8 = undefined;
- var stdout = std.Io.File.stdout().writer(io, &stdout_buf);
- var first_query = true;
-
- for (kinds[0..nkinds]) |kind| {
- var rows: []const u8 = "";
- for (0..reps) |r| {
- // one warm pause for the whole battery, right after the very
- // first (cold, server-spawning) query — the status lines on
- // stderr narrate what the server does with the time
- var out: std.Io.Writer.Allocating = .init(gpa);
- defer out.deinit();
- var scratch: std.heap.ArenaAllocator = .init(gpa);
- defer scratch.deinit();
- const t0 = nowUs();
- try lsp.query(gpa, scratch.allocator(), .{
- .kind = kind,
- .path = path,
- .source = source,
- .offset = @intCast(off),
- .arg = arg,
- .root = root,
- }, &out.writer);
- timings[r] = nowUs() -| t0;
- rows = try arena.dupe(u8, out.written());
- if (first_query) {
- first_query = false;
- if (warm_s > 0) for (0..warm_s * 10) |_| {
- _ = usleep(100_000);
- };
- }
- }
-
- try stdout.interface.print("## {t}\n", .{kind});
- try stdout.interface.writeAll(rows);
- try stdout.interface.flush();
-
- const n = std.mem.count(u8, rows, "\n");
- const cold = timings[0];
- std.mem.sort(u64, timings[0..reps], {}, std.sort.asc(u64));
- if (reps == 1)
- std.debug.print("# {t}: {d} row(s) in {d}us\n", .{ kind, n, cold })
- else
- std.debug.print("# {t}: {d} row(s); first {d}us, min {d}us, median {d}us ({d} reps)\n", .{
- kind, n, cold, timings[0], timings[reps / 2], reps,
- });
- }
-}