Profiling
tecs.utils.profile exposes two independent sessions:
- Sampling answers where CPU time goes.
- Trace tracking reports where LuaJIT abandons compilation.
Require this supported utility directly:
local profile <const> = require("tecs.utils.profile")Only one sampling session and one trace session may run at a time. Stopping a session twice raises.
Sampling a run
The sampler writes collapsed stacks for Speedscope, FlameGraph, Inferno, Pyroscope, and similar tools. Pipeline zones attribute samples to fixed-loop regions, phases, and systems.
This system records five seconds and writes the result under the writable root:
local session <const> = profile.sample({
intervalMs = 10,
stackDepth = 16,
})
world:addSystem({
name = "profile.StopSample",
phase = tecs.ecs.phases.First,
runIf = tecs.ecs.runif.after(5),
run = function()
session:stop(tecs.io.files.writablePath("tecs.collapsed"))
end,
})Longer intervals reduce overhead. Deeper stacks cost more per sample. A zone prefix can restrict output to one pipeline subtree.
Add jit.zone around expensive regions inside a system:
local zone <const> = require("jit.zone")
zone("uploadBuffers")
uploadBuffers()
zone()Leave useful zone calls in production code. They do almost nothing when no profiling session needs them. Do not push and pop zones inside hot row loops.
Trace failures
Sampling shows slow code but cannot say whether LuaJIT compiled it. profile.trace aggregates trace aborts by reason, source location, and active zone:
local session <const> = profile.trace()
-- Run the workload.
local report <const> = session:stop(
tecs.io.files.writablePath("aborts.csv")
)
print(report.totalAborts, report.blacklisted)The report and its file use RFC 4180 CSV. Rows sort actionable failures first.
| Severity | Meaning | Response |
|---|---|---|
blacklist |
LuaJIT stopped attempting the trace for this run. | Investigate hot sites. |
warn |
Recording hit unsupported bytecode or a trace limit. | Rewrite only when the site matters. |
info |
Normal trace-formation events. | Include only during a focused investigation. |
Many aborts occur in cold code and cost nothing important. Use the zone and location to decide whether a row belongs to a hot workload.
Pause and resume either session to exclude setup or teardown while preserving data from both sides of the pause.
Measurements outside the profiler
Sampling and trace reports cannot answer every performance question.
Input latency measures the time from the oldest consumed input event to the submission of the frame that reacted to it. Frame averages cannot substitute for that measurement. A pipelined frame may improve throughput while increasing latency.
Allocation measurements need a separate run. collectgarbage("count") reports heap size, not allocated bytes, and a collection can erase the delta. Stop the collector during the measurement window. Do not put collectgarbage probes inside the frame you want to characterize because the probe itself aborts traces.
Run once without frame probes for the total, then run again with coarse phase probes for attribution. Use trace tracking to reject runs in which the probes changed compilation.