chore: v1.0.18 — track id_task to prevent stale slot state

This commit is contained in:
Troed Sångberg
2026-07-08 13:11:50 +02:00
parent dfd3591f16
commit 0fac5bafac
3 changed files with 52 additions and 30 deletions
+21 -12
View File
@@ -16,6 +16,22 @@ I made this for my own usage. If you find it useful as well I'm just happy.
_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:
@@ -23,7 +39,8 @@ 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
- tps (TG) -- idle
n/a -- unable to reach llama-server
```
## Detection and Calculation
@@ -88,19 +105,11 @@ The following changes to the `/slots` endpoint would improve the plugin's functi
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.
## Installation
## Source code repo
```bash
opencode plugin @troed/oc-ls-stats@latest --global
```
For known issues, posting new ones, forking or contributing:
Requires `opencode` `1.3.14` or newer.
TUI plugins are loaded from `~/.config/opencode/tui.json`:
```json
{"plugin": ["@troed/oc-ls-stats@latest"]}
```
https://codeberg.org/troed/oc-ls-stats
## Debug Logging
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "@troed/oc-ls-stats",
"version": "1.0.17",
"version": "1.0.18",
"type": "module",
"exports": {
"./tui": {
+30 -17
View File
@@ -3,7 +3,7 @@ import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui"
import { createMemo, createSignal } from "solid-js"
import { appendFileSync } from "fs"
const DEBUG_ENABLED = false
const DEBUG_ENABLED = true
const DEBUG_LOG = "/tmp/oc-ls-stats-debug.log"
function debug(...args: unknown[]) {
if (!DEBUG_ENABLED) return
@@ -28,7 +28,7 @@ type MessageTiming = {
lastTokenAt?: number
}
type TrackerState = {
type TrackerState = {
streamSamplesBySession: Record<string, StreamSample[]>
messageTimingByID: Record<string, MessageTiming>
lastPrefillRate: number
@@ -41,7 +41,7 @@ type TrackerState = {
generateSlotId: number | null
generatePrevNd: number
generateStartAt: number
prevNdBySlot: Record<number, { baseline: number; hasIncreased: boolean }>
prevNdBySlot: Record<number, { baseline: number; hasIncreased: boolean; idTask: number }>
failure: string | null
}
@@ -59,23 +59,32 @@ function stripBaseUrlPath(baseURL: string): string {
}
function extractProviderUrls(config: unknown): string[] {
if (!config || typeof config !== "object") return []
if (!config || typeof config !== "object") { debug("extractProviderUrls: config is null/undefined or not object"); return [] }
const obj = config as Record<string, unknown>
const provider = obj.provider
if (!provider || typeof provider !== "object") return []
if (!provider || typeof provider !== "object") { debug("extractProviderUrls: no provider in config"); return [] }
const urls: string[] = []
let total = 0
let matched = 0
for (const [key, val] of Object.entries(provider as Record<string, unknown>)) {
total++
if (val && typeof val === "object") {
const opts = (val as Record<string, unknown>).options
if (opts && typeof opts === "object") {
const baseURL = (opts as Record<string, unknown>).baseURL || (opts as Record<string, unknown>).base_url
debug(`extractProviderUrls: provider="${key}" baseURL=${baseURL}`)
if (typeof baseURL === "string" && key.toLowerCase().includes("llama") && !key.toLowerCase().includes("ollama")) {
urls.push(stripBaseUrlPath(baseURL))
matched++
debug(`extractProviderUrls: MATCH provider="${key}" -> ${urls[urls.length-1]}`)
} else {
debug(`extractProviderUrls: SKIP provider="${key}" (llama=${key.toLowerCase().includes("llama")} ollama=${key.toLowerCase().includes("ollama")})`)
}
}
}
}
debug(`extractProviderUrls: total=${total} matched=${matched} urls=${JSON.stringify(urls)}`)
return urls
}
@@ -235,9 +244,13 @@ const tui: TuiPlugin = async (api) => {
const prevProcessingSlotIds = new Set(Object.keys(tracker.prevNdBySlot).map(Number))
const currProcessingSlotIds = new Set<number>()
debug(`pollMetrics: urls=${JSON.stringify(llamaServerUrls)} model=${model}`)
for (const baseUrl of llamaServerUrls) {
debug(`pollMetrics: polling ${baseUrl}`)
const slots = await fetchSlots(baseUrl, model)
debug(`pollMetrics: ${baseUrl} response=${JSON.stringify(slots)?.slice(0, 500)}`)
const slotList = Array.isArray(slots) ? slots : slots ? Object.values(slots) : []
debug(`pollMetrics: ${baseUrl} slotList.length=${slotList.length}`)
if (slotList.length === 0) {
tracker.failure = "n/a"
bump()
@@ -256,13 +269,7 @@ const tui: TuiPlugin = async (api) => {
for (const slotId of prevProcessingSlotIds) {
if (!currProcessingSlotIds.has(slotId)) {
const prev = tracker.prevNdBySlot[slotId]
if (prev) {
prev.hasIncreased = false
prev.baseline = 0
} else {
delete tracker.prevNdBySlot[slotId]
}
delete tracker.prevNdBySlot[slotId]
}
}
@@ -272,15 +279,20 @@ const tui: TuiPlugin = async (api) => {
if (!slot?.is_processing) continue
const slotId = slot.id
const nd = slot.next_token?.[0]?.n_decoded ?? 0
const idTask = slot.id_task ?? 0
const entry = tracker.prevNdBySlot[slotId]
const baseline = entry?.baseline ?? nd
const hasIncreased = entry?.hasIncreased ?? false
const baseline = entry?.idTask === idTask ? entry.baseline : nd
const hasIncreased = entry?.idTask === idTask ? entry.hasIncreased : false
if (entry && entry.idTask !== idTask) {
debug(`slot ${slotId} id_task changed from ${entry.idTask} to ${idTask}, resetting state`)
}
if (!hasIncreased && nd <= baseline) {
prefillSlot = slot
break
} else if (nd > baseline) {
tracker.prevNdBySlot[slotId] = { baseline, hasIncreased: true }
tracker.prevNdBySlot[slotId] = { baseline, hasIncreased: true, idTask }
generateSlot = slot
}
}
@@ -289,9 +301,10 @@ const tui: TuiPlugin = async (api) => {
if (slot?.is_processing) {
const slotId = slot.id
const nd = slot.next_token?.[0]?.n_decoded ?? 0
const idTask = slot.id_task ?? 0
const entry = tracker.prevNdBySlot[slotId]
if (entry === undefined) {
tracker.prevNdBySlot[slotId] = { baseline: nd, hasIncreased: false }
if (entry === undefined || entry.idTask !== idTask) {
tracker.prevNdBySlot[slotId] = { baseline: nd, hasIncreased: false, idTask }
}
}
}