Diagnostics
The built-in frame-time performance metrics, the console FPS overlay, and the pluggable logging system with console, void, and Elasticsearch backends.
Meep ships two diagnostic tools: a set of ring-buffer performance metrics that the engine populates every frame and prints to the console on an interval, and a pluggable logging system that routes structured messages to one or more backends.
Performance metrics
What the engine tracks
Every Engine instance owns a MetricCollection at engine.performance. The
engine pre-creates three named metrics and records into them on every frame:
| Name | What is recorded |
|---|---|
"frame_delay" | Wall-clock time between animation frames (seconds) |
"render_time" | Time spent inside GraphicsEngine.render() (seconds) |
"simulation_time" | Time spent advancing EntityManager.simulate() (seconds) |
Reading metrics
Each metric is a RingBufferMetric holding the last 128 samples. Call
computeStats (zero-allocation) or access the .stats getter (allocates a
new MetricStatistics):
import { MetricStatistics }
from "@woosh/meep-engine/src/engine/development/performance/MetricStatistics.js";
const stats = new MetricStatistics();
engine.performance.get("render_time").computeStats(stats);
console.log(`render mean: ${(stats.mean * 1000).toFixed(2)} ms`);
console.log(`render max: ${(stats.max * 1000).toFixed(2)} ms`);
// stats also has .min and .median
getLastRecord() returns the most recent value without computing the full
statistics.
Automatic console output
On engine.start() the engine begins a PeriodicConsolePrinter that fires
every 15 seconds. It computes stats for all three metrics, clears each buffer,
and logs a line like:
FPS: 59.97, RENDER: 2.34ms, SIMULATION: 1.12ms
This runs without any setup. To stop it — for instance in a production build —
call engine.__performacne_monitor.stop() after start() resolves. (The
field is private by convention; there is no public API for this yet.)
Adding custom metrics
import { MetricCollection }
from "@woosh/meep-engine/src/engine/development/performance/MetricCollection.js";
// engine.performance is already a MetricCollection.
const metric = engine.performance.create({ name: "pathfinding_ms", buffer_size: 64 });
// in your update loop:
const t0 = performance.now();
runPathfinding();
metric.record(performance.now() - t0);
create({ name, buffer_size }) adds a RingBufferMetric and returns it.
buffer_size defaults to 128; larger values give more accurate statistics at
the cost of memory. metric.clear() flushes all samples without removing the
metric from the collection.
Streaming to the console
MetricCollectionConsoleMonitor wraps any MetricCollection and prints all
of its metrics on a configurable interval:
import { MetricCollectionConsoleMonitor }
from "@woosh/meep-engine/src/engine/development/performance/monitor/MetricCollectionConsoleMonitor.js";
MetricCollectionConsoleMonitor.from(engine.performance, 5).start();
// prints all metric stats every 5 seconds
from(metrics, timeout_seconds) returns a PeriodicConsolePrinter. Call
.start() to begin and .stop() to end.
FPS overlay widget
EngineHarness.addFpsCounter(engine) mounts a Three.js Stats panel in the
engine’s view stack. It hooks into engine.graphics.on.postRender and shows a
small real-time FPS/ms graph in the top-left corner of the viewport:
import { EngineHarness } from "@woosh/meep-engine/src/engine/EngineHarness.js";
// after bootstrap:
EngineHarness.addFpsCounter(engine);
buildBasics also accepts showFps: true (the default) and calls this
automatically. The overlay is Three.js’s stats.module — it has no engine
coupling and can be removed by not calling this method.
Logging
Global logger
logger is a process-wide Logger instance exported from
engine/logging/GlobalLogger.js. It starts with no backends — messages are
silently dropped until you add one.
import { logger }
from "@woosh/meep-engine/src/engine/logging/GlobalLogger.js";
import { ConsoleLoggerBackend }
from "@woosh/meep-engine/src/engine/logging/ConsoleLoggerBackend.js";
logger.addBackend(ConsoleLoggerBackend.INSTANCE);
EngineHarness calls this automatically during construction.
Log levels
LogLevel is a numeric enum — lower numbers are more severe:
| Name | Value | console method used by ConsoleLoggerBackend |
|---|---|---|
Severe | 0 | console.error |
Error | 1 | console.error |
Warning | 2 | console.warn |
Info | 3 | console.log |
Debug | 4 | console.log |
Each backend has a level threshold. A message is only forwarded if its
level is at or below the backend’s threshold. The default threshold is Info
(3), so Debug messages are suppressed by default.
import { LogLevel }
from "@woosh/meep-engine/src/engine/logging/LogLevel.js";
backend.setLevel(LogLevel.Debug); // see everything
backend.setLevel(LogLevel.Warning); // suppress Info and Debug
Logging from application code
logger.info("scene loaded");
logger.warn("asset missing, using fallback");
logger.error("physics body escaped the world");
// or with explicit level:
logger.log(LogLevel.Debug, "collision narrowphase iteration 42");
Backends
| Class | Behavior |
|---|---|
ConsoleLoggerBackend | Forwards to console.log / .warn / .error. Singleton at .INSTANCE. |
VoidLoggerBackend | Discards all messages. Useful in tests or production builds. Singleton at .INSTANCE. |
ElasticSearchLogger | Buffers records in a deque and bulk-posts to an Elasticsearch index via XMLHttpRequest. |
Elasticsearch backend
import { ElasticSearchLogger }
from "@woosh/meep-engine/src/engine/logging/elastic/ElasticSearchLogger.js";
const es = new ElasticSearchLogger({
url: "https://logs.example.com",
target: `game-log-${new Date().toISOString()}`
});
logger.addBackend(es);
The backend buffers records locally and flushes when either 500 records
accumulate or 2 000 ms pass since the last flush, whichever comes first.
Each flushed batch is a POST to <url>/<target>/_bulk. Timestamps come from
performance.now().
Registering multiple backends
logger.addBackend and logger.removeBackend work on the same list. A
message is dispatched to every backend whose threshold allows it:
logger.addBackend(ConsoleLoggerBackend.INSTANCE); // development
logger.addBackend(es); // production telemetry
// later:
logger.removeBackend(ConsoleLoggerBackend.INSTANCE);
Writing a custom backend
Extend LoggerBackend and implement log(level, message):
import { LoggerBackend }
from "@woosh/meep-engine/src/engine/logging/LoggerBackend.js";
import { LogLevel }
from "@woosh/meep-engine/src/engine/logging/LogLevel.js";
class RemoteBackend extends LoggerBackend {
constructor() {
super();
this.setLevel(LogLevel.Warning); // only warnings and above
}
log(level, message) {
fetch("/api/logs", {
method: "POST",
body: JSON.stringify({ level, message, ts: Date.now() }),
headers: { "Content-Type": "application/json" }
});
}
}
logger.addBackend(new RemoteBackend());