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; defaults toINFO. Useooc.LOG_LEVEL_TRACE|DEBUG|INFO|WARN|ERROR|FATAL.- 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”| Name | Level | Description |
|---|---|---|
| SIM_LOG_LEVEL_TRACE | 0 | Very detailed debug information |
| SIM_LOG_LEVEL_DEBUG | 1 | General debug information |
| SIM_LOG_LEVEL_INFO | 2 | Informational messages |
| SIM_LOG_LEVEL_WARN | 3 | Warning conditions |
| SIM_LOG_LEVEL_ERROR | 4 | Error conditions |
| SIM_LOG_LEVEL_FATAL | 5 | Fatal conditions |
💾 Async Logging
Section titled “💾 Async Logging”sim_async_logger_create([capacity]) -> loggerAllocates an in-memory async ring-buffer logger (default capacity 256, floored to at least 64) and returns a Logger userdata. It does not touch the filesystem; records are queued for a consumer to read—safe for multi-threaded or asynchronous pipelines. Pass 0 or omit the argument to use the default capacity.
Logger methods:
logger:log(level, message): enqueue a record.logger:pop(): returns the next record table{timestamp_ns, thread_id, level, message}ornilif empty.logger:clear(): drop all queued records.
Example:
local logger = ooc.sim_async_logger_create(0)
logger:log(ooc.LOG_LEVEL_INFO, "Async Logger test...")logger:log(ooc.LOG_LEVEL_TRACE, string.format("N = %d, dt = %f", N, dt))
while true do local rec = logger:pop() if not rec then break end print(ctx, string.format("[%d] %s", rec.level, rec.message))end💡 Tips
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 (
log("%s %d", 1)will fail). - Use
LOG_LEVEL_*constants exposed byluaopen_libsimcorefor clarity. - When integrating with
sim_on_step, keep log volume low to avoid slowing the simulation.
📊 Profiling & Metrics
Section titled “📊 Profiling & Metrics”These helpers surface runtime data to Lua. Scheduler profiling requires enable_profiling = true in the config passed to sim_run. Adaptive timestep heuristics can be toggled with enable_timestep_heuristics (defaults to true).
Context Metrics
Section titled “Context Metrics”sim_context_metrics(ctx) -> tableReturns summary metadata:
local metrics = ooc.sim_context_metrics(ctx)
-- fields: field_count, operator_count, plan_operator_count, plan_valid,-- backend ("CPU"|"CUDA"|"Metal"), step_index, time_seconds, dt, total_bytes
ooc.log("Backend: %s, Field Count=%d, Operator Count=%d, dt=%.4f, Plan Valid=%s", metrics.backend, metrics.field_count, metrics.operator_count, metrics.dt, metrics.plan_valid and "True" or "False")Field Stats
Section titled “Field Stats”sim_field_stats(ctx_or_field, [field_index]) -> table|nilComputes per-field statistics (mean, RMS, spectral entropy, phase coherence, continuity counts, etc.). You can pass either a field handle or an index. Returns nil if unavailable.
Available statistics:
| Statistic | Description |
|---|---|
| mean_re | Mean of real parts |
| mean_im | Mean of imaginary parts |
| mean_abs | Mean of magnitudes |
| rms | Root mean square of magnitudes |
| var_re | Variance of real parts |
| var_im | Variance of imaginary parts |
| var_abs | Variance of magnitudes |
| max_abs | Maximum magnitude |
| phase_coherence | Phase coherence metric |
| circularity | Circularity metric |
| spectral_entropy | Spectral entropy |
| spectral_bandwidth | Spectral bandwidth |
| phase_coherence_weighted | Weighted phase coherence |
| phase_coherence_ema | EMA of phase coherence |
| phase_coherence_k0 | Phase coherence k0 |
| phase_sample_count | Number of phase samples |
| phase_lock_state | Phase lock state |
| phase_regime | Phase regime |
| count | Number of samples |
-- Create a fieldlocal field = ooc.sim_add_field(ctx, {N}, { type = "complex_double", fill = {0.0, 0.0}})
-- Local time step counterlocal t = 0
-- Pointer to stats of field index 0local stats = ooc.sim_field_stats(ctx, 0)
-- Create a stats operatorlocal stats_op = ooc.sim_add_operator(ctx, "stats_op", function(context) t = t + 1
-- Print results every 1000 steps if t % 1000 == 0 then ooc.log("-----------[ Field Stats ]----------") ooc.log("%-24s %.10f", "Mean Real", stats.mean_re) ooc.log("%-24s %.10f", "Mean Imag", stats.mean_im) ooc.log("%-24s %.10f", "Var Real", stats.var_re) ooc.log("%-24s %.10f", "Var Imag", stats.var_im) ooc.log("%-24s %.10f", "Var Abs", stats.var_abs) ooc.log("%-24s %.10f", "RMS", stats.rms) ooc.log("%-24s %.10f", "Max Abs", stats.max_abs) ooc.log("%-24s %.10f", "Phase Coherence", stats.phase_coherence) ooc.log("%-24s %.10f", "Phase Coherence Weighted", stats.phase_coherence_weighted) ooc.log("%-24s %.10f", "Phase Coherence EMA", stats.phase_coherence_ema) ooc.log("%-24s %.10f", "Phase Coherence k0", stats.phase_coherence_k0) ooc.log("%-24s %.10f", "Phase Regime", stats.phase_regime) ooc.log("%-24s %.10f", "Circularity", stats.circularity) ooc.log("%-24s %.10f", "Spectral Entropy", stats.spectral_entropy) ooc.log("%-24s %.10f", "Spectral Bandwidth", stats.spectral_bandwidth) end
return true -- or return 0 (SIM_RESULT_OK)end)Example output:
-----------[ Field Stats ]----------Mean Real 0.0004454985Mean Imag -0.0003130782Var Real 0.0365573493Var Imag 0.0622247355Var Abs 0.0198729688RMS 0.3142966455Max Abs 0.4487944696Phase Coherence 0.0232840599Phase Coherence Weighted 0.0019383776Phase Coherence EMA 0.0019383776Phase Coherence k0 0.8311182350Phase Regime 3.0000000000Circularity 0.2664554384Spectral Entropy 0.2546378467Spectral Bandwidth 3.7603059001Field Views & Manual Stats (Advanced)
Section titled “Field Views & Manual Stats (Advanced)”These helpers are lower-level building blocks behind sim_field_stats(...). They’re useful for tooling (custom UI, offline analysis, or custom sampling loops) where you want explicit control.
sim_field_view_from_field(field) -> view_tablesim_field_stats_compute(field) -> stats_tableview_table is a lightweight description of a field buffer:
data(lightuserdata) raw pointer (only present when available)count(integer) number of elementstype(integer) field data type enumtype_name("double"or"complex_double")
Streaming Accumulator
Section titled “Streaming Accumulator”Use the accumulator when you want to feed samples manually (without having a Field):
sim_field_stats_accumulator_begin([acc_or_nil]) -> accsim_field_stats_accumulate_real(acc, value)sim_field_stats_accumulate_complex(acc, re, im)sim_field_stats_accumulator_finish(acc) -> stats_tableSpectral + Phase Post-Processing
Section titled “Spectral + Phase Post-Processing”sim_field_stats_compute_spectral_view(field_or_view, stats_table) -> ok, dominant_ksim_field_stats_compute_phase_metrics(field_or_view, stats_table, [phase_config], [dominant_k]) -> nilsim_field_stats_update_phase_lock(ctx, field_index, stats_table) -> nilsim_field_stats_compute_spectral_viewupdatesstats_table(e.g. spectral entropy/bandwidth) and returnsdominant_k(a frequency-bin index) when available.sim_field_stats_compute_phase_metricsupdates phase metrics (coherence/EMA/lock state) in-place; you can pass an explicitphase_configtable and/ordominant_k.sim_field_stats_update_phase_lockwrites lock-state history back into the context for a givenfield_index(mutatingstats_tablein-place).
Phase Config (Global)
Section titled “Phase Config (Global)”sim_field_stats_get_phase_config() -> config_tablesim_field_stats_set_phase_config(config_table)config_table keys:
abs_threshold,rel_thresholdweighted(boolean)lock_on,lock_offsmoothing_constantderamp_enabled
These are the same underlying knobs as sim_set_phase_coherence_* (those convenience helpers update this global config).
Phase Coherence Tuning
Section titled “Phase Coherence Tuning”These functions tune the phase coherence and phase-lock heuristics used by sim_field_stats. They accept a context for convenience, but update the global phase configuration (shared across contexts).
sim_set_phase_coherence_thresholds(ctx, abs_threshold, rel_threshold)sim_set_phase_coherence_weighted(ctx, weighted)sim_set_phase_coherence_lock_thresholds(ctx, lock_on, lock_off)sim_set_phase_coherence_smoothing(ctx, smoothing_seconds)sim_set_phase_coherence_deramp(ctx, enabled)Notes:
sim_set_phase_coherence_thresholdstreats negative inputs as “leave default”.sim_set_phase_coherence_weighted(true)makes lock/EMA logic preferphase_coherence_weightedoverphase_coherence.
Profiler Snapshot (Visual)
Section titled “Profiler Snapshot (Visual)”sim_profiler_snapshot(ctx) -> table|nilReturns a snapshot of the last profiled frame with fields:
frame_start_nsframe_end_nstotal_nsaverage_operator_ns
local t = 0local snap_op = ooc.sim_add_operator(ctx, "snap_op", function(context) if t % 1000 == 0 then
ooc.log("-------[ Profiler Snapshot ]--------") local snap = ooc.sim_profiler_snapshot(ctx)
ooc.log("Frame=%g ns, Average Op=%g ns", snap.total_ns, snap.average_operator_ns) end return trueend)Operator Profiler
Section titled “Operator Profiler”sim_operator_profiler(ctx) -> entries, total_msPer-operator timing and RMS deltas for the last profiled frame. Returns a list of entries with fields:
nameinclusive_msrms_delta
local profiler_op = ooc.sim_add_operator(ctx, "profiler_op", function(context) if t % 1000 == 0 then ooc.log("-------[ Operator Profiler ]--------") local entries, total_ms = ooc.sim_operator_profiler(ctx)
if entries then for _, e in ipairs(entries) do ooc.log("%-24s %.5f ms", e.name, e.inclusive_ms) end ooc.log("%-24s %.5f ms", "total", total_ms) end end return trueend)Example output:
-------[ Operator Profiler ]--------mixer_53#0 0.00029 msstimulus_sine_54#0 0.00000 msstimulus_sine_55#0 0.00000 msmixer_56#0 0.00000 msprofiler_op 0.00000 mstotal 0.00029 msRaw Profiler Snapshot & Counters
Section titled “Raw Profiler Snapshot & Counters”These are low-level profiler views intended for tooling and headless scripts:
sim_context_profiler_snapshot(ctx) -> table|nilsim_context_profiler_counters(ctx) -> counters|nilsim_context_profiler_counters returns an array of counter tables (plan index order) with:
inclusive_nsinvocationsdelta_rms_sumdelta_sample_count
When the scheduler is running with profiling enabled, you can also query the scheduler’s last frame:
sim_scheduler_profiler_snapshot(ctx) -> table|nilsim_scheduler_profiler_counters(ctx) -> counters|nilManual Profiler
Section titled “Manual Profiler”sim_profiler_new(operator_count) -> profilerCreates a standalone profiler userdata you can drive manually (distinct from the scheduler profiler). 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}
record_operator_delta records RMS-change metadata for an operator (native: sim_profiler_record_operator_delta).
Example:
local p = ooc.sim_profiler_new(3)
p:begin_frame()-- ... measure work ...p:record_operator(1, 250000) -- 0-based indexp:end_frame()
local snap = p:snapshot()
print("manual frame ns", snap.total_ns)⚙️ Configuration Flags
Section titled “⚙️ Configuration Flags”Pass these in the table to ooc.sim_run(ctx, config_table):
enable_profiling = trueto collect scheduler profiler data forsim_profiler_snapshot/sim_operator_profiler(andsim_scheduler_profiler_*).enable_logging = trueto turn on the async logger backend.enable_timestep_heuristics = falseto disable adaptive dt decisions (andsim_timestep_decisionwill reportavailable = false).
ooc.sim_run(ctx, { enable_profiling = true, enable_logging = true, enable_timestep_heuristics = true,})