diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 22:58:53 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | b1485c97b493f7eaf65c6afdfb82e3065306b8fe (patch) | |
| tree | 890d411820859ef33f1adf3164176fe29e007961 /tools/lspprobe.zig | |
| parent | f0e162d287f44a562ff79e7c1d2cf81ace00c836 (diff) | |
| download | pardes-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/lspprobe.zig')
| -rw-r--r-- | tools/lspprobe.zig | 184 |
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, - }); - } -} |
