-- System: CPU load as a per-core equaliser, memory, and every temperature. -- -- The hwmon bindings and temperature ceilings below are host-specific, which -- is why they live here rather than in lib/data.lua: that file is the part -- that ports to another machine, this is the part that does not. local card = require 'lib.card' local data = require 'lib.data' local M = {} -- chip, file, label, warn, crit. -- -- Ceilings are per sensor because a single pair cannot serve both: 70C is -- unremarkable on a CPU package and alarming on an NVMe. Starting values, -- easy to retune once real numbers under load are known. local TEMPS = { { 'k10temp', 'temp1_input', 'CPU', 75, 90 }, { 'k10temp', 'temp3_input', 'CCD1', 75, 90 }, { 'nvme', 'temp1_input', 'NVME', 60, 70 }, { 'gigabyte_wmi', 'temp2_input', 'BOARD', 60, 70 }, { 'gigabyte_wmi', 'temp3_input', 'BOARD', 60, 70 }, } -- One counter per core, created on first draw and kept for the process's life: -- load is a delta between samples, so the state has to outlive the frame. local counters = {} local total_counter = data.new_cpu_counter() function M.draw(cr, rect, colors) local inner = card.card(cr, rect, colors) local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end local label_size = clamp(inner.w * 0.030, 11, 18) local big_size = clamp(inner.w * 0.085, 26, 56) local row_size = clamp(inner.w * 0.030, 11, 17) local stat = data.slurp('/proc/stat') local agg_total, agg_idle = data.cpu_times(stat or '') local load = total_counter:sample(agg_total, agg_idle) local y = inner.y -- Aggregate load, the headline number. card.font(cr, card.FONT_MONO, label_size, false) card.rgba(cr, colors.label) card.text(cr, inner.x, y + label_size, 'CPU') card.font(cr, card.FONT_HEAVY, big_size, false) card.rgba(cr, load and card.threshold(load, 25, 75, colors) or colors.label) card.text(cr, inner.x, y + label_size + big_size, load and string.format('%d%%', math.floor(load + 0.5)) or '--') local ey = y + label_size + big_size + 14 -- The equaliser: one bar per core, rising from a common baseline. local cores = data.per_cpu_times(stat or '') local n = #cores if n > 0 then local gap = math.max(2, inner.w * 0.006) local bw = (inner.w - gap * (n - 1)) / n local eh = clamp(inner.h * 0.42, 24, 220) -- Below about 3px a row of sixteen bars is an illegible smear; drop to the -- aggregate bar instead of drawing one. if bw >= 3 then for i, c in ipairs(cores) do counters[i] = counters[i] or data.new_cpu_counter() local pct = counters[i]:sample(c.total, c.idle) or 0 card.vbar(cr, inner.x + (i - 1) * (bw + gap), ey + eh, bw, eh, pct / 100, card.threshold(pct, 25, 75, colors), colors) end else card.bar(cr, inner.x, ey + eh - 8, inner.w, 8, (load or 0) / 100, card.threshold(load or 0, 25, 75, colors), colors) end ey = ey + eh + 16 end -- Memory. MemAvailable, not free: it already excludes reclaimable cache, so -- it is the figure a person recognises as "used". local mem = data.mem_info(data.slurp('/proc/meminfo') or '') if mem then local frac = mem.used / mem.total card.font(cr, card.FONT_MONO, row_size, false) card.rgba(cr, colors.label) card.text(cr, inner.x, ey, 'RAM') card.rgba(cr, colors.value) card.text_right(cr, inner.x + inner.w, ey, string.format('%s / %s', card.human(mem.used * 1024), card.human(mem.total * 1024))) ey = ey + 8 card.bar(cr, inner.x, ey, inner.w, 8, frac, card.threshold(frac * 100, 25, 75, colors), colors) ey = ey + 22 end -- Temperatures, each against its own ceiling. card.font(cr, card.FONT_MONO, row_size, false) local step = row_size * 1.8 for _, t in ipairs(TEMPS) do if ey + step > inner.y + inner.h then break end -- out of room: stop local v = data.sensor(t[1], t[2]) card.rgba(cr, colors.label) card.text(cr, inner.x, ey, t[3]) card.rgba(cr, v and card.threshold(v, t[4], t[5], colors) or colors.label) card.text_right(cr, inner.x + inner.w, ey, v and string.format('%d\u{00B0}', v) or '--') ey = ey + step end end return M