diff --git a/internal/cron/cron.go b/internal/cron/cron.go index 2189925..2c16e87 100644 --- a/internal/cron/cron.go +++ b/internal/cron/cron.go @@ -390,7 +390,7 @@ var cronLogRe = regexp.MustCompile(`^\((\S+)\) CMD \((.*)\)$`) // lastRunsFromJournal maps user+command to the most recent "(user) CMD (command)" line. func (m *Manager) lastRunsFromJournal(ctx context.Context) map[string]int64 { m.mu.Lock() - if time.Since(m.lastAt) < 60*time.Second && m.lastRuns != nil { + if time.Since(m.lastAt) < 5*time.Second && m.lastRuns != nil { r := m.lastRuns m.mu.Unlock() return r diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go index b964c80..6f11ba2 100644 --- a/internal/metrics/metrics.go +++ b/internal/metrics/metrics.go @@ -29,7 +29,7 @@ import ( const ( DefaultInterval = 2 * time.Second MinInterval = 100 * time.Millisecond - MaxInterval = 2 * time.Second + MaxInterval = 10 * time.Second History = 300 // samples kept; the window is History × interval (30 s at 100 ms, 10 min at 2 s) fsEvery = 30 * time.Second // filesystem usage changes slowly and statfs on network mounts isn't free upsEvery = 10 * time.Second // UPS daemons are an exec each; don't hammer them at fast intervals @@ -982,7 +982,7 @@ func (c *Collector) Processes() ProcTable { c.prevProc = map[int]procTimes{} c.procInfo = map[int]procStatic{} } - if now.Sub(c.procAt) < 500*time.Millisecond && c.lastTable != nil { // coalesce bursts of clients + if now.Sub(c.procAt) < 100*time.Millisecond && c.lastTable != nil { // coalesce simultaneous clients only return *c.lastTable } ents, err := os.ReadDir("/proc") diff --git a/internal/services/services.go b/internal/services/services.go index c466c18..99be81f 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -107,7 +107,7 @@ func run(ctx context.Context, args ...string) (string, error) { // Cached for a few seconds because the UI polls it. func (m *Manager) List(ctx context.Context) ([]Unit, error) { m.mu.Lock() - if time.Since(m.cacheAt) < 4*time.Second && m.cache != nil { + if time.Since(m.cacheAt) < 200*time.Millisecond && m.cache != nil { // coalesce simultaneous clients only c := m.cache m.mu.Unlock() return c, nil diff --git a/web/src/Dashboard.tsx b/web/src/Dashboard.tsx index 6673410..57c6e62 100644 --- a/web/src/Dashboard.tsx +++ b/web/src/Dashboard.tsx @@ -25,7 +25,7 @@ const MODULES: Module[] = [ const byId = (id: string) => MODULES.find((m) => m.id === id) const DEFAULT_INTERVAL = 2000 -const clampSampler = (ms: number) => Math.min(2000, Math.max(100, ms)) +const clampSampler = (ms: number) => Math.min(10000, Math.max(100, ms)) interface Layout { order: string[]; hidden: Set; hiddenItems: Set; intervalMs: number } diff --git a/web/src/Settings.tsx b/web/src/Settings.tsx index a819945..741a722 100644 --- a/web/src/Settings.tsx +++ b/web/src/Settings.tsx @@ -2,7 +2,7 @@ import { FormEvent, useCallback, useEffect, useState } from 'react' import { api, Me } from './api' import { applyTheme, currentThemeId, THEMES } from './themes' import { Btn, Loading, toast } from './ui' -import { fmtMs, getRefreshMs, REFRESH_OPTIONS, setRefreshMs, LIST_FLOOR } from './prefs' +import { fmtMs, getRefreshMs, REFRESH_OPTIONS, setRefreshMs } from './prefs' const fmtUptime = (s: number) => { const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600), m = Math.floor((s % 3600) / 60); return d > 0 ? `${d}d ${h}h` : h > 0 ? `${h}h ${m}m` : `${m}m` } @@ -81,8 +81,8 @@ export default function Settings({ me }: { me: Me }) { {REFRESH_OPTIONS.map((ms) => )}
- Dashboard charts, the Processes tab, disk activity and the machine list all poll at this rate. The dashboard's sampler on each machine follows it too (fastest 100 ms, slowest 2 s; charts keep 300 points, so the window is 300 × rate). - Lists that shell out on the host — services, cron, updates — never go faster than {fmtMs(LIST_FLOOR)}. + Every page polls at exactly this rate: dashboard, processes, disks, services, cron, updates, machines. The sampler on each machine follows it too, so charts keep 300 points and cover 300 × rate. + Fast rates cost CPU on the host, especially on the list pages, which run commands to refresh. Nothing is throttled behind your back.
diff --git a/web/src/Shell.tsx b/web/src/Shell.tsx index b82d739..4cf7f0f 100644 --- a/web/src/Shell.tsx +++ b/web/src/Shell.tsx @@ -74,7 +74,7 @@ export default function Shell({ me: hubMe, onLoggedOut }: { me: Me; onLoggedOut: // Machine list for the switcher (hub-local), refreshed while the console is open. const loadMachines = useCallback(async () => { try { setMachines((await api.machines()).nodes) } catch { /* not a hub or offline */ } }, []) - useEffect(() => { loadMachines(); const t = setInterval(loadMachines, Math.max(listRate, 5000)); return () => clearInterval(t) }, [loadMachines, listRate]) + useEffect(() => { loadMachines(); const t = setInterval(loadMachines, listRate); return () => clearInterval(t) }, [loadMachines, listRate]) // Switching machines: reset everything that belongs to a host, then re-fetch its identity. const switchMachine = (name: string | null) => { @@ -88,7 +88,7 @@ export default function Shell({ me: hubMe, onLoggedOut }: { me: Me; onLoggedOut: let stop = false const tick = async () => { try { const m = await api.me(); if (!stop) { setRemoteMe(m); setRemoteErr('') } } catch (e) { if (!stop) setRemoteErr(e instanceof Error ? e.message : 'unreachable') } } tick() - const t = setInterval(tick, Math.max(listRate, 5000)) + const t = setInterval(tick, listRate) return () => { stop = true; clearInterval(t) } }, [machine, listRate]) diff --git a/web/src/prefs.ts b/web/src/prefs.ts index cf155ed..53a92c4 100644 --- a/web/src/prefs.ts +++ b/web/src/prefs.ts @@ -4,8 +4,8 @@ import { useEffect, useState } from 'react' export const REFRESH_OPTIONS = [250, 500, 1000, 2000, 5000] as const export const DEFAULT_REFRESH = 2000 -// Lists that shell out on the host (services, cron, updates, machines) never poll faster than this. -export const LIST_FLOOR = 2000 +// No floors: the rate means exactly what it says on every page, at the host's expense. +export const LIST_FLOOR = 0 const KEY = 'hamlab.refresh' export function getRefreshMs(): number {