fleet-manager: chrony tracking metrics relative to the NTP source

The agent only reported chrony as synced|unsynced — nothing about how well the
clock tracks. Parse `chronyc tracking` for the source-relative metrics.

- proto: HealthSnapshot.chrony_offset_seconds (16, signed system-time offset,
  fast=+), chrony_source (17, reference name/IP), chrony_stratum (18); regenerated.
- agent: replace chronyState() with chronyTracking()/parseChrony() — one exec,
  parses Leap status + Reference ID + Stratum + System time offset (+ test).
- manager: Health.{ChronyOffset,ChronySource,ChronyStratum}; snapshotToHealth maps
  them; influx writes chrony_offset_seconds/_stratum/_source and whitelists the
  offset + stratum as graphable fields.
- UI: chronySummary() + offsetMs() — the chrony row now shows
  "synced · 192.168.50.10 · s3 · +0.01ms" instead of just "synced".
This commit is contained in:
Audiobuzz
2026-07-10 06:47:27 +00:00
parent df7dad81b7
commit 1c68c37824
11 changed files with 222 additions and 45 deletions
+21
View File
@@ -36,6 +36,27 @@ export function shortSerial(s) {
return s ? s.slice(0, 8) : '—'
}
// offsetMs formats a signed seconds offset as milliseconds ("+0.02ms" / "-1.3ms").
export function offsetMs(sec) {
if (sec === undefined || sec === null || Number.isNaN(sec)) return null
const ms = sec * 1000
const digits = Math.abs(ms) < 1 ? 3 : 2
return (ms >= 0 ? '+' : '') + ms.toFixed(digits) + 'ms'
}
// chronySummary renders the chrony health as "synced · <source> · s<stratum> ·
// <offset>" — the sync state plus how it's tracking relative to the NTP source.
export function chronySummary(h) {
if (!h) return '—'
const extra = []
if (h.chrony_source) extra.push(h.chrony_source)
if (h.chrony_stratum) extra.push('s' + h.chrony_stratum)
const off = offsetMs(h.chrony_offset_seconds)
if (off && h.chrony) extra.push(off)
const state = h.chrony || '—'
return extra.length ? `${state} · ${extra.join(' · ')}` : state
}
// dur formats a span of SECONDS as a compact "3d 4h" / "5h 12m" / "45s".
export function dur(s) {
s = Math.max(0, Math.floor(s || 0))
@@ -3,7 +3,7 @@
import Panel from '../components/Panel.svelte'
import StatePill from '../components/StatePill.svelte'
import Chart from '../components/Chart.svelte'
import { relTime, pct, num, healthState, shortSerial, dur, rebootStatus } from '../lib/fmt.js'
import { relTime, pct, num, healthState, shortSerial, dur, rebootStatus, chronySummary } from '../lib/fmt.js'
let hosts = $state([])
let error = $state('')
@@ -103,7 +103,7 @@
<dt class="text-faint">auth</dt><dd class="text-muted">{selected.health?.auth || '—'}</dd>
<dt class="text-faint">nvme root</dt><dd class="text-muted">{selected.health?.nvme_root || '—'}</dd>
<dt class="text-faint">selinux</dt><dd class="text-muted">{selected.health?.selinux || '—'}</dd>
<dt class="text-faint">chrony</dt><dd class="text-muted">{selected.health?.chrony || '—'}</dd>
<dt class="text-faint">chrony</dt><dd class="text-muted">{chronySummary(selected.health)}</dd>
<dt class="text-faint">kiosk</dt><dd class="text-muted">{selected.health?.kiosk_active ? 'active' : 'idle'} · {selected.usage?.session_uptime || '—'}</dd>
<dt class="text-faint">uptime</dt><dd class="text-muted">{selected.health?.uptime_seconds ? dur(selected.health.uptime_seconds) : '—'}</dd>
{#if rebootStatus(selected)}