diff --git a/halyde/apps/help.lua b/halyde/apps/help.lua index 4bcbca6..29d67b4 100644 --- a/halyde/apps/help.lua +++ b/halyde/apps/help.lua @@ -128,7 +128,7 @@ handle:close() --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 terminal.write("\27[1mUsage: \27[0m\n") diff --git a/halyde/apps/helpdb/echo b/halyde/apps/helpdb/echo index c850904..6a54326 100644 --- a/halyde/apps/helpdb/echo +++ b/halyde/apps/helpdb/echo @@ -1,9 +1,9 @@ COMMAND echo USAGE [TEXT]... -DESCRIPTION Concatenates and prints text to the terminal. +DESCRIPTION Concatenates and prints text to the standard output. ARG1 TEXT ARG1DESCRIPTION Text to print. EXAMPLE1 echo test EXAMPLE2 echo Hello World! -EXAMPLE1DESCRIPTION Prints "test" to the terminal. -EXAMPLE2DESCRIPTION Prints "Hello World!" to the terminal. +EXAMPLE1DESCRIPTION Prints "test" to the standard output. +EXAMPLE2DESCRIPTION Prints "Hello World!" to the standard output. diff --git a/halyde/apps/profile.lua b/halyde/apps/profile.lua new file mode 100644 index 0000000..8976b5a --- /dev/null +++ b/halyde/apps/profile.lua @@ -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") diff --git a/halyde/apps/test.lua b/halyde/apps/test.lua new file mode 100644 index 0000000..3cbbb59 --- /dev/null +++ b/halyde/apps/test.lua @@ -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 diff --git a/halyde/kernel/boot.lua b/halyde/kernel/boot.lua index 4903bf1..ffa6696 100644 --- a/halyde/kernel/boot.lua +++ b/halyde/kernel/boot.lua @@ -11,6 +11,7 @@ gpu.bind(screenAddress) gpu.setResolution(gpu.maxResolution()) local log = assert(loadfile("/lib/log.lua")(loadfile)) +_G.profiler = assert(loadfile("/lib/profiler.lua")()) 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") require("/halyde/kernel/modload.lua") -package.preload("component") -package.preload("computer") -package.preload("log") -package.preload("event") +local toPreload = { "component", "computer", "log", "event" } +for _, p in pairs(toPreload) do + profiler.profile("pre-loading " .. p, package.preload, p) +end local computer = require("computer") function wait(seconds) diff --git a/halyde/kernel/modload.lua b/halyde/kernel/modload.lua index 6ef1cc9..4d5f569 100644 --- a/halyde/kernel/modload.lua +++ b/halyde/kernel/modload.lua @@ -19,8 +19,10 @@ local moduleTypes = {} local modulesLoaded = {} local function loadModule(modName) + local stop = profiler.start("loadModule(" .. tostring(modName) .. ")") if table.find(modulesLoaded, modName) then log.kernel.warn(string.format("[modload: %s] Module was already loaded - skipping", modName)) + stop() return true end @@ -28,6 +30,7 @@ local function loadModule(modName) if not moduleData then log.kernel.warn(string.format("[modload: %s] Could not find module data.", modName)) table.remove(moduleList, table.find(moduleList, modName)) + stop() return true end local ready = false @@ -42,6 +45,7 @@ local function loadModule(modName) end if not ready then log.kernel.info(string.format("[modload: %s] Module not ready - skipping", modName)) + stop() return false end if type(moduleData.dependencies) == "table" then @@ -74,16 +78,19 @@ local function loadModule(modName) tostring(err or "unknown error") ) ) + stop() return false else table.insert(modulesLoaded, modName) table.remove(moduleList, table.find(moduleList, modName)) end end + stop() return true end 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)) local moduleData 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 end ::continue:: + stop() end local function loadAllModules() -- attempt at loading all modules, unless if they're not ready diff --git a/halyde/kernel/modules/defenv.lua b/halyde/kernel/modules/defenv.lua index 854f3ef..be0979b 100644 --- a/halyde/kernel/modules/defenv.lua +++ b/halyde/kernel/modules/defenv.lua @@ -1,6 +1,6 @@ local module = {} -module.dependencies = { "terminal" } +module.dependencies = { "io" } function module.check() return true -- This module should always be loaded diff --git a/halyde/kernel/modules/terminal.lua b/halyde/kernel/modules/terminal.lua index 03de7d1..bf5f1d3 100644 --- a/halyde/kernel/modules/terminal.lua +++ b/halyde/kernel/modules/terminal.lua @@ -17,9 +17,8 @@ function module.init() local event = require("event") local component = require("component") - local computer = require("computer") local gpu = component.gpu - _G._PUBLIC.terminal = {} + _G._PUBLIC.io = {} local readHistory = {} function _PUBLIC.terminal.getHistory(id) @@ -145,9 +144,6 @@ function module.init() 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 printState = 0 -- 0:none 1:in ESC 2:in CSI local color = { @@ -566,7 +562,7 @@ function module.init() local args = {...} local stringArgs = {} for _, arg in pairs(args) do - if type(arg)=="table" then + if type(arg) == "table" then table.insert(stringArgs, serialize(arg)) elseif tostring(arg) then table.insert(stringArgs, tostring(arg)) @@ -815,7 +811,7 @@ function module.init() end function module.exit() - _G._PUBLIC.terminal = nil + _G._PUBLIC.io = nil end return module diff --git a/lib/profiler.lua b/lib/profiler.lua new file mode 100644 index 0000000..94e6148 --- /dev/null +++ b/lib/profiler.lua @@ -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 diff --git a/webinstall.lua b/webinstall.lua index 9a565c2..aa3b204 100644 --- a/webinstall.lua +++ b/webinstall.lua @@ -1,4 +1,4 @@ -local io = require("io") +local io = require("terminal") local component = require("component") if not component.isAvailable("internet") then io.stderr.write("This program requires an internet card to run.")