#!/usr/bin/lua -- Created by jiaqiang.ye@lnxall.com -- Dump Application running time -- 2023/03/27 local posix = require "posix" local invoker = require "invoker" -- kernel configuration, CONFIG_HZ: local g_HZ = nil local function dump_appinfo(pid) local path = invoker.readlink(string.format('/proc/%d/exe', pid)) if type(path) ~= "string" or string.len(path) == 0 then return false end local appn = string.match(path, "/([^/]+)$") if not appn then io.stderr:write(string.format("Error, failed to determine name: '%s'\n", path)) io.stderr:flush() return false end local appst = invoker.readfile(string.format('/proc/%d/stat', pid), invoker.TRIMEND) if type(appst) ~= "string" or string.len(appst) == 0 then return false end local s0, s1 = string.find(appst, "%(.+%)") if not s0 then io.stderr:write(string.format("Error, failed to determine AppName: '%s'\n", appn)) io.stderr:flush() return false end -- get invoke name: local appm = (s1 - s0) > 0x2 and appst:sub(s0 + 1, s1 - 1) or appn s1 = s1 + 0x2 if s1 >= string.len(appst) then io.stderr:write(string.format("Error, invalid '/proc/xxx/stat' for '%s'\n", appn)) io.stderr:flush() return false end local runtime = nil local left, count = appst:sub(s1), 0 for field in string.gmatch(left, "([^%s]+)") do count = count + 0x1 if count >= 20 then runtime = tonumber(field) break end end if not runtime then io.stderr:write(string.format("Error, cannot determine runtime for '%s'\n", appn)) io.stderr:flush() return false end local uptim = invoker.uptime() local duration = math.floor(runtime / g_HZ) -- seconds if uptim < duration then io.stderr:write(string.format("Error, invalid uptime and duration for '%s': %d, %d\n", appn, uptim, duration)) io.stderr:flush() return false end duration = uptim - duration local hour = math.floor(duration / 3600) local mins = math.floor((duration % 3600) / 60) return { ["appn"] = appn, ["appm"] = appm, ["pid"] = pid, ["duration"] = duration, ["hours"] = hour, ["mins"] = mins } end local function warn_about_hz() io.stderr:write("Warning, cannot determine CONFIG_HZ, select one of:\n") io.stderr:write("\texport CONFIG_HZ=100\n") io.stderr:write("\texport CONFIG_HZ=250\n") io.stderr:write("\texport CONFIG_HZ=300\n") io.stderr:write("\texport CONFIG_HZ=1000\n\n") g_HZ = 100 io.stderr:write("\t\Setting CONFIG_HZ=100, 这可能不是你想要滴\n\n") io.stderr:flush() return true end local function fetch_config_hz() local info = "==== NOTE THAT CONFIG_HZ=%d\n" local cfghz = posix.getenv("CONFIG_HZ") if type(cfghz) == "string" and string.len(cfghz) > 0 then local hz = tonumber(cfghz) if type(hz) == "number" and hz > 0 and hz == math.floor(hz) then g_HZ = hz io.stdout:write(string.format(info, hz)) io.stdout:flush() return true end end local cfgz = '/proc/config.gz' if posix.access(cfgz) ~= 0 then return warn_about_hz() end local gunzip = io.popen(string.format("gunzip -c %s", cfgz)) if not gunzip then io.stderr:write(string.format("Error, failed to decompress '%s'\n", cfgz)) io.stderr:flush() return false end for cfg in gunzip:lines() do local hz = string.match(cfg, "^CONFIG_HZ=(%d+)$") if hz then hz = tonumber(hz) end if type(hz) == "number" and hz > 0 and hz == math.floor(hz) then g_HZ = hz io.stdout:write(string.format(info, hz)) io.stdout:flush() local dummy = gunzip:read("*a") -- read the rest data gunzip:close(); gunzip = nil -- close read handle return true end end gunzip:close(); gunzip = nil return warn_about_hz() end local function padstr(ostr, lstr, right) local olen = string.len(ostr) if olen > lstr then return ostr:sub(1, lstr) end if olen == lstr then return ostr end if right then return string.format("%s%s", string.rep(" ", lstr - olen), ostr) end return string.format("%s%s", ostr, string.rep(" ", lstr - olen)) end local function dump_allapp() local apps = posix.glob("/proc/*/exe") if type(apps) ~= "table" then io.stderr:write("Error, no application found.\n") io.stderr:flush() return false end local appInfo, appnum, namelen = {}, 0, 1 for _, what in ipairs(apps) do local pid = string.match(what, "/proc/(%d+)/exe") if pid then pid = tonumber(pid) end if type(pid) == "number" then local appi = dump_appinfo(pid) if appi then local nlen = string.len(appi["appn"]) if nlen > namelen then namelen = nlen end appnum = appnum + 0x1 appInfo[appnum] = appi end end end namelen = namelen + 0x1 for _, what in ipairs(appInfo) do io.stdout:write(string.format( "%s => %s, PID: %d, duration: %d seconds, (%d hours, %d mins)\n", padstr(what.appn, namelen, true), padstr(what.appm, 15), what.pid, what.duration, what.hours, what.mins)) io.stdout:flush() end return true end if not fetch_config_hz() then os.exit(1) end dump_allapp(); os.exit(0)