diff --git a/evalharness/progress/rich_terminal.py b/evalharness/progress/rich_terminal.py index 5d749c7..c78d256 100644 --- a/evalharness/progress/rich_terminal.py +++ b/evalharness/progress/rich_terminal.py @@ -43,7 +43,7 @@ class RichTerminalProgress: TextColumn("[progress.description]{task.description}"), BarColumn(complete_style="green", finished_style="bold green"), TaskProgressColumn(), - TextColumn("• Completed {task.completed}/{task.total}"), + TextColumn("• Completed {task.completed}/{task.total} [dim]{task.fields[new]}[/dim]"), TextColumn("• in-flight [yellow]{task.fields[inflight]}[/yellow] ([yellow]{task.fields[cur]}[/yellow])"), TextColumn("• [red]retries {task.fields[retries]}[/red]"), TextColumn("• [dim]{task.fields[rate]}/s[/dim]"), @@ -59,6 +59,7 @@ class RichTerminalProgress: self.started = 0.0 self.current_started = 0.0 self.inflight = 0 + self.restored = 0 # checkpoint head start (drives the '+N new' marker) self.heartbeat_task = None def set_overall(self, total: int, done: int, label: str = 'benches'): @@ -73,8 +74,8 @@ class RichTerminalProgress: # overall task the same fields or rendering raises KeyError self.overall_id = self.progress.add_task( f'[cyan]{label}[/cyan]', total=total, completed=min(done, total), - inflight=0, rate='0.00', success=done, failed=0, - waiting='00:00', last_result='') + new='', inflight=0, cur='0s', retries=0, rate='0.00', + elapsed='0s', eta='-') else: self.progress.update(self.overall_id, completed=min(done, total)) @@ -88,6 +89,7 @@ class RichTerminalProgress: if self.disabled: return self.started = time.monotonic() + self.restored = max(completed, 0) if self.task_id is not None: return # one live reporter at a time; reuse across benchmarks self.progress.start() @@ -95,18 +97,24 @@ class RichTerminalProgress: f"[green]{description}", total=total, completed=min(completed, total), - success=completed, - failed=0, + new=self._new_txt(completed), rate="0.00", inflight=0, cur="0s", + retries=0, elapsed="0s", eta="-", - waiting="00:00", - last_result="restored", ) self.heartbeat_task = asyncio.create_task(self._heartbeat()) + def _new_txt(self, absolute_done: int) -> str: + """'(+N new)' marker: samples completed by THIS run, i.e. absolute + progress minus the checkpoint-restored head start. Empty when the + run started fresh (nothing was restored, nothing to distinguish).""" + if not getattr(self, 'restored', 0): + return '' + return f'(+{max(absolute_done - self.restored, 0)} new)' + def reset_samples(self, total: int, description: str, completed: int = 0): if self.disabled: return @@ -114,20 +122,20 @@ class RichTerminalProgress: self.started = time.monotonic() self.inflight = 0 self.bench_name = description + self.restored = max(completed, 0) # checkpoint head start this bench desc = f'[green]{self.bench_tag}{description} · generating[/green]' if self.task_id is None: self.progress.start() self.task_id = self.progress.add_task( desc, total=total, completed=min(completed, total), - success=completed, failed=0, rate='0.00', inflight=0, - cur='0s', elapsed='0s', eta='-', retries=0, - waiting='00:00', last_result='restored') + new=self._new_txt(completed), rate='0.00', inflight=0, + cur='0s', elapsed='0s', eta='-', retries=0) else: self.progress.update(self.task_id, description=desc, total=total, completed=min(completed, total), - success=completed, failed=0, rate='0.00', - inflight=0, cur='0s', elapsed='0s', eta='-', retries=0, - last_result='redone') + new=self._new_txt(completed), rate='0.00', + inflight=0, cur='0s', elapsed='0s', eta='-', + retries=0) # ALWAYS recreate the heartbeat: the previous one may have died during # pause/resume cycles between benchmarks (stale reference -> silent # death -> frozen clock while the spinner still animates) @@ -161,8 +169,7 @@ class RichTerminalProgress: return self.inflight += 1 self.current_started = time.monotonic() - self.progress.update(self.task_id, inflight=self.inflight, cur='0s', - waiting="00:00", last_result=f"waiting {label}") + self.progress.update(self.task_id, inflight=self.inflight, cur='0s') def set_retries(self, n: int): """Show the retry count on the bar (from the adapter's attempt).""" @@ -185,22 +192,20 @@ class RichTerminalProgress: return task = self.progress.tasks[self.task_id] completed = task.completed + 1 - ok = task.fields["success"] + (1 if success else 0) - failed = task.fields["failed"] + (0 if success else 1) self.inflight = max(0, self.inflight - 1) elapsed = max(time.monotonic() - self.started, 1e-6) + # rate/eta over THIS RUN's fresh samples only: counting the restored + # head start would print 141/6s = 23/s when 1 sample was generated + fresh = max(completed - getattr(self, 'restored', 0), 0) self.progress.update( self.task_id, advance=1, - success=ok, - failed=failed, - rate=f"{completed / elapsed:.2f}", + new=self._new_txt(completed), + rate=f"{fresh / elapsed:.2f}", inflight=self.inflight, cur='0s', elapsed=_fmt(elapsed), - eta=_fmt((task.total - completed) * elapsed / completed) - if completed and task.total and task.total > completed else '-', - waiting="00:00", - last_result="success" if success else "failed", + eta=_fmt((task.total - completed) * elapsed / fresh) + if fresh and task.total and task.total > completed else '-', ) async def _heartbeat(self):