-- Advprofiler -- A mod profiler written in need for a profiler for advtrains -- (c)2019 orwell96 advprofiler = {} local WINDOW_SIZE = 1024 --[[ Unit table: ["unit"] = { -- st=step, run = one enter..leave st_counts = {...} -- total count values (indexed by step) st_times_tot = {...} -- total time spent in unit per step st_times_max = {...} -- max time spent in single run st_times_avg = {...} -- avg time spent in single run st_stepcount = 3 -- count of actual steps in st table* rn_times = {...} -- times of individual runs in current step rn_runcount = 3 -- count of actual runs in current step* rn_count = 4 -- 'count' value in current step [enter_ts = ...] -- Timestamp when unit was last entered } * to prevent expensive reallocations in tables, track the current table length in external variables and only overwrite values. ]] local _profs = {} advprofiler._profilers = _profs local function logs(t) minetest.log("action", "[advprofiler] "..t) end local function warning(prof, unit, text) minetest.log("warning", "[advprofiler] ("..prof.name..":"..unit..") "..text) end local clock = os.clock local function accumulate(list, count) local tot, max = 0, nil local li for i=1,count do li = list[i] tot = tot + li max = math.max((max or li), li) end local avg = tot/count return tot, avg, max end -- prototype for profiler instances (index metatable) local profiler = {} function profiler:unit_get(unit) if self.units[unit] then return self.units[unit] end -- create unit new self.units[unit] = { st_counts = {}, st_times_tot = {}, st_times_max = {}, st_times_avg = {}, st_stepcount = 0, rn_times = {}, rn_runcount = 0, rn_count = 0, } return self.units[unit] end function profiler:count(unitn, count_inc) local unit = self:unit_get(unitn) unit.rn_count = unit.rn_count + (count_inc or 1) end function profiler:enter(unitn, count_inc) local unit = self:unit_get(unitn) if unit.enter_ts then warning(self,unitn,"Unit is already entered!") return end unit.rn_count = unit.rn_count + (count_inc or 1) unit.enter_ts = clock() end function profiler:leave(unitn) local t1 = clock() local unit = self:unit_get(unitn) if not unit.enter_ts then warning(self,unitn,"Unit was never entered!") return end local td = t1 - unit.enter_ts if td<0 then warning(self,unitn,"Time difference smaller than 0 ?! td="..td) return end unit.rn_runcount = unit.rn_runcount + 1 unit.rn_times[unit.rn_runcount] = td unit.enter_ts = nil end function profiler:end_step() for unitn, unit in pairs(self.units) do local sc = unit.st_stepcount + 1 unit.st_counts[sc] = unit.rn_count local rt_tot, rt_avg, rt_max = accumulate(unit.rn_times, unit.rn_runcount) unit.st_times_tot[sc] = rt_tot unit.st_times_max[sc] = rt_max unit.st_times_avg[sc] = rt_avg unit.st_stepcount = sc unit.rn_runcount = 0 unit.rn_count = 0 end end function profiler:end_window(print_table) table.insert(print_table, "=== "..self.name.." ===") for unitn, unit in pairs(self.units) do if unit.enter_ts then warning(self,unitn,"Unit still entered at end of step! (discarding)") unit.enter_ts = nil end local sc = unit.st_stepcount local rep = { profiler = self, unit = unitn, } rep.count_tot, rep.count_avg, rep.count_max = accumulate(unit.st_counts, sc) rep.time_tot, rep.time_avg, rep.time_max = accumulate(unit.st_times_tot, sc) local report_f = unit.report or advprofiler.report table.insert(print_table, unitn..":\t"..report_f(rep)) unit.st_stepcount = 0 end end --===========-- local prof_mt = {__index = profiler} -- creates a new profiler instance and returns it function advprofiler.new_profiler(name) local new = { name = name, units = {}, } setmetatable(new, prof_mt) table.insert(_profs, new) logs("Registered profiler "..name) return new end function advprofiler.step() for _,prof in ipairs(_profs) do prof:end_step() end end function advprofiler.end_window_and_report(cyc) local print_table = { "--- Profiling report for cycle "..cyc.." ---" } for _,prof in ipairs(_profs) do prof:end_window(print_table) end for _,l in ipairs(print_table) do logs(l) end end --[[ Report table: (all values are 'per step' ,tot=total over whole window) rep.count_tot, rep.count_avg, rep.count_max rep.time_tot, rep.time_avg, rep.time_max ]] function advprofiler.report(rep) return string.format("Count avg=%d\tmax=%d\ttotal=%d\tTime avg=%dms\tmax=%dms\ttotal=%dms", rep.count_avg, rep.count_max, rep.count_tot, rep.time_avg*1000, rep.time_max*1000, rep.time_tot*1000) end --===========-- local stepcnt = 0 local cyccnt = 0 minetest.register_globalstep(function(dtime) advprofiler.step() stepcnt = (stepcnt + 1) % WINDOW_SIZE if stepcnt == 0 then cyccnt = cyccnt + 1 advprofiler.end_window_and_report(cyccnt) end end)