@ymys4ever/opencode-throughput-v2 4.0.0 → 4.0.2
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.
- package/dist/index.js +698 -0
- package/dist/tui.js +663 -0
- package/package.json +9 -9
- package/src/benchmark.ts +0 -112
- package/src/core/events.ts +0 -141
- package/src/core/log-codec.ts +0 -90
- package/src/core/ownership.ts +0 -13
- package/src/core/stats.ts +0 -105
- package/src/core/step-tracker.ts +0 -204
- package/src/format/entry.ts +0 -39
- package/src/format/perf.ts +0 -14
- package/src/index.ts +0 -19
- package/src/io/log-store.ts +0 -55
- package/src/io/perf-file.ts +0 -30
- package/src/plugins/throughput.ts +0 -124
- package/src/runtime/pipeline.ts +0 -163
- package/src/tui/controller.ts +0 -191
- package/src/tui.tsx +0 -36
- package/src/types.ts +0 -44
- package/src/ui/sidebar.ts +0 -90
- package/src/ui/throughput-widget.tsx +0 -95
package/src/types.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/** frozen on-disk JSONL schema, never rename fields */
|
|
2
|
-
export type LogEntry = {
|
|
3
|
-
ts: string
|
|
4
|
-
/** composite key: providerID/modelID (frozen) */
|
|
5
|
-
model: string
|
|
6
|
-
providerID: string
|
|
7
|
-
modelID: string
|
|
8
|
-
sessionID: string
|
|
9
|
-
messageID: string
|
|
10
|
-
ttft_ms: number | null
|
|
11
|
-
tps: number | null
|
|
12
|
-
latency_ms: number | null
|
|
13
|
-
inputTokens: number
|
|
14
|
-
outputTokens: number
|
|
15
|
-
reasoningTokens: number
|
|
16
|
-
cacheReadTokens: number
|
|
17
|
-
cacheWriteTokens: number
|
|
18
|
-
cost: number
|
|
19
|
-
finish: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Aggregated per-model statistics. Metric fields (avg/min/max/maxTPS) are null
|
|
24
|
-
* when no entry provided a valid sample; total* fields are plain sums, count counts every entry.
|
|
25
|
-
*/
|
|
26
|
-
export type ModelStats = {
|
|
27
|
-
model: string
|
|
28
|
-
count: number
|
|
29
|
-
avgTTFT: number | null
|
|
30
|
-
avgTPS: number | null
|
|
31
|
-
avgLatency: number | null
|
|
32
|
-
minTTFT: number | null
|
|
33
|
-
maxTTFT: number | null
|
|
34
|
-
minLatency: number | null
|
|
35
|
-
maxLatency: number | null
|
|
36
|
-
maxTPS: number | null
|
|
37
|
-
totalInputTokens: number
|
|
38
|
-
totalOutputTokens: number
|
|
39
|
-
totalReasoningTokens: number
|
|
40
|
-
totalCacheReadTokens: number
|
|
41
|
-
totalCacheWriteTokens: number
|
|
42
|
-
totalCost: number
|
|
43
|
-
lastUpdated: number
|
|
44
|
-
}
|
package/src/ui/sidebar.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import type { ModelStats } from "../types.js"
|
|
2
|
-
|
|
3
|
-
/** Three-column model key: provider initial, first version digit, final non-version token initial. */
|
|
4
|
-
export function abbreviateModel(modelID: string): string {
|
|
5
|
-
const tokens = modelID.split(/[^a-zA-Z0-9]+/).filter(Boolean)
|
|
6
|
-
const versionIndex = tokens.findIndex((token) => /\d/.test(token))
|
|
7
|
-
const provider = (tokens[0]?.[0] ?? "?").toLowerCase()
|
|
8
|
-
if (versionIndex < 0) return (provider + (tokens[1]?.[0] ?? " ").toLowerCase() + " ").slice(0, 3).padEnd(3, " ")
|
|
9
|
-
const version = tokens[versionIndex].match(/\d/)?.[0] ?? " "
|
|
10
|
-
const suffix = tokens.filter((token, index) => index !== 0 && index !== versionIndex && !/^\d+(?:\.\d+)*$/.test(token)).at(-1)
|
|
11
|
-
return `${provider}${version}${(suffix?.[0] ?? " ").toLowerCase()}`
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Sidebar column widths shared by the header and every data row. */
|
|
15
|
-
const COL_ID = 3
|
|
16
|
-
const COL_TIME = 4
|
|
17
|
-
const COL_TOKEN = 5
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Collision-aware display IDs: the first model keeps the 3-char
|
|
21
|
-
* abbreviateModel form; later colliders widen to 4 then 5 chars of
|
|
22
|
-
* modelID until unique (a still-colliding 5-char form is kept as-is,
|
|
23
|
-
* never widened further). Deterministic for a given input sequence:
|
|
24
|
-
* first-seen order wins, so existing IDs stay stable as new models appear.
|
|
25
|
-
*/
|
|
26
|
-
export function buildDisplayIds(modelIDs: readonly string[]): Map<string, string> {
|
|
27
|
-
const taken = new Set<string>()
|
|
28
|
-
const ids = new Map<string, string>()
|
|
29
|
-
for (const modelID of modelIDs) {
|
|
30
|
-
if (ids.has(modelID)) continue
|
|
31
|
-
const candidates = [abbreviateModel(modelID), modelID.slice(0, COL_ID + 1), modelID.slice(0, COL_TOKEN)]
|
|
32
|
-
let chosen = candidates[candidates.length - 1]
|
|
33
|
-
for (const candidate of candidates) {
|
|
34
|
-
if (!taken.has(candidate)) {
|
|
35
|
-
chosen = candidate
|
|
36
|
-
break
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
taken.add(chosen)
|
|
40
|
-
ids.set(modelID, chosen)
|
|
41
|
-
}
|
|
42
|
-
return ids
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** One sidebar stats row: aggregated metrics plus the structured display modelID
|
|
46
|
-
* and its collision-aware display ID (see buildDisplayIds). */
|
|
47
|
-
export type StatsRow = { stats: ModelStats; modelID: string; displayID: string }
|
|
48
|
-
|
|
49
|
-
/** Sidebar duration in seconds: compact precision without a unit suffix. */
|
|
50
|
-
export function formatSidebarSeconds(ms: number | null): string {
|
|
51
|
-
if (ms == null) return " N/A"
|
|
52
|
-
return fitColumn4(ms / 1000)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Sidebar metric without a unit suffix, fitted to a 4-char column. */
|
|
56
|
-
export function formatSidebarMetric(value: number | null): string {
|
|
57
|
-
if (value == null) return " N/A"
|
|
58
|
-
return fitColumn4(value)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Fit into 4 chars: one decimal (xxx.x), else the integer as-is. A 5-digit integer overflows by design rather than losing digits. */
|
|
62
|
-
function fitColumn4(value: number): string {
|
|
63
|
-
const s = value.toFixed(1)
|
|
64
|
-
return s.length > COL_TIME ? value.toFixed(0) : s
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Sidebar token volume in a 5-char column: the unit (none / k / m) is
|
|
69
|
-
* chosen so the numeric part always fits with rounding-safe boundaries.
|
|
70
|
-
* @param value non-negative token count (clamped)
|
|
71
|
-
*/
|
|
72
|
-
export function formatSidebarTokens(value: number): string {
|
|
73
|
-
value = Math.max(0, Math.round(value))
|
|
74
|
-
if (value < 1_000) return value.toFixed(1) // "892.0"
|
|
75
|
-
if (value < 99_950) return (value / 1e3).toFixed(1) + "k" // "1.0k".."99.9k"
|
|
76
|
-
if (value < 999_500) return (value / 1e3).toFixed(0) + "k" // "100k".."999k"
|
|
77
|
-
if (value < 99_950_000) return (value / 1e6).toFixed(1) + "m" // "1.0m".."99.9m"
|
|
78
|
-
if (value < 999_500_000) return (value / 1e6).toFixed(0) + "m"
|
|
79
|
-
return "999m" // cap
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Sidebar data row; `displayID` is a collision-aware display ID from buildDisplayIds (3-5 chars, rendered as-is). */
|
|
83
|
-
export function formatSidebarRow(displayID: string, ttft: number | null, tps: number | null, latency: number | null, input: number, output: number): string {
|
|
84
|
-
return ` ${displayID.padEnd(COL_ID)} ${formatSidebarSeconds(ttft).padStart(COL_TIME)} ${formatSidebarMetric(tps).padStart(COL_TIME)} ${formatSidebarSeconds(latency).padStart(COL_TIME)} ${formatSidebarTokens(input).padStart(COL_TOKEN)} ${formatSidebarTokens(output).padStart(COL_TOKEN)}`
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Sidebar header sharing the exact column layout of formatSidebarRow. */
|
|
88
|
-
export function formatSidebarHeader(): string {
|
|
89
|
-
return ` ${"ID".padEnd(COL_ID)} ${"TTFT".padStart(COL_TIME)} ${"TPS".padStart(COL_TIME)} ${"Lat".padStart(COL_TIME)} ${"↑In".padStart(COL_TOKEN)} ${"↓Out".padStart(COL_TOKEN)}`
|
|
90
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { abbreviateModel, formatSidebarHeader, formatSidebarRow } from "./sidebar.js"
|
|
2
|
-
import type { LogEntry } from "../types.js"
|
|
3
|
-
import type { StatsRow } from "./sidebar.js"
|
|
4
|
-
|
|
5
|
-
// Structural subset of ResolvedTheme from @opencode/theme/tui; kept local so
|
|
6
|
-
// the TUI entry does not need a direct @opencode/theme dependency.
|
|
7
|
-
export type ThemeTokens = {
|
|
8
|
-
readonly text?: {
|
|
9
|
-
readonly base?: string | undefined
|
|
10
|
-
readonly muted?: string | undefined
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Plain JSX expressions only — plugin-local solid may not notify the renderer (see tui/controller.ts header).
|
|
15
|
-
export function ThroughputWidget(props: {
|
|
16
|
-
readonly theme: ThemeTokens
|
|
17
|
-
readonly recent: () => LogEntry[]
|
|
18
|
-
readonly stats: () => StatsRow[]
|
|
19
|
-
readonly open: () => boolean
|
|
20
|
-
readonly onToggle: () => void
|
|
21
|
-
}) {
|
|
22
|
-
const fg = () => props.theme.text?.base
|
|
23
|
-
const muted = () => props.theme.text?.muted
|
|
24
|
-
const totalCount = () => props.stats().reduce((sum, row) => sum + row.stats.count, 0)
|
|
25
|
-
|
|
26
|
-
// Same-screen consistency: recent rows resolve their display ID through
|
|
27
|
-
// the stats rows (built from the controller's collision-aware map), so a
|
|
28
|
-
// widened collider renders identically in both sections.
|
|
29
|
-
const displayIDFor = (modelID: string): string => {
|
|
30
|
-
for (const row of props.stats()) {
|
|
31
|
-
if (row.modelID === modelID) return row.displayID
|
|
32
|
-
}
|
|
33
|
-
// Every recorded model has a row; this fallback only guards hostless
|
|
34
|
-
// edge cases and keeps the plain 3-char abbreviation.
|
|
35
|
-
return abbreviateModel(modelID)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<box>
|
|
40
|
-
<box
|
|
41
|
-
flexDirection="row"
|
|
42
|
-
gap={1}
|
|
43
|
-
onMouseDown={() => totalCount() > 0 && props.onToggle()}
|
|
44
|
-
>
|
|
45
|
-
{totalCount() > 0 ? <text fg={fg()}>{props.open() ? "▼" : "▶"}</text> : null}
|
|
46
|
-
<text fg={fg()}>
|
|
47
|
-
<b>Throughput</b>
|
|
48
|
-
</text>
|
|
49
|
-
{totalCount() > 0 ? (
|
|
50
|
-
<text fg={muted()}>{totalCount() + " reqs"}</text>
|
|
51
|
-
) : null}
|
|
52
|
-
</box>
|
|
53
|
-
{totalCount() === 0 ? (
|
|
54
|
-
<text fg={muted()}>{" Waiting for requests..."}</text>
|
|
55
|
-
) : null}
|
|
56
|
-
{props.open() && totalCount() > 0 ? (
|
|
57
|
-
<box flexDirection="column">
|
|
58
|
-
<text fg={muted()}>{formatSidebarHeader()}</text>
|
|
59
|
-
{props.stats().map((row) => (
|
|
60
|
-
<text fg={muted()}>
|
|
61
|
-
{formatSidebarRow(
|
|
62
|
-
row.displayID,
|
|
63
|
-
row.stats.avgTTFT,
|
|
64
|
-
row.stats.avgTPS,
|
|
65
|
-
row.stats.avgLatency,
|
|
66
|
-
row.stats.totalInputTokens,
|
|
67
|
-
row.stats.totalOutputTokens,
|
|
68
|
-
)}
|
|
69
|
-
</text>
|
|
70
|
-
))}
|
|
71
|
-
{props.recent().length > 0 ? (
|
|
72
|
-
<box flexDirection="column">
|
|
73
|
-
<text fg={fg()}>
|
|
74
|
-
{" "}
|
|
75
|
-
<b>Recent</b>
|
|
76
|
-
</text>
|
|
77
|
-
{props.recent().map((entry: LogEntry) => (
|
|
78
|
-
<text fg={muted()}>
|
|
79
|
-
{formatSidebarRow(
|
|
80
|
-
displayIDFor(entry.modelID),
|
|
81
|
-
entry.ttft_ms,
|
|
82
|
-
entry.tps,
|
|
83
|
-
entry.latency_ms,
|
|
84
|
-
entry.inputTokens,
|
|
85
|
-
entry.outputTokens,
|
|
86
|
-
)}
|
|
87
|
-
</text>
|
|
88
|
-
))}
|
|
89
|
-
</box>
|
|
90
|
-
) : null}
|
|
91
|
-
</box>
|
|
92
|
-
) : null}
|
|
93
|
-
</box>
|
|
94
|
-
)
|
|
95
|
-
}
|