Add memory profiling script for CC:Tweaked / Opus OS
This commit is contained in:
200
sys/apps/memprofile.lua
Normal file
200
sys/apps/memprofile.lua
Normal file
@@ -0,0 +1,200 @@
|
||||
-- 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()
|
||||
Reference in New Issue
Block a user