From 518b2969108ed3d728e562614c05e40400129240 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Thu, 17 Sep 2026 12:10:22 +0200 Subject: feat: parse df and du output for the disk and cache cards --- lib/data.lua | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) (limited to 'lib') diff --git a/lib/data.lua b/lib/data.lua index 9bd5433..acf7a0f 100644 --- a/lib/data.lua +++ b/lib/data.lua @@ -152,4 +152,80 @@ function M.sensor_raw(chip, file) return tonumber(s:match('^%s*(-?%d+)')) end +-- Parse `df -P -B1` output into a list of filesystems. +-- +-- The mountpoint is taken as the LAST field, not the sixth. df -P guarantees +-- one line per filesystem with the mountpoint last, and an NFS device reads +-- 'server:/export', so counting fields from the left is fragile in a way +-- counting from the right is not. +-- +-- Sizes are bytes because the sampler passes -B1. Never parse `df -h` output +-- here: the user's shell aliases df to df -h, so a sampler calling bare df +-- would hand this function '1.6G' where it expects an integer. +function M.df_parse(text) + local out = {} + if type(text) ~= 'string' then return out end + for line in text:gmatch('[^\n]+') do + -- A data row ends in 'NN% /some/path'. The header ends in 'Mounted on', + -- which fails the percent match, so it is skipped without a special case. + local size, used, pct, mount = line:match('(%d+)%s+(%d+)%s+%d+%s+(%d+)%%%s+(%S+)%s*$') + if size then + -- The device is kept so a caller can tell an NFS mount from a local one + -- and recover its server, which is how the disks card labels the two + -- shares without hardcoding an address in the repo. + local dev = line:match('^(%S+)') + out[#out + 1] = { + mount = mount, + dev = dev, + host = dev and dev:match('^([^/:]+):') or nil, + size = tonumber(size), + used = tonumber(used), + pct = tonumber(pct), + } + end + end + return out +end + +-- Human-readable size to bytes: '1.1G' -> 1181116006. +-- +-- Needed because du -sh is what the sampler runs (its output is also what the +-- card displays), and 'biggest' has to be decided numerically: sorted as +-- strings, '245M' beats '1.1G'. +local SUFFIX = { K = 1024, M = 1024^2, G = 1024^3, T = 1024^4, P = 1024^5 } + +function M.human_bytes(s) + if type(s) ~= 'string' then return nil end + local n, suf = s:match('^%s*([%d%.]+)%s*([KMGTP]?)') + n = tonumber(n) + if not n then return nil end + return n * (SUFFIX[suf] or 1) +end + +-- Parse `du -sh` output: the first line is the total, the rest are children +-- already sorted largest-first by the sampler. Returns nil when there is +-- nothing usable, so the widget can draw its "no data" state. +function M.du_parse(text) + if type(text) ~= 'string' or text == '' then return nil end + local lines = {} + for line in text:gmatch('[^\n]+') do lines[#lines + 1] = line end + if #lines == 0 then return nil end + + local function entry(line) + local label, path = line:match('^(%S+)%s+(.+)$') + if not label then return nil end + return { label = label, bytes = M.human_bytes(label) or 0, + name = path:match('([^/]+)/?$') or path, path = path } + end + + local total = entry(lines[1]) + if not total then return nil end + local items = {} + for i = 2, #lines do + local e = entry(lines[i]) + if e then items[#items + 1] = e end + end + return { total = total, items = items } +end + return M -- cgit v1.2.3