2 --Copyright (C) 2016 T4im
4 --This program is free software; you can redistribute it and/or modify
5 --it under the terms of the GNU Lesser General Public License as published by
6 --the Free Software Foundation; either version 2.1 of the License, or
7 --(at your option) any later version.
9 --This program is distributed in the hope that it will be useful,
10 --but WITHOUT ANY WARRANTY; without even the implied warranty of
11 --MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 --GNU Lesser General Public License for more details.
14 --You should have received a copy of the GNU Lesser General Public License along
15 --with this program; if not, write to the Free Software Foundation, Inc.,
16 --51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 local setmetatable = setmetatable
18 local pairs, format = pairs, string.format
19 local min, max, huge = math.min, math.max, math.huge
23 -- Split sampler and profile up, to possibly allow for rotation later.
27 local logged_time, logged_data
30 get_time_avg = function(self)
31 return self.time_all/self.samples
33 get_part_avg = function(self)
34 if not self.part_all then
35 return 100 -- Extra handling for "total"
37 return self.part_all/self.samples
40 _stat_mt.__index = _stat_mt
42 function sampler.reset()
43 -- Accumulated logged time since last sample.
44 -- This helps determining, the relative time a mod used up.
46 -- The measurements taken through instrumentation since last sample.
50 -- Current mod statistics (max/min over the entire mod lifespan)
51 -- Mod specific instrumentation statistics are nested within.
53 -- Current stats over all mods.
54 stats_total = setmetatable({
63 stats_total = profile.stats_total
65 -- Provide access to the most recent profile.
66 sampler.profile = profile
70 -- Log a measurement for the sampler to pick up later.
71 -- Keep `log` and its often called functions lean.
72 -- It will directly add to the instrumentation overhead.
74 function sampler.log(modname, instrument_name, time_diff)
75 if time_diff <= 0 then
77 -- This **might** have happened on a semi-regular basis with huge mods,
78 -- resulting in negative statistics (perhaps midnight time jumps or ntp corrections?).
79 core.log("warning", format(
80 "Time travel of %s::%s by %dµs.",
81 modname, instrument_name, time_diff
84 -- Throwing these away is better, than having them mess with the overall result.
88 local mod_data = logged_data[modname]
89 if mod_data == nil then
91 logged_data[modname] = mod_data
94 mod_data[instrument_name] = (mod_data[instrument_name] or 0) + time_diff
95 -- Update logged time since last sample.
96 logged_time = logged_time + time_diff
100 -- Return a requested statistic.
101 -- Initialize if necessary.
103 local function get_statistic(stats_table, name)
104 local statistic = stats_table[name]
105 if statistic == nil then
106 statistic = setmetatable({
115 stats_table[name] = statistic
121 -- Update a statistic table
123 local function update_statistic(stats_table, time)
124 stats_table.samples = stats_table.samples + 1
126 -- Update absolute time (µs) spend by the subject
127 stats_table.time_min = min(stats_table.time_min, time)
128 stats_table.time_max = max(stats_table.time_max, time)
129 stats_table.time_all = stats_table.time_all + time
131 -- Update relative time (%) of this sample spend by the subject
132 local current_part = (time/logged_time) * 100
133 stats_table.part_min = min(stats_table.part_min, current_part)
134 stats_table.part_max = max(stats_table.part_max, current_part)
135 stats_table.part_all = stats_table.part_all + current_part
139 -- Sample all logged measurements each server step.
140 -- Like any globalstep function, this should not be too heavy,
141 -- but does not add to the instrumentation overhead.
143 local function sample(dtime)
144 -- Rare, but happens and is currently of no informational value.
145 if logged_time == 0 then
149 for modname, instruments in pairs(logged_data) do
150 local mod_stats = get_statistic(profile.stats, modname)
151 if mod_stats.instruments == nil then
152 -- Current statistics for each instrumentation component
153 mod_stats.instruments = {}
157 for instrument_name, time in pairs(instruments) do
159 mod_time = mod_time + time
160 local instrument_stats = get_statistic(mod_stats.instruments, instrument_name)
162 -- Update time of this sample spend by the instrumented function.
163 update_statistic(instrument_stats, time)
164 -- Reset logged data for the next sample.
165 instruments[instrument_name] = 0
169 -- Update time of this sample spend by this mod.
170 update_statistic(mod_stats, mod_time)
173 -- Update the total time spend over all mods.
174 stats_total.time_min = min(stats_total.time_min, logged_time)
175 stats_total.time_max = max(stats_total.time_max, logged_time)
176 stats_total.time_all = stats_total.time_all + logged_time
178 stats_total.samples = stats_total.samples + 1
183 -- Setup empty profile and register the sampling function
185 function sampler.init()
188 if core.setting_getbool("instrument.profiler") then
189 core.register_globalstep(function()
190 if logged_time == 0 then
193 return profiler.empty_instrument()
195 core.register_globalstep(profiler.instrument {
198 class = "Sampler (update stats)",
202 core.register_globalstep(sample)