mirror of
https://git.sync.wtf/troed/oc-ls-stats.git
synced 2026-08-31 09:43:38 +03:00
140 lines
7.6 KiB
Markdown
140 lines
7.6 KiB
Markdown
# oc-ls-stats
|
|
|
|
A TUI plugin for OpenCode that displays live prefill rate (PP) and generation rate (TG) from a llama.cpp-based llama-server.
|
|
|
|
There are many plugins to show tokens per second, but the reason for this one is that when running a local model I often found myself not knowing what the server was currently doing, which meant constantly switching over to a console where I could see its output. Especially during prefill/prompt processing which can take more than a minute with no feedback in the UI from other plugins I tried.
|
|
|
|
Another motivation was to display the data of interest, but in a non-intrusive way with no UI elements jumping around. This plugin thus only shows a single numeric value, tokens per second, with an indicator as to whether the model is currently doing prompt processing or inference (token generation).
|
|
|
|
I'm using the llama-server /slots endpoint to get the needed data, which means if you connect opencode to another provider the plugin will just display "-" since it's not getting any data to display.
|
|
|
|
Note: As explained in further detail below the data that's displayed has to be deduced from llama-server's output. Sometimes the plugin might display PP for prompt processing while in reality the model is doing TG. If additional developments are made to the llama-server output data the plugin might be able to discern between them in a better way, but I think is as good as it gets for now.
|
|
|
|
I made this for my own usage. If you find it useful as well I'm just happy.
|
|
|
|
/Troed
|
|
|
|
_thanks to Tarquinen for their [oc-tps](https://github.com/Tarquinen/oc-tps), which I used as a base although I guess most of the code has now been replaced_
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
opencode plugin @troed/oc-ls-stats@latest --global
|
|
```
|
|
|
|
Requires `opencode` `1.3.14` or newer.
|
|
|
|
TUI plugins are loaded from `~/.config/opencode/tui.json`, which after installation should look like this:
|
|
|
|
```json
|
|
{
|
|
"plugin": ["@troed/oc-ls-stats@latest"]
|
|
}
|
|
```
|
|
|
|
## Display Format
|
|
|
|
The plugin renders a single line in the session prompt right slot:
|
|
|
|
```
|
|
1247 tps (PP) -- during prefill
|
|
25 tps (TG) -- during generation
|
|
- tps (TG) -- idle
|
|
n/a -- unable to reach llama-server
|
|
```
|
|
|
|
## Detection and Calculation
|
|
|
|
### Server Discovery
|
|
|
|
By default the plugin discovers the llama-server URL by reading the OpenCode configuration via the TUI API and extracting `baseURL`/`base_url` fields from provider options whose name contains "llama" (but excludes providers whose name contains "ollama"). Falls back to `http://localhost:8080` if no matching provider is found.
|
|
|
|
If your llama-server provider is named differently, configure the endpoint explicitly via the plugin's `server` option in `tui.json`. An explicit server replaces discovery entirely:
|
|
|
|
```json
|
|
{
|
|
"plugin": [
|
|
["@troed/oc-ls-stats@latest", { "server": "http://headless.local:8080" }]
|
|
]
|
|
}
|
|
```
|
|
|
|
A `baseURL`-style value ending in `/v1` is also accepted.
|
|
|
|
### Slot Polling
|
|
|
|
Every 500ms, the plugin polls `GET /slots?model=<model>` on each discovered server. The model parameter is required by the `/slots` endpoint. If the model cannot be discovered from the current route's session, the plugin skips polling.
|
|
|
|
When a server returns an error (e.g. HTTP 502) or is unreachable, the plugin backs off with an exponentially increasing delay per server (1s, 2s, 4s, ...) capped at 10s, with a small random jitter to stagger multiple servers. Backoff resets as soon as the server responds successfully again.
|
|
|
|
### State Classification
|
|
|
|
Each processing slot is classified as prefill (PP) or generation (TG) by comparing `n_decoded` and `n_prompt_tokens` against per-slot/per-task baselines. The plugin tracks two baseline maps keyed by slot ID:
|
|
|
|
- `prevNdBySlot[slotId]` — stores `{ prevNd, idTask, classification }`
|
|
- `prevPromptTokensBySlot[slotId]` — stores `{ tokens, idTask }`
|
|
|
|
The `idTask` field ensures baselines are invalidated when a slot starts a new task (task reuse).
|
|
|
|
Classification logic:
|
|
|
|
1. **First time or after task change/reset**: If we have no baseline for this slot+task, or `n_decoded` or `n_prompt_tokens` dropped below baseline (indicating a new request), use a heuristic: `n_decoded > 0` → TG, otherwise → PP.
|
|
2. **Compare against baseline**: If `n_decoded > baselineNd` → TG. If `n_prompt_tokens > baselineNt` → PP.
|
|
3. **Neither increased**: Persist the previous classification. This handles frequent polling where neither field increases on every poll cycle.
|
|
|
|
When a slot stops being processed, all tracked state for that slot is cleared.
|
|
|
|
### Prefill Rate (PP)
|
|
|
|
During prefill, the plugin calculates the instantaneous prompt processing rate:
|
|
|
|
1. On first detection of a prefill slot, the current `n_prompt_tokens` is captured as the baseline.
|
|
2. On subsequent polls, the delta in `n_prompt_tokens` is divided by the elapsed time in seconds.
|
|
3. The rate is updated only when both `dt > 0` and `delta > 0`.
|
|
|
|
The per-slot `n_prompt_tokens` field is used instead of the global `llamacpp:prompt_tokens_total` from `/metrics` because the global counter includes tokens from all slots, producing inflated values when multiple slots are active simultaneously.
|
|
|
|
### Generation Rate (TG)
|
|
|
|
During generation, the plugin calculates the instantaneous token generation rate:
|
|
|
|
1. On first detection of a generation slot, the current `n_decoded` is captured as the baseline.
|
|
2. On subsequent polls (same slot ID), the delta in `n_decoded` is divided by the elapsed time in seconds.
|
|
3. The rate is updated only when both `dt > 0` and `delta > 0`.
|
|
|
|
Slot reuse is tracked via `generateSlotId` to detect when a new generation starts on a different slot.
|
|
|
|
## Limitations
|
|
|
|
### Progress Percentage
|
|
|
|
The plugin cannot display prefill progress percentage. The `/slots` endpoint returns `n_prompt_tokens` (current prompt size) and `n_prompt_tokens_processed` (tokens processed), but not the final prompt size (`task->n_tokens()` from llama.cpp). Progress requires the ratio `n_prompt_tokens_processed / task->n_tokens()`.
|
|
|
|
### What Would Improve Compatibility
|
|
|
|
The following changes to the `/slots` endpoint would improve the plugin's functionality:
|
|
|
|
1. **Expose final prompt size**: Add `n_prompt_tokens_total` (or `n_tokens`) to the `/slots` output, representing `task->n_tokens()` from llama.cpp. This would enable prefill progress percentage calculation as `(n_prompt_tokens_processed / n_prompt_tokens_total) * 100`.
|
|
|
|
2. **Per-slot metrics endpoints**: Currently, the `/metrics` endpoint provides only global counters (`llamacpp:prompt_tokens_total`, `llamacpp:prompt_tokens_seconds`). Per-slot metrics would allow independent rate tracking without relying on slot state classification.
|
|
|
|
3. **Slot transition notifications**: The plugin polls every 500ms to detect state transitions. A WebSocket or SSE-based notification system for slot state changes would reduce polling overhead and improve detection latency.
|
|
|
|
4. **Stall detection**: When generation stalls (e.g., due to context window limits), `n_decoded` remains constant while `n_remain` stops decreasing. The plugin detects this via zero delta but has no way to distinguish a stall from normal generation. An explicit `stalled` flag in the slot output would help.
|
|
|
|
5. **Model-agnostic slot data**: The `/slots` endpoint requires a model parameter. Returning all slots without model filtering, or supporting `*` as a wildcard, would simplify discovery when multiple models are loaded.
|
|
|
|
## Source code repo
|
|
|
|
For known issues, posting new ones, forking or contributing:
|
|
|
|
https://git.sync.wtf/troed/oc-ls-stats
|
|
|
|
## Debug Logging
|
|
|
|
Debug logging is controlled by the `DEBUG_ENABLED` constant in `tui.tsx`. When enabled, full slot state data is written to `/tmp/oc-ls-stats-debug.log` on every poll.
|
|
|
|
## License
|
|
|
|
Creative Commons Zero (CC0 1.0 Universal)
|