Blender Add-on¶
The Micromegas Blender add-on captures session telemetry — user actions, lifecycle events, and performance metrics — from a running Blender instance and ships them to a Micromegas ingestion server. This gives your team visibility into Blender stability and performance without modifying Blender itself or requiring a custom build.
Architecture¶
Blender process
├── Blender C/C++ core ──(crash)──► Blender's own *.crash.txt
└── Embedded CPython
└── micromegas_blender add-on
├── crash_harvester (ships prior crash files on next launch)
├── handlers (bpy.app.handlers + bpy.msgbus + periodic metrics)
├── recorder (persistent modal operator — raw input events)
├── actions (operator-history poller — semantic actions)
└── binding (ctypes → libmicromegas_capi)
│
libmicromegas_capi.{so,dll}
└── HttpEventSink ── OS thread ──► ingestion-srv
The native library owns its own OS thread for HTTP transport. Python code never blocks on the network.
Installation¶
Prerequisites¶
- Blender 4.2 or later (stock public build, x86-64)
- A running Micromegas ingestion server
Install the add-on¶
- Download the pre-built extension zip from the releases page.
- The extension zip bundles the pre-compiled
libmicromegas_capi.so/micromegas_capi.dllfor both platforms. - In Blender: Edit → Preferences → Extensions → Install from Disk… → select the
.zipfile. - Enable Micromegas Telemetry.
Manual install (development)¶
# 1. Build the native library
cd rust/
cargo build --package micromegas-capi --release
# 2. Copy it into the add-on's lib/ directory
cp target/release/libmicromegas_capi.so \
../blender/micromegas_blender/lib/
# 3. Symlink or copy the add-on directory into Blender's extensions path
ln -s $(pwd)/../blender/micromegas_blender \
~/.config/blender/4.x/extensions/user_default/micromegas_blender
Configuration¶
Runtime configuration is via environment variables set before launching Blender (system-wide or via the launcher script used in your studio pipeline).
| Variable | Required | Description |
|---|---|---|
MICROMEGAS_TELEMETRY_URL |
Yes | Ingestion server, e.g. http://ingestion:9000 |
MICROMEGAS_INGESTION_API_KEY |
No | API key for authenticated ingestion |
MICROMEGAS_OIDC_TOKEN_ENDPOINT |
No | OIDC token endpoint (alternative auth) |
MICROMEGAS_OIDC_CLIENT_ID |
No | OIDC client ID |
MICROMEGAS_OIDC_CLIENT_SECRET |
No | OIDC client secret |
Add-on preferences¶
One setting lives in Edit → Preferences → Add-ons → Micromegas Telemetry rather than in the environment:
| Preference | Default | Description |
|---|---|---|
| Keep Alive (Dev Only) | off | Keep the telemetry session alive across an add-on disable/enable within the same Blender process. Leave off for normal use. |
The native telemetry layer can only be initialized once per process, so
disabling and re-enabling the add-on inside a running Blender normally leaves it
inactive until Blender is restarted — which tooling that re-enables the add-on
on every launch (notably the VS Code Blender Development extension) hits on
every debug session. With Keep Alive on, the live session is parked on
disable instead of shut down, and the next enable in the same process reuses it
(same session_id). A session that is already parked stays reusable even if the
preference is turned off afterward; turning it off only takes effect on the
next disable, which then shuts the session down for good until Blender restarts.
What is captured¶
Process fingerprint (at startup)¶
These properties are attached to the process record and are the dimensions that make stability and performance analysis possible:
- Blender version + build hash
- OS version, platform
- Per-launch session UUID
- Add-on version
- GPU renderer, vendor, backend, and driver/GL version (
gpu_renderer,gpu_vendor,gpu_backend,gpu_driver) — the #1 crash dimension for a DCC tool (skipped in--backgroundmode where no GPU context exists) - Enabled third-party add-ons as a sorted
name@versionlist (enabled_addons, capped ~2 KB) — a leading cause of Blender instability - CPU core count, total RAM (
cpu_count,total_ram_mb) - Embedded Python version (
python_version) - Active render engine (
render_engine) - Headless vs interactive (
background)
User actions (continuous)¶
Two complementary streams:
Raw input events — captured via a persistent modal operator (recorder):
- Discrete key/mouse events (type, area) — logged at TRACE level
- Throttled mouse motion (at most once per second) — logged at TRACE level
Semantic actions — captured by the operator-history poller (actions):
- Each operator the user invoked (
bl_idname, name, and parameters when available) drained frombpy.context.window_manager.operatorsand logged toblender.actionat TRACE level. This is the "what did the user click" stream — adding a cube, switching to edit mode, running a modifier. - Edits made via the redo panel (F9) after an operator has run — Blender
re-executes the same operator with new parameters via
undo_postrather thanredo_post— are detected separately and logged toblender.action_redoat TRACE level with the updated parameters. A detected redo-panel edit replaces theblender.lifecycle"undo" log for that event — or the "redo" log, on any Blender version that does route the edit throughredo_post(see below) — since the underlying fact is a parameter change, not an undo. - Mode / workspace / active-tool transitions →
blender.mode,blender.workspace,blender.tool(TRACE). - Runtime add-on enable/disable →
blender.addon_state(INFO) — a mid-session enable is a prime crash trigger.
Draining is event-driven: on every discrete input event (key/mouse/scroll)
the recorder modal calls drain_operators() so the ring is drained at
per-keystroke cadence, well within the 32-operator hard cap. A 0.1 s timer runs
as a backstop for periods when the recorder modal is suspended or receiving only
motion events.
The ring is small and ordered (oldest→newest); each drain emits the entries not
seen on the previous drain, tracked by each entry's stable per-entry identity
(op.as_pointer(), the address of the underlying Blender operator record)
rather than by position — this is exact even when the recent operator history
repeats a pattern. A gap is logged only when a full ring turns over entirely
between two consecutive drains (e.g. a script/macro burst) — the sole condition
that means entries were dropped before they could be seen — rather than
silently dropping them. Two metrics track capture health:
| Metric | Unit | Description |
|---|---|---|
blender.action_captured |
count | Operators successfully logged, including redo-panel updates (emitted only when > 0) |
blender.action_gap |
count | Ring-overflow events between drains |
Coverage is high but not 100%
The recorder modal is suspended while a full-screen sub-modal (knife, grab, …)
is running; the timer backstop covers that window. The ring is hard-capped at
32 entries by Blender (MAX_OP_REGISTERED) and cannot be resized via Python.
Operator parameter extraction from stored history entries is best-effort:
when parameters are unavailable the action is still logged with its bl_idname
and name.
Lifecycle events (logged at INFO unless noted)¶
- Blend file loaded / saved
- Undo / redo (DEBUG) — the "undo"/"redo" log is suppressed when the same event
is instead recognized as a redo-panel edit and logged to
blender.action_redo(see above) - Render start / complete — cancel is logged at WARN
Python exceptions¶
Unhandled exceptions that reach the embedded interpreter's top level are shipped
as ERROR-level logs to the blender.exception target with the full traceback
(capped ~4 KB), then chained to the previous sys.excepthook.
Backstop, not full coverage
Blender wraps operators, bpy.app.handlers, and bpy.app.timers in its own
C-level execution handlers and reports their exceptions through its console,
so those never reach sys.excepthook. The add-on's own timers and
handlers guard themselves; this hook catches the comparatively rare
exceptions that do propagate to the interpreter top level.
Performance metrics¶
| Metric | Unit | Description |
|---|---|---|
blender.depsgraph_update_interval_ms |
ms | Wall-clock interval between consecutive depsgraph updates (includes idle time between edits — not pure evaluation time; Blender exposes no pre-eval hook) |
blender.render_duration_s |
s | Wall time per render |
blender.blend_size_mb |
mb | .blend file size at save |
blender.rss_mb |
mb | Process resident memory, sampled every ~30 s (Linux + Windows) |
blender.object_count |
count | Objects in the active scene, sampled every ~30 s |
blender.frame |
frame | Current frame number at frame-change |
Crash capture (harvest on next launch)¶
On startup the add-on scans for *.crash.txt files written by Blender after
an abnormal exit. If found, the file is claimed via atomic rename and
uploaded as a FATAL-level log entry.
- Dedup-safe: two concurrent Blender instances each try to
rename()the crash file; only the winner uploads it. - Best-effort: if the upload fails after claiming, the crash is lost — there is no retry queue.
Cardinality¶
The add-on is built for environments where the telemetry is wanted in full, so there is no privacy gating, scrubbing, or opt-in: full Python tracebacks, operator parameters, and the enabled-add-on list (with real names) are all captured by default. The one remaining discipline is cardinality, a producer-side constraint unrelated to privacy:
- Metric names and log targets are always from a bounded, low-cardinality
set (e.g.
blender.action,blender.rss_mb) — never per-asset names, file paths, or session IDs. - Free-form, high-cardinality values (operator parameters, file paths, tracebacks, the add-on list) go only in the log message body, which is unbounded-safe.
Querying the data¶
After the add-on is active, use the Micromegas query interface:
-- Last hour of Blender log events
SELECT time, level, target, msg
FROM log_entries
WHERE target LIKE 'blender.%'
ORDER BY time DESC
LIMIT 100;
-- Average render duration by day
SELECT
date_trunc('day', time) AS day,
avg(value) AS avg_render_s
FROM metrics
WHERE name = 'blender.render_duration_s'
GROUP BY 1
ORDER BY 1 DESC;
-- Crash events from the last 7 days
SELECT time, msg
FROM log_entries
WHERE target = 'blender.crash'
AND level = 1 -- FATAL
ORDER BY time DESC;
-- Reconstruct one session's action sequence (what the user clicked),
-- including redo-panel parameter edits.
-- Scope to the process with view_instance so only that session's blocks are
-- read, instead of scanning the whole log_entries view.
SELECT time, msg
FROM view_instance('log_entries', 'my_process_id')
WHERE target IN ('blender.action', 'blender.action_redo')
ORDER BY time DESC
LIMIT 100;
Troubleshooting¶
Add-on loads but no data appears in Micromegas
- Check that
MICROMEGAS_TELEMETRY_URLis set and reachable from the Blender machine. - Check Blender's system console (Window → Toggle System Console on Windows, or
launch from terminal on Linux) for
[Micromegas]error messages.
"native library not found" in the system console
The prebuilt libmicromegas_capi.so / micromegas_capi.dll is missing from the
add-on's lib/ directory. Re-install from the extension zip, or follow the
manual install steps above.
Add-on inactive after disabling then re-enabling it in the same Blender session
The native telemetry layer initializes once per process and cannot be
reinitialized, so telemetry init failed is printed on the second enable.
Restart Blender. To avoid it next time, turn on Keep Alive (Dev Only) in
the add-on preferences before disabling — enabling it after the session has
already been shut down does not bring it back (see
Add-on preferences).
Multiple Blender instances — will they conflict?
No. Each instance initializes its own TelemetryGuard with a unique
session UUID. The transport thread is per-process. Crash-file claiming via
atomic rename ensures no double-reporting.