benchkit-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- benchkit/__init__.py +1 -0
- benchkit/_pi_proxy.py +219 -0
- benchkit/answer_key_guard.ts +49 -0
- benchkit/artifacts.py +87 -0
- benchkit/benchmarks/__init__.py +105 -0
- benchkit/benchmarks/aider_polyglot.py +405 -0
- benchkit/benchmarks/arc.py +52 -0
- benchkit/benchmarks/banking77.py +149 -0
- benchkit/benchmarks/base.py +19 -0
- benchkit/benchmarks/boolq.py +49 -0
- benchkit/benchmarks/chartqa.py +133 -0
- benchkit/benchmarks/code_verifier.py +77 -0
- benchkit/benchmarks/downloads.py +166 -0
- benchkit/benchmarks/evalplus.py +288 -0
- benchkit/benchmarks/git_surgery.py +485 -0
- benchkit/benchmarks/gpqa.py +56 -0
- benchkit/benchmarks/gsm8k.py +97 -0
- benchkit/benchmarks/hellaswag.py +49 -0
- benchkit/benchmarks/humaneval.py +103 -0
- benchkit/benchmarks/ifeval.py +56 -0
- benchkit/benchmarks/ifeval_instructions.py +477 -0
- benchkit/benchmarks/mbpp.py +60 -0
- benchkit/benchmarks/mc_arena.py +479 -0
- benchkit/benchmarks/mcq.py +94 -0
- benchkit/benchmarks/medfailbench.py +537 -0
- benchkit/benchmarks/mmlu.py +54 -0
- benchkit/benchmarks/mmlu_pro.py +62 -0
- benchkit/benchmarks/mmvp.py +115 -0
- benchkit/benchmarks/ocrbench.py +96 -0
- benchkit/benchmarks/openbookqa.py +52 -0
- benchkit/benchmarks/patcheval.py +929 -0
- benchkit/benchmarks/piqa.py +52 -0
- benchkit/benchmarks/ruler.py +172 -0
- benchkit/benchmarks/ruler_generator.py +535 -0
- benchkit/benchmarks/sanity.py +174 -0
- benchkit/benchmarks/treejs_arena.py +268 -0
- benchkit/benchmarks/truthfulqa.py +52 -0
- benchkit/benchmarks/utils.py +25 -0
- benchkit/benchmarks/vision.py +230 -0
- benchkit/benchmarks/vstar.py +108 -0
- benchkit/benchmarks/winogrande.py +52 -0
- benchkit/benchmarks/xstest.py +116 -0
- benchkit/browser.py +429 -0
- benchkit/catalog.py +121 -0
- benchkit/cli.py +1000 -0
- benchkit/client.py +1413 -0
- benchkit/controls.py +87 -0
- benchkit/datasets/MMLU_LICENSE.txt +21 -0
- benchkit/datasets/README.md +142 -0
- benchkit/datasets/arc_challenge.jsonl +1172 -0
- benchkit/datasets/banking77.jsonl +3080 -0
- benchkit/datasets/banking77_intents.json +79 -0
- benchkit/datasets/boolq.jsonl +3270 -0
- benchkit/datasets/code_verifier.jsonl +500 -0
- benchkit/datasets/gpqa_diamond.jsonl +198 -0
- benchkit/datasets/gsm8k.jsonl +1319 -0
- benchkit/datasets/hellaswag.jsonl +1000 -0
- benchkit/datasets/humaneval.jsonl +164 -0
- benchkit/datasets/ifeval.jsonl +541 -0
- benchkit/datasets/mbpp.jsonl +500 -0
- benchkit/datasets/mc_blocks_1_20_1.jsonl +1003 -0
- benchkit/datasets/medfailbench_hard30.jsonl +30 -0
- benchkit/datasets/medfailbench_hard30.md +13 -0
- benchkit/datasets/openbookqa.jsonl +500 -0
- benchkit/datasets/piqa.jsonl +1838 -0
- benchkit/datasets/sanity_code.jsonl +6 -0
- benchkit/datasets/truthfulqa.jsonl +817 -0
- benchkit/datasets/winogrande.jsonl +1267 -0
- benchkit/datasets/xstest.csv +451 -0
- benchkit/decision.py +194 -0
- benchkit/demo.py +451 -0
- benchkit/dockerfiles.py +247 -0
- benchkit/engine.py +1270 -0
- benchkit/evaluation.py +195 -0
- benchkit/events.py +141 -0
- benchkit/executor.py +96 -0
- benchkit/git_surgery/backport-release-stack/setup.sh +328 -0
- benchkit/git_surgery/backport-release-stack/verify.sh +119 -0
- benchkit/git_surgery/bisect-the-regression/setup.sh +49 -0
- benchkit/git_surgery/bisect-the-regression/verify.sh +34 -0
- benchkit/git_surgery/rebase-conflict-chain/setup.sh +65 -0
- benchkit/git_surgery/rebase-conflict-chain/verify.sh +43 -0
- benchkit/git_surgery/recover-complex-stash/setup.sh +152 -0
- benchkit/git_surgery/recover-complex-stash/verify.sh +95 -0
- benchkit/git_surgery/recover-lost-work/setup.sh +40 -0
- benchkit/git_surgery/recover-lost-work/verify.sh +36 -0
- benchkit/git_surgery/repair-force-pushed-remote/setup.sh +171 -0
- benchkit/git_surgery/repair-force-pushed-remote/verify.sh +83 -0
- benchkit/git_surgery/revert-merge-with-followups/setup.sh +198 -0
- benchkit/git_surgery/revert-merge-with-followups/verify.sh +98 -0
- benchkit/git_surgery/secret-in-history/README.md +27 -0
- benchkit/git_surgery/secret-in-history/setup.sh +107 -0
- benchkit/git_surgery/secret-in-history/verify.sh +106 -0
- benchkit/git_surgery/split-the-mega-commit/setup.sh +50 -0
- benchkit/git_surgery/split-the-mega-commit/verify.sh +48 -0
- benchkit/git_surgery/untangle-nested-submodules/setup.sh +193 -0
- benchkit/git_surgery/untangle-nested-submodules/verify.sh +90 -0
- benchkit/history.py +474 -0
- benchkit/jobs.py +332 -0
- benchkit/leakage.py +51 -0
- benchkit/looping.py +873 -0
- benchkit/mc_render.py +274 -0
- benchkit/mc_viewer/dist/atlas.png +0 -0
- benchkit/mc_viewer/dist/blockStates.json.gz +0 -0
- benchkit/mc_viewer/dist/version.json +4 -0
- benchkit/mc_viewer/dist/viewer.html +23 -0
- benchkit/mc_viewer/dist/viewer.js +2 -0
- benchkit/mc_viewer/dist/viewer.js.LICENSE.txt +25 -0
- benchkit/mc_viewer/dist/worker.js +2 -0
- benchkit/mc_viewer/dist/worker.js.LICENSE.txt +19 -0
- benchkit/metrics.py +100 -0
- benchkit/ollama.py +60 -0
- benchkit/perf.py +353 -0
- benchkit/perf_report.py +207 -0
- benchkit/perturbations.py +208 -0
- benchkit/pi_agent.py +476 -0
- benchkit/pi_package/package-lock.json +1827 -0
- benchkit/pi_package/package.json +8 -0
- benchkit/report.py +142 -0
- benchkit/report_markdown.py +645 -0
- benchkit/results.py +688 -0
- benchkit/retry.py +163 -0
- benchkit/runner.py +769 -0
- benchkit/sandbox.py +1167 -0
- benchkit/template_check.py +291 -0
- benchkit/templates/arena.html +504 -0
- benchkit/templates/history.html +58 -0
- benchkit/templates/perf.html +509 -0
- benchkit/templates/report.html +769 -0
- benchkit/tui/__init__.py +5 -0
- benchkit/tui/app.py +87 -0
- benchkit/tui/app.tcss +512 -0
- benchkit/tui/formatting.py +84 -0
- benchkit/tui/screens/__init__.py +1 -0
- benchkit/tui/screens/connect.py +191 -0
- benchkit/tui/screens/detail.py +288 -0
- benchkit/tui/screens/help.py +84 -0
- benchkit/tui/screens/modals.py +544 -0
- benchkit/tui/screens/results.py +361 -0
- benchkit/tui/screens/run.py +988 -0
- benchkit/tui/screens/setup.py +733 -0
- benchkit/tui/theme.py +135 -0
- benchkit/tui/widgets.py +120 -0
- benchkit_cli-0.1.0.dist-info/METADATA +294 -0
- benchkit_cli-0.1.0.dist-info/RECORD +149 -0
- benchkit_cli-0.1.0.dist-info/WHEEL +5 -0
- benchkit_cli-0.1.0.dist-info/entry_points.txt +3 -0
- benchkit_cli-0.1.0.dist-info/licenses/LICENSE +201 -0
- benchkit_cli-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"""Results screen: sortable summary plus drill-down into every task."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
from textual import events
|
|
9
|
+
from textual.app import ComposeResult
|
|
10
|
+
from textual.binding import Binding
|
|
11
|
+
from textual.containers import Horizontal, Vertical
|
|
12
|
+
from textual.screen import Screen
|
|
13
|
+
from textual.widgets import DataTable, Footer, Header, Static
|
|
14
|
+
|
|
15
|
+
from benchkit.metrics import (
|
|
16
|
+
aggregate_tok_s,
|
|
17
|
+
decision_speed,
|
|
18
|
+
effective_concurrency,
|
|
19
|
+
is_decision,
|
|
20
|
+
latency_text,
|
|
21
|
+
stream_tok_s,
|
|
22
|
+
)
|
|
23
|
+
from benchkit.tui.formatting import (
|
|
24
|
+
bar,
|
|
25
|
+
fmt_count,
|
|
26
|
+
fmt_duration,
|
|
27
|
+
score_color,
|
|
28
|
+
throughput_stat,
|
|
29
|
+
)
|
|
30
|
+
from benchkit.tui.screens.detail import JobDetailScreen
|
|
31
|
+
from benchkit.tui.widgets import SectionTitle, StatCard, apply_compact
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _benchmark_label(result: dict) -> str:
|
|
35
|
+
return str(result.get("benchmark_label") or result["benchmark"])
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _is_parallel(result: dict) -> bool:
|
|
39
|
+
return result.get("concurrency", 1) > 1
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _display_tok_s(result: dict) -> float:
|
|
43
|
+
return aggregate_tok_s(result) if _is_parallel(result) else stream_tok_s(result)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _display_speed(result: dict) -> float:
|
|
47
|
+
if is_decision(result):
|
|
48
|
+
return float(result.get("decisions_per_s") or 0.0)
|
|
49
|
+
return _display_tok_s(result)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _delta_cell(result: dict) -> Text | str:
|
|
53
|
+
value = result.get("score_delta_pp")
|
|
54
|
+
if value is None:
|
|
55
|
+
return "—"
|
|
56
|
+
delta = float(value)
|
|
57
|
+
style = "green" if delta > 0 else "red" if delta < 0 else "dim"
|
|
58
|
+
return Text(f"{delta:+.1f}", style=style)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _harness_delta_cell(result: dict) -> Text | str:
|
|
62
|
+
value = result.get("harness_score_delta_pp")
|
|
63
|
+
if value is None:
|
|
64
|
+
return "—"
|
|
65
|
+
delta = float(value)
|
|
66
|
+
style = "green" if delta > 0 else "red" if delta < 0 else "dim"
|
|
67
|
+
return Text(f"{delta:+.1f}", style=style)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _repair_delta_cell(result: dict) -> Text | str:
|
|
71
|
+
if not result.get("repair_attempts"):
|
|
72
|
+
return "—"
|
|
73
|
+
delta = float(result.get("repair_delta_pp", 0))
|
|
74
|
+
style = "green" if delta > 0 else "red" if delta < 0 else "dim"
|
|
75
|
+
return Text(f"{delta:+.1f}", style=style)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
SORT_KEYS = {
|
|
79
|
+
"model": lambda r: (r["model"], r["benchmark"]),
|
|
80
|
+
"benchmark": lambda r: (_benchmark_label(r), -r["score"]),
|
|
81
|
+
"harness": lambda r: (r.get("harness", "direct"), -r["score"]),
|
|
82
|
+
"harness_delta": lambda r: -float(r.get("harness_score_delta_pp", 0)),
|
|
83
|
+
"repair_delta": lambda r: -float(r.get("repair_delta_pp", 0)),
|
|
84
|
+
"score": lambda r: -r["score"],
|
|
85
|
+
"delta": lambda r: -float(r.get("score_delta_pp", 0)),
|
|
86
|
+
"regressions": lambda r: -int(r.get("regressions", 0)),
|
|
87
|
+
"passed": lambda r: -r["passed"],
|
|
88
|
+
"errors": lambda r: -r.get("errors", 0),
|
|
89
|
+
"loops": lambda r: -r.get("loop_rate", 0),
|
|
90
|
+
"parallel": lambda r: -r.get("concurrency", 1),
|
|
91
|
+
"aggregate": lambda r: -aggregate_tok_s(r),
|
|
92
|
+
"stream": lambda r: -stream_tok_s(r),
|
|
93
|
+
"effective": lambda r: -effective_concurrency(r),
|
|
94
|
+
"avg": lambda r: r["avg_response_time"],
|
|
95
|
+
"time": lambda r: r["total_time"],
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ResultsScreen(Screen[None]):
|
|
100
|
+
"""What the run produced, and where it was written."""
|
|
101
|
+
|
|
102
|
+
BINDINGS = [
|
|
103
|
+
Binding("s", "sort('score')", "Sort score"),
|
|
104
|
+
Binding("m", "sort('model')", "Sort model"),
|
|
105
|
+
Binding("t", "sort('time')", "Sort time"),
|
|
106
|
+
Binding("r", "again", "New run"),
|
|
107
|
+
Binding("c", "copy_path", "Copy path"),
|
|
108
|
+
Binding("escape", "again", "Back", show=False),
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
def __init__(self, results: list[dict], output: Path | None) -> None:
|
|
112
|
+
super().__init__()
|
|
113
|
+
self.results = list(results)
|
|
114
|
+
self.has_parallel = any(_is_parallel(result) for result in self.results)
|
|
115
|
+
# Decision rows are measured in dec/s, so a table that mixes them with
|
|
116
|
+
# generating rows names the unit in every cell instead of the header.
|
|
117
|
+
self.has_decisions = any(is_decision(result) for result in self.results)
|
|
118
|
+
self.has_perturbations = any(
|
|
119
|
+
result.get("perturbation") for result in self.results
|
|
120
|
+
)
|
|
121
|
+
self.has_harness_pairs = any(
|
|
122
|
+
result.get("harness_score_delta_pp") is not None for result in self.results
|
|
123
|
+
)
|
|
124
|
+
self.has_repairs = any(
|
|
125
|
+
result.get("repair_attempts", 0) for result in self.results
|
|
126
|
+
)
|
|
127
|
+
self.output = output
|
|
128
|
+
self.sort_key = "score"
|
|
129
|
+
|
|
130
|
+
def compose(self) -> ComposeResult:
|
|
131
|
+
yield Header(show_clock=True)
|
|
132
|
+
with Vertical(id="results-root"):
|
|
133
|
+
with Horizontal(id="stat-row"):
|
|
134
|
+
yield StatCard("Best score", "--", id="stat-best")
|
|
135
|
+
yield StatCard("Runs", "0", id="stat-runs")
|
|
136
|
+
yield StatCard("Tasks", "0", id="stat-tasks")
|
|
137
|
+
yield StatCard("Loops", "0", id="stat-loops")
|
|
138
|
+
yield StatCard("Peak throughput", "--", id="stat-fastest")
|
|
139
|
+
yield StatCard("Total time", "--", id="stat-time")
|
|
140
|
+
with Vertical(classes="pane", id="results-pane"):
|
|
141
|
+
yield SectionTitle(
|
|
142
|
+
"Results",
|
|
143
|
+
"enter to inspect · click a header to sort",
|
|
144
|
+
id="results-title",
|
|
145
|
+
)
|
|
146
|
+
yield DataTable(id="summary", cursor_type="row", zebra_stripes=True)
|
|
147
|
+
yield Static("", id="output-path", classes="hint")
|
|
148
|
+
yield Footer()
|
|
149
|
+
|
|
150
|
+
def on_mount(self) -> None:
|
|
151
|
+
self.sub_title = "run complete"
|
|
152
|
+
table = self.query_one("#summary", DataTable)
|
|
153
|
+
table.add_column("Model", key="model")
|
|
154
|
+
table.add_column("Harness", key="harness", width=18)
|
|
155
|
+
table.add_column("Benchmark", key="benchmark")
|
|
156
|
+
table.add_column("Score", key="score", width=8)
|
|
157
|
+
table.add_column("", key="bar", width=14)
|
|
158
|
+
if self.has_harness_pairs:
|
|
159
|
+
table.add_column("Harness score Δ", key="harness_delta", width=15)
|
|
160
|
+
if self.has_repairs:
|
|
161
|
+
table.add_column("Repair Δ", key="repair_delta", width=9)
|
|
162
|
+
table.add_column("Fixed", key="repair_fixed", width=8)
|
|
163
|
+
if self.has_perturbations:
|
|
164
|
+
table.add_column("Δ pp", key="delta", width=7)
|
|
165
|
+
table.add_column("Regress", key="regressions", width=9)
|
|
166
|
+
table.add_column("Passed", key="passed", width=10)
|
|
167
|
+
table.add_column("Failures F/L/T/Len/H", key="errors", width=21)
|
|
168
|
+
if self.has_parallel:
|
|
169
|
+
table.add_column("Parallel", key="parallel", width=8)
|
|
170
|
+
table.add_column(
|
|
171
|
+
"Agg speed" if self.has_decisions else "Agg tok/s",
|
|
172
|
+
key="aggregate",
|
|
173
|
+
width=14 if self.has_decisions else 10,
|
|
174
|
+
)
|
|
175
|
+
table.add_column("Stream", key="stream", width=8)
|
|
176
|
+
table.add_column("Eff", key="effective", width=7)
|
|
177
|
+
else:
|
|
178
|
+
table.add_column(
|
|
179
|
+
"Speed" if self.has_decisions else "Tok/s",
|
|
180
|
+
key="stream",
|
|
181
|
+
width=14 if self.has_decisions else 9,
|
|
182
|
+
)
|
|
183
|
+
table.add_column("Avg", key="avg", width=8)
|
|
184
|
+
table.add_column("Wall", key="time", width=9)
|
|
185
|
+
|
|
186
|
+
self._fill_table()
|
|
187
|
+
self._fill_stats()
|
|
188
|
+
self.watch(self.app, "theme", self._theme_changed, init=False)
|
|
189
|
+
|
|
190
|
+
if self.output is not None:
|
|
191
|
+
path = self.output.resolve()
|
|
192
|
+
self.query_one("#output-path", Static).update(
|
|
193
|
+
f"Reports → {path} · results.json · csv · md · html"
|
|
194
|
+
)
|
|
195
|
+
else:
|
|
196
|
+
self.query_one("#output-path", Static).update(
|
|
197
|
+
"Nothing was written to disk."
|
|
198
|
+
)
|
|
199
|
+
table.focus()
|
|
200
|
+
|
|
201
|
+
def on_resize(self, event: events.Resize) -> None:
|
|
202
|
+
apply_compact(self, event.size.height)
|
|
203
|
+
|
|
204
|
+
def _theme_changed(self, _theme: str) -> None:
|
|
205
|
+
# Table cell colours are baked in, so redraw them for the new palette.
|
|
206
|
+
self._fill_table()
|
|
207
|
+
|
|
208
|
+
# Rendering --------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
def _sorted(self) -> list[tuple[int, dict]]:
|
|
211
|
+
key = SORT_KEYS.get(self.sort_key, SORT_KEYS["score"])
|
|
212
|
+
return sorted(enumerate(self.results), key=lambda pair: key(pair[1]))
|
|
213
|
+
|
|
214
|
+
def _fill_table(self) -> None:
|
|
215
|
+
table = self.query_one("#summary", DataTable)
|
|
216
|
+
table.clear()
|
|
217
|
+
dark = self.app.current_theme.dark
|
|
218
|
+
for index, result in self._sorted():
|
|
219
|
+
score = result["score"]
|
|
220
|
+
color = score_color(score, dark)
|
|
221
|
+
cells: list[object] = [
|
|
222
|
+
result["model"],
|
|
223
|
+
result.get("harness_label", "Direct"),
|
|
224
|
+
_benchmark_label(result)
|
|
225
|
+
+ (f" [{result['slice']}]" if result.get("slice") else ""),
|
|
226
|
+
Text(f"{score:.1f}%", style=f"bold {color}"),
|
|
227
|
+
Text(bar(score / 100, 12), style=color),
|
|
228
|
+
]
|
|
229
|
+
if self.has_harness_pairs:
|
|
230
|
+
cells.append(_harness_delta_cell(result))
|
|
231
|
+
if self.has_repairs:
|
|
232
|
+
cells.extend(
|
|
233
|
+
[
|
|
234
|
+
_repair_delta_cell(result),
|
|
235
|
+
(
|
|
236
|
+
f"{result.get('repair_successes', 0)}/"
|
|
237
|
+
f"{result.get('repair_attempted', 0)}"
|
|
238
|
+
if result.get("repair_attempts")
|
|
239
|
+
else "—"
|
|
240
|
+
),
|
|
241
|
+
]
|
|
242
|
+
)
|
|
243
|
+
if self.has_perturbations:
|
|
244
|
+
cells.extend(
|
|
245
|
+
[
|
|
246
|
+
_delta_cell(result),
|
|
247
|
+
(
|
|
248
|
+
f"{result['regressions']}/{result['paired_total']}"
|
|
249
|
+
if result.get("paired_total") is not None
|
|
250
|
+
else "—"
|
|
251
|
+
),
|
|
252
|
+
]
|
|
253
|
+
)
|
|
254
|
+
cells.extend(
|
|
255
|
+
[
|
|
256
|
+
f"{result['passed']}/{result.get('scored_total', result['total'])}",
|
|
257
|
+
f"{result.get('failures', 0)}/{result.get('loop_kills', 0)}/"
|
|
258
|
+
f"{result.get('timeouts', 0)}/"
|
|
259
|
+
f"{result.get('length_exceeded', 0)}/"
|
|
260
|
+
f"{result.get('harness_errors', 0)}",
|
|
261
|
+
]
|
|
262
|
+
)
|
|
263
|
+
decisions = is_decision(result)
|
|
264
|
+
unit = " tok/s" if self.has_decisions else ""
|
|
265
|
+
if self.has_parallel:
|
|
266
|
+
parallel = _is_parallel(result)
|
|
267
|
+
cells.extend(
|
|
268
|
+
[
|
|
269
|
+
str(result.get("concurrency", 1)),
|
|
270
|
+
decision_speed(result)[0]
|
|
271
|
+
if decisions
|
|
272
|
+
else f"{aggregate_tok_s(result):.1f}{unit}"
|
|
273
|
+
if parallel
|
|
274
|
+
else "—",
|
|
275
|
+
"—" if decisions else f"{stream_tok_s(result):.1f}",
|
|
276
|
+
f"{effective_concurrency(result):.2f}x" if parallel else "—",
|
|
277
|
+
]
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
cells.append(
|
|
281
|
+
decision_speed(result)[0]
|
|
282
|
+
if decisions
|
|
283
|
+
else f"{stream_tok_s(result):.1f}{unit}"
|
|
284
|
+
)
|
|
285
|
+
cells.extend([latency_text(result), fmt_duration(result["total_time"])])
|
|
286
|
+
table.add_row(
|
|
287
|
+
*cells,
|
|
288
|
+
key=str(index),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def _fill_stats(self) -> None:
|
|
292
|
+
if not self.results:
|
|
293
|
+
return
|
|
294
|
+
overall_results = [
|
|
295
|
+
result for result in self.results if result.get("include_in_overall", True)
|
|
296
|
+
]
|
|
297
|
+
best = max(overall_results or self.results, key=lambda r: r["score"])
|
|
298
|
+
# Tokens/s and decisions/s are different units, so the fastest card
|
|
299
|
+
# compares generating runs and only falls back to decision runs.
|
|
300
|
+
generating = [r for r in self.results if not is_decision(r)]
|
|
301
|
+
fastest = max(generating or self.results, key=_display_speed)
|
|
302
|
+
tasks = sum(r["total"] for r in self.results)
|
|
303
|
+
total_time = sum(r["total_time"] for r in self.results)
|
|
304
|
+
loops = sum(r.get("loops", 0) for r in self.results)
|
|
305
|
+
loop_kills = sum(r.get("loop_kills", 0) for r in self.results)
|
|
306
|
+
|
|
307
|
+
self.query_one("#stat-best", StatCard).set_state(
|
|
308
|
+
f"{best['score']:.1f}%",
|
|
309
|
+
f"{best['model']} · {best.get('harness_label', 'Direct')} · "
|
|
310
|
+
f"{_benchmark_label(best)}",
|
|
311
|
+
)
|
|
312
|
+
self.query_one("#stat-runs", StatCard).set_state(str(len(self.results)))
|
|
313
|
+
self.query_one("#stat-tasks", StatCard).set_state(fmt_count(tasks))
|
|
314
|
+
self.query_one("#stat-loops", StatCard).set_state(
|
|
315
|
+
str(loops),
|
|
316
|
+
(f"{loops / tasks * 100:.1f}% · {loop_kills} killed" if tasks else ""),
|
|
317
|
+
)
|
|
318
|
+
fastest_card = self.query_one("#stat-fastest", StatCard)
|
|
319
|
+
value, hint = (
|
|
320
|
+
decision_speed(fastest)
|
|
321
|
+
if is_decision(fastest)
|
|
322
|
+
else throughput_stat(
|
|
323
|
+
concurrency=fastest.get("concurrency", 1),
|
|
324
|
+
aggregate=aggregate_tok_s(fastest),
|
|
325
|
+
stream=stream_tok_s(fastest),
|
|
326
|
+
effective=effective_concurrency(fastest),
|
|
327
|
+
precision=0,
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
fastest_card.set_state(value, f"{fastest['model']} · {hint}")
|
|
331
|
+
self.query_one("#stat-time", StatCard).set_state(fmt_duration(total_time))
|
|
332
|
+
|
|
333
|
+
# Events -----------------------------------------------------------
|
|
334
|
+
|
|
335
|
+
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
|
336
|
+
index = int(str(event.row_key.value))
|
|
337
|
+
self.app.push_screen(JobDetailScreen(self.results[index]))
|
|
338
|
+
|
|
339
|
+
def on_data_table_header_selected(self, event: DataTable.HeaderSelected) -> None:
|
|
340
|
+
key = str(event.column_key.value)
|
|
341
|
+
if key in SORT_KEYS:
|
|
342
|
+
self.action_sort(key)
|
|
343
|
+
|
|
344
|
+
# Actions ----------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
def action_sort(self, key: str) -> None:
|
|
347
|
+
self.sort_key = key
|
|
348
|
+
self._fill_table()
|
|
349
|
+
self.query_one("#results-title", SectionTitle).set_detail(
|
|
350
|
+
f"sorted by {key} · enter to inspect"
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def action_again(self) -> None:
|
|
354
|
+
self.app.back_to_setup()
|
|
355
|
+
|
|
356
|
+
def action_copy_path(self) -> None:
|
|
357
|
+
if self.output is None:
|
|
358
|
+
return
|
|
359
|
+
path = str(self.output.resolve())
|
|
360
|
+
self.app.copy_to_clipboard(path)
|
|
361
|
+
self.notify(f"Copied {path}", timeout=3)
|