mod_profiling.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. -- Minetest: builtin/game/mod_profiling.lua
  2. local mod_statistics = {}
  3. mod_statistics.step_total = 0
  4. mod_statistics.data = {}
  5. mod_statistics.stats = {}
  6. mod_statistics.stats["total"] = {
  7. min_us = math.huge,
  8. max_us = 0,
  9. avg_us = 0,
  10. min_per = 100,
  11. max_per = 100,
  12. avg_per = 100
  13. }
  14. local replacement_table = {
  15. "register_globalstep",
  16. "register_on_placenode",
  17. "register_on_dignode",
  18. "register_on_punchnode",
  19. "register_on_generated",
  20. "register_on_newplayer",
  21. "register_on_dieplayer",
  22. "register_on_respawnplayer",
  23. "register_on_prejoinplayer",
  24. "register_on_joinplayer",
  25. "register_on_leaveplayer",
  26. "register_on_cheat",
  27. "register_on_chat_message",
  28. "register_on_player_receive_fields",
  29. "register_on_mapgen_init",
  30. "register_on_craft",
  31. "register_craft_predict",
  32. "register_on_item_eat"
  33. }
  34. --------------------------------------------------------------------------------
  35. function mod_statistics.log_time(type, modname, time_in_us)
  36. if modname == nil then
  37. modname = "builtin"
  38. end
  39. if mod_statistics.data[modname] == nil then
  40. mod_statistics.data[modname] = {}
  41. end
  42. if mod_statistics.data[modname][type] == nil then
  43. mod_statistics.data[modname][type] = 0
  44. end
  45. mod_statistics.data[modname][type] =
  46. mod_statistics.data[modname][type] + time_in_us
  47. mod_statistics.step_total = mod_statistics.step_total + time_in_us
  48. end
  49. --------------------------------------------------------------------------------
  50. function mod_statistics.update_statistics(dtime)
  51. for modname,types in pairs(mod_statistics.data) do
  52. if mod_statistics.stats[modname] == nil then
  53. mod_statistics.stats[modname] = {
  54. min_us = math.huge,
  55. max_us = 0,
  56. avg_us = 0,
  57. min_per = 100,
  58. max_per = 0,
  59. avg_per = 0
  60. }
  61. end
  62. local modtime = 0
  63. for type,time in pairs(types) do
  64. modtime = modtime + time
  65. if mod_statistics.stats[modname].types == nil then
  66. mod_statistics.stats[modname].types = {}
  67. end
  68. if mod_statistics.stats[modname].types[type] == nil then
  69. mod_statistics.stats[modname].types[type] = {
  70. min_us = math.huge,
  71. max_us = 0,
  72. avg_us = 0,
  73. min_per = 100,
  74. max_per = 0,
  75. avg_per = 0
  76. }
  77. end
  78. local toupdate = mod_statistics.stats[modname].types[type]
  79. --update us values
  80. toupdate.min_us = math.min(time, toupdate.min_us)
  81. toupdate.max_us = math.max(time, toupdate.max_us)
  82. --TODO find better algorithm
  83. toupdate.avg_us = toupdate.avg_us * 0.99 + modtime * 0.01
  84. --update percentage values
  85. local cur_per = (time/mod_statistics.step_total) * 100
  86. toupdate.min_per = math.min(toupdate.min_per, cur_per)
  87. toupdate.max_per = math.max(toupdate.max_per, cur_per)
  88. --TODO find better algorithm
  89. toupdate.avg_per = toupdate.avg_per * 0.99 + cur_per * 0.01
  90. mod_statistics.data[modname][type] = 0
  91. end
  92. --per mod statistics
  93. --update us values
  94. mod_statistics.stats[modname].min_us =
  95. math.min(modtime, mod_statistics.stats[modname].min_us)
  96. mod_statistics.stats[modname].max_us =
  97. math.max(modtime, mod_statistics.stats[modname].max_us)
  98. --TODO find better algorithm
  99. mod_statistics.stats[modname].avg_us =
  100. mod_statistics.stats[modname].avg_us * 0.99 + modtime * 0.01
  101. --update percentage values
  102. local cur_per = (modtime/mod_statistics.step_total) * 100
  103. mod_statistics.stats[modname].min_per =
  104. math.min(mod_statistics.stats[modname].min_per, cur_per)
  105. mod_statistics.stats[modname].max_per =
  106. math.max(mod_statistics.stats[modname].max_per, cur_per)
  107. --TODO find better algorithm
  108. mod_statistics.stats[modname].avg_per =
  109. mod_statistics.stats[modname].avg_per * 0.99 + cur_per * 0.01
  110. end
  111. --update "total"
  112. mod_statistics.stats["total"].min_us =
  113. math.min(mod_statistics.step_total, mod_statistics.stats["total"].min_us)
  114. mod_statistics.stats["total"].max_us =
  115. math.max(mod_statistics.step_total, mod_statistics.stats["total"].max_us)
  116. --TODO find better algorithm
  117. mod_statistics.stats["total"].avg_us =
  118. mod_statistics.stats["total"].avg_us * 0.99 +
  119. mod_statistics.step_total * 0.01
  120. mod_statistics.step_total = 0
  121. end
  122. --------------------------------------------------------------------------------
  123. local function build_callback(log_id, fct)
  124. return function( toregister )
  125. local modname = core.get_current_modname()
  126. fct(function(...)
  127. local starttime = core.get_us_time()
  128. -- note maximum 10 return values are supported unless someone finds
  129. -- a way to store a variable lenght return value list
  130. local r0, r1, r2, r3, r4, r5, r6, r7, r8, r9 = toregister(...)
  131. local delta = core.get_us_time() - starttime
  132. mod_statistics.log_time(log_id, modname, delta)
  133. return r0, r1, r2, r3, r4, r5, r6, r7, r8, r9
  134. end
  135. )
  136. end
  137. end
  138. --------------------------------------------------------------------------------
  139. function profiling_print_log(cmd, filter)
  140. print("Filter:" .. dump(filter))
  141. core.log("action", "Values below show times/percentages per server step.")
  142. core.log("action", "Following suffixes are used for entities:")
  143. core.log("action", "\t#oa := on_activate, #os := on_step, #op := on_punch, #or := on_rightclick, #gs := get_staticdata")
  144. core.log("action",
  145. string.format("%16s | %25s | %10s | %10s | %10s | %9s | %9s | %9s",
  146. "modname", "type" , "min µs", "max µs", "avg µs", "min %", "max %", "avg %")
  147. )
  148. core.log("action",
  149. "-----------------+---------------------------+-----------+" ..
  150. "-----------+-----------+-----------+-----------+-----------")
  151. for modname,statistics in pairs(mod_statistics.stats) do
  152. if modname ~= "total" then
  153. if (filter == "") or (modname == filter) then
  154. if modname:len() > 16 then
  155. modname = "..." .. modname:sub(-13)
  156. end
  157. core.log("action",
  158. string.format("%16s | %25s | %9d | %9d | %9d | %9d | %9d | %9d",
  159. modname,
  160. " ",
  161. statistics.min_us,
  162. statistics.max_us,
  163. statistics.avg_us,
  164. statistics.min_per,
  165. statistics.max_per,
  166. statistics.avg_per)
  167. )
  168. if core.setting_getbool("detailed_profiling") then
  169. if statistics.types ~= nil then
  170. for type,typestats in pairs(statistics.types) do
  171. if type:len() > 25 then
  172. type = "..." .. type:sub(-22)
  173. end
  174. core.log("action",
  175. string.format(
  176. "%16s | %25s | %9d | %9d | %9d | %9d | %9d | %9d",
  177. " ",
  178. type,
  179. typestats.min_us,
  180. typestats.max_us,
  181. typestats.avg_us,
  182. typestats.min_per,
  183. typestats.max_per,
  184. typestats.avg_per)
  185. )
  186. end
  187. end
  188. end
  189. end
  190. end
  191. end
  192. core.log("action",
  193. "-----------------+---------------------------+-----------+" ..
  194. "-----------+-----------+-----------+-----------+-----------")
  195. if filter == "" then
  196. core.log("action",
  197. string.format("%16s | %25s | %9d | %9d | %9d | %9d | %9d | %9d",
  198. "total",
  199. " ",
  200. mod_statistics.stats["total"].min_us,
  201. mod_statistics.stats["total"].max_us,
  202. mod_statistics.stats["total"].avg_us,
  203. mod_statistics.stats["total"].min_per,
  204. mod_statistics.stats["total"].max_per,
  205. mod_statistics.stats["total"].avg_per)
  206. )
  207. end
  208. core.log("action", " ")
  209. return true
  210. end
  211. --------------------------------------------------------------------------------
  212. local function initialize_profiling()
  213. core.log("action", "Initialize tracing")
  214. mod_statistics.entity_callbacks = {}
  215. -- first register our own globalstep handler
  216. core.register_globalstep(mod_statistics.update_statistics)
  217. local rp_register_entity = core.register_entity
  218. core.register_entity = function(name, prototype)
  219. local modname = core.get_current_modname()
  220. local new_on_activate = nil
  221. local new_on_step = nil
  222. local new_on_punch = nil
  223. local new_rightclick = nil
  224. local new_get_staticdata = nil
  225. if prototype.on_activate ~= nil then
  226. local cbid = name .. "#oa"
  227. mod_statistics.entity_callbacks[cbid] = prototype.on_activate
  228. new_on_activate = function(self, staticdata, dtime_s)
  229. local starttime = core.get_us_time()
  230. mod_statistics.entity_callbacks[cbid](self, staticdata, dtime_s)
  231. local delta = core.get_us_time() -starttime
  232. mod_statistics.log_time(cbid, modname, delta)
  233. end
  234. end
  235. if prototype.on_step ~= nil then
  236. local cbid = name .. "#os"
  237. mod_statistics.entity_callbacks[cbid] = prototype.on_step
  238. new_on_step = function(self, dtime)
  239. local starttime = core.get_us_time()
  240. mod_statistics.entity_callbacks[cbid](self, dtime)
  241. local delta = core.get_us_time() -starttime
  242. mod_statistics.log_time(cbid, modname, delta)
  243. end
  244. end
  245. if prototype.on_punch ~= nil then
  246. local cbid = name .. "#op"
  247. mod_statistics.entity_callbacks[cbid] = prototype.on_punch
  248. new_on_punch = function(self, hitter)
  249. local starttime = core.get_us_time()
  250. mod_statistics.entity_callbacks[cbid](self, hitter)
  251. local delta = core.get_us_time() -starttime
  252. mod_statistics.log_time(cbid, modname, delta)
  253. end
  254. end
  255. if prototype.rightclick ~= nil then
  256. local cbid = name .. "#rc"
  257. mod_statistics.entity_callbacks[cbid] = prototype.rightclick
  258. new_rightclick = function(self, clicker)
  259. local starttime = core.get_us_time()
  260. mod_statistics.entity_callbacks[cbid](self, clicker)
  261. local delta = core.get_us_time() -starttime
  262. mod_statistics.log_time(cbid, modname, delta)
  263. end
  264. end
  265. if prototype.get_staticdata ~= nil then
  266. local cbid = name .. "#gs"
  267. mod_statistics.entity_callbacks[cbid] = prototype.get_staticdata
  268. new_get_staticdata = function(self)
  269. local starttime = core.get_us_time()
  270. local retval = mod_statistics.entity_callbacks[cbid](self)
  271. local delta = core.get_us_time() -starttime
  272. mod_statistics.log_time(cbid, modname, delta)
  273. return retval
  274. end
  275. end
  276. prototype.on_activate = new_on_activate
  277. prototype.on_step = new_on_step
  278. prototype.on_punch = new_on_punch
  279. prototype.rightclick = new_rightclick
  280. prototype.get_staticdata = new_get_staticdata
  281. rp_register_entity(name,prototype)
  282. end
  283. for i,v in ipairs(replacement_table) do
  284. local to_replace = core[v]
  285. core[v] = build_callback(v, to_replace)
  286. end
  287. local rp_register_abm = core.register_abm
  288. core.register_abm = function(spec)
  289. local modname = core.get_current_modname()
  290. local replacement_spec = {
  291. nodenames = spec.nodenames,
  292. neighbors = spec.neighbors,
  293. interval = spec.interval,
  294. chance = spec.chance,
  295. action = function(pos, node, active_object_count, active_object_count_wider)
  296. local starttime = core.get_us_time()
  297. spec.action(pos, node, active_object_count, active_object_count_wider)
  298. local delta = core.get_us_time() - starttime
  299. mod_statistics.log_time("abm", modname, delta)
  300. end
  301. }
  302. rp_register_abm(replacement_spec)
  303. end
  304. core.log("action", "Mod profiling initialized")
  305. end
  306. initialize_profiling()