diff options
Diffstat (limited to 'widgets/system.lua')
| -rw-r--r-- | widgets/system.lua | 198 |
1 files changed, 0 insertions, 198 deletions
diff --git a/widgets/system.lua b/widgets/system.lua deleted file mode 100644 index c1cdf67..0000000 --- a/widgets/system.lua +++ /dev/null @@ -1,198 +0,0 @@ --- 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() - --- Sampled every ~2s whatever the draw rate; the values between samples are --- the last ones read. 1.9 not 2: draws jitter by a few ms either way. -local gate = data.new_gate(1.9) -local load, pcts = nil, {} - --- Read once, not per frame. Neither can change without a reboot, and --- re-reading /proc/cpuinfo every two seconds for a constant is waste: the file --- repeats its model-name line once per thread. --- --- `false` is the cached miss, distinct from nil meaning "not looked up yet", --- so an absent DMI table is not re-read on every draw. -local cpu_name, board = nil, nil - -local function hardware() - if cpu_name == nil then - cpu_name = data.cpu_model(data.slurp('/proc/cpuinfo') or '') or false - end - if board == nil then - local dmi = '/sys/devices/virtual/dmi/id/' - board = data.board_name(data.slurp(dmi .. 'board_vendor'), - data.slurp(dmi .. 'board_name')) or false - end - return cpu_name or nil, board or nil -end - -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 - - if gate() then - local stat = data.slurp('/proc/stat') or '' - load = total_counter:sample(data.cpu_times(stat)) - for i, c in ipairs(data.per_cpu_times(stat)) do - counters[i] = counters[i] or data.new_cpu_counter() - pcts[i] = counters[i]:sample(c.total, c.idle) or 0 - end - end - local load_txt = load and string.format('%d%%', math.floor(load + 0.5)) or '--' - local temp_n = #TEMPS - local cpu_txt, board_txt = hardware() - - -- Fluid type. One base size S drives the label, headline, RAM and every - -- temperature row; the equaliser then absorbs the vertical room left over, - -- so the card fills its cell and S shrinks only where a row would overrun - -- the width. card.fit_unit supplies the width side of that. - local LABEL_F, BIG_F, ROW_F = 0.30, 1.0, 0.32 - local groups = { - { { card.title_str(card.ICON.system, 'CPU'), card.FONT_MONO, LABEL_F }, - { load_txt, card.FONT_HEAVY, BIG_F } }, - { { cpu_txt or '', card.FONT_MONO, ROW_F } }, - { { board_txt or '', card.FONT_MONO, ROW_F } }, - { { 'RAM', card.FONT_MONO, ROW_F }, { '999.9G / 999.9G', card.FONT_MONO, ROW_F } }, - } - for _, t in ipairs(TEMPS) do - groups[#groups + 1] = { { t[3], card.FONT_MONO, ROW_F }, - { '888\u{00B0}', card.FONT_MONO, ROW_F } } - end - -- Height the non-equaliser content needs, in units of S, plus a reserve for - -- the equaliser band. The smaller of that budget and the width fit is S. - -- The two identification rows come out of the equaliser's budget: it is the - -- card's designated slack absorber, so it is what gives up the height. - local id_rows = (cpu_txt and 1 or 0) + (board_txt and 1 or 0) - local fixed_units = 1.00 + 1.13 + temp_n * ROW_F * 1.8 + 0.10 - + id_rows * ROW_F * 1.5 - local S = clamp(math.min(inner.h * 0.96 / (fixed_units + 1.7), - card.fit_unit(cr, inner.w * 0.96, groups, 100)), 10, 72) - local label_size = S * LABEL_F - local big_size = S * BIG_F - local row_size = S * ROW_F - - local y = inner.y - - -- Aggregate load, the headline number. It takes the header line, right - -- aligned like the big value on every other card, with the label top-left. - 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_right(cr, inner.x + inner.w, y + big_size, load_txt) - - card.font(cr, card.FONT_MONO, label_size, false) - card.title(cr, inner.x, y + label_size, card.ICON.system, 'CPU', colors) - - -- The hardware this card is about, under the header. Truncated by character, - -- never by byte: a model string can carry a multi-byte character and half of - -- one draws as a replacement box. - -- Two accents rather than one label colour: the rows name two different - -- pieces of hardware, and giving each its own hue separates them at a glance - -- without a prefix like 'CPU:' eating the width. `heading` and `highlight` - -- are the two roles that carry no state meaning, so they can stand for an - -- identity; ok/warning/critical could not, since those say how something is - -- doing and these strings never change. - local id_y = y + big_size + row_size * 0.2 - if cpu_txt or board_txt then - card.font(cr, card.FONT_MONO, row_size, false) - local id_limit = math.max(8, math.floor(inner.w / (row_size * 0.55))) - if cpu_txt then - card.rgba(cr, colors.heading) - card.text(cr, inner.x, id_y, card.truncate(cpu_txt, id_limit)) - id_y = id_y + row_size * 1.5 - end - if board_txt then - card.rgba(cr, colors.highlight) - card.text(cr, inner.x, id_y, card.truncate(board_txt, id_limit)) - id_y = id_y + row_size * 1.5 - end - end - - -- The equaliser: one bar per core, rising from a common baseline. It takes - -- the room left between the header and the RAM/temperature block, so it is - -- the card's slack absorber. - -- Below the identification rows when there are any, otherwise where it was. - local eq_top = (cpu_txt or board_txt) and (id_y + S * 0.10) or (y + big_size + S * 0.30) - local bar_h = S * 0.18 - local step = row_size * 1.8 - -- The floor was a flat 18px. With the identification rows above it the - -- equaliser can genuinely run out of room, and a fixed floor means it keeps - -- height it does not have and the temperature rows draw over it. Zero is a - -- legitimate outcome: the guard below skips the band when it has no height. - local eh = clamp((inner.y + inner.h) - eq_top - (S * 1.13 + temp_n * step), - 0, math.huge) - local n = #pcts - if n > 0 and eh >= 8 then - local gap = math.max(2, inner.w * 0.006) - local bw = (inner.w - gap * (n - 1)) / n - -- 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, pct in ipairs(pcts) do - card.vbar(cr, inner.x + (i - 1) * (bw + gap), eq_top + eh, bw, eh, - pct / 100, card.threshold(pct, 25, 75, colors), colors) - end - else - card.bar(cr, inner.x, eq_top + eh - bar_h, inner.w, bar_h, (load or 0) / 100, - card.threshold(load or 0, 25, 75, colors), colors) - end - end - local ey = eq_top + eh + S * 0.30 - - -- 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 + S * 0.30 - card.bar(cr, inner.x, ey, inner.w, bar_h, frac, - card.threshold(frac * 100, 25, 75, colors), colors) - ey = ey + bar_h + S * 0.35 - end - - -- Temperatures, each against its own ceiling. - card.font(cr, card.FONT_MONO, row_size, false) - 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 |
