Logging & Profiling
On this page
Basic Logging 📋
log([ctx], [level], fmt, ...)
- If the first argument is a context, it is ignored for logging but allowed for convenience.
levelis optional and must be an integer log-level constant such asooc.LOG_LEVEL_INFO.- Remaining arguments follow Lua
string.formatsemantics; the message is formatted before printing.
Examples:
ooc.log("Hello from Lua")
ooc.log(ooc.LOG_LEVEL_DEBUG, "value=%.3f", 1.234)
ooc.log(ctx, ooc.LOG_LEVEL_WARN, "step %d exceeded threshold", step)
Log Levels
| Constant | Level | Description |
|---|---|---|
ooc.LOG_LEVEL_TRACE |
0 | Very detailed debug information |
ooc.LOG_LEVEL_DEBUG |
1 | General debug information |
ooc.LOG_LEVEL_INFO |
2 | Informational messages |
ooc.LOG_LEVEL_WARN |
3 | Warning conditions |
ooc.LOG_LEVEL_ERROR |
4 | Error conditions |
ooc.LOG_LEVEL_FATAL |
5 | Fatal conditions |
String level shorthands are accepted by scheduler_drain_logs, not by log: "trace", "debug", "info", "warn" (or "warning"), "error", "fatal".
Runtime Utilities ⏱️
seed(seed_value)
seed(seed_value) -> normalized_seed
Seeds Oakfield’s Lua-side RNG stream and returns the normalized seed actually used.
local normalized = ooc.seed(1234)
ooc.log("rng seeded with %d", normalized)
random(...)
random() -> number -- uniform in [0, 1)
random(upper) -> number|integer
random(lower, upper) -> number|integer
Behavior matches the argument types you pass:
random()returns a floating-point number in[0, 1).random(integer_upper)returns an integer in[1, upper].random(number_upper)returns a float in[0, upper).random(integer_lower, integer_upper)returns an integer in[lower, upper].random(number_lower, number_upper)returns a float in[lower, upper].
ooc.seed(42)
local u = ooc.random()
local die = ooc.random(6)
local signed = ooc.random(-1.0, 1.0)
monotonic_time()
monotonic_time() -> seconds
Returns wall-clock time from a monotonic source in seconds. This is useful for benchmarking and elapsed-time reporting.
local t0 = ooc.monotonic_time()
for i = 1, 1000 do
ooc.step(ctx)
end
local elapsed = ooc.monotonic_time() - t0
ooc.log("elapsed %.6f s", elapsed)
Async Logging 💾
async_logger_create([capacity]) -> logger
Allocates an in-memory async ring-buffer logger and returns a Logger userdata. The optional capacity argument defaults to 256.
Logger methods:
logger:log(level, message) -> true
level must be an integer log-level constant. The method raises a Lua error if the record cannot be appended.
Example:
local logger = ooc.async_logger_create()
assert(logger:log(ooc.LOG_LEVEL_INFO, "Async Logger test..."))
assert(logger:log(ooc.LOG_LEVEL_TRACE, string.format("N = %d, dt = %f", N, dt)))
Profiling & Metrics 📊
These helpers surface runtime data to Lua. Scheduler profiling requires enable_profiling = true in the config passed to run.
Context Metrics
context_metrics(ctx) -> table
Returns a lightweight health snapshot useful for logging and diagnostics: field_count, operator_count, plan_operator_count, plan_valid, backend, step_index, time_seconds, dt, total_bytes. See Introspection for the full reference.
local m = ooc.context_metrics(ctx)
ooc.log("t=%.3f dt=%.4f backend=%s plan_valid=%s",
m.time_seconds, m.dt, m.backend, tostring(m.plan_valid))
Profiler Snapshot
profiler_snapshot(ctx) -> table|nil
Returns the last profiled frame snapshot, or nil when no snapshot is available. The table contains:
frame_start_nsframe_end_nstotal_nsaverage_operator_ns
local snap = ooc.profiler_snapshot(ctx)
if snap then
ooc.log("frame=%.3f ms avg=%.3f ns", snap.total_ns / 1e6, snap.average_operator_ns)
end
Operator Profiler
operator_profiler(ctx) -> entries, total_ms
operator_profiler(ctx) -> nil
Returns per-operator timing for the last profiled frame. When available, entries is an array of:
nameinclusive_msinvocationsdelta_rms
If no profiled frame is available, the binding returns nil.
local entries, total_ms = ooc.operator_profiler(ctx)
if entries then
ooc.log("operator profiler total=%.4f ms", total_ms)
for _, e in ipairs(entries) do
ooc.log("%-24s %.5f ms", e.name, e.inclusive_ms)
end
end
If the context has zero operators, the binding returns an empty array and 0.0.
Raw Profiler Snapshot & Counters
These are low-level profiler views intended for tooling and headless scripts:
context_profiler_snapshot(ctx) -> table|nil
context_profiler_counters(ctx) -> counters|nil
context_profiler_counters returns an array of counter tables in plan-index order, each with:
inclusive_nsinvocationsdelta_rms_sumdelta_sample_count
When the scheduler is running with profiling enabled, you can also query the scheduler’s last frame:
scheduler_profiler_snapshot(ctx) -> table|nil
scheduler_profiler_counters(ctx) -> counters|nil
Scheduler Log Drain
scheduler_drain_logs(ctx, [min_level], [callback]) -> boolean
Accepted call patterns:
scheduler_drain_logs(ctx)drains and discards all pending recordsscheduler_drain_logs(ctx, "warn")drains onlywarnand abovescheduler_drain_logs(ctx, function(level, message) ... end)drains and invokes the callbackscheduler_drain_logs(ctx, "info", function(level, message) ... end)combines filtering and callbacks
The callback receives the numeric log level and the message string. If the callback raises, scheduler_drain_logs raises a Lua error.
local seen = 0
ooc.scheduler_drain_logs(ctx, "info", function(level, message)
seen = seen + 1
ooc.log(ooc.LOG_LEVEL_DEBUG, "scheduler[%d] %s", level, message)
end)
Manual Profiler
profiler_new(operator_count) -> profiler
Creates a standalone profiler userdata you can drive manually. Methods:
profiler:begin_frame()profiler:end_frame()profiler:record_operator(index, duration_ns)profiler:record_operator_delta(index, delta_rms, [sample_count])profiler:snapshot() -> {frame_start_ns, frame_end_ns, total_ns, average_operator_ns}|nil
Example:
local p = ooc.profiler_new(3)
p:begin_frame()
p:record_operator(0, 250000)
p:record_operator_delta(0, 1.0e-4, 4)
p:end_frame()
local snap = p:snapshot()
if snap then
print("manual frame ns", snap.total_ns)
end
Neural Model Registry 🧠
These runtime helpers register backend models for Neural Infer / Neural Hybrid workflows and inspect their runtime stats.
register_neural_model(ctx, config)
register_neural_model(ctx, config) -> model_index
config keys:
model_idorid(required)backenddefault_precisiondeterministicexternal_commandorcommandnotesupported_devices
Accepted backend strings:
callbackexternal_processonnx_runtimelibtorch
Accepted default_precision strings:
defaultfp32fp64mixedfp16bf16
supported_devices may be:
- a string such as
"cpu,cuda"or"cpu|mps" - a Lua array like
{ "cpu", "cuda" } - an integer device mask
From Lua, the practical backends are external_process, onnx_runtime, and libtorch. The callback backend requires a native callback pointer, which the Lua registration helper does not expose.
local model_index = ooc.register_neural_model(ctx, {
model_id = "demo",
backend = "external_process",
command = "/bin/true",
default_precision = "fp32",
supported_devices = { "cpu" },
deterministic = true,
note = "placeholder process-backed model",
})
neural_model_stats(ctx[, model_id])
neural_model_stats(ctx, model_id) -> model_table|nil
neural_model_stats(ctx) -> model_table[]
With a model_id, returns one table or nil if the model is not found. Without a model_id, returns a 1-based array of all registered models.
Each table may include:
model_idbackendbackend_indexdeterministicdefault_precision_indexexternal_commandnotesupported_device_masksupported_devicesinvocation_countsuccess_countfailure_counttotal_elementslast_wall_mstotal_wall_mslast_step_indexlast_sim_timelast_resultlast_devicelast_error
local one = ooc.neural_model_stats(ctx, "demo")
if one then
ooc.log("model=%s backend=%s calls=%d",
one.model_id, one.backend, one.invocation_count)
end
for _, model in ipairs(ooc.neural_model_stats(ctx)) do
ooc.log("%s supports %s", model.model_id, table.concat(model.supported_devices, ","))
end
Tips 💡
logprepends level tags when not in GUI mode; in GUI mode it emits raw text.- Formatting errors raise a Lua error with the reason.
- Use
ooc.LOG_LEVEL_*constants rather than raw integers for clarity. monotonic_time()is preferable toos.clock()for wall-clock benchmarking.seed()andrandom()control Oakfield’s Lua RNG stream only; integrator RNGs and context RNGs use their own APIs.
Configuration Flags 📋
Pass these in the table to ooc.run(ctx, config_table):
enable_profiling = trueto collect scheduler profiler data forprofiler_snapshot,operator_profiler, andscheduler_profiler_*enable_logging = trueto turn on the scheduler async logger backendenable_timestep_heuristics = falseto disable adaptive dt decisions
ooc.run(ctx, {
enable_profiling = true,
enable_logging = true,
enable_timestep_heuristics = true,
})