mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-10-30 15:05:32 +01:00 
			
		
		
		
	This unifies the settings APIs. This also unifies the sync and async registration APIs, since the async registration API did not support adding non-functions to the API table.
		
			
				
	
	
		
			207 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			207 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| --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
 |