123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- --Minetest
- --Copyright (C) 2016 T4im
- --
- --This program is free software; you can redistribute it and/or modify
- --it under the terms of the GNU Lesser General Public License as published by
- --the Free Software Foundation; either version 2.1 of the License, or
- --(at your option) any later version.
- --
- --This program is distributed in the hope that it will be useful,
- --but WITHOUT ANY WARRANTY; without even the implied warranty of
- --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- --GNU Lesser General Public License for more details.
- --
- --You should have received a copy of the GNU Lesser General Public License along
- --with this program; if not, write to the Free Software Foundation, Inc.,
- --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- local setmetatable = setmetatable
- local pairs, format = pairs, string.format
- local min, max, huge = math.min, math.max, math.huge
- local core = core
- local profiler = ...
- -- Split sampler and profile up, to possibly allow for rotation later.
- local sampler = {}
- local profile
- local stats_total
- local logged_time, logged_data
- local _stat_mt = {
- get_time_avg = function(self)
- return self.time_all/self.samples
- end,
- get_part_avg = function(self)
- if not self.part_all then
- return 100 -- Extra handling for "total"
- end
- return self.part_all/self.samples
- end,
- }
- _stat_mt.__index = _stat_mt
- function sampler.reset()
- -- Accumulated logged time since last sample.
- -- This helps determining, the relative time a mod used up.
- logged_time = 0
- -- The measurements taken through instrumentation since last sample.
- logged_data = {}
- profile = {
- -- Current mod statistics (max/min over the entire mod lifespan)
- -- Mod specific instrumentation statistics are nested within.
- stats = {},
- -- Current stats over all mods.
- stats_total = setmetatable({
- samples = 0,
- time_min = huge,
- time_max = 0,
- time_all = 0,
- part_min = 100,
- part_max = 100
- }, _stat_mt)
- }
- stats_total = profile.stats_total
- -- Provide access to the most recent profile.
- sampler.profile = profile
- end
- ---
- -- Log a measurement for the sampler to pick up later.
- -- Keep `log` and its often called functions lean.
- -- It will directly add to the instrumentation overhead.
- --
- function sampler.log(modname, instrument_name, time_diff)
- if time_diff <= 0 then
- if time_diff < 0 then
- -- This **might** have happened on a semi-regular basis with huge mods,
- -- resulting in negative statistics (perhaps midnight time jumps or ntp corrections?).
- core.log("warning", format(
- "Time travel of %s::%s by %dµs.",
- modname, instrument_name, time_diff
- ))
- end
- -- Throwing these away is better, than having them mess with the overall result.
- return
- end
- local mod_data = logged_data[modname]
- if mod_data == nil then
- mod_data = {}
- logged_data[modname] = mod_data
- end
- mod_data[instrument_name] = (mod_data[instrument_name] or 0) + time_diff
- -- Update logged time since last sample.
- logged_time = logged_time + time_diff
- end
- ---
- -- Return a requested statistic.
- -- Initialize if necessary.
- --
- local function get_statistic(stats_table, name)
- local statistic = stats_table[name]
- if statistic == nil then
- statistic = setmetatable({
- samples = 0,
- time_min = huge,
- time_max = 0,
- time_all = 0,
- part_min = 100,
- part_max = 0,
- part_all = 0,
- }, _stat_mt)
- stats_table[name] = statistic
- end
- return statistic
- end
- ---
- -- Update a statistic table
- --
- local function update_statistic(stats_table, time)
- stats_table.samples = stats_table.samples + 1
- -- Update absolute time (µs) spend by the subject
- stats_table.time_min = min(stats_table.time_min, time)
- stats_table.time_max = max(stats_table.time_max, time)
- stats_table.time_all = stats_table.time_all + time
- -- Update relative time (%) of this sample spend by the subject
- local current_part = (time/logged_time) * 100
- stats_table.part_min = min(stats_table.part_min, current_part)
- stats_table.part_max = max(stats_table.part_max, current_part)
- stats_table.part_all = stats_table.part_all + current_part
- end
- ---
- -- Sample all logged measurements each server step.
- -- Like any globalstep function, this should not be too heavy,
- -- but does not add to the instrumentation overhead.
- --
- local function sample(dtime)
- -- Rare, but happens and is currently of no informational value.
- if logged_time == 0 then
- return
- end
- for modname, instruments in pairs(logged_data) do
- local mod_stats = get_statistic(profile.stats, modname)
- if mod_stats.instruments == nil then
- -- Current statistics for each instrumentation component
- mod_stats.instruments = {}
- end
- local mod_time = 0
- for instrument_name, time in pairs(instruments) do
- if time > 0 then
- mod_time = mod_time + time
- local instrument_stats = get_statistic(mod_stats.instruments, instrument_name)
- -- Update time of this sample spend by the instrumented function.
- update_statistic(instrument_stats, time)
- -- Reset logged data for the next sample.
- instruments[instrument_name] = 0
- end
- end
- -- Update time of this sample spend by this mod.
- update_statistic(mod_stats, mod_time)
- end
- -- Update the total time spend over all mods.
- stats_total.time_min = min(stats_total.time_min, logged_time)
- stats_total.time_max = max(stats_total.time_max, logged_time)
- stats_total.time_all = stats_total.time_all + logged_time
- stats_total.samples = stats_total.samples + 1
- logged_time = 0
- end
- ---
- -- Setup empty profile and register the sampling function
- --
- function sampler.init()
- sampler.reset()
- if core.settings:get_bool("instrument.profiler") then
- core.register_globalstep(function()
- if logged_time == 0 then
- return
- end
- return profiler.empty_instrument()
- end)
- core.register_globalstep(profiler.instrument {
- func = sample,
- mod = "*profiler*",
- class = "Sampler (update stats)",
- label = false,
- })
- else
- core.register_globalstep(sample)
- end
- end
- return sampler
|