Files
fs25-adsmartpickup/FS25_ADSmartPickup/adProfiler.lua
T

124 lines
5.4 KiB
Lua

-- AD Profiler
-- Måler Smart Pickups indgange (AutoDrive-hooks, update-løkken, GUI) i spillet: kald, samlet tid og det
-- værste enkeltkald pr. minut, plus det værste frame i alt. Tager noget over REPORT_MIN_MS, skrives top-listen
-- i log.txt ("ADSmartPickup: profil …"), så et hak kan føres tilbage til præcis hvilken del der gjorde det.
-- Indpakningen sker på modul-tabellernes felter; hooksene slår funktionerne op ved hvert kald, så de måles.
ADProfiler = {}
ADProfiler.WINDOW_MS = 60000
ADProfiler.REPORT_MIN_MS = 2
ADProfiler.TOP = 6
ADProfiler.stats = {}
ADProfiler.frameMs = 0
ADProfiler.worstFrameMs = 0
ADProfiler.windowStart = nil
ADProfiler.depth = 0
ADProfiler.installed = false
function ADProfiler.clock()
local ok, ms = pcall(function() return netGetTime() end)
if ok and type(ms) == "number" then return ms end
return nil
end
local function record(name, started, ...)
local finished = ADProfiler.clock()
ADProfiler.depth = ADProfiler.depth - 1
if started ~= nil and finished ~= nil then
local spent = finished - started
local stat = ADProfiler.stats[name]
if stat == nil then
stat = {calls = 0, total = 0, max = 0}
ADProfiler.stats[name] = stat
end
stat.calls = stat.calls + 1
stat.total = stat.total + spent
if spent > stat.max then stat.max = spent end
-- kun yderste måling tæller i frame-summen (indlejrede måles også hver for sig)
if ADProfiler.depth == 0 then ADProfiler.frameMs = ADProfiler.frameMs + spent end
end
return ...
end
-- Pak tbl[key] ind under navnet name (gør intet hvis funktionen ikke findes).
function ADProfiler.wrap(tbl, key, name)
if type(tbl) ~= "table" or type(tbl[key]) ~= "function" then return end
local original = tbl[key]
tbl[key] = function(...)
ADProfiler.depth = ADProfiler.depth + 1
return record(name, ADProfiler.clock(), original(...))
end
end
-- Kaldes én gang pr. frame fra ADSmartPickup:update: afslutter frame-summen og rapporterer pr. minut.
function ADProfiler.frame(now)
if ADProfiler.frameMs > ADProfiler.worstFrameMs then ADProfiler.worstFrameMs = ADProfiler.frameMs end
ADProfiler.frameMs = 0
-- (en fejl i en indpakket funktion springer record over; dybden nulstilles derfor hvert frame)
ADProfiler.depth = 0
ADProfiler.windowStart = ADProfiler.windowStart or now
if now - ADProfiler.windowStart < ADProfiler.WINDOW_MS then return end
ADProfiler.report()
ADProfiler.windowStart = now
end
function ADProfiler.report()
local rows = {}
for name, stat in pairs(ADProfiler.stats) do
table.insert(rows, {name = name, stat = stat})
end
table.sort(rows, function(a, b) return a.stat.max > b.stat.max end)
local worst = rows[1] ~= nil and rows[1].stat.max or 0
if worst >= ADProfiler.REPORT_MIN_MS or ADProfiler.worstFrameMs >= ADProfiler.REPORT_MIN_MS then
local parts = {}
for index = 1, math.min(ADProfiler.TOP, #rows) do
local stat = rows[index].stat
table.insert(parts, string.format("%s max %.1f ms (%d kald, i alt %.0f ms)", rows[index].name, stat.max, stat.calls, stat.total))
end
Logging.info("ADSmartPickup: profil sidste minut — værste frame %.1f ms: %s", ADProfiler.worstFrameMs, table.concat(parts, "; "))
end
ADProfiler.stats = {}
ADProfiler.worstFrameMs = 0
end
-- Indpak alle kendte indgange. Kaldes én gang efter at hooksene er installeret.
function ADProfiler.install()
if ADProfiler.installed then return end
ADProfiler.installed = true
local targets = {
{ADUnloadWait, "beforeUpdate", "aflæsning/Wait (pr. vogn)"},
{ADUnloadWait, "adoptOrphans", "genoptag ventende"},
{ADUnloadWait, "releaseInactive", "slip standsede"},
{ADOutbound, "beforeLoadUpdate", "udkørsel pålæsning (pr. vogn)"},
{ADOutbound, "beforeUnloadUpdate", "udkørsel aflæsning (pr. vogn)"},
{ADOutbound, "beforeLoadFinished", "udkørsel runde"},
{ADOutbound, "choosePickup", "udkørsel turvalg"},
{ADOutbound, "holdSupplyVehicle", "forsyning hold"},
{ADOutbound, "releaseInactive", "udkørsel slip"},
{ADLoadSwap, "tryFromWait", "læs-bytte fra Wait"},
{ADSmartPickup, "shouldWaitForLoading", "vent ved pålæsning"},
{ADSmartPickup, "choosePickup", "forsyning kildevalg"},
{ADSmartPickup, "findSupplyPickup", "forsyning kildesøgning"},
{ADRunsController, "applyVehicleNames", "traktornavne"},
{ADFieldJobs, "tick", "markarbejde tick"},
{ADFields, "scanStep", "markscanning"},
{ADFieldWork, "follow", "markarbejde sæt"},
{ADFieldWork, "dispatch", "markarbejde udsendelse"},
{ADFieldWork, "getRigs", "markflåde"},
{ADBuildings, "list", "bygningsliste"},
{ADSources, "getFarmInventory", "gårdens lagre"},
{ADWaitPool, "hasRouteTo", "rutetjek"},
{ADWaitPool, "hasRouteBothWays", "rutetjek frem+tilbage"},
{ADChain, "evaluate", "kæde"},
{ADChainData, "collect", "kæde: læs gården"},
}
for _, target in ipairs(targets) do
ADProfiler.wrap(target[1], target[2], target[3])
end
if SmartPickupFrame ~= nil then
ADProfiler.wrap(SmartPickupFrame, "refreshLive", "menu opdatering")
ADProfiler.wrap(SmartPickupFrame, "rebuild", "menu genopbygning")
end
end