-- Readers for /proc and /sys. -- -- Every function takes the file *contents* as a string rather than reading the -- file itself, so the parsers are testable against fixtures without mocking the -- filesystem. The thin read-and-parse wrappers live at the bottom. local M = {} -- Aggregate CPU jiffies from /proc/stat. Returns total, idle. -- iowait counts as idle: a core waiting on disk is not doing work, and -- treating it as busy makes a disk-bound system look CPU-bound. function M.cpu_times(stat) local line = stat:match('^cpu%s+([^\n]+)') if not line then return nil, nil end local v = {} for n in line:gmatch('%d+') do v[#v + 1] = tonumber(n) end if #v < 5 then return nil, nil end local total = 0 for _, n in ipairs(v) do total = total + n end return total, v[4] + v[5] -- idle + iowait end -- /proc/meminfo, values in kB as the file states them. function M.mem_info(meminfo) local function field(name) return tonumber(meminfo:match(name .. ':%s+(%d+)')) end local total = field('MemTotal') local available = field('MemAvailable') if not (total and available) then return nil end return { total = total, free = field('MemFree'), available = available, -- MemAvailable already excludes reclaimable cache, so this is the figure a -- user recognises as "used", unlike total-free which counts cache. used = total - available, } end -- hwmon temp*_input is millidegrees C. Returns whole degrees, or nil. -- nil rather than 0 on failure: 0 C is a legitimate reading. function M.millidegrees(s) if not s then return nil end local n = tonumber(s:match('^%s*(-?%d+)')) if not n then return nil end return math.floor(n / 1000 + 0.5) end -- Read a whole file, returning nil if it cannot be read. Used by the wrappers -- so a vanished sysfs path degrades to nil instead of raising. function M.slurp(path) local f = io.open(path, 'r') if not f then return nil end local s = f:read('*a') f:close() return s end -- Find a hwmon directory by the exact contents of its `name` file. -- -- Globbing by name, never by a fixed hwmon index: indices drift across kernel -- and hardware reorders, and a stale index silently reports a different chip. -- Carried over from the previous conky config, where this was the hard-won bit. function M.hwmon_dir(name) local p = io.popen('grep -lx ' .. ("%q"):format(name) .. ' /sys/class/hwmon/hwmon*/name 2>/dev/null') if not p then return nil end local hit = p:read('*l') p:close() if not hit then return nil end return hit:match('^(.*)/name$') end return M