Logging & Profiling
Basic Logging 📋
Section titled “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
Section titled “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 ⏱️
Section titled “Runtime Utilities ⏱️”seed(seed_value)
Section titled “seed(seed_value)”seed(seed_value) -> normalized_seedSeeds 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(...)
Section titled “random(...)”random() -> number -- uniform in [0, 1)random(upper) -> number|integerrandom(lower, upper) -> number|integerBehavior 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()
Section titled “monotonic_time()”monotonic_time() -> secondsReturns 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)endlocal elapsed = ooc.monotonic_time() - t0ooc.log("elapsed %.6f s", elapsed)Async Logging 💾
Section titled “Async Logging 💾”async_logger_create([capacity]) -> loggerAllocates 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 📊
Section titled “Profiling & Metrics 📊”These helpers surface runtime data to Lua. Scheduler profiling requires enable_profiling = true in the config passed to run.
Context Metrics
Section titled “Context Metrics”context_metrics(ctx) -> tableReturns 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
Section titled “Profiler Snapshot”profiler_snapshot(ctx) -> table|nilReturns 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)endOperator Profiler
Section titled “Operator Profiler”operator_profiler(ctx) -> entries, total_msoperator_profiler(ctx) -> nilReturns 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) endendIf the context has zero operators, the binding returns an empty array and 0.0.
Raw Profiler Snapshot & Counters
Section titled “Raw Profiler Snapshot & Counters”These are low-level profiler views intended for tooling and headless scripts:
context_profiler_snapshot(ctx) -> table|nilcontext_profiler_counters(ctx) -> counters|nilcontext_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|nilscheduler_profiler_counters(ctx) -> counters|nilScheduler Log Drain
Section titled “Scheduler Log Drain”scheduler_drain_logs(ctx, [min_level], [callback]) -> booleanAccepted 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 = 0ooc.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
Section titled “Manual Profiler”profiler_new(operator_count) -> profilerCreates 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)endNeural Model Registry 🧠
Section titled “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)
Section titled “register_neural_model(ctx, config)”register_neural_model(ctx, config) -> model_indexconfig 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])
Section titled “neural_model_stats(ctx[, model_id])”neural_model_stats(ctx, model_id) -> model_table|nilneural_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, ","))endTips 💡
Section titled “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 📋
Section titled “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,})