Basalt2 update
- Finished themes - Added state plugins for persistance - Finished reactive plugin - Added debug plugin (80% finished) - Added benchmark plugin - Added Tree, Table, List, Dropdown and Menu Elements - Bugfixes
This commit is contained in:
325
src/plugins/benchmark.lua
Normal file
325
src/plugins/benchmark.lua
Normal file
@@ -0,0 +1,325 @@
|
||||
local log = require("log")
|
||||
|
||||
local activeProfiles = setmetatable({}, {__mode = "k"})
|
||||
|
||||
local function createProfile()
|
||||
return {
|
||||
methods = {},
|
||||
}
|
||||
end
|
||||
|
||||
local function wrapMethod(element, methodName)
|
||||
local originalMethod = element[methodName]
|
||||
|
||||
if not activeProfiles[element] then
|
||||
activeProfiles[element] = createProfile()
|
||||
end
|
||||
if not activeProfiles[element].methods[methodName] then
|
||||
activeProfiles[element].methods[methodName] = {
|
||||
calls = 0,
|
||||
totalTime = 0,
|
||||
minTime = math.huge,
|
||||
maxTime = 0,
|
||||
lastTime = 0,
|
||||
startTime = 0,
|
||||
path = {},
|
||||
methodName = methodName,
|
||||
originalMethod = originalMethod
|
||||
}
|
||||
end
|
||||
|
||||
element[methodName] = function(self, ...)
|
||||
self:startProfile(methodName)
|
||||
local result = originalMethod(self, ...)
|
||||
self:endProfile(methodName)
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
local BaseElement = {}
|
||||
|
||||
function BaseElement:startProfile(methodName)
|
||||
local profile = activeProfiles[self]
|
||||
if not profile then
|
||||
profile = createProfile()
|
||||
activeProfiles[self] = profile
|
||||
end
|
||||
|
||||
if not profile.methods[methodName] then
|
||||
profile.methods[methodName] = {
|
||||
calls = 0,
|
||||
totalTime = 0,
|
||||
minTime = math.huge,
|
||||
maxTime = 0,
|
||||
lastTime = 0,
|
||||
startTime = 0,
|
||||
path = {},
|
||||
methodName = methodName
|
||||
}
|
||||
end
|
||||
|
||||
local methodProfile = profile.methods[methodName]
|
||||
methodProfile.startTime = os.clock() * 1000
|
||||
methodProfile.path = {}
|
||||
|
||||
local current = self
|
||||
while current do
|
||||
table.insert(methodProfile.path, 1, current.get("name") or current.get("id"))
|
||||
current = current.parent
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement:endProfile(methodName)
|
||||
local profile = activeProfiles[self]
|
||||
if not profile or not profile.methods[methodName] then return self end
|
||||
|
||||
local methodProfile = profile.methods[methodName]
|
||||
local endTime = os.clock() * 1000
|
||||
local duration = endTime - methodProfile.startTime
|
||||
|
||||
methodProfile.calls = methodProfile.calls + 1
|
||||
methodProfile.totalTime = methodProfile.totalTime + duration
|
||||
methodProfile.minTime = math.min(methodProfile.minTime, duration)
|
||||
methodProfile.maxTime = math.max(methodProfile.maxTime, duration)
|
||||
methodProfile.lastTime = duration
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement:benchmark(methodName)
|
||||
if not self[methodName] then
|
||||
log.error("Method " .. methodName .. " does not exist")
|
||||
return self
|
||||
end
|
||||
|
||||
activeProfiles[self] = createProfile()
|
||||
activeProfiles[self].methodName = methodName
|
||||
activeProfiles[self].isRunning = true
|
||||
|
||||
wrapMethod(self, methodName)
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement:logBenchmark(methodName)
|
||||
local profile = activeProfiles[self]
|
||||
if not profile or not profile.methods[methodName] then return self end
|
||||
|
||||
local stats = profile.methods[methodName]
|
||||
if stats then
|
||||
local averageTime = stats.calls > 0 and (stats.totalTime / stats.calls) or 0
|
||||
log.info(string.format(
|
||||
"Benchmark results for %s.%s: " ..
|
||||
"Path: %s " ..
|
||||
"Calls: %d " ..
|
||||
"Average time: %.2fms " ..
|
||||
"Min time: %.2fms " ..
|
||||
"Max time: %.2fms " ..
|
||||
"Last time: %.2fms " ..
|
||||
"Total time: %.2fms",
|
||||
table.concat(stats.path, "."),
|
||||
stats.methodName,
|
||||
table.concat(stats.path, "/"),
|
||||
stats.calls,
|
||||
averageTime,
|
||||
stats.minTime ~= math.huge and stats.minTime or 0,
|
||||
stats.maxTime,
|
||||
stats.lastTime,
|
||||
stats.totalTime
|
||||
))
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement:stopBenchmark(methodName)
|
||||
local profile = activeProfiles[self]
|
||||
if not profile or not profile.methods[methodName] then return self end
|
||||
|
||||
local stats = profile.methods[methodName]
|
||||
if stats and stats.originalMethod then
|
||||
self[methodName] = stats.originalMethod
|
||||
end
|
||||
|
||||
profile.methods[methodName] = nil
|
||||
if not next(profile.methods) then
|
||||
activeProfiles[self] = nil
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function BaseElement:getBenchmarkStats(methodName)
|
||||
local profile = activeProfiles[self]
|
||||
if not profile or not profile.methods[methodName] then return nil end
|
||||
|
||||
local stats = profile.methods[methodName]
|
||||
return {
|
||||
averageTime = stats.totalTime / stats.calls,
|
||||
totalTime = stats.totalTime,
|
||||
calls = stats.calls,
|
||||
minTime = stats.minTime,
|
||||
maxTime = stats.maxTime,
|
||||
lastTime = stats.lastTime
|
||||
}
|
||||
end
|
||||
|
||||
local Container = {}
|
||||
|
||||
function Container:benchmarkContainer(methodName)
|
||||
self:benchmark(methodName)
|
||||
|
||||
for _, child in pairs(self.get("children")) do
|
||||
child:benchmark(methodName)
|
||||
|
||||
if child:isType("Container") then
|
||||
child:benchmarkContainer(methodName)
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Container:logContainerBenchmarks(methodName, depth)
|
||||
depth = depth or 0
|
||||
local indent = string.rep(" ", depth)
|
||||
local childrenTotalTime = 0
|
||||
local childrenStats = {}
|
||||
|
||||
for _, child in pairs(self.get("children")) do
|
||||
local profile = activeProfiles[child]
|
||||
if profile and profile.methods[methodName] then
|
||||
local stats = profile.methods[methodName]
|
||||
childrenTotalTime = childrenTotalTime + stats.totalTime
|
||||
table.insert(childrenStats, {
|
||||
element = child,
|
||||
type = child.get("type"),
|
||||
calls = stats.calls,
|
||||
totalTime = stats.totalTime,
|
||||
avgTime = stats.totalTime / stats.calls
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local profile = activeProfiles[self]
|
||||
if profile and profile.methods[methodName] then
|
||||
local stats = profile.methods[methodName]
|
||||
local selfTime = stats.totalTime - childrenTotalTime
|
||||
local avgSelfTime = selfTime / stats.calls
|
||||
|
||||
log.info(string.format(
|
||||
"%sBenchmark %s (%s): " ..
|
||||
"%.2fms/call (Self: %.2fms/call) " ..
|
||||
"[Total: %dms, Calls: %d]",
|
||||
indent,
|
||||
self.get("type"),
|
||||
methodName,
|
||||
stats.totalTime / stats.calls,
|
||||
avgSelfTime,
|
||||
stats.totalTime,
|
||||
stats.calls
|
||||
))
|
||||
|
||||
if #childrenStats > 0 then
|
||||
for _, childStat in ipairs(childrenStats) do
|
||||
if childStat.element:isType("Container") then
|
||||
childStat.element:logContainerBenchmarks(methodName, depth + 1)
|
||||
else
|
||||
log.info(string.format("%s> %s: %.2fms/call [Total: %dms, Calls: %d]",
|
||||
indent .. " ",
|
||||
childStat.type,
|
||||
childStat.avgTime,
|
||||
childStat.totalTime,
|
||||
childStat.calls
|
||||
))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Container:stopContainerBenchmark(methodName)
|
||||
for _, child in pairs(self.get("children")) do
|
||||
if child:isType("Container") then
|
||||
child:stopContainerBenchmark(methodName)
|
||||
else
|
||||
child:stopBenchmark(methodName)
|
||||
end
|
||||
end
|
||||
|
||||
self:stopBenchmark(methodName)
|
||||
return self
|
||||
end
|
||||
|
||||
local API = {
|
||||
start = function(name, options)
|
||||
options = options or {}
|
||||
local profile = createProfile()
|
||||
profile.name = name
|
||||
profile.startTime = os.clock() * 1000
|
||||
profile.custom = true
|
||||
activeProfiles[name] = profile
|
||||
end,
|
||||
|
||||
stop = function(name)
|
||||
local profile = activeProfiles[name]
|
||||
if not profile or not profile.custom then return end
|
||||
|
||||
local endTime = os.clock() * 1000
|
||||
local duration = endTime - profile.startTime
|
||||
|
||||
profile.calls = profile.calls + 1
|
||||
profile.totalTime = profile.totalTime + duration
|
||||
profile.minTime = math.min(profile.minTime, duration)
|
||||
profile.maxTime = math.max(profile.maxTime, duration)
|
||||
profile.lastTime = duration
|
||||
|
||||
log.info(string.format(
|
||||
"Custom Benchmark '%s': " ..
|
||||
"Calls: %d " ..
|
||||
"Average time: %.2fms " ..
|
||||
"Min time: %.2fms " ..
|
||||
"Max time: %.2fms " ..
|
||||
"Last time: %.2fms " ..
|
||||
"Total time: %.2fms",
|
||||
name,
|
||||
profile.calls,
|
||||
profile.totalTime / profile.calls,
|
||||
profile.minTime,
|
||||
profile.maxTime,
|
||||
profile.lastTime,
|
||||
profile.totalTime
|
||||
))
|
||||
end,
|
||||
|
||||
getStats = function(name)
|
||||
local profile = activeProfiles[name]
|
||||
if not profile then return nil end
|
||||
|
||||
return {
|
||||
averageTime = profile.totalTime / profile.calls,
|
||||
totalTime = profile.totalTime,
|
||||
calls = profile.calls,
|
||||
minTime = profile.minTime,
|
||||
maxTime = profile.maxTime,
|
||||
lastTime = profile.lastTime
|
||||
}
|
||||
end,
|
||||
|
||||
clear = function(name)
|
||||
activeProfiles[name] = nil
|
||||
end,
|
||||
|
||||
clearAll = function()
|
||||
for k,v in pairs(activeProfiles) do
|
||||
if v.custom then
|
||||
activeProfiles[k] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
return {
|
||||
BaseElement = BaseElement,
|
||||
Container = Container,
|
||||
API = API
|
||||
}
|
||||
Reference in New Issue
Block a user