#!/usr/bin/lua

-- Created by jiaqiang.ye@lnxall.com
-- Simple log files monitor for WEMS
-- 2025/10/31

local bit32 = require "bit32"
local invoker = require "invoker"
local tasklua = require "tasklua"

local gfmt = string.format

-- gloabl CONSTANTS:
local g_LOGDIR   = "/opt/wems/wems_boot_v2/logs"

-- global variables:
local g_compmap  = {}
local g_compressing = nil

local function glob_logs()
	local files = invoker.glob(gfmt("%s/*.log", g_LOGDIR), 0x4)
	if not files then return nil end

	local today = os.time()
	local logtab, lognum = {}, 0
	for _, file in ipairs(files) do
		local ftype, size, _, _, mtime = invoker.statfile(file)
		if ftype == "regular" then
			local year, month, day = string.match(file, "-(%d%d%d%d)-(%d%d)-(%d%d)%.")
			if day then
				year, month, day = tonumber(year), tonumber(month), tonumber(day)
			end

			local modify = mtime - (mtime % 86400)
			if day then
				modify = os.time({["year"] = year, ["month"] = month, ["day"] = day})
			end

			if today >= (modify + 3 * 86400) then
				io.stdout:write(gfmt("Found old logfile to be compressed: %s\n", file))
				io.stdout:flush()
				lognum = lognum + 1
				logtab[lognum] = { ["name"] = file, ["size"] = size, ["mtime"] = mtime }
			-- else
				-- io.stderr:write(gfmt("Skip NOT-SO-OLD uncompressed logfile: %s\n", file))
				-- io.stderr:flush()
			end
		end
	end

	if lognum == 0 then return nil end
	return logtab
end

local function entry_compress_task()
	local iflags = bit32.bor(invoker.NOWAIT, invoker.LOWPRI)

	while true do
		local nextlog, comp_pid, comp_time = nil, nil, 0
		for file, _ in pairs(g_compmap) do
			if invoker.statfile(file) == "regular" then
				nextlog = file
				g_compressing = true
				comp_time = invoker.uptime()
				invoker.unlink(file .. ".gz")
				comp_pid = invoker.invoke(iflags, "gzip", "-9", file)
				break
			end
		end

		if comp_pid then
			while true do
				local exited, eval = invoker.waitpid(comp_pid, true)
				local nowtim = invoker.uptime()
				if exited == nil or exited then
					if type(eval) ~= "number" then eval = 2021 end
					io.stdout:write(gfmt("Compressing logfile done: %s => %d, duration: %d seconds\n",
						nextlog, eval, nowtim - comp_time))
					io.stdout:flush()
					break
				end

				if (nowtim - comp_time) >= 3600 then
					io.stderr:write(gfmt("Warning, gzip takes too long, PID: %d, duration: %d seconds\n",
						pid, nowtim - comp_time))
					io.stderr:flush()
					invoker.kill(pid, 9)
				end
				tasklua.delay(5000)
			end
		end

		if nextlog then
			g_compmap[nextlog] = nil
			invoker.unlink(nextlog) -- delete existing file
		else
			if g_compressing then
				g_compressing = false
			end
			tasklua.delay(120000)
		end
	end
end

local function entry_glob_task()
	local upt = invoker.uptime()
	if upt < 240 then tasklua.delay((240 - upt) * 1000) end

	while true do
		local logs = glob_logs()
		if logs then
			for _, linfo in pairs(logs) do
				local file = linfo.name
				if not g_compmap[file] then
					g_compmap[file] = linfo
				end
			end

			-- remove table explicitly:
			local num = #logs + 1
			for idx = 1, num do
				logs[idx] = nil
			end
		end
		-- delay for 5 minutes:
		tasklua.delay(300000)
	end
end

local function glob_and_unlink()
	local files = invoker.glob(gfmt("%s/*.gz", g_LOGDIR), 0x4)
	if not files then return nil end

	local today = os.time()
	for _, file in ipairs(files) do
		local ftype, size, _, _, mtime = invoker.statfile(file)
		if ftype == "regular" then
			local year, month, day = string.match(file, "-(%d%d%d%d)-(%d%d)-(%d%d)%.")
			if day then
				year, month, day = tonumber(year), tonumber(month), tonumber(day)
			end

			local isold, modify = false, mtime - (mtime % 86400)
			if day then
				modify = os.time({["year"] = year, ["month"] = month, ["day"] = day})
			end

			if today >= (modify + 7 * 86400) then
				io.stdout:write(gfmt("Deleting old compressed logfile: %s\n", file))
				io.stdout:flush()
				invoker.unlink(file)
			end
		end
	end
	return true
end

local function entry_remove_task()
	tasklua.delay(30000)
	while true do
		if not g_compressing then
			glob_and_unlink()
		end
		tasklua.delay(600000)
	end
end

local function entry_coredump_task()
	local itercnt, empty = 0, {}
	local patterns = { [0] = "/run/*.core", [1] = "/tmp/*.core", }
	while true do
		itercnt = itercnt + 1
		local sorted, num, tsize = {}, 0, 0
		local cores = invoker.glob(patterns[bit32.band(itercnt, 1)], 4)
		if not cores then cores = empty end
		for _, core in ipairs(cores) do
			local ftype, fsize, _, _, mtime = invoker.statfile(core)
			if ftype == "regular" then
				num = num + 1
				tsize = tsize + fsize
				sorted[num] = { ["name"] = core, ["size"] = fsize, ["mtime"] = mtime }
			end
		end

		if num >= 2 then
			table.sort(sorted, function (fa, fb)
				if fa and fb then return fa.mtime < fb.mtime end
				return false
			end)
		end
		local delsize = 0
		for _, corei in ipairs(sorted) do
			if (tsize - delsize) >= 536870912 then -- 512MB
				invoker.unlink(corei.name)
				delsize = delsize + corei.size
				io.stdout:write(gfmt("Deleting corefile: %s, size: %d, mtime: %s\n",
					corei.name, corei.size, os.date(nil, corei.mtime)))
			else
				io.stdout:write(gfmt("Keeping corefile: %s, size: %d, mtime: %s\n",
					corei.name, corei.size, os.date(nil, corei.mtime)))
			end
			io.stdout:flush()
		end
		tasklua.delay(600000)
	end
end

local function mainfunc()
	invoker.setname("WEMSLOGS")
	tasklua.taskadd(entry_compress_task,  true)
	tasklua.taskadd(entry_glob_task,      true)
	tasklua.taskadd(entry_remove_task,    true)
	tasklua.taskadd(entry_coredump_task,  true)
	while true do
		if not tasklua.taskloop(900000) then
			break
		end
	end
end
mainfunc()
os.exit(1)
