diff --git a/frontend/src/components/FleetRightNow.tsx b/frontend/src/components/FleetRightNow.tsx index 86e42dd..88b3e4a 100644 --- a/frontend/src/components/FleetRightNow.tsx +++ b/frontend/src/components/FleetRightNow.tsx @@ -48,10 +48,11 @@ function Constellation({ data, flips }: { data: RightNowData; flips: Set }).filter(c => c.dots.length > 0); const n = data.now; - const pct = n.machines ? Math.round((n.running / n.machines) * 100) : 0; const running = data.projects.running; const totalRunning = Object.values(running).reduce((s, v) => s + v, 0) || 1; - const topProject = order.reduce((a, b) => (running[b] > running[a] ? b : a), order[0]); + const share = (k: RightNowProject) => (running[k] / totalRunning) * 100; + const ranked = [...order].sort((a, b) => running[b] - running[a]); + const [topProject, runnerUp] = ranked; return (
@@ -61,14 +62,24 @@ function Constellation({ data, flips }: { data: RightNowData; flips: Set
Right now
+ {/* The Fleet Load card above owns the running/pending counts; this panel's + headline says the one thing only it knows: whose work the fleet is doing. */}

- - Math.round(v).toLocaleString()} /> - {" "} - of {n.machines.toLocaleString()} machines are building and testing Firefox - ({pct}%), with{" "} - Math.round(v).toLocaleString()} />{" "} - tasks waiting in line. + {running[topProject] > 0 ? ( + <> + {data.projects.labels[topProject]} is the fleet's biggest customer, with{" "} + + `${Math.round(v)}%`} /> + {" "} + of running work + {runnerUp && running[runnerUp] > 0 && ( + , ahead of {data.projects.labels[runnerUp]} at {Math.round(share(runnerUp))}% + )} + . + + ) : ( + No Firefox work running right now. + )}

@@ -118,12 +129,12 @@ function Constellation({ data, flips }: { data: RightNowData; flips: Set {/* whose work it is */}
+
+
Math.round(v).toLocaleString()} />
+
machines
+
Whose work
-

- {data.projects.labels[topProject]} is the biggest customer right now, with{" "} - {Math.round((running[topProject] / totalRunning) * 100)}% of running tasks. -

-
+
{[...order, "other" as const].map(k => { const v = running[k]; const color = k === "other" ? OTHER_WORK : PROJECT_COLOR[k]; diff --git a/frontend/src/lib/kit.ts b/frontend/src/lib/kit.ts index f3ac492..50841fa 100644 --- a/frontend/src/lib/kit.ts +++ b/frontend/src/lib/kit.ts @@ -60,10 +60,14 @@ function median(xs: number[]): number { } const n = (v: number) => Math.round(v).toLocaleString(); +/** Percent difference between a and b, relative to `base` (defaults to b). */ +const pct = (a: number, b: number, base = b) => Math.round(((a - b) / base) * 100); /** How Kit reads the fleet right now. Fleet-wide pending is never small (a couple of * pools are always backed up), so every judgement is against this fleet's own last - * 48 hours rather than a fixed number. Checked in priority order: the first match wins. */ + * 48 hours rather than a fixed number. Checked in priority order: the first match wins. + * The Fleet Load card already shows the running/pending counts, so Kit never repeats + * them; it says how today compares instead. */ export function kitMood(r: FleetReading): KitMood { if (r.shipsToday) return { pose: "proud", says: `Firefox ${r.shipsToday} ships today.` }; if (r.pending == null || r.running == null) return { pose: r.idlePose, says: "Checking on the fleet…" }; @@ -73,16 +77,16 @@ export function kitMood(r: FleetReading): KitMood { const usualRunning = median(r.history.map(h => h.running)); if (enoughHistory && r.pending >= 500 && r.pending >= usualPending * 1.5) { - return { pose: "alert", says: `The queue's long: ${n(r.pending)} waiting, ${(r.pending / usualPending).toFixed(1)}× the usual.` }; + return { pose: "alert", says: `The queue's ${(r.pending / usualPending).toFixed(1)}× its usual length.` }; } if (r.macAttention >= 10 && r.macTotal > 0 && r.macAttention / r.macTotal >= 0.05) { return { pose: "inquisitive", says: `${n(r.macAttention)} Macs need a look.` }; } if (enoughHistory && usualRunning > 0 && r.running >= usualRunning * 1.1) { - return { pose: "juggling", says: `Juggling ${n(r.running)} tasks, busier than usual.` }; + return { pose: "juggling", says: `Busier than usual: ${pct(r.running, usualRunning)}% above the last two days.` }; } if (enoughHistory && usualRunning > 0 && r.running <= usualRunning * 0.6) { - return { pose: "meditating", says: `Quiet hours. ${n(r.running)} tasks running.` }; + return { pose: "meditating", says: `Quiet hours: ${pct(usualRunning, r.running, usualRunning)}% below the usual.` }; } - return { pose: r.idlePose, says: `Keeping watch: ${n(r.running)} running, ${n(r.pending)} waiting.` }; + return { pose: r.idlePose, says: enoughHistory ? "All steady. Nothing unusual in the fleet." : "Keeping watch while the history fills in." }; } diff --git a/frontend/src/pages/Overview.tsx b/frontend/src/pages/Overview.tsx index 374bd9c..17228ba 100644 --- a/frontend/src/pages/Overview.tsx +++ b/frontend/src/pages/Overview.tsx @@ -21,7 +21,6 @@ const yardstickAll = `${YARDSTICK_BASE}&var-provisioner=$__all&var-workerType=$_ // health ring / sparkline accents and leaves emerald free for "healthy"/production. // Android gets its own iconic robot-green — it sits outside the gradient (its devices are // Taskcluster-only, not in the worker DB) and the label disambiguates it from health greens. -const PLATFORM_COLORS = { macOS: "#FF9400", Linux: "#FF1AD9", Windows: "#9059FF", Android: "#3DDC84" } as const; function timeAgo(iso: string | null, now = Date.now()) { if (!iso) return "never"; @@ -93,27 +92,6 @@ function Sparkline({ points, className = "w-full h-16" }: { points: number[]; cl } /** Fleet-health ring with a gradient arc; children render in the center. */ -function HealthRing({ pct, children }: { pct: number; children: React.ReactNode }) { - const r = 54, c = 2 * Math.PI * r, dash = (Math.max(0, Math.min(100, pct)) / 100) * c; - return ( -
- - - - - - - - - - - -
{children}
-
- ); -} - // ── Monitored pools (user-pinned; follows the IAP-authenticated user across devices) ── const MONITOR_KEY = "hangar.monitoredPools"; // legacy per-browser store, migrated once into the backend const MONITOR_MAX = 8; @@ -1105,19 +1083,12 @@ export function Overview() { const [data, setData] = useState(null); const [load, setLoad] = useState(null); const [scale, setScale] = useState(null); - // Android device pools are Taskcluster-only (no worker DB rows), so pull their live - // device total separately to fold into the platform breakdown below. - const [androidTotal, setAndroidTotal] = useState(null); const [error, setError] = useState(""); - const loadAndroid = () => - api.fleet.androidPools().then(d => setAndroidTotal(d.pools.reduce((s, p) => s + p.total, 0))); - useEffect(() => { api.fleet.summary().then(setData).catch(e => setError(e.message)); api.fleet.loadHistory(48).then(setLoad).catch(() => {}); api.fleet.showcase().then(d => setScale(d.scale)).catch(() => {}); - loadAndroid().catch(() => {}); }, []); // Keep the dashboard live: silently refresh everything while the tab is visible. @@ -1125,7 +1096,6 @@ export function Overview() { api.fleet.summary().then(setData).catch(() => {}); api.fleet.loadHistory(48).then(setLoad).catch(() => {}); api.fleet.showcase().then(d => setScale(d.scale)).catch(() => {}); - loadAndroid().catch(() => {}); }, 60_000); if (error) return
{error}
; @@ -1136,26 +1106,9 @@ export function Overview() {
); - const platforms = (() => { - let mac = 0, linux = 0, windows = 0; - Object.entries(data.by_pool).forEach(([name, count]) => { - if (name.includes("osx")) mac += count; - else if (name.includes("linux")) linux += count; - else if (name.includes("win") || name.includes("nuc")) windows += count; - }); - return [ - { name: "macOS" as const, value: mac, color: PLATFORM_COLORS.macOS }, - { name: "Linux" as const, value: linux, color: PLATFORM_COLORS.Linux }, - { name: "Windows" as const, value: windows, color: PLATFORM_COLORS.Windows }, - { name: "Android" as const, value: androidTotal ?? 0, color: PLATFORM_COLORS.Android }, - ].filter(d => d.value > 0); - })(); - const platformTotal = platforms.reduce((s, p) => s + p.value, 0) || 1; - - // Health ring is scoped to macOS hardware — the fleet RelOps actually owns — to cut noise. + // macOS hardware is the fleet RelOps actually owns; Kit reads it. const attention = data.attention_mac.quarantined + data.attention_mac.missing_from_tc; - const macTotal = platforms.find(p => p.name === "macOS")?.value ?? 0; - const healthPct = macTotal > 0 ? Math.round((1 - attention / macTotal) * 100) : 100; + const macTotal = Object.entries(data.by_pool).reduce((sum, [name, count]) => sum + (name.includes("osx") ? count : 0), 0); // Greeting (client-local time) const now = new Date(); @@ -1272,7 +1225,8 @@ export function Overview() {
- {/* The fleet right now — every machine, lit by whose work it's running */} + {/* The fleet right now — every machine, lit by whose work it's running, with + fleet size (this card is the one source for fleet counts) */} {/* Monitored pools — user-pinned */} @@ -1282,43 +1236,6 @@ export function Overview() { {pinned.try && togglePin("try")} />} {pinned.needinfo && togglePin("needinfo")} />} - {/* Total workers breakdown */} -
-
- - {healthPct}% - healthy - - -
-
-
-
-
Total workers
-
-
- -
-
- {platforms.map(p => ( -
- ))} -
-
- {platforms.map(p => ( -
- - {p.name} - {p.value.toLocaleString()} - {Math.round((p.value / platformTotal) * 100)}% -
- ))} -
-
-
-
-
-
); }