201 lines
5.4 KiB
Lua
201 lines
5.4 KiB
Lua
-- Memory Profiler for CC:Tweaked / Opus OS
|
|
-- Usage: memprofile [--watch] [--interval <seconds>]
|
|
--
|
|
-- 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 <secs>]')
|
|
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()
|