#!/opt/lnxall_app/bin/lua

-- Created by jiaqiang.ye@lnxall.com
-- Simple fetch_cpu_mem_status.sh for non-openwrt system
-- 2023/07/27

local cjson = require "cjson"
local invoker = require "invoker"

-- global variables
local PLATINFO = "/opt/lnxall_app/platspec/platinfo.json"

local function memory_usage()
	local meminfo = invoker.readfile("/proc/meminfo")
	if type(meminfo) ~= "string" then
		io.stderr:write("Error, failed to read '/proc/meminfo'\n")
		io.stderr:flush()
		return "0"
	end

	local total = string.match(meminfo, "MemTotal:%s+(%d+)%s+kB")
	if total then total = tonumber(total) end
	local freem = string.match(meminfo, "MemFree:%s+(%d+)%s+kB")
	if freem then freem = tonumber(freem) end
	if type(total) ~= "number" or type(freem) ~= "number" or freem > total then
		io.stderr:write("Error, failed to parse '/proc/meminfo'\n")
		io.stderr:flush()
		return "0"
	end
	return tostring(math.floor((total - freem) / total * 100))
end

local function cpu_usage()
	local busy = "0"
	local okay, topinfo = invoker.invoke(invoker.NOSTDIO + invoker.OUTPUT,
		"busybox", "top", "-d2", "-n1") -- only process output from busybox/top utility
	if okay ~= 0 or type(topinfo) ~= "string" then
		io.stderr:write("Error, failed to execute top for CPU usage.\n")
		io.stderr:flush()
		return busy
	end

	for line in string.gmatch(topinfo, "[^\r\n]+") do
		if string.len(line) >= 16 and line:sub(1, 4) == "CPU:" then
			local idle = string.match(line, 'nic%s+([%d.]+)%%%s+idle')
			if idle then idle = tonumber(idle) end
			if type(idle) == "number" then
				busy = tostring(math.floor(100.50 - idle))
			else
				io.stderr:write(string.format("Error, invalid CPU usage: '%s'\n", line))
				io.stderr:flush()
			end
			break
		end
	end
	return busy
end

local function cpu_temp()
	local num, total, idx = 0, 0, 0
	local thermal = "/sys/class/thermal/thermal_zone%d/temp"
	while true do
		local tpath = string.format(thermal, idx)
		local temp = invoker.readfile(tpath, invoker.TRIMEND)
		if type(temp) ~= "string" or #temp == 0 then
			break
		end
		temp = tonumber(temp)
		if type(temp) == "number" then
			num = num + 1
			total = total + temp
		end
		idx = idx + 1
	end

	if num == 0 then return "0" end
	return tostring(math.floor(total / (num * 1000)))
end

local function get_ipv4addr(ndev)
	local eval, ipaddr = invoker.invoke(invoker.NOSTDIO + invoker.OUTPUT,
		"ip", "addr", "show", "dev", ndev)
	if eval ~= 0 or type(ipaddr) ~= "string" then return nil end

	local ipv4 = string.match(ipaddr, "%s+inet%s+([%d./]+)%s+")
	if not ipv4 then return nil end
	return ipv4
end

local function get_gwaddr(ndev)
	local eval, iproute = invoker.invoke(invoker.NOSTDIO + invoker.OUTPUT,
		"ip", "route", "show")
	if eval ~= 0 or type(iproute) ~= "string" then return nil end
	local gwip = string.match(iproute, "default via%s+([%d.]+)%s+dev%s+" .. ndev)
	if not gwip then return nil end
	return gwip
end

local function add_ipinfo(itab)
	local phdl = io.open(PLATINFO, "rb")
	if not phdl then return false end
	local pdat = phdl:read("*a")
	phdl:close(); phdl = nil
	if type(pdat) ~= "string" then return false end

	local okay, jdat = pcall(cjson.decode, pdat)
	if not okay or type(jdat) ~= "table" then
		io.stderr:write(string.format("Error, corrupted JSON file: %s\n", PLATINFO))
		io.stderr:flush()
		return false
	end

	local neti = jdat["netdev"]
	if type(neti) ~= "table" then return false end

	local wandev = neti["wan"]
	local ipaddr = type(wandev) == "string" and get_ipv4addr(wandev) or nil
	if not ipaddr then
		wandev = neti["wwan"]
		ipaddr = type(wandev) == "string" and get_ipv4addr(wandev) or nil
	end
	if ipaddr then
		itab["wansubnet"] = ipaddr
		itab["gwipaddr"] = get_gwaddr(wandev)
	end
	return true
end

local function mainfunc()
	local infotab = {}
	infotab["mem"] = memory_usage()
	infotab["cpu"] = cpu_usage()
	infotab["uptime"] = invoker.uptime()
	infotab["temp"] = cpu_temp()
	add_ipinfo(infotab)

	io.stdout:write(cjson.encode(infotab))
	io.stdout:flush()
end

mainfunc()
os.exit(0)
