Platform

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:

NameWhat 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:

NameValueconsole method used by ConsoleLoggerBackend
Severe0console.error
Error1console.error
Warning2console.warn
Info3console.log
Debug4console.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

ClassBehavior
ConsoleLoggerBackendForwards to console.log / .warn / .error. Singleton at .INSTANCE.
VoidLoggerBackendDiscards all messages. Useful in tests or production builds. Singleton at .INSTANCE.
ElasticSearchLoggerBuffers 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());