diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-16 19:24:48 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-16 19:24:48 +0200 |
| commit | ca37eff5b6817e95ce5b6cfd768fd27c9af96914 (patch) | |
| tree | 74b86f6e509be51a1a943e7c95cb389d695b55c9 | |
| parent | f822a250f9b2759207d64cc53b3d80c0c08d2082 (diff) | |
| download | conky-theme-udt-ca37eff5b6817e95ce5b6cfd768fd27c9af96914.tar.gz conky-theme-udt-ca37eff5b6817e95ce5b6cfd768fd27c9af96914.zip | |
feat: add stateful CPU percentage counter
Load is a delta, so one reading cannot yield a percentage. The first
call returns nil rather than a fabricated number: 100% on startup reads
as a real spike.
A non-advancing counter also returns nil instead of dividing by zero,
and the result is clamped because a suspend/resume can produce a
nonsense delta.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
| -rw-r--r-- | lib/data.lua | 27 | ||||
| -rw-r--r-- | test/test_data.lua | 18 |
2 files changed, 45 insertions, 0 deletions
diff --git a/lib/data.lua b/lib/data.lua index 96fc172..28dce1e 100644 --- a/lib/data.lua +++ b/lib/data.lua @@ -72,4 +72,31 @@ function M.hwmon_dir(name) return hit:match('^(.*)/name$') end +-- A stateful CPU-load counter. +-- +-- Load is work done between two samples, so a single reading cannot produce a +-- percentage. Each counter keeps its own previous sample, which also means a +-- per-core counter is just another instance. +function M.new_cpu_counter() + return { + prev_total = nil, + prev_idle = nil, + -- Returns busy percent since the previous sample, or nil when there is no + -- usable delta (first call, or the counters did not advance). + sample = function(self, total, idle) + if not (total and idle) then return nil end + local pt, pi = self.prev_total, self.prev_idle + self.prev_total, self.prev_idle = total, idle + if not pt then return nil end + local dt = total - pt + if dt <= 0 then return nil end + local busy = (dt - (idle - pi)) / dt * 100 + -- Clamp: a counter reset or a suspend/resume can produce a nonsense + -- delta, and a bar drawn at -12% or 340% is worse than a clamped one. + if busy < 0 then busy = 0 elseif busy > 100 then busy = 100 end + return busy + end, + } +end + return M diff --git a/test/test_data.lua b/test/test_data.lua index 08c20f9..44e9ab3 100644 --- a/test/test_data.lua +++ b/test/test_data.lua @@ -36,4 +36,22 @@ assert(data.millidegrees(nil) == nil, 'nil input must give nil') assert(data.millidegrees('') == nil, 'empty input must give nil') assert(data.millidegrees('garbage') == nil, 'unparseable input must give nil') +-- cpu_percent is a delta between two samples, so it holds state. +-- The first call has no previous sample and must report nil, not a number: +-- any number it invented would be wrong, and 100% on startup looks like a +-- real spike. +local c = data.new_cpu_counter() +assert(c:sample(1000, 900) == nil, 'first sample must give nil') + +-- Second sample: 100 more total jiffies, 50 of them idle, so 50% busy. +-- Held in a local first: calling sample() again inside the assert message would +-- advance the counter a third time. +local busy = c:sample(1100, 950) +assert(busy == 50.0, 'second sample, got ' .. tostring(busy)) + +-- A counter that did not advance means no elapsed time, not 0% load. +local c2 = data.new_cpu_counter() +c2:sample(1000, 900) +assert(c2:sample(1000, 900) == nil, 'zero delta must give nil, not a division by zero') + print('test_data: all assertions passed') |
