From 6d6b43daf7140f24f5ce2532192821c2fbfd1c6b Mon Sep 17 00:00:00 2001 From: MayaTheShy Date: Sun, 22 Mar 2026 04:08:53 -0400 Subject: [PATCH] Add memory profiling script for CC:Tweaked / Opus OS --- sys/apps/memprofile.lua | 200 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 sys/apps/memprofile.lua diff --git a/sys/apps/memprofile.lua b/sys/apps/memprofile.lua new file mode 100644 index 0000000..239f8b9 --- /dev/null +++ b/sys/apps/memprofile.lua @@ -0,0 +1,200 @@ +-- Memory Profiler for CC:Tweaked / Opus OS +-- Usage: memprofile [--watch] [--interval ] +-- +-- Shows current Lua memory usage with breakdown estimates. +-- Use --watch to continuously monitor. +-- Useful for detecting memory leaks and understanding overhead. + +local args = { ... } +local watch = false +local interval = 3 + +for i, arg in ipairs(args) do + if arg == '--watch' or arg == '-w' then + watch = true + elseif arg == '--interval' or arg == '-i' then + interval = tonumber(args[i + 1]) or 3 + elseif arg == '--help' or arg == '-h' then + print('Usage: memprofile [--watch] [--interval ]') + print('') + print('Options:') + print(' --watch, -w Continuously monitor memory') + print(' --interval, -i N Update interval in seconds (default: 3)') + print(' --help, -h Show this help') + return + end +end + +local term = _G.term +local os = _G.os + +local function formatBytes(bytes) + if bytes < 1024 then + return string.format('%d B', bytes) + elseif bytes < 1024 * 1024 then + return string.format('%.1f KB', bytes / 1024) + else + return string.format('%.2f MB', bytes / (1024 * 1024)) + end +end + +local function countTable(t, seen) + if type(t) ~= 'table' or seen[t] then return 0, 0 end + seen[t] = true + local entries = 0 + local nested = 0 + for k, v in pairs(t) do + entries = entries + 1 + if type(v) == 'table' then + local e, n = countTable(v, seen) + nested = nested + 1 + e + entries = entries + n + end + if type(k) == 'table' then + local e, n = countTable(k, seen) + nested = nested + 1 + e + entries = entries + n + end + end + return entries, nested +end + +local function getSnapshot() + -- Force a full GC cycle to get accurate usage + collectgarbage('collect') + collectgarbage('collect') + local memKB = collectgarbage('count') -- returns KB as float + + local snapshot = { + totalKB = memKB, + totalBytes = math.floor(memKB * 1024), + timestamp = os.clock(), + } + + -- Count entries in major global tables + local seen = {} + local globals = {} + + local interesting = { + { name = '_G (globals)', tbl = _G }, + { name = 'kernel', tbl = _G.kernel }, + { name = 'network', tbl = _G.network }, + { name = 'device', tbl = _G.device }, + } + + for _, item in ipairs(interesting) do + if type(item.tbl) == 'table' then + local entries, nested = countTable(item.tbl, seen) + table.insert(globals, { + name = item.name, + entries = entries, + nested = nested, + }) + end + end + + snapshot.globals = globals + + -- Count routines if kernel is available + if _G.kernel and _G.kernel.routines then + snapshot.routines = #_G.kernel.routines + end + + -- Count loaded modules + if package and package.loaded then + local count = 0 + for _ in pairs(package.loaded) do + count = count + 1 + end + snapshot.loadedModules = count + end + + return snapshot +end + +local function printSnapshot(snap, prev) + term.clear() + term.setCursorPos(1, 1) + + local w = term.getSize() + local sep = string.rep('-', w) + + term.setTextColor(colors.yellow) + print('=== Memory Profile ===') + term.setTextColor(colors.white) + print('') + + -- Total memory + local memStr = formatBytes(snap.totalBytes) + local deltaStr = '' + if prev then + local delta = snap.totalBytes - prev.totalBytes + if delta > 0 then + deltaStr = string.format(' (+%s)', formatBytes(delta)) + term.setTextColor(colors.red) + elseif delta < 0 then + deltaStr = string.format(' (-%s)', formatBytes(-delta)) + term.setTextColor(colors.green) + end + end + term.setTextColor(colors.white) + print(string.format('Total Memory: %s%s', memStr, deltaStr)) + print(string.format('Uptime: %.1fs', snap.timestamp)) + print('') + + -- Table sizes + term.setTextColor(colors.lightBlue) + print('Global Tables:') + term.setTextColor(colors.white) + print(sep) + print(string.format(' %-20s %8s %8s', 'Name', 'Entries', 'Nested')) + print(sep) + for _, g in ipairs(snap.globals) do + print(string.format(' %-20s %8d %8d', g.name, g.entries, g.nested)) + end + print(sep) + print('') + + -- Kernel info + if snap.routines then + term.setTextColor(colors.lightBlue) + print('Kernel:') + term.setTextColor(colors.white) + print(string.format(' Active routines: %d', snap.routines)) + end + + if snap.loadedModules then + print(string.format(' Loaded modules: %d', snap.loadedModules)) + end + + print('') + + -- CC:Tweaked limits + term.setTextColor(colors.gray) + print('Note: CC:Tweaked default memory limit is ~128MB per computer.') + print('High memory usage may cause slowdowns or crashes.') + + if watch then + print('') + term.setTextColor(colors.yellow) + print(string.format('Refreshing every %ds... (Ctrl+T to stop)', interval)) + end +end + +local function run() + local prev = nil + + if watch then + while true do + local snap = getSnapshot() + printSnapshot(snap, prev) + prev = snap + os.sleep(interval) + end + else + local snap = getSnapshot() + printSnapshot(snap, nil) + end +end + +run()