aboutsummaryrefslogtreecommitdiffstats
path: root/widgets/system.lua
diff options
context:
space:
mode:
Diffstat (limited to 'widgets/system.lua')
-rw-r--r--widgets/system.lua198
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