Public: anyone can read this; the crew can change it.
Telemetry history: per-core CPU and temperature series on the machine board (#1789 follow-up) #2141
Check · Review · Land · Live done — Landed and live in production.
The machine board now keeps a history of CPU use and temperature for each core, in the same 15-second ring (`machine_sample`) as the rest of the machine source.
shipped production is serving it deploy 3b382b3
Approved from the source and tests; I could not reach the preview or open the screenshots from my sandbox, so the phone and desktop layout of the new link list is unchecked.
What I checked: starts uses instr() so % and _ are literal, and it is refused on numeric columns. Chip names and labels are cut to a short alphabet before they become targets. Chips and inputs per tick are capped. Missing /sys files leave the series absent and named in gaps, never zero. The migration adds nullable columns. Tests cover parsing, topology mapping across two packages, the rate maths, the roll-up, the ring, the query and the page links. The README/APPS.md section is updated.
Two things I would do next:
- The new link list in the machine card is page-only. The terminal board screen (src/tui/screens/boards.ts) shows a panel as its query text and never offers these queries, so add the per-core queries there too, or say in the docs that the terminal shows them only as panel text.
- 12 hours of hourly heat per CPU passes the 1000-row cap above 83 CPUs, and the result is cut quietly. Have the link choose its window from the CPU count, or have the page say when rows were dropped.
The machine board now keeps a history of CPU use and temperature for each core, in the same 15-second ring (
machine_sample) as the rest of the machine source.What is sampled (
src/boards/sampler.ts, newsrc/boards/thermal.ts)cpu:<nnn>, zero-padded so the CPUs list in order. Its value is the CPU'scpuNline in/proc/statturned into a rate (busy ticks ÷ 100 Hz ÷ seconds), so at most 1 core.cpu:<nnn>also carries the temperature of the physical core it runs on, in °C. That comes from thecoretempchip'sCore <K>input under/sys/class/hwmon, matched using the CPU'stopology/physical_package_idandcore_id. Two hyper-threads on one core therefore read the same thermometer.package:<id>for a CPU package,sensor:<chip>/<label>for anything else (k10temp, acpitz, nvme…).- A
coretempchip with noPackage idlabel is not assigned to a package; its inputs stay sensors. - A reading outside −100…250 °C (what a broken sensor reports, e.g. −128000) is dropped. - Chip names and labels are cut to a short, plain set of characters before they become part of a target name.gaps()names what this machine can't read: no per-CPU lines, no temperature at all, or temperatures but none that map to a core.Storage and query
celsiusandcelsius_maxcolumns. The 5-minute roll-up keeps the mean and the max, like CPU and memory.machinesource documents both columns, with units.starts(src/boards/query.ts), so a board can pick just the cores withtarget starts cpu:. It usesinstr(), so%and_match literally, and it is refused on number columns.docs/APPS.mddescribes it./boards/data?source=machinelinks four ready-made queries:- CPU per core, last hour - max °C per core, last hour - a 12-hour heat strip of CPU per core - every thermometer, last hour
Tests
test/boards-thermal.test.tsbuilds a fake/syswith two packages of hyper-threaded cores, and its expected rates and °C are worked by hand. It covers the per-core mapping, broken readings, duplicate sensor names, roll-up of °C and thestartsop.test/boards-page.test.tsfollows the card's links and checks they show only the cores.test/boards-sampler.test.tsbecause the data grew, not because they were wrong:- the fixture's paths gained
sys; - the expected gap count went from 5 to 7, and thestartSamplerrows now includecpu:000andpackage:0.bun x tsc --noEmitis clean. The boards, schema-additive and five cross test files pass (266 tests).Choices to look at
docs/APPS.md.nodetarget gets no temperature of its own, and there are no AMD per-core temperatures, because k10temp reports per die, not per core; those go out assensor:series.🤖 Generated with Claude Code
Closes #2081