Implement profiler

This commit is contained in:
2026-06-20 14:21:26 +03:00
parent 45a09284c2
commit ab48b57e1b
10 changed files with 294 additions and 17 deletions
+1 -1
View File
@@ -128,7 +128,7 @@ handle:close()
--print(require("serialize")(data, "\t")) --print(require("serialize")(data, "\t"))
-- Halyde terminal doesn't support bold (CSI 1 m) but who cares -- Halyde io doesn't support bold (CSI 1 m) but who cares
if data.command then if data.command then
terminal.write("\27[1mUsage: \27[0m\n") terminal.write("\27[1mUsage: \27[0m\n")
+3 -3
View File
@@ -1,9 +1,9 @@
COMMAND echo COMMAND echo
USAGE [TEXT]... USAGE [TEXT]...
DESCRIPTION Concatenates and prints text to the terminal. DESCRIPTION Concatenates and prints text to the standard output.
ARG1 TEXT ARG1 TEXT
ARG1DESCRIPTION Text to print. ARG1DESCRIPTION Text to print.
EXAMPLE1 echo test EXAMPLE1 echo test
EXAMPLE2 echo Hello World! EXAMPLE2 echo Hello World!
EXAMPLE1DESCRIPTION Prints "test" to the terminal. EXAMPLE1DESCRIPTION Prints "test" to the standard output.
EXAMPLE2DESCRIPTION Prints "Hello World!" to the terminal. EXAMPLE2DESCRIPTION Prints "Hello World!" to the standard output.
+222
View File
@@ -0,0 +1,222 @@
-- TODO: make it somehow not depend on ESC s and ESC u
local profiler = require("profiler")
local PAL = {
{ fg = "\27[31m", bg = "\27[41m" },
{ fg = "\27[32m", bg = "\27[42m" },
{ fg = "\27[33m", bg = "\27[43m" },
{ fg = "\27[34m", bg = "\27[44m" },
{ fg = "\27[35m", bg = "\27[45m" },
{ fg = "\27[36m", bg = "\27[46m" },
{ fg = "\27[37m", bg = "\27[47m" }
}
local function pal(i) return PAL[((i - 1) % #PAL) + 1] end
local results = profiler.results()
if not results then
print("No profiling data")
return
end
local radius
local W = terminal.getResolution()
if (W == 160) then radius = 20
elseif (W == 80) then radius = 6
else radius = 0; PAL = {{fg = "", bg = ""}} end
local MIN_PC = math.min(3, 100 / (radius * 2))
local total = 0
for _, r in ipairs(results) do total = total + r.time end
local main = {}
local other = {}
local ot = 0
local on_ = 0
for _, r in ipairs(results) do
if r.time / total * 100 >= MIN_PC then
table.insert(main, r)
else
table.insert(other, r)
ot = ot + r.time;
on_ = on_ + 1
end
end
if on_ then
table.insert(main, { label = "other (" .. on_ .. ")", time = ot })
end
local START = -math.pi / 2
local slices = {}
local cur = START
for i, m in ipairs(main) do
local sw = m.time / total * 2 * math.pi
slices[i] = {
label = m.label,
time = m.time,
pc = ("%.1f%%"):format(m.time / total * 100),
color = pal(i),
sa = cur,
sw = sw
}
cur = cur + sw
end
if on_ then slices[#slices].color={fg = "\27[30m", bg = "\27[40m"} end
local ASP = 2
local cx = radius * ASP
local cy = radius
local function slice_at(sx, sy)
local dy = sy - cy
local dx = (sx - cx) / ASP
if dx * dx + dy * dy > radius * radius then return nil end
local a = math.atan2(dy, dx)
if a < START then a = a + 2 * math.pi end
for i, sl in ipairs(slices) do
if a >= sl.sa and a < sl.sa + sl.sw then return i end
end
return #slices
end
local SUB = {
{ dx = -0.25, dy = -0.375 },
{ dx = -0.25, dy = -0.125 },
{ dx = -0.25, dy = 0.125 },
{ dx = 0.25, dy = -0.375 },
{ dx = 0.25, dy = -0.125 },
{ dx = 0.25, dy = 0.125 },
{ dx = -0.25, dy = 0.375 },
{ dx = 0.25, dy = 0.375 },
}
for y = 0, radius * 2 do
local lx, rx = math.ceil(cx - radius * ASP), math.floor(cx + radius * ASP)
terminal.write(("\27[%dC"):format(lx))
for x = lx, rx do
local c = {}
local n = 0
for k = 1, 8 do
local s = slice_at(x + SUB[k].dx, y + SUB[k].dy)
c[k] = s
if s then n = n + 1 end
end
if n == 0 then
terminal.write("\27[0m ")
else
local counts = {}
local order = {}
for k = 1, 8 do
if c[k] then
local key = c[k]
if not counts[key] then
counts[key] = 0
table.insert(order, key)
end
counts[key] = counts[key] + 1
end
end
table.sort(order, function(a, b) return counts[a] > counts[b] end)
local dom = order[1]
local sub = order[2]
if n == 8 then
terminal.write(slices[dom].color.bg)
if sub == nil then
terminal.write(" ")
else
local mask = 0
for k = 1, 8 do
if c[k] == sub then mask = mask + (1 << (k - 1)) end
end
terminal.write(slices[sub].color.fg)
terminal.write(utf8.char(0x2800 + mask))
end
else
local mask = 0
for k = 1, 8 do
if c[k] == dom then mask = mask + (1 << (k - 1)) end
end
terminal.write("\27[0m")
terminal.write(slices[dom].color.fg)
terminal.write(utf8.char(0x2800 + mask))
end
end
end
terminal.write('\n')
end
terminal.write("\27[" .. radius * 2 + 1 .. "A\27[s")
local function draw_text(col, row, s)
terminal.write("\27[u\27[" .. col .. "C\27[" .. row - 1 .. "B" .. s)
end
terminal.write("\27[0m")
if radius == 0 then goto tier1gpu end
for _, sl in ipairs(slices) do
local y = math.floor(cy + radius * 0.62 * math.sin(sl.sa + sl.sw / 2) + 0.5)
local function in_slice(x, row)
local a = math.atan2(row - cy, (x - cx) / ASP)
if a < START then
a = a + 2 * math.pi
end
return a >= sl.sa and a < sl.sa + sl.sw
end
local function centered_draw(row, text)
local dy = row - cy
local half_w = math.sqrt(radius * radius - dy * dy) * ASP
local lx, rx = math.ceil(cx - half_w), math.floor(cx + half_w)
while lx <= rx and not in_slice(lx, row) do
lx = lx + 1
end
while rx >= lx and not in_slice(rx, row) do
rx = rx - 1
end
local avail = rx - lx + 1
if avail < 3 then
return
end
local t = #text > avail and text:sub(1, avail - 3) .. "..." or text
local start_x = lx + math.floor((avail - #t) / 2)
draw_text(start_x, row, sl.color.bg .. t)
end
centered_draw(y, sl.label)
centered_draw(y + 1, sl.pc)
end
::tier1gpu::
local max_w = 0
for _, sl in pairs(slices) do
max_w = math.max(#sl.label + 1, max_w)
end
for _, sl in pairs(other) do
max_w = math.max(#sl.label + 3, max_w)
end
local x = radius * 2 * ASP + 2
if radius == 0 then x = 0 end
local cw = W - x - 1
local suffix_w = 18
local max_lbl = math.max(4, cw - suffix_w)
terminal.write("\27[u\27[" .. x .. "C" .. ("\27[0m%" .. max_w .. "s %9s %6s"):format("label", "time", "share"))
terminal.write("\n")
for _, sl in ipairs(slices) do
local lbl = sl.label
if #lbl > max_lbl then
lbl = sl.label:sub(1, max_lbl - 1) .. ""
end
terminal.write("\n\27[" .. x .. "C" .. ("%s%" .. max_w .. "s\27[0m %9.4fs %6s"):format(sl.color.bg, lbl, sl.time, sl.pc))
end
for _, sl in ipairs(other) do
local lbl = sl.label
if #lbl > max_lbl - 2 then
lbl = sl.label:sub(1, max_lbl - 3) .. ""
end
terminal.write("\n\27[" .. x .. "C" .. ("%s%" .. max_w .. "s\27[0m %9.4fs %6s"):format(slices[#slices].color.bg, lbl, sl.time, ("%.1f%%"):format(sl.time / total * 100)))
end
if radius * 2 + 2 > #slices + #other then
terminal.write("\27[" .. radius * 2 + 2 .. "B")
end
terminal.write("\n")
+15
View File
@@ -0,0 +1,15 @@
for i = 1, 100 do
profiler.start("math_work")
local x = 0
for j = 1, 200000 do x = x + math.sqrt(j) end
profiler.stop("math_work")
profiler.start("string_work")
local s = ""
for j = 1, 2000 do s = s .. tostring(j) end
profiler.stop("string_work")
end
for _, r in ipairs(profiler.results()) do
print(r.label, r.time)
end
+5 -4
View File
@@ -11,6 +11,7 @@ gpu.bind(screenAddress)
gpu.setResolution(gpu.maxResolution()) gpu.setResolution(gpu.maxResolution())
local log = assert(loadfile("/lib/log.lua")(loadfile)) local log = assert(loadfile("/lib/log.lua")(loadfile))
_G.profiler = assert(loadfile("/lib/profiler.lua")())
log.kernel.info("Bound GPU to screen " .. tostring(screenAddress)) log.kernel.info("Bound GPU to screen " .. tostring(screenAddress))
@@ -67,10 +68,10 @@ require("/halyde/kernel/datatools.lua") -- If this is not imported BEFORE modloa
log.kernel.info("Loading modules") log.kernel.info("Loading modules")
require("/halyde/kernel/modload.lua") require("/halyde/kernel/modload.lua")
package.preload("component") local toPreload = { "component", "computer", "log", "event" }
package.preload("computer") for _, p in pairs(toPreload) do
package.preload("log") profiler.profile("pre-loading " .. p, package.preload, p)
package.preload("event") end
local computer = require("computer") local computer = require("computer")
function wait(seconds) function wait(seconds)
+8
View File
@@ -19,8 +19,10 @@ local moduleTypes = {}
local modulesLoaded = {} local modulesLoaded = {}
local function loadModule(modName) local function loadModule(modName)
local stop = profiler.start("loadModule(" .. tostring(modName) .. ")")
if table.find(modulesLoaded, modName) then if table.find(modulesLoaded, modName) then
log.kernel.warn(string.format("[modload: %s] Module was already loaded - skipping", modName)) log.kernel.warn(string.format("[modload: %s] Module was already loaded - skipping", modName))
stop()
return true return true
end end
@@ -28,6 +30,7 @@ local function loadModule(modName)
if not moduleData then if not moduleData then
log.kernel.warn(string.format("[modload: %s] Could not find module data.", modName)) log.kernel.warn(string.format("[modload: %s] Could not find module data.", modName))
table.remove(moduleList, table.find(moduleList, modName)) table.remove(moduleList, table.find(moduleList, modName))
stop()
return true return true
end end
local ready = false local ready = false
@@ -42,6 +45,7 @@ local function loadModule(modName)
end end
if not ready then if not ready then
log.kernel.info(string.format("[modload: %s] Module not ready - skipping", modName)) log.kernel.info(string.format("[modload: %s] Module not ready - skipping", modName))
stop()
return false return false
end end
if type(moduleData.dependencies) == "table" then if type(moduleData.dependencies) == "table" then
@@ -74,16 +78,19 @@ local function loadModule(modName)
tostring(err or "unknown error") tostring(err or "unknown error")
) )
) )
stop()
return false return false
else else
table.insert(modulesLoaded, modName) table.insert(modulesLoaded, modName)
table.remove(moduleList, table.find(moduleList, modName)) table.remove(moduleList, table.find(moduleList, modName))
end end
end end
stop()
return true return true
end end
for _, modName in pairs(moduleList) do -- Get all the module types for _, modName in pairs(moduleList) do -- Get all the module types
local stop = profiler.start("getting module " .. modName .. ")")
log.kernel.info(string.format("[modload: %s] Getting data from module", modName)) log.kernel.info(string.format("[modload: %s] Getting data from module", modName))
local moduleData local moduleData
local status, err = pcall(function() local status, err = pcall(function()
@@ -123,6 +130,7 @@ for _, modName in pairs(moduleList) do -- Get all the module types
moduleTypes[modName] = moduleData.type -- Not the other way around because there can be multiple modules of the same type, but there can't be multiple entries with the same key moduleTypes[modName] = moduleData.type -- Not the other way around because there can be multiple modules of the same type, but there can't be multiple entries with the same key
end end
::continue:: ::continue::
stop()
end end
local function loadAllModules() -- attempt at loading all modules, unless if they're not ready local function loadAllModules() -- attempt at loading all modules, unless if they're not ready
+1 -1
View File
@@ -1,6 +1,6 @@
local module = {} local module = {}
module.dependencies = { "terminal" } module.dependencies = { "io" }
function module.check() function module.check()
return true -- This module should always be loaded return true -- This module should always be loaded
+3 -7
View File
@@ -17,9 +17,8 @@ function module.init()
local event = require("event") local event = require("event")
local component = require("component") local component = require("component")
local computer = require("computer")
local gpu = component.gpu local gpu = component.gpu
_G._PUBLIC.terminal = {} _G._PUBLIC.io = {}
local readHistory = {} local readHistory = {}
function _PUBLIC.terminal.getHistory(id) function _PUBLIC.terminal.getHistory(id)
@@ -145,9 +144,6 @@ function module.init()
local ANSIColorPalette = getColorPalette(gpu.maxDepth()) local ANSIColorPalette = getColorPalette(gpu.maxDepth())
local expecting_unicode_bytes = 0
local unicode_bytes_left = 0
local unicode_codepoint = 0
local cursor = { x = 1, y = 1, X = nil, Y = nil } -- X and Y are managed by ESC s and ESC u local cursor = { x = 1, y = 1, X = nil, Y = nil } -- X and Y are managed by ESC s and ESC u
local printState = 0 -- 0:none 1:in ESC 2:in CSI local printState = 0 -- 0:none 1:in ESC 2:in CSI
local color = { local color = {
@@ -566,7 +562,7 @@ function module.init()
local args = {...} local args = {...}
local stringArgs = {} local stringArgs = {}
for _, arg in pairs(args) do for _, arg in pairs(args) do
if type(arg)=="table" then if type(arg) == "table" then
table.insert(stringArgs, serialize(arg)) table.insert(stringArgs, serialize(arg))
elseif tostring(arg) then elseif tostring(arg) then
table.insert(stringArgs, tostring(arg)) table.insert(stringArgs, tostring(arg))
@@ -815,7 +811,7 @@ function module.init()
end end
function module.exit() function module.exit()
_G._PUBLIC.terminal = nil _G._PUBLIC.io = nil
end end
return module return module
+35
View File
@@ -0,0 +1,35 @@
local thing = _G._PUBLIC or _G
thing.__PROFILER_INSTANCE = thing.__PROFILER_INSTANCE or { timers = {} }
local timers = thing.__PROFILER_INSTANCE.timers
local profiler = {}
function profiler.start(label, overwrite)
thing.__PROFILER_INSTANCE.lastadded = label
timers[label] = timers[label] or {}
if not timers[label].start or overwrite then
timers[label].start = os.clock()
end
return function() timers[label].time = timers[label].time or os.clock() - timers[label].start end
end
function profiler.results()
local _now = nil
local function now()
_now = _now or os.clock()
return _now
end
local out = {}
for label, t in pairs(timers) do
table.insert(out, { label = label, time = t.time or now() - t.start })
end
table.sort(out, function(a, b) return a.time > b.time end)
return out
end
function profiler.profile(label, func, ...)
local stop = profiler.start(label)
func(...)
stop()
end
return profiler
+1 -1
View File
@@ -1,4 +1,4 @@
local io = require("io") local io = require("terminal")
local component = require("component") local component = require("component")
if not component.isAvailable("internet") then if not component.isAvailable("internet") then
io.stderr.write("This program requires an internet card to run.") io.stderr.write("This program requires an internet card to run.")