From 0fac5bafaccb867db443dfdd28891c71529ee151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Troed=20S=C3=A5ngberg?= Date: Wed, 8 Jul 2026 13:11:50 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20v1.0.18=20=E2=80=94=20track=20id=5Ftas?= =?UTF-8?q?k=20to=20prevent=20stale=20slot=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 33 +++++++++++++++++++++------------ package.json | 2 +- tui.tsx | 47 ++++++++++++++++++++++++++++++----------------- 3 files changed, 52 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 24f3400..f25d159 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/package.json b/package.json index dd5f2a7..20facab 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/tui.tsx b/tui.tsx index f74026f..5b19b6a 100644 --- a/tui.tsx +++ b/tui.tsx @@ -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 messageTimingByID: Record lastPrefillRate: number @@ -41,7 +41,7 @@ type TrackerState = { generateSlotId: number | null generatePrevNd: number generateStartAt: number - prevNdBySlot: Record + prevNdBySlot: Record 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 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)) { + total++ if (val && typeof val === "object") { const opts = (val as Record).options if (opts && typeof opts === "object") { const baseURL = (opts as Record).baseURL || (opts as Record).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() + 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 } } } }