mirror of
https://git.sync.wtf/troed/oc-ls-stats.git
synced 2026-08-31 09:43:38 +03:00
chore: v1.0.18 — track id_task to prevent stale slot state
This commit is contained in:
@@ -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
@@ -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": {
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user