From bf09a8844ae7b03b9a90d9332f92c2d0e72599d6 Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 12 Aug 2026 00:12:34 -0400 Subject: [PATCH] profiler --- profiler.lua | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 profiler.lua diff --git a/profiler.lua b/profiler.lua new file mode 100644 index 0000000..82e159d --- /dev/null +++ b/profiler.lua @@ -0,0 +1,59 @@ +-- Developer-only CPU sampler. It is inert until F9 is pressed. +-- +-- On stop it writes `chroma-solstice.folded` in LÖVE's save directory. The +-- file uses Brendan Gregg's folded-stack format and can be given directly to +-- FlameGraph's flamegraph.pl. +PerfProfiler = { + active = false, + samples = {}, + status = "F9: start CPU profile", +} + +local ok, profile = pcall(require, "jit.profile") +PerfProfiler.available = ok +if not ok then + PerfProfiler.status = "LuaJIT sampler unavailable" +end + +local function sample(thread, count, vmstate) + -- Negative depth produces caller -> callee order, which is what folded + -- flamegraphs expect. Function-level samples keep the output compact. + local stack = profile.dumpstack(thread, "pF;", -64):gsub(";$", "") + if stack ~= "" then + PerfProfiler.samples[stack] = (PerfProfiler.samples[stack] or 0) + count + end +end + +function PerfProfiler:toggle() + if not self.available then return false end + if not self.active then + self.samples = {} + profile.start("fi2", sample) + self.active = true + self.status = "CPU profile recording (F9 to write)" + return true + end + + profile.stop() + self.active = false + local lines, sampleCount = {}, 0 + for stack, count in pairs(self.samples) do + table.insert(lines, stack .. " " .. count) + sampleCount = sampleCount + count + end + table.sort(lines) + local path = "chroma-solstice.folded" + local saved = love.filesystem.write(path, table.concat(lines, "\n") .. "\n") + local directory = love.filesystem.getSaveDirectory() + self.status = saved and ("CPU profile saved (" .. sampleCount .. " samples); see terminal") + or "Could not write CPU profile" + if saved then print("CPU profile: " .. directory .. "/" .. path) else print(self.status) end + return saved +end + +function PerfProfiler:draw() + if not self.active and self.status == "F9: start CPU profile" then return end + love.graphics.setColor(self.active and 1 or 0.6, self.active and 0.75 or 1, self.active and 0.2 or 0.6, 1) + love.graphics.print(self.active and "PROFILING: F9 to save flamegraph data" or self.status, 10, 28) + love.graphics.setColor(1, 1, 1, 1) +end