mirror of
https://git.sync.wtf/troed/oc-ls-stats.git
synced 2026-08-31 09:43:38 +03:00
chore: v1.2.2 — add staggered backoff for llama-server polling errors
This commit is contained in:
@@ -53,6 +53,8 @@ The plugin discovers the llama-server URL by reading the OpenCode configuration
|
||||
|
||||
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:
|
||||
@@ -114,7 +116,7 @@ The following changes to the `/slots` endpoint would improve the plugin's functi
|
||||
|
||||
For known issues, posting new ones, forking or contributing:
|
||||
|
||||
https://codeberg.org/troed/oc-ls-stats
|
||||
https://git.sync.wtf/troed/oc-ls-stats
|
||||
|
||||
## Debug Logging
|
||||
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@troed/oc-ls-stats",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./tui": {
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"prepack": "npx -y tsx test-stats.ts"
|
||||
"prepack": "npx -y tsx test-backoff.ts"
|
||||
},
|
||||
"engines": {
|
||||
"opencode": ">=1.3.14"
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
export function backoffDelayMs(failures: number, baseMs: number = 1000, maxMs: number = 10000): number {
|
||||
return Math.min(maxMs, baseMs * 2 ** (failures - 1))
|
||||
}
|
||||
|
||||
export interface SlotState {
|
||||
id: number
|
||||
is_processing: boolean
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { test } from "node:test"
|
||||
import assert from "node:assert/strict"
|
||||
import { backoffDelayMs } from "./src/stats.ts"
|
||||
|
||||
test("first failure retries after the base delay", () => {
|
||||
assert.equal(backoffDelayMs(1), 1000)
|
||||
})
|
||||
|
||||
test("second failure doubles the delay", () => {
|
||||
assert.equal(backoffDelayMs(2), 2000)
|
||||
})
|
||||
|
||||
test("delay grows exponentially", () => {
|
||||
assert.equal(backoffDelayMs(3), 4000)
|
||||
assert.equal(backoffDelayMs(4), 8000)
|
||||
})
|
||||
|
||||
test("delay is capped at the maximum", () => {
|
||||
assert.equal(backoffDelayMs(5), 10000)
|
||||
assert.equal(backoffDelayMs(10), 10000)
|
||||
assert.equal(backoffDelayMs(20), 10000)
|
||||
})
|
||||
|
||||
test("uses provided base and max", () => {
|
||||
assert.equal(backoffDelayMs(1, 200, 5000), 200)
|
||||
assert.equal(backoffDelayMs(5, 200, 5000), 3200)
|
||||
assert.equal(backoffDelayMs(20, 200, 5000), 5000)
|
||||
})
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { TextRenderable } from "@opentui/core"
|
||||
import type { TuiPlugin, TuiPluginModule } from "@opencode-ai/plugin/tui"
|
||||
import { onCleanup } from "solid-js"
|
||||
import { processPoll } from "./src/stats.ts"
|
||||
import { backoffDelayMs, processPoll } from "./src/stats.ts"
|
||||
|
||||
type StreamSample = {
|
||||
at: number
|
||||
@@ -11,6 +11,8 @@ type StreamSample = {
|
||||
|
||||
const STREAM_WINDOW_MS = 5_000
|
||||
const SLOT_POLL_MS = 500
|
||||
const BACKOFF_BASE_MS = 1_000
|
||||
const BACKOFF_MAX_MS = 10_000
|
||||
type MessageTiming = {
|
||||
sessionID: string
|
||||
requestStartAt: number
|
||||
@@ -214,6 +216,7 @@ const tui: TuiPlugin = async (api) => {
|
||||
|
||||
let llamaServerUrls: string[] = []
|
||||
let llamaServerModel: string | undefined
|
||||
const backoffByUrl = new Map<string, { failures: number; nextRetryAt: number }>()
|
||||
|
||||
const pollMetrics = async () => {
|
||||
if (!llamaServerUrls.length) return
|
||||
@@ -230,8 +233,30 @@ const tui: TuiPlugin = async (api) => {
|
||||
}
|
||||
|
||||
for (const baseUrl of llamaServerUrls) {
|
||||
const now = Date.now()
|
||||
const backoff = backoffByUrl.get(baseUrl)
|
||||
if (backoff && now < backoff.nextRetryAt) {
|
||||
tracker.failure = "n/a"
|
||||
bump()
|
||||
continue
|
||||
}
|
||||
|
||||
const slots = await fetchSlots(baseUrl, model)
|
||||
const slotList = Array.isArray(slots) ? slots : slots ? Object.values(slots) : []
|
||||
if (slots == null) {
|
||||
const failures = (backoff?.failures ?? 0) + 1
|
||||
const delay = backoffDelayMs(failures, BACKOFF_BASE_MS, BACKOFF_MAX_MS)
|
||||
const jitter = Math.floor(Math.random() * (delay / 4))
|
||||
backoffByUrl.set(baseUrl, {
|
||||
failures,
|
||||
nextRetryAt: Date.now() + Math.min(delay + jitter, BACKOFF_MAX_MS),
|
||||
})
|
||||
tracker.failure = "n/a"
|
||||
bump()
|
||||
continue
|
||||
}
|
||||
|
||||
backoffByUrl.delete(baseUrl)
|
||||
const slotList = Array.isArray(slots) ? slots : Object.values(slots)
|
||||
if (slotList.length === 0) {
|
||||
tracker.failure = "n/a"
|
||||
bump()
|
||||
|
||||
Reference in New Issue
Block a user