perf(ui,watchdog): adaptive UI polling and zero-UCI cached settings context

- Feat(ui): implement adaptive polling in runtimeUiState service (1s -> 2s during active tasks, 10s idle)
- Perf(controller): cache settings snapshot in current_tick_ctx across all probe setting() calls to eliminate repeated uci_core.get_all()
- Perf(controller): reuse ctx.proxy_port in probe_proxy to eliminate duplicate proxy_port() evaluation
- Perf(watchdog): guard adaptive_normal_interval behind 120s check and clear tick context at tick boundary
- Test: add static and behavioral assertions in tests/watchdog_adaptive_intervals.sh
This commit is contained in:
Dushnilin
2026-09-15 08:37:43 +03:00
parent 1c7feb4aac
commit d6d39dac81
5 changed files with 88 additions and 10 deletions
@@ -5,7 +5,9 @@ import { applyUiStateToStore } from './uiState.service';
const RUNTIME_UI_STATE_REFRESH_MIN_INTERVAL_MS = 500;
const RUNTIME_UI_STATE_IDLE_POLL_INTERVAL_MS = 10_000;
const RUNTIME_UI_STATE_ACTIVE_POLL_INTERVAL_MS = 500;
const RUNTIME_UI_STATE_ACTIVE_INITIAL_POLL_INTERVAL_MS = 1_000;
const RUNTIME_UI_STATE_ACTIVE_SLOW_POLL_INTERVAL_MS = 2_000;
const RUNTIME_UI_STATE_ACTIVE_SLOW_THRESHOLD_MS = 4_000;
// A hidden tab must not keep spawning shell commands on the router:
// back off to one request per 60s while hidden (a force refresh on
// visibilitychange covers the moment the tab comes back).
@@ -20,6 +22,7 @@ let runtimeStateResumeRefreshRegistered = false;
let runtimeStatePollTimer: ReturnType<typeof setTimeout> | null = null;
let runtimeStatePollingStarted = false;
let runtimeStateHasRunningAction = false;
let runningActionStartedAt = 0;
const runtimeUiStateListeners = new Set<RuntimeUiStateListener>();
function isDocumentVisible() {
@@ -40,9 +43,17 @@ function getNextPollDelay() {
if (!isDocumentVisible()) {
return RUNTIME_UI_STATE_HIDDEN_POLL_INTERVAL_MS;
}
return runtimeStateHasRunningAction
? RUNTIME_UI_STATE_ACTIVE_POLL_INTERVAL_MS
: RUNTIME_UI_STATE_IDLE_POLL_INTERVAL_MS;
if (!runtimeStateHasRunningAction) {
runningActionStartedAt = 0;
return RUNTIME_UI_STATE_IDLE_POLL_INTERVAL_MS;
}
if (runningActionStartedAt === 0) {
runningActionStartedAt = Date.now();
}
const elapsed = Date.now() - runningActionStartedAt;
return elapsed < RUNTIME_UI_STATE_ACTIVE_SLOW_THRESHOLD_MS
? RUNTIME_UI_STATE_ACTIVE_INITIAL_POLL_INTERVAL_MS
: RUNTIME_UI_STATE_ACTIVE_SLOW_POLL_INTERVAL_MS;
}
function scheduleRuntimeUiStatePoll(delay = getNextPollDelay()) {
@@ -94,6 +105,9 @@ export async function refreshRuntimeUiState({
}
lastRuntimeUiStateRefreshAt = now;
if (force) {
runningActionStartedAt = now;
}
const promise = TachyonShellMethods.getUiState()
.then((response) => {
@@ -104,6 +118,9 @@ export async function refreshRuntimeUiState({
applyUiStateToStore(response.data);
lastRuntimeUiState = response.data;
runtimeStateHasRunningAction = hasRunningAction(response.data);
if (!runtimeStateHasRunningAction) {
runningActionStartedAt = 0;
}
notifyRuntimeUiStateListeners(response.data);
return response.data;
})