SensorDeck 1.0.0 Hardware Health Hub - sustained-load soak testing for desktops and laptops ================================================================================ READ THIS FIRST: WHAT SENSORDECK DOES NOT DO ================================================================================ SensorDeck does NOT read temperatures. SensorDeck does NOT read fan speeds. SensorDeck does NOT read SMART attributes. SensorDeck does NOT read power limits, voltages, or throttle-reason registers. It has no thermal sensor access of any kind, and it will never print a number in degrees or in RPM. This is not an oversight and it is not a limitation we are apologising for. It is the honest position. Reading those values requires OS-privileged, vendor-specific native interfaces: Windows WMI / MSAcpi_ThermalZoneTemperature, vendor WMI providers, or a signed kernel driver such as the ones OEM tuning utilities ship macOS IOKit / SMC keys, which are undocumented, differ between Intel and Apple Silicon, and change between OS releases Linux hwmon, coretemp, k10temp, and the NVMe/ATA passthrough ioctls for SMART, all of which vary by kernel version, chipset and permissions A portable, dependency-free Go command-line program cannot reach any of that without shipping per-platform native code and asking for administrator rights. Plenty of tools claim to and quietly return zeroes, guesses, or a single meaningless "package" reading. SensorDeck does not pretend. ================================================================================ WHAT SENSORDECK MEASURES INSTEAD ================================================================================ It measures the EFFECT rather than the cause. A machine that is thermally throttling gets measurably slower the longer you keep it busy. A well-cooled machine does not. So SensorDeck runs one fixed unit of CPU work over and over, with no idle time in between, for as long as you ask - minutes, not milliseconds - and records how long each unit took. The critical property is that every unit of work is IDENTICAL. Not "approximately the same amount of work"; literally the same work. SensorDeck calibrates an exact operation count once, up front, then freezes it for the whole run. Every interval then performs exactly that many primality checks, over exactly the same band of numbers, visited in exactly the same order, by exactly the same number of worker goroutines. The operation count in every per-interval record of a saved run is the same number. Only the elapsed time varies. That is what makes the decay figure meaningful. If the workload drifted - if later intervals checked bigger numbers, or did more of them - a rising elapsed time would prove nothing at all. From that series SensorDeck reports: * the baseline: the median throughput of the first window of the run * the final figure: the median throughput of the last window of the run * the decay: how far the final figure fell below the baseline, in percent * the first interval that dropped more than 5% below baseline, and when * min / median / p95 / max throughput across all intervals * an ASCII sparkline, one cell per interval, so the SHAPE is visible - a slow steady slide, a sudden cliff, and random noise look different * a verdict: STABLE, MILD DECAY, or SIGNIFICANT DECAY The window is the first and last 60 seconds for runs of three minutes or more. For shorter runs it shrinks to one third of the run, so the two windows stay disjoint and each still contains samples. The baseline and final figures are MEDIANS of their window, not means. Real machines throw the occasional freak interval - an antivirus scan wakes up, a backup kicks off, a noisy neighbour on a shared or virtual host grabs a core for five seconds. A mean lets one such interval move the number several percent and manufacture decay that is not there. A median shrugs it off, while still tracking a genuine sustained slowdown, which by definition affects most of the intervals in the window rather than one of them. ================================================================================ THIS IS INDIRECT EVIDENCE. IT IS NOT A DIAGNOSIS. ================================================================================ Say it plainly: throughput decay under sustained load is a SIGNAL WORTH INVESTIGATING, not a diagnosis. Thermal throttling is one possible cause. It is not the only one. All of these produce the same curve, and SensorDeck cannot tell them apart: * Background processes. An indexer, an update, a virus scan, a sync client or a build that starts partway through the soak takes cores away from the workload and looks exactly like throttling. * Power and battery profiles. A laptop that drops off AC, hits a battery threshold, or switches to a "balanced"/"quiet" profile steps the CPU down on purpose. That is the machine working as designed, not a fault. * Other tenants. On a virtual machine, a container host, a shared workstation or a cloud instance, the CPU capacity you are handed varies with what everyone else is doing. Burstable cloud instances in particular are DESIGNED to slow down after a few minutes at full load - a textbook SIGNIFICANT DECAY that has nothing to do with heat. * The operating system. Scheduler changes, core parking, security mitigations toggling, a hypervisor migrating the VM. And a STABLE verdict is not proof of good cooling either. A machine can be running hot and dusty and simply not have hit its thermal limit during the window you tested, especially at a comfortable ambient temperature. What SensorDeck genuinely tells you is this: over the last N minutes of real sustained load, this machine did or did not hold its speed. That is a measurement, it is reproducible, it is comparable before and after a change, and it is more than a two-second benchmark can tell you. Use it to decide whether a machine is worth opening up - then confirm the cause with vendor tooling that has the sensor access SensorDeck does not. ================================================================================ COMMANDS ================================================================================ sensordeck soak [--minutes 5] [--interval 10s] [--workers N] [--save run.json] [--json] Run the soak and print the report. --minutes accepts fractions (1.5). --interval is the TARGET wall time for one unit of work; SensorDeck calibrates an operation count to hit it and then holds that count fixed, so the actual interval durations drift around the target - that drift is the measurement. --workers defaults to the logical core count. --save writes the whole run, including every per-interval record, as JSON. The save path is checked for writability BEFORE the soak starts, so a bad path fails in a second rather than after five minutes. sensordeck compare --before a.json --after b.json [--json] The "did that actually help" command. Run a soak, clean the fans / repaste the heatsink / lift the laptop off the blanket / move the tower out of the cupboard, run another soak, then compare the two. Reports the change in baseline throughput, in final throughput, in median throughput, and in decay (in percentage points), plus both sparklines side by side and whether the verdict changed. For this to mean anything, keep everything else the same: same power profile, same AC/battery state, same background software, same room. sensordeck show --run run.json [--json] Re-print a saved run's per-interval table and full report without re-running anything. -h, --help, help Show usage and exit 0. --json on any of the three commands emits machine-readable JSON instead of the text report. ================================================================================ HOW TO USE IT WELL ================================================================================ 1. Close what you can. Every background process you leave running is a confounder. Note the ones you cannot close. 2. Plug the laptop in and set the power profile you actually care about. Comparing a soak on battery to a soak on AC measures the power profile, not the cooling. 3. Run long enough. Thermal effects need time. Two minutes may not be enough for a desktop with a large heatsink to heat-soak; five to ten minutes is a more useful default, and that is why the default is five. 4. Save every run. Decay figures are far more useful compared against the same machine's own history than judged in isolation. 5. Read the sparkline, not just the percentage. A steady downward slide that flattens out is the classic shape of a machine hitting a thermal or power limit and settling at its sustainable clock. A single deep notch is almost certainly a background process. A curve that goes UP is usually the machine ramping to full clock at the start. 6. Repeat before you conclude anything. Run the same soak twice. If the two decay figures disagree by more than a few points, the machine is too noisy for the result to mean much yet - find out what else is running. ================================================================================ INTERPRETING THE VERDICT ================================================================================ STABLE decay below 5% The machine held its speed for the whole soak. MILD DECAY decay 5% to 15% Real but modest. Worth a second run to confirm it reproduces before you act on it. SIGNIFICANT DECAY decay 15% or more The machine lost a large fraction of its sustained throughput. Worth investigating: check for background load first, then power profiles, then cooling. Typical run-to-run noise on an otherwise idle machine is a few percent; the 5% floor is set above that deliberately. On a busy or shared machine the noise is much larger, and decay figures should be treated with corresponding suspicion. ================================================================================ SIGNALS AND INTERRUPTION ================================================================================ Ctrl-C (SIGINT) or SIGTERM during a soak leaves a USABLE PARTIAL RUN. The interval that was in flight is discarded, because a partial unit of work is not comparable to a full one; every interval that made it into the report is a complete, full-size unit. SensorDeck then analyses what it has, prints the normal report marked as interrupted, writes the --save file if one was asked for, and exits 0. A run interrupted before the first interval finished has nothing to report and exits 1 with a message. ================================================================================ STATISTICS, STATED EXPLICITLY ================================================================================ throughput of an interval operations / elapsed seconds, where operations is the same constant for every interval median middle sample of the ascending-sorted values; for an even count, the mean of the two middle values (identical to Python's statistics.median) p95 NEAREST-RANK, no interpolation: sort ascending, take the element at 1-based index ceil(0.95 * n), clamped to [1, n] (identical to Python's sorted(xs)[math.ceil(0.95*len(xs))-1]) baseline median of the intervals starting within the first window final median of the intervals starting within the last window decay (baseline - final) / baseline * 100, so a positive number means slower at the end and a negative number means the machine sped up sparkline one cell per interval, eight levels; the lowest sample gets the lowest bar, the highest the highest, everything between linearly scaled and rounded to the nearest level Every one of these is recomputable by hand from the per-interval numbers in `--json` or a `--save` file. That is the point of publishing the raw series. ================================================================================ THE WORKLOAD ================================================================================ Trial-division primality checking over a fixed band of odd numbers, roughly 1.000e6 to 1.131e6, cycled round by each worker. Integer-arithmetic bound, tiny working set, no allocation in the hot loop, no I/O, no syscalls, no shared counters and no locks between workers. The band wraps deliberately. If a worker marched off into ever-larger numbers, trial division would have to run further before it could stop, the cost of a single "operation" would creep up through the run, and SensorDeck would report fake decay. Confining the workload to a narrow band keeps sqrt(n) - and therefore the cost of one check - essentially flat, so an operation means the same thing in the last interval as in the first. This is a CPU and memory-subsystem test. It does not exercise the GPU, the disk or the network, so it will not detect a GPU that throttles or an SSD that slows down when its controller gets hot. ================================================================================ ROADMAP ================================================================================ * Real sensor access, per platform, as an optional privileged component: hwmon/coretemp/k10temp on Linux, IOKit/SMC on macOS, WMI plus vendor providers on Windows. Reported alongside the throughput curve and always clearly labelled as to which platform actually supplied a reading, with the decay measurement continuing to work unprivileged when it cannot. * SMART / NVMe health attributes on the same optional-privileged basis, including drive temperature, so a storage-side thermal problem can be told apart from a CPU-side one. * Correlating decay with OS power and thermal events: Windows Kernel-Processor-Power ETW events and the ACPI thermal zone, macOS pmset thermlog and CPU_Speed_Limit, Linux thermal-throttle counters in /sys and RAPL energy readings. When the curve drops, say whether the OS logged a thermal or power-limit event at that exact moment - which is what turns "something slowed down" into an actual diagnosis. * Per-core decay, so a single failing core or one hot package in a multi-die part can be separated from a whole-machine effect. * GPU and storage soak modes. * A fleet mode that rolls saved runs from many machines into one ranking, feeding the same JSON into the PCHealth sibling. ================================================================================ BUILDING ================================================================================ go build -o sensordeck . Go standard library only. No third-party dependencies, no cgo, no network access at build time or at run time. GOPROXY=off go build works.