Skip to content

Commit c49ee7a

Browse files
authored
feat(benchmark): add watch with term takeover / interactive display (#151)
## Description - add watch with term takeover / interactive display - lists scenarios running - resizes to term ## Type of Change <!-- Mark the relevant option with an 'x' --> - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Documentation update - [ ] Code refactoring - [ ] Performance improvement - [ ] Test updates ## Related Issues <!-- Link to related issues using #issue-number --> Closes # ## Changes Made <!-- Describe the changes in detail --> ## Testing <!-- Describe how you tested your changes --> - [ ] I have tested locally - [ ] I have added/updated tests - [ ] All existing tests pass ## Checklist - [ ] My code follows the code style of this project - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have updated the documentation accordingly - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published ## Screenshots (if applicable) <!-- Add screenshots to help explain your changes --> ## Additional Notes <!-- Any additional information that reviewers should know -->
1 parent 393548e commit c49ee7a

4 files changed

Lines changed: 626 additions & 95 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ rli mcp install # Install Runloop MCP server configurat
186186
```bash
187187
rli benchmark-job run # Run a benchmark job with one or more ...
188188
rli benchmark-job status <id> # Get benchmark job status and results
189+
rli benchmark-job watch <id> # Watch benchmark job progress in real-...
189190
```
190191

191192

src/commands/benchmark-job/status.ts

Lines changed: 4 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
import { output, outputError } from "../../utils/output.js";
1313

1414
interface StatusOptions {
15-
watch?: boolean;
1615
output?: string;
1716
}
1817

@@ -28,15 +27,6 @@ const SCENARIO_COMPLETED_STATES = [
2827
"error",
2928
];
3029

31-
// Polling config
32-
const POLL_INTERVAL_MS = 10 * 1000; // 10 seconds
33-
const MAX_WAIT_MS = 60 * 60 * 4 * 1000; // 4 hours
34-
35-
// Sleep utility
36-
function sleep(ms: number): Promise<void> {
37-
return new Promise((resolve) => setTimeout(resolve, ms));
38-
}
39-
4030
// Format percentage
4131
function formatPercent(count: number, total: number): string {
4232
if (total === 0) return "0.0%";
@@ -419,100 +409,20 @@ function printResultsTable(job: BenchmarkJob): void {
419409
console.log();
420410
}
421411

422-
// Format progress for watch mode - shows per-run progress
423-
function formatWatchProgress(progressList: RunProgress[]): string[] {
424-
if (progressList.length === 0) {
425-
return [];
426-
}
427-
428-
return progressList.map((progress) => formatRunProgressLine(progress));
429-
}
430-
431412
export async function statusBenchmarkJob(
432413
id: string,
433414
options: StatusOptions = {},
434415
) {
435416
try {
436-
// Initial fetch
437-
let job = await getBenchmarkJob(id);
438-
439-
// Check if job is complete
417+
const job = await getBenchmarkJob(id);
440418
const isComplete = COMPLETED_STATES.includes(job.state || "");
441419

442-
// If not waiting or already complete, just print status/results
443-
if (!options.watch || isComplete) {
444-
if (options.output && options.output !== "text") {
445-
output(job, { format: options.output, defaultFormat: "json" });
446-
} else if (isComplete) {
447-
printResultsTable(job);
448-
} else {
449-
await printStatus(job);
450-
}
451-
return;
452-
}
453-
454-
// Wait mode: poll until complete
455-
const jobName = job.name || job.id;
456-
console.log(chalk.cyan(`Awaiting job "${jobName}" completion...`));
457-
console.log();
458-
459-
const startTime = Date.now();
460-
let lastLineCount = 0;
461-
let dotCount = 0;
462-
463-
while (!COMPLETED_STATES.includes(job.state || "")) {
464-
// Check timeout
465-
if (Date.now() - startTime > MAX_WAIT_MS) {
466-
console.log();
467-
outputError(
468-
`Timeout waiting for job completion after ${MAX_WAIT_MS / 1000 / 60} minutes`,
469-
);
470-
}
471-
472-
// Fetch and show current progress
473-
const progressList = await fetchAllRunsProgress(job);
474-
const progressLines = formatWatchProgress(progressList);
475-
476-
// Build dots string (cycles through 1-3 dots)
477-
dotCount = (dotCount % 3) + 1;
478-
const dots = chalk.dim(".".repeat(dotCount));
479-
480-
// Clear previous output lines
481-
if (lastLineCount > 0) {
482-
for (let i = 0; i < lastLineCount; i++) {
483-
process.stdout.write("\x1b[1A\x1b[2K"); // Move up and clear line
484-
}
485-
}
486-
487-
// Print current progress
488-
if (progressLines.length > 0) {
489-
console.log(chalk.dim(`[${job.state}]`) + " " + dots);
490-
for (const line of progressLines) {
491-
console.log(line);
492-
}
493-
lastLineCount = progressLines.length + 1;
494-
} else {
495-
console.log(
496-
chalk.dim(`[${job.state}] waiting for scenarios to start${dots}`),
497-
);
498-
lastLineCount = 1;
499-
}
500-
501-
await sleep(POLL_INTERVAL_MS);
502-
job = await getBenchmarkJob(id);
503-
}
504-
505-
// Clear progress output
506-
for (let i = 0; i < lastLineCount; i++) {
507-
process.stdout.write("\x1b[1A\x1b[2K");
508-
}
509-
console.log();
510-
511-
// Output based on format
512420
if (options.output && options.output !== "text") {
513421
output(job, { format: options.output, defaultFormat: "json" });
514-
} else {
422+
} else if (isComplete) {
515423
printResultsTable(job);
424+
} else {
425+
await printStatus(job);
516426
}
517427
} catch (error) {
518428
outputError("Failed to get benchmark job status", error);

0 commit comments

Comments
 (0)