diff --git a/.audit/oberstet_fix_1983.md b/.audit/oberstet_fix_1983.md
new file mode 100644
index 000000000..998fb7a9c
--- /dev/null
+++ b/.audit/oberstet_fix_1983.md
@@ -0,0 +1,8 @@
+- [ ] I did **not** use any AI-assistance tools to help create this pull request.
+- [x] I **did** use AI-assistance tools to *help* create this pull request.
+- [x] I have read, understood and followed the project's AI_POLICY.md when creating code, documentation etc. for this pull request.
+
+Submitted by: @oberstet
+Date: 2026-10-13
+Related issue(s): #1983
+Branch: oberstet:fix_1983
diff --git a/docs/index.rst b/docs/index.rst
index 3d0a84715..bc14d953d 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -44,6 +44,7 @@ Contents
overview
installation
wheels-inventory
+ memory-footprint
getting-started
programming-guide/index
architecture/statically-typed-region-lifecycle-python.md
diff --git a/docs/memory-footprint.rst b/docs/memory-footprint.rst
new file mode 100644
index 000000000..53b322e68
--- /dev/null
+++ b/docs/memory-footprint.rst
@@ -0,0 +1,144 @@
+.. _memory-footprint:
+
+Memory Footprint
+================
+
+This page shows how much memory a Python process uses after importing autobahn,
+compared with the frameworks and libraries it builds on, for CPython and PyPy. It
+helps to see what a given use of autobahn costs, and it documents how that cost
+changed across releases.
+
+Background
+----------
+
+Since 25.12.1, autobahn includes its own copy of the
+`FlatBuffers `_ runtime, which it needs for WAMP
+FlatBuffers serialization (before that, ``flatbuffers`` was an optional dependency,
+the ``serialization`` extra). That runtime imports **numpy** at module level whenever
+numpy is installed. Up to 26.7.1, autobahn imported the runtime eagerly, so every
+process that imported autobahn also loaded numpy - including processes that use
+only WebSocket, not WAMP, such as Django Channels applications served by
+`Daphne`_ [#fn-daphne]_. See `issue #1983 `_.
+
+Since then, the goals are:
+
+1. The autobahn package, and the wheels we build, include the vendored FlatBuffers
+ runtime, including reflection.
+2. For an application that depends on both autobahn and zlmdb, both of which vendor
+ FlatBuffers, such as Crossbar.io, the two vendored versions are exactly the same:
+ :func:`autobahn.check_zlmdb_flatbuffers_version_in_sync` checks that.
+3. A user of autobahn who uses **WebSocket only, not WAMP**, with either Twisted or
+ asyncio, loads no FlatBuffers code and no numpy.
+
+autobahn now loads the FlatBuffers runtime only when a FlatBuffers message is first
+built or parsed.
+
+What is measured
+----------------
+
+Each row is one import, run in a fresh interpreter. The value is the peak resident
+set size of that process right after the import, in MB (MiB): ``VmHWM`` from
+``/proc/self/status`` on Linux. It is the memory the import costs at least, not the
+memory a running application uses under load.
+
+(``ru_maxrss`` from :func:`resource.getrusage` would be the obvious choice, but on
+Linux it carries over the peak memory of the process that started the measured one,
+so for a child of a Python process it never drops below that parent's size.)
+
+* Every venv has numpy installed, so the tables show whether an import loads it.
+ A value marked **†** means numpy was loaded by that import.
+* Each import runs once as a warm-up (which may compile ``.pyc`` files) and is then
+ measured 5 times; the table shows the median.
+* Each autobahn version is measured in its own fresh venv, together with Twisted,
+ cryptography, numpy and Daphne. The rows that do not involve autobahn are measured
+ in every venv, so their spread across the columns shows the measurement noise.
+* Crossbar.io [#fn-crossbar]_ is measured in a separate venv per autobahn version, with
+ crossbar |crossbar_version| from PyPI. No crossbar release supports autobahn 25.10.2,
+ hence "n/a".
+* Columns: **25.10.2** is the last release with FlatBuffers as an optional
+ dependency, measured *without* that extra (as Daphne installs it). **26.7.1** is the
+ latest release. The last column is the current development version.
+
+.. |crossbar_version| replace:: 26.7.1
+
+CPython 3.14
+------------
+
+.. include:: memory-footprint/cpy314.inc
+
+.. include:: memory-footprint/cpy314-versions.inc
+
+PyPy 3.11
+---------
+
+PyPy is a different runtime (a tracing JIT compiler with its own garbage collector),
+so compare these values with each other, not with the CPython table. On PyPy, the
+venvs use numpy 2.4.x, the last numpy release with PyPy wheels.
+
+.. include:: memory-footprint/pypy311.inc
+
+.. include:: memory-footprint/pypy311-versions.inc
+
+Findings
+--------
+
+* **This change:** every row that imports autobahn drops by about 13-17 MB on
+ CPython, and by about 18-24 MB on PyPy, and none of them loads numpy any more. For
+ example, ``from daphne.server import Server`` drops from 64.6 MB to 51.6 MB on CPython
+ 3.14, and ``import autobahn`` alone is back to the size of the bare interpreter.
+* **Compared with 25.10.2:** the WebSocket and WAMP rows remain about 1.3-1.6 MB above
+ 25.10.2 on CPython (about 0.4-0.8 MB on PyPy). In 25.10.2 without the ``serialization``
+ extra, cbor2 was not installed and the WAMP message module skipped that import. Since
+ 26.x, cbor2 is a regular dependency.
+* **WebSocket only:** with Twisted, most of what remains is Twisted itself; with
+ asyncio, the asyncio standard library.
+* **Twisted and cryptography:** ``import autobahn.twisted.websocket`` still loads the
+ native ``cryptography`` bindings (about 8 MB on CPython), because
+ ``autobahn/twisted/__init__.py`` imports ``autobahn.twisted.wamp``, which imports
+ ``autobahn.wamp.auth``. A pure WebSocket user does not need them. ``import cryptography``
+ alone costs next to nothing; the cost comes with the native bindings
+ (``from cryptography.hazmat.primitives import hashes``).
+* **WAMP:** WAMP users still load the FlatBuffers runtime (and numpy, if installed)
+ as soon as they import ``autobahn.wamp.serializer``, also when they use another
+ serializer.
+* **Crossbar.io** loads numpy itself, so its router worker loads it regardless of
+ this change; on CPython, its footprint is unchanged. On PyPy, the crossbar row comes
+ out about 16 MB lower with this change, in repeated measurements, but we have not
+ analysed why. Two things to keep in mind: on PyPy, the samples of a single import
+ can spread by about 10 MB from run to run (garbage collection and JIT timing), and
+ the two crossbar venvs differ in more than autobahn: on PyPy, autobahn 26.7.1 installs
+ cbor2 6.x, while the development version installs cbor2 5.9.0 (see the versions
+ below the PyPy table).
+
+Reproducing
+-----------
+
+The scripts are in `scripts/memory-footprint/
+`_.
+They create throwaway venvs with `uv `_, measure, and
+regenerate the tables on this page:
+
+.. code-block:: console
+
+ just memory-footprint cpy314
+ just memory-footprint pypy311
+
+``measure.py`` alone measures the venv it runs in, e.g. for your own environment:
+
+.. code-block:: console
+
+ python scripts/memory-footprint/measure.py --variant mine
+
+.. rubric:: Footnotes
+
+.. [#fn-daphne] `Daphne `_
+ (`repository `_), the ASGI server of Django
+ Channels. Its `server `_
+ and `WebSocket protocol `_
+ use ``autobahn.twisted.websocket``. Row: ``from daphne.server import Server``.
+
+.. [#fn-crossbar] `Crossbar.io `_, the WAMP
+ router. Row: ``from crossbar.edge.worker.router import ExtRouterController``, the
+ `router worker `_.
+
+.. _Daphne: https://github.com/django/daphne
diff --git a/docs/memory-footprint/cpy314-versions.inc b/docs/memory-footprint/cpy314-versions.inc
new file mode 100644
index 000000000..5a5f7168b
--- /dev/null
+++ b/docs/memory-footprint/cpy314-versions.inc
@@ -0,0 +1,7 @@
+* **25.10.2** (venv): autobahn 25.10.2, txaio 26.6.1, twisted 26.4.0, cryptography 50.0.2, numpy 2.5.4, daphne 4.2.3
+* **26.7.1** (venv): autobahn 26.7.1, txaio 26.6.1, twisted 26.4.0, cryptography 50.0.2, numpy 2.5.4, cbor2 6.1.5, daphne 4.2.3
+* **26.7.1** (crossbar venv): autobahn 26.7.1, txaio 26.6.1, twisted 26.4.0, cryptography 50.0.2, numpy 2.5.4, cbor2 6.1.5, crossbar 26.7.1, zlmdb 26.7.1
+* **fix_1983** (venv): autobahn 26.10.1.dev1, txaio 26.6.1, twisted 26.4.0, cryptography 50.0.2, numpy 2.5.4, cbor2 6.1.5, daphne 4.2.3
+* **fix_1983** (crossbar venv): autobahn 26.10.1.dev1, txaio 26.6.1, twisted 26.4.0, cryptography 50.0.2, numpy 2.5.4, cbor2 6.1.5, crossbar 26.7.1, zlmdb 26.7.1
+
+Interpreter: ``CPython 3.14.7`` (3.14.7 (main, Sep 1 2026, 14:18:09) [Clang 22.1.3 ]); Linux 6.12.95+deb12-amd64 x86_64, median of 5 runs.
diff --git a/docs/memory-footprint/cpy314.inc b/docs/memory-footprint/cpy314.inc
new file mode 100644
index 000000000..56dfac0a4
--- /dev/null
+++ b/docs/memory-footprint/cpy314.inc
@@ -0,0 +1,89 @@
+.. list-table::
+ :header-rows: 1
+ :widths: 12 46 14 14 14
+
+ * - group
+ - import
+ - 25.10.2
+ - 26.7.1
+ - fix_1983
+ * - interpreter
+ - ``bare interpreter``
+ - 11.9 MB
+ - 11.9 MB
+ - 12.0 MB
+ * - cryptography
+ - ``import cryptography``
+ - 12.0 MB
+ - 12.0 MB
+ - 11.9 MB
+ * -
+ - ``from cryptography.hazmat.primitives import hashes``
+ - 19.9 MB
+ - 19.9 MB
+ - 19.8 MB
+ * - numpy
+ - ``import numpy``
+ - 28.4 MB †
+ - 28.4 MB †
+ - 28.4 MB †
+ * - asyncio
+ - ``import asyncio``
+ - 18.1 MB
+ - 18.2 MB
+ - 18.2 MB
+ * - twisted
+ - ``from twisted.internet import reactor``
+ - 43.3 MB
+ - 43.3 MB
+ - 43.4 MB
+ * - txaio
+ - ``import txaio``
+ - 12.0 MB
+ - 12.1 MB
+ - 12.1 MB
+ * -
+ - ``import txaio; txaio.use_asyncio()``
+ - 18.3 MB
+ - 18.3 MB
+ - 18.3 MB
+ * -
+ - ``import txaio; txaio.use_twisted()``
+ - 26.5 MB
+ - 26.5 MB
+ - 26.5 MB
+ * - autobahn
+ - ``import autobahn``
+ - 12.1 MB
+ - 28.8 MB †
+ - 12.0 MB
+ * -
+ - ``txaio.use_asyncio(); import autobahn.wamp``
+ - 31.9 MB
+ - 47.6 MB †
+ - 33.2 MB
+ * -
+ - ``txaio.use_twisted(); import autobahn.wamp``
+ - 38.4 MB
+ - 52.9 MB †
+ - 39.8 MB
+ * -
+ - ``import autobahn.asyncio.websocket``
+ - 36.3 MB
+ - 51.1 MB †
+ - 37.8 MB
+ * -
+ - ``import autobahn.twisted.websocket``
+ - 48.9 MB
+ - 63.2 MB †
+ - 50.3 MB
+ * - daphne
+ - ``from daphne.server import Server``
+ - 50.3 MB
+ - 64.6 MB †
+ - 51.6 MB
+ * - crossbar
+ - ``from crossbar.edge.worker.router import ExtRouterController``
+ - n/a
+ - 144.7 MB †
+ - 144.7 MB †
diff --git a/docs/memory-footprint/pypy311-versions.inc b/docs/memory-footprint/pypy311-versions.inc
new file mode 100644
index 000000000..d68ae3d0d
--- /dev/null
+++ b/docs/memory-footprint/pypy311-versions.inc
@@ -0,0 +1,7 @@
+* **25.10.2** (venv): autobahn 25.10.2, txaio 26.6.1, twisted 26.4.0, cryptography 50.0.2, numpy 2.4.6, daphne 4.2.3
+* **26.7.1** (venv): autobahn 26.7.1, txaio 26.6.1, twisted 26.4.0, cryptography 50.0.2, numpy 2.4.6, cbor2 6.1.5, daphne 4.2.3
+* **26.7.1** (crossbar venv): autobahn 26.7.1, txaio 26.6.1, twisted 26.4.0, cryptography 50.0.2, numpy 2.4.6, cbor2 6.1.5, crossbar 26.7.1, zlmdb 26.7.1
+* **fix_1983** (venv): autobahn 26.10.1.dev1, txaio 26.6.1, twisted 26.4.0, cryptography 50.0.2, numpy 2.4.6, cbor2 5.9.0, daphne 4.2.3
+* **fix_1983** (crossbar venv): autobahn 26.10.1.dev1, txaio 26.6.1, twisted 26.4.0, cryptography 50.0.2, numpy 2.4.6, cbor2 5.9.0, crossbar 26.7.1, zlmdb 26.7.1
+
+Interpreter: ``PyPy 3.11.15`` (3.11.15 (194f9f44b505, May 25 2026, 19:34:11) [PyPy 7.3.23 with GCC 10.2.1 20210130 (Red Hat 10.2.1-11)]); Linux 6.12.95+deb12-amd64 x86_64, median of 5 runs.
diff --git a/docs/memory-footprint/pypy311.inc b/docs/memory-footprint/pypy311.inc
new file mode 100644
index 000000000..8cd895aba
--- /dev/null
+++ b/docs/memory-footprint/pypy311.inc
@@ -0,0 +1,89 @@
+.. list-table::
+ :header-rows: 1
+ :widths: 12 46 14 14 14
+
+ * - group
+ - import
+ - 25.10.2
+ - 26.7.1
+ - fix_1983
+ * - interpreter
+ - ``bare interpreter``
+ - 64.4 MB
+ - 64.4 MB
+ - 64.4 MB
+ * - cryptography
+ - ``import cryptography``
+ - 64.3 MB
+ - 64.4 MB
+ - 64.4 MB
+ * -
+ - ``from cryptography.hazmat.primitives import hashes``
+ - 72.1 MB
+ - 72.0 MB
+ - 72.0 MB
+ * - numpy
+ - ``import numpy``
+ - 88.1 MB †
+ - 88.1 MB †
+ - 88.2 MB †
+ * - asyncio
+ - ``import asyncio``
+ - 78.7 MB
+ - 78.9 MB
+ - 78.8 MB
+ * - twisted
+ - ``from twisted.internet import reactor``
+ - 106.1 MB
+ - 106.1 MB
+ - 106.1 MB
+ * - txaio
+ - ``import txaio``
+ - 64.4 MB
+ - 64.4 MB
+ - 64.2 MB
+ * -
+ - ``import txaio; txaio.use_asyncio()``
+ - 79.0 MB
+ - 79.0 MB
+ - 79.1 MB
+ * -
+ - ``import txaio; txaio.use_twisted()``
+ - 88.0 MB
+ - 87.9 MB
+ - 88.0 MB
+ * - autobahn
+ - ``import autobahn``
+ - 64.4 MB
+ - 87.9 MB †
+ - 64.4 MB
+ * -
+ - ``txaio.use_asyncio(); import autobahn.wamp``
+ - 93.0 MB
+ - 112.4 MB †
+ - 93.7 MB
+ * -
+ - ``txaio.use_twisted(); import autobahn.wamp``
+ - 100.1 MB
+ - 118.8 MB †
+ - 100.5 MB
+ * -
+ - ``import autobahn.asyncio.websocket``
+ - 98.4 MB
+ - 117.2 MB †
+ - 99.0 MB
+ * -
+ - ``import autobahn.twisted.websocket``
+ - 113.2 MB
+ - 133.5 MB †
+ - 113.8 MB
+ * - daphne
+ - ``from daphne.server import Server``
+ - 114.8 MB
+ - 135.8 MB †
+ - 115.6 MB
+ * - crossbar
+ - ``from crossbar.edge.worker.router import ExtRouterController``
+ - n/a
+ - 239.5 MB †
+ - 224.1 MB †
diff --git a/justfile b/justfile
index 2377099bc..7882db5b3 100644
--- a/justfile
+++ b/justfile
@@ -1325,7 +1325,8 @@ test-asyncio venv="" use_nvx="": (install-tools venv) (install-dev venv)
# Run the import smoke test - imports core autobahn modules with crypto extras
# present, guarding against import-time annotation regressions (e.g. #1878) that
-# only surface on CPython < 3.14. (usage: `just test-imports cpy311`)
+# only surface on CPython < 3.14, and checks that WebSocket-only imports load
+# neither FlatBuffers nor numpy (#1983). (usage: `just test-imports cpy311`)
test-imports venv="": (install-tools venv) (install-dev venv)
#!/usr/bin/env bash
set -e
@@ -1337,7 +1338,8 @@ test-imports venv="": (install-tools venv) (install-dev venv)
fi
VENV_PYTHON=$(just --quiet _get-venv-python "${VENV_NAME}")
echo "==> Running import smoke test in ${VENV_NAME}..."
- ${VENV_PYTHON} -m pytest -v src/autobahn/test/test_import_all.py
+ ${VENV_PYTHON} -m pytest -v src/autobahn/test/test_import_all.py \
+ src/autobahn/test/test_import_websocket_only.py
# Run the import smoke test against the LOWEST versions our direct dependencies
# allow (uv --resolution lowest-direct), in a separate '-lowest' venv. This
@@ -1813,6 +1815,11 @@ bump-flatbuffers:
update-flatbuffers:
./scripts/update_flatbuffers.sh
+# Measure import memory footprint in throwaway venvs, regenerate docs/memory-footprint.rst tables (usage: `just memory-footprint cpy314`)
+memory-footprint env="cpy314" label="":
+ ./scripts/memory-footprint/run.sh {{ env }} {{ label }}
+ python3 ./scripts/memory-footprint/render.py {{ env }}
+
# Build wheel only (usage: `just build cpy314`)
build venv="": (install-build-tools venv)
#!/usr/bin/env bash
diff --git a/pyproject.toml b/pyproject.toml
index 6c1df64ba..c28c5de25 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "autobahn"
-version = "26.9.1.dev2"
+version = "26.10.1.dev1"
description = "WebSocket client & server library, WAMP real-time framework"
readme = "README.md"
requires-python = ">=3.11"
diff --git a/scripts/memory-footprint/measure.py b/scripts/memory-footprint/measure.py
new file mode 100755
index 000000000..5ac18ef17
--- /dev/null
+++ b/scripts/memory-footprint/measure.py
@@ -0,0 +1,234 @@
+#!/usr/bin/env python
+###############################################################################
+#
+# The MIT License (MIT)
+#
+# Copyright (c) typedef int GmbH
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+"""
+Measure the memory footprint (peak RSS, ``VmHWM`` from ``/proc/self/status``, Linux)
+of importing autobahn and related packages, each in a fresh interpreter.
+
+Run this with the Python of the venv to measure (it uses ``sys.executable`` for the
+child processes) - see ``run.sh``, which creates the venvs. The result is written as
+JSON to stdout, or to the file given with ``--output``.
+
+Each scenario runs ``--runs`` times in a fresh process (``python -I``, without
+``USE_TWISTED`` / ``USE_ASYNCIO`` in the environment), after one discarded warm-up
+run (which may compile ``.pyc`` files). The median is reported, in KiB. A scenario whose imports are not installed in the venv
+is reported as unavailable.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import platform
+import statistics
+import subprocess
+import sys
+from importlib import metadata
+
+# (group, label, code) - the order here is the order of the rows in the docs tables
+SCENARIOS = [
+ ("interpreter", "bare interpreter", "pass"),
+ ("cryptography", "import cryptography", "import cryptography"),
+ (
+ "cryptography",
+ "from cryptography.hazmat.primitives import hashes",
+ "from cryptography.hazmat.primitives import hashes",
+ ),
+ ("numpy", "import numpy", "import numpy"),
+ ("asyncio", "import asyncio", "import asyncio"),
+ (
+ "twisted",
+ "from twisted.internet import reactor",
+ "from twisted.internet import reactor",
+ ),
+ ("txaio", "import txaio", "import txaio"),
+ ("txaio", "import txaio; txaio.use_asyncio()", "import txaio; txaio.use_asyncio()"),
+ ("txaio", "import txaio; txaio.use_twisted()", "import txaio; txaio.use_twisted()"),
+ ("autobahn", "import autobahn", "import autobahn"),
+ (
+ "autobahn",
+ "txaio.use_asyncio(); import autobahn.wamp",
+ "import txaio; txaio.use_asyncio(); import autobahn.wamp",
+ ),
+ (
+ "autobahn",
+ "txaio.use_twisted(); import autobahn.wamp",
+ "import txaio; txaio.use_twisted(); import autobahn.wamp",
+ ),
+ (
+ "autobahn",
+ "import autobahn.asyncio.websocket",
+ "import autobahn.asyncio.websocket",
+ ),
+ (
+ "autobahn",
+ "import autobahn.twisted.websocket",
+ "import autobahn.twisted.websocket",
+ ),
+ ("daphne", "from daphne.server import Server", "from daphne.server import Server"),
+ (
+ "crossbar",
+ "from crossbar.edge.worker.router import ExtRouterController",
+ "from crossbar.edge.worker.router import ExtRouterController",
+ ),
+]
+
+# the packages whose versions are recorded with the results
+PACKAGES = [
+ "autobahn",
+ "txaio",
+ "twisted",
+ "cryptography",
+ "numpy",
+ "cbor2",
+ "daphne",
+ "crossbar",
+ "zlmdb",
+]
+
+# runs in the child: execute the scenario, then report peak RSS and whether numpy
+# got loaded; an import error of the scenario itself means "not installed here".
+#
+# Peak RSS is read as VmHWM from /proc/self/status: the high-water mark of this
+# process's own address space, which starts fresh at exec(). getrusage()'s ru_maxrss
+# is not usable here: on Linux it carries over the peak of the address space replaced
+# by exec(), i.e. of the measuring parent process (this script).
+CHILD = """
+import json, sys
+try:
+ exec(sys.argv[1])
+except ImportError as e:
+ print(json.dumps({"error": f"{type(e).__name__}: {e}"}))
+else:
+ with open("/proc/self/status") as f:
+ hwm = next(int(line.split()[1]) for line in f if line.startswith("VmHWM:"))
+ print(json.dumps({"maxrss_kib": hwm, "numpy": "numpy" in sys.modules}))
+"""
+
+
+def run_once(code: str) -> dict:
+ env = {
+ k: v for k, v in os.environ.items() if k not in ("USE_TWISTED", "USE_ASYNCIO")
+ }
+ result = subprocess.run(
+ [sys.executable, "-I", "-c", CHILD, code],
+ capture_output=True,
+ text=True,
+ env=env,
+ )
+ if result.returncode != 0:
+ return {
+ "error": result.stderr.strip().splitlines()[-1]
+ if result.stderr
+ else "failed"
+ }
+ return json.loads(result.stdout.strip().splitlines()[-1])
+
+
+def measure(code: str, runs: int) -> dict:
+ first = run_once(code)
+ if "error" in first:
+ return {"available": False, "error": first["error"]}
+ samples = [run_once(code) for _ in range(runs)]
+ errors = [s["error"] for s in samples if "error" in s]
+ if errors:
+ return {"available": False, "error": errors[0]}
+ return {
+ "available": True,
+ "maxrss_kib": int(statistics.median(s["maxrss_kib"] for s in samples)),
+ "samples_kib": [s["maxrss_kib"] for s in samples],
+ "numpy": any(s["numpy"] for s in samples),
+ }
+
+
+def installed_versions() -> dict:
+ versions = {}
+ for name in PACKAGES:
+ try:
+ versions[name] = metadata.version(name)
+ except metadata.PackageNotFoundError:
+ pass
+ return versions
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(
+ description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
+ )
+ parser.add_argument(
+ "--variant", required=True, help="label of the autobahn variant, e.g. 26.7.1"
+ )
+ parser.add_argument(
+ "--runs", type=int, default=5, help="measured runs per scenario (default: 5)"
+ )
+ parser.add_argument(
+ "--only-group", action="append", help="measure only these groups (repeatable)"
+ )
+ parser.add_argument(
+ "--skip-group", action="append", help="do not measure these groups (repeatable)"
+ )
+ parser.add_argument("--output", help="write JSON here instead of stdout")
+ args = parser.parse_args()
+
+ results = []
+ for group, label, code in SCENARIOS:
+ if args.only_group and group not in args.only_group:
+ continue
+ if args.skip_group and group in args.skip_group:
+ continue
+ result = measure(code, args.runs)
+ results.append({"group": group, "label": label, "code": code, **result})
+ print(
+ f" {label:60s} {result.get('maxrss_kib', '-')!s:>8} KiB", file=sys.stderr
+ )
+
+ report = {
+ "variant": args.variant,
+ "python": {
+ "implementation": platform.python_implementation(),
+ "version": platform.python_version(),
+ "build": " ".join(sys.version.split()),
+ },
+ "platform": {
+ "system": platform.system(),
+ "release": platform.release(),
+ "machine": platform.machine(),
+ },
+ "runs": args.runs,
+ "packages": installed_versions(),
+ "results": results,
+ }
+ text = json.dumps(report, indent=2) + "\n"
+ if args.output:
+ with open(args.output, "w") as f:
+ f.write(text)
+ else:
+ sys.stdout.write(text)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/memory-footprint/render.py b/scripts/memory-footprint/render.py
new file mode 100644
index 000000000..4a348c2bb
--- /dev/null
+++ b/scripts/memory-footprint/render.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python
+###############################################################################
+#
+# The MIT License (MIT)
+#
+# Copyright (c) typedef int GmbH
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+"""
+Render the results of ``run.sh`` (``results/-*.json``) as reStructuredText
+fragments, included by ``docs/memory-footprint.rst``:
+
+- ``docs/memory-footprint/.inc``: the table, one column per autobahn variant
+- ``docs/memory-footprint/-versions.inc``: interpreter, platform and package versions
+
+usage: render.py [ ...]
+"""
+
+from __future__ import annotations
+
+import json
+import pathlib
+import sys
+
+from measure import PACKAGES, SCENARIOS
+
+HERE = pathlib.Path(__file__).resolve().parent
+RESULTS = HERE / "results"
+DOCS = HERE.parent.parent / "docs" / "memory-footprint"
+
+# released variants first, in this order; any other (local) variant after them
+RELEASES = ["25.10.2", "26.7.1"]
+
+
+def load(env: str) -> tuple[list[str], dict, dict]:
+ """
+ Returns the variants (column order), the results by (variant, label), and the
+ reports (without results) by variant, for the main and the crossbar venv.
+ """
+ values, reports = {}, {}
+ for path in sorted(RESULTS.glob(f"{env}-*.json")):
+ report = json.loads(path.read_text())
+ variant = report["variant"]
+ kind = "crossbar" if path.stem.endswith("-crossbar") else "main"
+ reports[(variant, kind)] = report
+ for result in report["results"]:
+ # never let "not installed in this venv" replace a measured value
+ if result.get("available") or (variant, result["label"]) not in values:
+ values[(variant, result["label"])] = result
+ found = {variant for variant, _ in reports}
+ variants = [v for v in RELEASES if v in found] + sorted(found - set(RELEASES))
+ return variants, values, reports
+
+
+def cell(result: dict | None) -> str:
+ if not result or not result.get("available"):
+ return "n/a"
+ text = f"{result['maxrss_kib'] / 1024:.1f} MB"
+ return f"{text} †" if result["numpy"] else text
+
+
+def render_table(env: str, variants: list[str], values: dict) -> str:
+ lines = [
+ ".. list-table::",
+ " :header-rows: 1",
+ " :widths: 12 46" + " 14" * len(variants),
+ "",
+ " * - group",
+ " - import",
+ ]
+ lines += [f" - {v}" for v in variants]
+ previous_group = None
+ for group, label, _ in SCENARIOS:
+ if not any((v, label) in values for v in variants):
+ continue
+ lines.append(f" * - {group if group != previous_group else ''}".rstrip())
+ lines.append(f" - ``{label}``")
+ lines += [f" - {cell(values.get((v, label)))}" for v in variants]
+ previous_group = group
+ return "\n".join(lines) + "\n"
+
+
+def render_versions(variants: list[str], reports: dict) -> str:
+ lines = []
+ for variant in variants:
+ for kind in ("main", "crossbar"):
+ report = reports.get((variant, kind))
+ if not report:
+ continue
+ python, plat = report["python"], report["platform"]
+ packages = ", ".join(
+ f"{name} {report['packages'][name]}"
+ for name in PACKAGES
+ if name in report["packages"]
+ )
+ venv = "crossbar venv" if kind == "crossbar" else "venv"
+ lines.append(f"* **{variant}** ({venv}): {packages}")
+ build = f"{python['implementation']} {python['version']}"
+ context = f"{plat['system']} {plat['release']} {plat['machine']}, median of {report['runs']} runs"
+ lines += ["", f"Interpreter: ``{build}`` ({python['build']}); {context}."]
+ return "\n".join(lines) + "\n"
+
+
+def main() -> None:
+ envs = sys.argv[1:]
+ if not envs:
+ sys.exit(__doc__)
+ DOCS.mkdir(parents=True, exist_ok=True)
+ for env in envs:
+ variants, values, reports = load(env)
+ if not variants:
+ sys.exit(f"no results for {env} in {RESULTS}")
+ (DOCS / f"{env}.inc").write_text(render_table(env, variants, values))
+ (DOCS / f"{env}-versions.inc").write_text(render_versions(variants, reports))
+ print(f"wrote {DOCS / f'{env}.inc'} and {DOCS / f'{env}-versions.inc'}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/scripts/memory-footprint/results/cpy314-25.10.2.json b/scripts/memory-footprint/results/cpy314-25.10.2.json
new file mode 100644
index 000000000..1d7ba2522
--- /dev/null
+++ b/scripts/memory-footprint/results/cpy314-25.10.2.json
@@ -0,0 +1,249 @@
+{
+ "variant": "25.10.2",
+ "python": {
+ "implementation": "CPython",
+ "version": "3.14.7",
+ "build": "3.14.7 (main, Sep 1 2026, 14:18:09) [Clang 22.1.3 ]"
+ },
+ "platform": {
+ "system": "Linux",
+ "release": "6.12.95+deb12-amd64",
+ "machine": "x86_64"
+ },
+ "runs": 5,
+ "packages": {
+ "autobahn": "25.10.2",
+ "txaio": "26.6.1",
+ "twisted": "26.4.0",
+ "cryptography": "50.0.2",
+ "numpy": "2.5.4",
+ "daphne": "4.2.3"
+ },
+ "results": [
+ {
+ "group": "interpreter",
+ "label": "bare interpreter",
+ "code": "pass",
+ "available": true,
+ "maxrss_kib": 12216,
+ "samples_kib": [
+ 12212,
+ 12208,
+ 12216,
+ 12300,
+ 12304
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "import cryptography",
+ "code": "import cryptography",
+ "available": true,
+ "maxrss_kib": 12256,
+ "samples_kib": [
+ 12212,
+ 12304,
+ 12188,
+ 12256,
+ 12304
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "from cryptography.hazmat.primitives import hashes",
+ "code": "from cryptography.hazmat.primitives import hashes",
+ "available": true,
+ "maxrss_kib": 20352,
+ "samples_kib": [
+ 20292,
+ 20260,
+ 20352,
+ 20380,
+ 20380
+ ],
+ "numpy": false
+ },
+ {
+ "group": "numpy",
+ "label": "import numpy",
+ "code": "import numpy",
+ "available": true,
+ "maxrss_kib": 29048,
+ "samples_kib": [
+ 29092,
+ 29044,
+ 29232,
+ 29044,
+ 29048
+ ],
+ "numpy": true
+ },
+ {
+ "group": "asyncio",
+ "label": "import asyncio",
+ "code": "import asyncio",
+ "available": true,
+ "maxrss_kib": 18552,
+ "samples_kib": [
+ 18552,
+ 18536,
+ 18604,
+ 18540,
+ 18636
+ ],
+ "numpy": false
+ },
+ {
+ "group": "twisted",
+ "label": "from twisted.internet import reactor",
+ "code": "from twisted.internet import reactor",
+ "available": true,
+ "maxrss_kib": 44380,
+ "samples_kib": [
+ 44484,
+ 44380,
+ 44400,
+ 44356,
+ 44356
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio",
+ "code": "import txaio",
+ "available": true,
+ "maxrss_kib": 12336,
+ "samples_kib": [
+ 12332,
+ 12336,
+ 12296,
+ 12384,
+ 12376
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_asyncio()",
+ "code": "import txaio; txaio.use_asyncio()",
+ "available": true,
+ "maxrss_kib": 18708,
+ "samples_kib": [
+ 18744,
+ 18688,
+ 18752,
+ 18708,
+ 18668
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_twisted()",
+ "code": "import txaio; txaio.use_twisted()",
+ "available": true,
+ "maxrss_kib": 27104,
+ "samples_kib": [
+ 27044,
+ 27068,
+ 27104,
+ 27132,
+ 27112
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn",
+ "code": "import autobahn",
+ "available": true,
+ "maxrss_kib": 12340,
+ "samples_kib": [
+ 12392,
+ 12340,
+ 12276,
+ 12300,
+ 12384
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_asyncio(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_asyncio(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 32652,
+ "samples_kib": [
+ 32636,
+ 32684,
+ 32632,
+ 32652,
+ 32768
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_twisted(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_twisted(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 39324,
+ "samples_kib": [
+ 39424,
+ 39324,
+ 39348,
+ 39312,
+ 39280
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.asyncio.websocket",
+ "code": "import autobahn.asyncio.websocket",
+ "available": true,
+ "maxrss_kib": 37148,
+ "samples_kib": [
+ 37084,
+ 37176,
+ 37232,
+ 37100,
+ 37148
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.twisted.websocket",
+ "code": "import autobahn.twisted.websocket",
+ "available": true,
+ "maxrss_kib": 50052,
+ "samples_kib": [
+ 50040,
+ 50068,
+ 50052,
+ 50016,
+ 50168
+ ],
+ "numpy": false
+ },
+ {
+ "group": "daphne",
+ "label": "from daphne.server import Server",
+ "code": "from daphne.server import Server",
+ "available": true,
+ "maxrss_kib": 51472,
+ "samples_kib": [
+ 51472,
+ 51468,
+ 51560,
+ 51512,
+ 51400
+ ],
+ "numpy": false
+ }
+ ]
+}
diff --git a/scripts/memory-footprint/results/cpy314-26.7.1-crossbar.json b/scripts/memory-footprint/results/cpy314-26.7.1-crossbar.json
new file mode 100644
index 000000000..da96c16fd
--- /dev/null
+++ b/scripts/memory-footprint/results/cpy314-26.7.1-crossbar.json
@@ -0,0 +1,41 @@
+{
+ "variant": "26.7.1",
+ "python": {
+ "implementation": "CPython",
+ "version": "3.14.7",
+ "build": "3.14.7 (main, Sep 1 2026, 14:18:09) [Clang 22.1.3 ]"
+ },
+ "platform": {
+ "system": "Linux",
+ "release": "6.12.95+deb12-amd64",
+ "machine": "x86_64"
+ },
+ "runs": 5,
+ "packages": {
+ "autobahn": "26.7.1",
+ "txaio": "26.6.1",
+ "twisted": "26.4.0",
+ "cryptography": "50.0.2",
+ "numpy": "2.5.4",
+ "cbor2": "6.1.5",
+ "crossbar": "26.7.1",
+ "zlmdb": "26.7.1"
+ },
+ "results": [
+ {
+ "group": "crossbar",
+ "label": "from crossbar.edge.worker.router import ExtRouterController",
+ "code": "from crossbar.edge.worker.router import ExtRouterController",
+ "available": true,
+ "maxrss_kib": 148136,
+ "samples_kib": [
+ 148280,
+ 148136,
+ 148136,
+ 148132,
+ 148016
+ ],
+ "numpy": true
+ }
+ ]
+}
diff --git a/scripts/memory-footprint/results/cpy314-26.7.1.json b/scripts/memory-footprint/results/cpy314-26.7.1.json
new file mode 100644
index 000000000..ed8efc2cb
--- /dev/null
+++ b/scripts/memory-footprint/results/cpy314-26.7.1.json
@@ -0,0 +1,250 @@
+{
+ "variant": "26.7.1",
+ "python": {
+ "implementation": "CPython",
+ "version": "3.14.7",
+ "build": "3.14.7 (main, Sep 1 2026, 14:18:09) [Clang 22.1.3 ]"
+ },
+ "platform": {
+ "system": "Linux",
+ "release": "6.12.95+deb12-amd64",
+ "machine": "x86_64"
+ },
+ "runs": 5,
+ "packages": {
+ "autobahn": "26.7.1",
+ "txaio": "26.6.1",
+ "twisted": "26.4.0",
+ "cryptography": "50.0.2",
+ "numpy": "2.5.4",
+ "cbor2": "6.1.5",
+ "daphne": "4.2.3"
+ },
+ "results": [
+ {
+ "group": "interpreter",
+ "label": "bare interpreter",
+ "code": "pass",
+ "available": true,
+ "maxrss_kib": 12224,
+ "samples_kib": [
+ 12192,
+ 12224,
+ 12208,
+ 12280,
+ 12268
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "import cryptography",
+ "code": "import cryptography",
+ "available": true,
+ "maxrss_kib": 12268,
+ "samples_kib": [
+ 12304,
+ 12224,
+ 12260,
+ 12268,
+ 12300
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "from cryptography.hazmat.primitives import hashes",
+ "code": "from cryptography.hazmat.primitives import hashes",
+ "available": true,
+ "maxrss_kib": 20340,
+ "samples_kib": [
+ 20344,
+ 20384,
+ 20340,
+ 20268,
+ 20256
+ ],
+ "numpy": false
+ },
+ {
+ "group": "numpy",
+ "label": "import numpy",
+ "code": "import numpy",
+ "available": true,
+ "maxrss_kib": 29132,
+ "samples_kib": [
+ 29144,
+ 29108,
+ 29132,
+ 29156,
+ 29128
+ ],
+ "numpy": true
+ },
+ {
+ "group": "asyncio",
+ "label": "import asyncio",
+ "code": "import asyncio",
+ "available": true,
+ "maxrss_kib": 18604,
+ "samples_kib": [
+ 18624,
+ 18536,
+ 18560,
+ 18604,
+ 18656
+ ],
+ "numpy": false
+ },
+ {
+ "group": "twisted",
+ "label": "from twisted.internet import reactor",
+ "code": "from twisted.internet import reactor",
+ "available": true,
+ "maxrss_kib": 44368,
+ "samples_kib": [
+ 44404,
+ 44368,
+ 44356,
+ 44492,
+ 44360
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio",
+ "code": "import txaio",
+ "available": true,
+ "maxrss_kib": 12356,
+ "samples_kib": [
+ 12356,
+ 12380,
+ 12316,
+ 12380,
+ 12276
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_asyncio()",
+ "code": "import txaio; txaio.use_asyncio()",
+ "available": true,
+ "maxrss_kib": 18780,
+ "samples_kib": [
+ 18780,
+ 18780,
+ 18784,
+ 18780,
+ 18728
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_twisted()",
+ "code": "import txaio; txaio.use_twisted()",
+ "available": true,
+ "maxrss_kib": 27132,
+ "samples_kib": [
+ 27132,
+ 27152,
+ 27140,
+ 27068,
+ 27048
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn",
+ "code": "import autobahn",
+ "available": true,
+ "maxrss_kib": 29492,
+ "samples_kib": [
+ 29536,
+ 29644,
+ 29468,
+ 29492,
+ 29440
+ ],
+ "numpy": true
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_asyncio(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_asyncio(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 48792,
+ "samples_kib": [
+ 48792,
+ 48752,
+ 48860,
+ 48808,
+ 48652
+ ],
+ "numpy": true
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_twisted(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_twisted(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 54148,
+ "samples_kib": [
+ 54148,
+ 54180,
+ 54028,
+ 54200,
+ 54068
+ ],
+ "numpy": true
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.asyncio.websocket",
+ "code": "import autobahn.asyncio.websocket",
+ "available": true,
+ "maxrss_kib": 52324,
+ "samples_kib": [
+ 52324,
+ 52320,
+ 52244,
+ 52328,
+ 52328
+ ],
+ "numpy": true
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.twisted.websocket",
+ "code": "import autobahn.twisted.websocket",
+ "available": true,
+ "maxrss_kib": 64672,
+ "samples_kib": [
+ 64852,
+ 64592,
+ 64712,
+ 64672,
+ 64652
+ ],
+ "numpy": true
+ },
+ {
+ "group": "daphne",
+ "label": "from daphne.server import Server",
+ "code": "from daphne.server import Server",
+ "available": true,
+ "maxrss_kib": 66180,
+ "samples_kib": [
+ 66188,
+ 66252,
+ 66068,
+ 66136,
+ 66180
+ ],
+ "numpy": true
+ }
+ ]
+}
diff --git a/scripts/memory-footprint/results/cpy314-fix_1983-crossbar.json b/scripts/memory-footprint/results/cpy314-fix_1983-crossbar.json
new file mode 100644
index 000000000..a87b17527
--- /dev/null
+++ b/scripts/memory-footprint/results/cpy314-fix_1983-crossbar.json
@@ -0,0 +1,41 @@
+{
+ "variant": "fix_1983",
+ "python": {
+ "implementation": "CPython",
+ "version": "3.14.7",
+ "build": "3.14.7 (main, Sep 1 2026, 14:18:09) [Clang 22.1.3 ]"
+ },
+ "platform": {
+ "system": "Linux",
+ "release": "6.12.95+deb12-amd64",
+ "machine": "x86_64"
+ },
+ "runs": 5,
+ "packages": {
+ "autobahn": "26.10.1.dev1",
+ "txaio": "26.6.1",
+ "twisted": "26.4.0",
+ "cryptography": "50.0.2",
+ "numpy": "2.5.4",
+ "cbor2": "6.1.5",
+ "crossbar": "26.7.1",
+ "zlmdb": "26.7.1"
+ },
+ "results": [
+ {
+ "group": "crossbar",
+ "label": "from crossbar.edge.worker.router import ExtRouterController",
+ "code": "from crossbar.edge.worker.router import ExtRouterController",
+ "available": true,
+ "maxrss_kib": 148160,
+ "samples_kib": [
+ 148124,
+ 148124,
+ 148160,
+ 148176,
+ 148192
+ ],
+ "numpy": true
+ }
+ ]
+}
diff --git a/scripts/memory-footprint/results/cpy314-fix_1983.json b/scripts/memory-footprint/results/cpy314-fix_1983.json
new file mode 100644
index 000000000..f32d871c4
--- /dev/null
+++ b/scripts/memory-footprint/results/cpy314-fix_1983.json
@@ -0,0 +1,250 @@
+{
+ "variant": "fix_1983",
+ "python": {
+ "implementation": "CPython",
+ "version": "3.14.7",
+ "build": "3.14.7 (main, Sep 1 2026, 14:18:09) [Clang 22.1.3 ]"
+ },
+ "platform": {
+ "system": "Linux",
+ "release": "6.12.95+deb12-amd64",
+ "machine": "x86_64"
+ },
+ "runs": 5,
+ "packages": {
+ "autobahn": "26.10.1.dev1",
+ "txaio": "26.6.1",
+ "twisted": "26.4.0",
+ "cryptography": "50.0.2",
+ "numpy": "2.5.4",
+ "cbor2": "6.1.5",
+ "daphne": "4.2.3"
+ },
+ "results": [
+ {
+ "group": "interpreter",
+ "label": "bare interpreter",
+ "code": "pass",
+ "available": true,
+ "maxrss_kib": 12260,
+ "samples_kib": [
+ 12284,
+ 12232,
+ 12260,
+ 12260,
+ 12212
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "import cryptography",
+ "code": "import cryptography",
+ "available": true,
+ "maxrss_kib": 12224,
+ "samples_kib": [
+ 12300,
+ 12224,
+ 12304,
+ 12224,
+ 12224
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "from cryptography.hazmat.primitives import hashes",
+ "code": "from cryptography.hazmat.primitives import hashes",
+ "available": true,
+ "maxrss_kib": 20296,
+ "samples_kib": [
+ 20296,
+ 20304,
+ 20280,
+ 20284,
+ 20304
+ ],
+ "numpy": false
+ },
+ {
+ "group": "numpy",
+ "label": "import numpy",
+ "code": "import numpy",
+ "available": true,
+ "maxrss_kib": 29096,
+ "samples_kib": [
+ 29136,
+ 29092,
+ 29148,
+ 29096,
+ 29096
+ ],
+ "numpy": true
+ },
+ {
+ "group": "asyncio",
+ "label": "import asyncio",
+ "code": "import asyncio",
+ "available": true,
+ "maxrss_kib": 18620,
+ "samples_kib": [
+ 18684,
+ 18632,
+ 18544,
+ 18620,
+ 18516
+ ],
+ "numpy": false
+ },
+ {
+ "group": "twisted",
+ "label": "from twisted.internet import reactor",
+ "code": "from twisted.internet import reactor",
+ "available": true,
+ "maxrss_kib": 44452,
+ "samples_kib": [
+ 44408,
+ 44468,
+ 44396,
+ 44452,
+ 44496
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio",
+ "code": "import txaio",
+ "available": true,
+ "maxrss_kib": 12344,
+ "samples_kib": [
+ 12292,
+ 12344,
+ 12316,
+ 12380,
+ 12348
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_asyncio()",
+ "code": "import txaio; txaio.use_asyncio()",
+ "available": true,
+ "maxrss_kib": 18700,
+ "samples_kib": [
+ 18676,
+ 18772,
+ 18688,
+ 18780,
+ 18700
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_twisted()",
+ "code": "import txaio; txaio.use_twisted()",
+ "available": true,
+ "maxrss_kib": 27108,
+ "samples_kib": [
+ 27184,
+ 27100,
+ 27068,
+ 27184,
+ 27108
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn",
+ "code": "import autobahn",
+ "available": true,
+ "maxrss_kib": 12312,
+ "samples_kib": [
+ 12312,
+ 12384,
+ 12312,
+ 12392,
+ 12304
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_asyncio(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_asyncio(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 33976,
+ "samples_kib": [
+ 33976,
+ 33984,
+ 33984,
+ 33948,
+ 33948
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_twisted(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_twisted(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 40732,
+ "samples_kib": [
+ 40672,
+ 40732,
+ 40684,
+ 40788,
+ 40732
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.asyncio.websocket",
+ "code": "import autobahn.asyncio.websocket",
+ "available": true,
+ "maxrss_kib": 38744,
+ "samples_kib": [
+ 38732,
+ 38684,
+ 38748,
+ 38744,
+ 38768
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.twisted.websocket",
+ "code": "import autobahn.twisted.websocket",
+ "available": true,
+ "maxrss_kib": 51484,
+ "samples_kib": [
+ 51468,
+ 51576,
+ 51484,
+ 51484,
+ 51388
+ ],
+ "numpy": false
+ },
+ {
+ "group": "daphne",
+ "label": "from daphne.server import Server",
+ "code": "from daphne.server import Server",
+ "available": true,
+ "maxrss_kib": 52828,
+ "samples_kib": [
+ 52872,
+ 52788,
+ 52824,
+ 52828,
+ 52988
+ ],
+ "numpy": false
+ }
+ ]
+}
diff --git a/scripts/memory-footprint/results/pypy311-25.10.2.json b/scripts/memory-footprint/results/pypy311-25.10.2.json
new file mode 100644
index 000000000..df953d128
--- /dev/null
+++ b/scripts/memory-footprint/results/pypy311-25.10.2.json
@@ -0,0 +1,249 @@
+{
+ "variant": "25.10.2",
+ "python": {
+ "implementation": "PyPy",
+ "version": "3.11.15",
+ "build": "3.11.15 (194f9f44b505, May 25 2026, 19:34:11) [PyPy 7.3.23 with GCC 10.2.1 20210130 (Red Hat 10.2.1-11)]"
+ },
+ "platform": {
+ "system": "Linux",
+ "release": "6.12.95+deb12-amd64",
+ "machine": "x86_64"
+ },
+ "runs": 5,
+ "packages": {
+ "autobahn": "25.10.2",
+ "txaio": "26.6.1",
+ "twisted": "26.4.0",
+ "cryptography": "50.0.2",
+ "numpy": "2.4.6",
+ "daphne": "4.2.3"
+ },
+ "results": [
+ {
+ "group": "interpreter",
+ "label": "bare interpreter",
+ "code": "pass",
+ "available": true,
+ "maxrss_kib": 65928,
+ "samples_kib": [
+ 66048,
+ 65884,
+ 65948,
+ 65928,
+ 65760
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "import cryptography",
+ "code": "import cryptography",
+ "available": true,
+ "maxrss_kib": 65888,
+ "samples_kib": [
+ 65888,
+ 65996,
+ 65824,
+ 65744,
+ 65968
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "from cryptography.hazmat.primitives import hashes",
+ "code": "from cryptography.hazmat.primitives import hashes",
+ "available": true,
+ "maxrss_kib": 73796,
+ "samples_kib": [
+ 73796,
+ 73752,
+ 73868,
+ 73840,
+ 73736
+ ],
+ "numpy": false
+ },
+ {
+ "group": "numpy",
+ "label": "import numpy",
+ "code": "import numpy",
+ "available": true,
+ "maxrss_kib": 90232,
+ "samples_kib": [
+ 90300,
+ 90232,
+ 90356,
+ 90208,
+ 90232
+ ],
+ "numpy": true
+ },
+ {
+ "group": "asyncio",
+ "label": "import asyncio",
+ "code": "import asyncio",
+ "available": true,
+ "maxrss_kib": 80616,
+ "samples_kib": [
+ 80548,
+ 80584,
+ 80664,
+ 80704,
+ 80616
+ ],
+ "numpy": false
+ },
+ {
+ "group": "twisted",
+ "label": "from twisted.internet import reactor",
+ "code": "from twisted.internet import reactor",
+ "available": true,
+ "maxrss_kib": 108608,
+ "samples_kib": [
+ 108604,
+ 108940,
+ 108692,
+ 108580,
+ 108608
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio",
+ "code": "import txaio",
+ "available": true,
+ "maxrss_kib": 65936,
+ "samples_kib": [
+ 66000,
+ 65780,
+ 66004,
+ 65936,
+ 65832
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_asyncio()",
+ "code": "import txaio; txaio.use_asyncio()",
+ "available": true,
+ "maxrss_kib": 80860,
+ "samples_kib": [
+ 80772,
+ 80948,
+ 81072,
+ 80800,
+ 80860
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_twisted()",
+ "code": "import txaio; txaio.use_twisted()",
+ "available": true,
+ "maxrss_kib": 90088,
+ "samples_kib": [
+ 90024,
+ 90156,
+ 90088,
+ 90088,
+ 90180
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn",
+ "code": "import autobahn",
+ "available": true,
+ "maxrss_kib": 65952,
+ "samples_kib": [
+ 65964,
+ 66048,
+ 65952,
+ 65892,
+ 65748
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_asyncio(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_asyncio(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 95208,
+ "samples_kib": [
+ 95208,
+ 95200,
+ 95248,
+ 95196,
+ 95348
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_twisted(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_twisted(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 102552,
+ "samples_kib": [
+ 102624,
+ 102488,
+ 102516,
+ 102552,
+ 102704
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.asyncio.websocket",
+ "code": "import autobahn.asyncio.websocket",
+ "available": true,
+ "maxrss_kib": 100724,
+ "samples_kib": [
+ 100640,
+ 100724,
+ 100648,
+ 100740,
+ 100788
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.twisted.websocket",
+ "code": "import autobahn.twisted.websocket",
+ "available": true,
+ "maxrss_kib": 115872,
+ "samples_kib": [
+ 115872,
+ 115600,
+ 116012,
+ 115944,
+ 115840
+ ],
+ "numpy": false
+ },
+ {
+ "group": "daphne",
+ "label": "from daphne.server import Server",
+ "code": "from daphne.server import Server",
+ "available": true,
+ "maxrss_kib": 117572,
+ "samples_kib": [
+ 117496,
+ 117688,
+ 117572,
+ 117620,
+ 117440
+ ],
+ "numpy": false
+ }
+ ]
+}
diff --git a/scripts/memory-footprint/results/pypy311-26.7.1-crossbar.json b/scripts/memory-footprint/results/pypy311-26.7.1-crossbar.json
new file mode 100644
index 000000000..ecf7bd551
--- /dev/null
+++ b/scripts/memory-footprint/results/pypy311-26.7.1-crossbar.json
@@ -0,0 +1,41 @@
+{
+ "variant": "26.7.1",
+ "python": {
+ "implementation": "PyPy",
+ "version": "3.11.15",
+ "build": "3.11.15 (194f9f44b505, May 25 2026, 19:34:11) [PyPy 7.3.23 with GCC 10.2.1 20210130 (Red Hat 10.2.1-11)]"
+ },
+ "platform": {
+ "system": "Linux",
+ "release": "6.12.95+deb12-amd64",
+ "machine": "x86_64"
+ },
+ "runs": 5,
+ "packages": {
+ "autobahn": "26.7.1",
+ "txaio": "26.6.1",
+ "twisted": "26.4.0",
+ "cryptography": "50.0.2",
+ "numpy": "2.4.6",
+ "cbor2": "6.1.5",
+ "crossbar": "26.7.1",
+ "zlmdb": "26.7.1"
+ },
+ "results": [
+ {
+ "group": "crossbar",
+ "label": "from crossbar.edge.worker.router import ExtRouterController",
+ "code": "from crossbar.edge.worker.router import ExtRouterController",
+ "available": true,
+ "maxrss_kib": 245296,
+ "samples_kib": [
+ 245760,
+ 245296,
+ 244868,
+ 244504,
+ 245824
+ ],
+ "numpy": true
+ }
+ ]
+}
diff --git a/scripts/memory-footprint/results/pypy311-26.7.1.json b/scripts/memory-footprint/results/pypy311-26.7.1.json
new file mode 100644
index 000000000..4f6946788
--- /dev/null
+++ b/scripts/memory-footprint/results/pypy311-26.7.1.json
@@ -0,0 +1,250 @@
+{
+ "variant": "26.7.1",
+ "python": {
+ "implementation": "PyPy",
+ "version": "3.11.15",
+ "build": "3.11.15 (194f9f44b505, May 25 2026, 19:34:11) [PyPy 7.3.23 with GCC 10.2.1 20210130 (Red Hat 10.2.1-11)]"
+ },
+ "platform": {
+ "system": "Linux",
+ "release": "6.12.95+deb12-amd64",
+ "machine": "x86_64"
+ },
+ "runs": 5,
+ "packages": {
+ "autobahn": "26.7.1",
+ "txaio": "26.6.1",
+ "twisted": "26.4.0",
+ "cryptography": "50.0.2",
+ "numpy": "2.4.6",
+ "cbor2": "6.1.5",
+ "daphne": "4.2.3"
+ },
+ "results": [
+ {
+ "group": "interpreter",
+ "label": "bare interpreter",
+ "code": "pass",
+ "available": true,
+ "maxrss_kib": 65928,
+ "samples_kib": [
+ 65992,
+ 65780,
+ 65932,
+ 65776,
+ 65928
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "import cryptography",
+ "code": "import cryptography",
+ "available": true,
+ "maxrss_kib": 65944,
+ "samples_kib": [
+ 65776,
+ 66008,
+ 66052,
+ 65924,
+ 65944
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "from cryptography.hazmat.primitives import hashes",
+ "code": "from cryptography.hazmat.primitives import hashes",
+ "available": true,
+ "maxrss_kib": 73740,
+ "samples_kib": [
+ 73740,
+ 74004,
+ 73656,
+ 73940,
+ 73592
+ ],
+ "numpy": false
+ },
+ {
+ "group": "numpy",
+ "label": "import numpy",
+ "code": "import numpy",
+ "available": true,
+ "maxrss_kib": 90236,
+ "samples_kib": [
+ 90352,
+ 90204,
+ 90392,
+ 90236,
+ 90200
+ ],
+ "numpy": true
+ },
+ {
+ "group": "asyncio",
+ "label": "import asyncio",
+ "code": "import asyncio",
+ "available": true,
+ "maxrss_kib": 80760,
+ "samples_kib": [
+ 80864,
+ 80760,
+ 80820,
+ 80648,
+ 80588
+ ],
+ "numpy": false
+ },
+ {
+ "group": "twisted",
+ "label": "from twisted.internet import reactor",
+ "code": "from twisted.internet import reactor",
+ "available": true,
+ "maxrss_kib": 108632,
+ "samples_kib": [
+ 108632,
+ 108704,
+ 108616,
+ 108704,
+ 108552
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio",
+ "code": "import txaio",
+ "available": true,
+ "maxrss_kib": 65936,
+ "samples_kib": [
+ 65924,
+ 65936,
+ 66004,
+ 66052,
+ 65824
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_asyncio()",
+ "code": "import txaio; txaio.use_asyncio()",
+ "available": true,
+ "maxrss_kib": 80908,
+ "samples_kib": [
+ 81072,
+ 80788,
+ 80968,
+ 80908,
+ 80856
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_twisted()",
+ "code": "import txaio; txaio.use_twisted()",
+ "available": true,
+ "maxrss_kib": 90060,
+ "samples_kib": [
+ 90020,
+ 90400,
+ 90060,
+ 89964,
+ 90236
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn",
+ "code": "import autobahn",
+ "available": true,
+ "maxrss_kib": 90060,
+ "samples_kib": [
+ 90104,
+ 90044,
+ 90060,
+ 90016,
+ 90116
+ ],
+ "numpy": true
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_asyncio(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_asyncio(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 115056,
+ "samples_kib": [
+ 115056,
+ 115076,
+ 114720,
+ 115060,
+ 115056
+ ],
+ "numpy": true
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_twisted(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_twisted(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 121692,
+ "samples_kib": [
+ 121692,
+ 121784,
+ 122016,
+ 121672,
+ 121672
+ ],
+ "numpy": true
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.asyncio.websocket",
+ "code": "import autobahn.asyncio.websocket",
+ "available": true,
+ "maxrss_kib": 120004,
+ "samples_kib": [
+ 119848,
+ 120032,
+ 120004,
+ 120084,
+ 119892
+ ],
+ "numpy": true
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.twisted.websocket",
+ "code": "import autobahn.twisted.websocket",
+ "available": true,
+ "maxrss_kib": 136688,
+ "samples_kib": [
+ 136868,
+ 135628,
+ 136904,
+ 135520,
+ 136688
+ ],
+ "numpy": true
+ },
+ {
+ "group": "daphne",
+ "label": "from daphne.server import Server",
+ "code": "from daphne.server import Server",
+ "available": true,
+ "maxrss_kib": 139032,
+ "samples_kib": [
+ 139084,
+ 139192,
+ 138856,
+ 139008,
+ 139032
+ ],
+ "numpy": true
+ }
+ ]
+}
diff --git a/scripts/memory-footprint/results/pypy311-fix_1983-crossbar.json b/scripts/memory-footprint/results/pypy311-fix_1983-crossbar.json
new file mode 100644
index 000000000..06a4e9f4d
--- /dev/null
+++ b/scripts/memory-footprint/results/pypy311-fix_1983-crossbar.json
@@ -0,0 +1,41 @@
+{
+ "variant": "fix_1983",
+ "python": {
+ "implementation": "PyPy",
+ "version": "3.11.15",
+ "build": "3.11.15 (194f9f44b505, May 25 2026, 19:34:11) [PyPy 7.3.23 with GCC 10.2.1 20210130 (Red Hat 10.2.1-11)]"
+ },
+ "platform": {
+ "system": "Linux",
+ "release": "6.12.95+deb12-amd64",
+ "machine": "x86_64"
+ },
+ "runs": 5,
+ "packages": {
+ "autobahn": "26.10.1.dev1",
+ "txaio": "26.6.1",
+ "twisted": "26.4.0",
+ "cryptography": "50.0.2",
+ "numpy": "2.4.6",
+ "cbor2": "5.9.0",
+ "crossbar": "26.7.1",
+ "zlmdb": "26.7.1"
+ },
+ "results": [
+ {
+ "group": "crossbar",
+ "label": "from crossbar.edge.worker.router import ExtRouterController",
+ "code": "from crossbar.edge.worker.router import ExtRouterController",
+ "available": true,
+ "maxrss_kib": 229484,
+ "samples_kib": [
+ 229500,
+ 229840,
+ 218460,
+ 219932,
+ 229484
+ ],
+ "numpy": true
+ }
+ ]
+}
diff --git a/scripts/memory-footprint/results/pypy311-fix_1983.json b/scripts/memory-footprint/results/pypy311-fix_1983.json
new file mode 100644
index 000000000..877e6f97c
--- /dev/null
+++ b/scripts/memory-footprint/results/pypy311-fix_1983.json
@@ -0,0 +1,250 @@
+{
+ "variant": "fix_1983",
+ "python": {
+ "implementation": "PyPy",
+ "version": "3.11.15",
+ "build": "3.11.15 (194f9f44b505, May 25 2026, 19:34:11) [PyPy 7.3.23 with GCC 10.2.1 20210130 (Red Hat 10.2.1-11)]"
+ },
+ "platform": {
+ "system": "Linux",
+ "release": "6.12.95+deb12-amd64",
+ "machine": "x86_64"
+ },
+ "runs": 5,
+ "packages": {
+ "autobahn": "26.10.1.dev1",
+ "txaio": "26.6.1",
+ "twisted": "26.4.0",
+ "cryptography": "50.0.2",
+ "numpy": "2.4.6",
+ "cbor2": "5.9.0",
+ "daphne": "4.2.3"
+ },
+ "results": [
+ {
+ "group": "interpreter",
+ "label": "bare interpreter",
+ "code": "pass",
+ "available": true,
+ "maxrss_kib": 65920,
+ "samples_kib": [
+ 65884,
+ 65956,
+ 65924,
+ 65812,
+ 65920
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "import cryptography",
+ "code": "import cryptography",
+ "available": true,
+ "maxrss_kib": 65936,
+ "samples_kib": [
+ 65808,
+ 65964,
+ 65884,
+ 65936,
+ 66000
+ ],
+ "numpy": false
+ },
+ {
+ "group": "cryptography",
+ "label": "from cryptography.hazmat.primitives import hashes",
+ "code": "from cryptography.hazmat.primitives import hashes",
+ "available": true,
+ "maxrss_kib": 73744,
+ "samples_kib": [
+ 73628,
+ 73744,
+ 73848,
+ 73588,
+ 73892
+ ],
+ "numpy": false
+ },
+ {
+ "group": "numpy",
+ "label": "import numpy",
+ "code": "import numpy",
+ "available": true,
+ "maxrss_kib": 90272,
+ "samples_kib": [
+ 90360,
+ 90232,
+ 90248,
+ 90280,
+ 90272
+ ],
+ "numpy": true
+ },
+ {
+ "group": "asyncio",
+ "label": "import asyncio",
+ "code": "import asyncio",
+ "available": true,
+ "maxrss_kib": 80728,
+ "samples_kib": [
+ 80732,
+ 80728,
+ 80796,
+ 80544,
+ 80652
+ ],
+ "numpy": false
+ },
+ {
+ "group": "twisted",
+ "label": "from twisted.internet import reactor",
+ "code": "from twisted.internet import reactor",
+ "available": true,
+ "maxrss_kib": 108596,
+ "samples_kib": [
+ 108648,
+ 108532,
+ 108596,
+ 108676,
+ 108552
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio",
+ "code": "import txaio",
+ "available": true,
+ "maxrss_kib": 65760,
+ "samples_kib": [
+ 65740,
+ 65764,
+ 65760,
+ 65940,
+ 65740
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_asyncio()",
+ "code": "import txaio; txaio.use_asyncio()",
+ "available": true,
+ "maxrss_kib": 81004,
+ "samples_kib": [
+ 81004,
+ 81060,
+ 81052,
+ 80920,
+ 80888
+ ],
+ "numpy": false
+ },
+ {
+ "group": "txaio",
+ "label": "import txaio; txaio.use_twisted()",
+ "code": "import txaio; txaio.use_twisted()",
+ "available": true,
+ "maxrss_kib": 90068,
+ "samples_kib": [
+ 90012,
+ 90152,
+ 90176,
+ 90068,
+ 89936
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn",
+ "code": "import autobahn",
+ "available": true,
+ "maxrss_kib": 65932,
+ "samples_kib": [
+ 65996,
+ 65776,
+ 65740,
+ 65960,
+ 65932
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_asyncio(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_asyncio(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 95900,
+ "samples_kib": [
+ 95904,
+ 95768,
+ 95872,
+ 95900,
+ 96100
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "txaio.use_twisted(); import autobahn.wamp",
+ "code": "import txaio; txaio.use_twisted(); import autobahn.wamp",
+ "available": true,
+ "maxrss_kib": 102944,
+ "samples_kib": [
+ 102936,
+ 103028,
+ 102876,
+ 103092,
+ 102944
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.asyncio.websocket",
+ "code": "import autobahn.asyncio.websocket",
+ "available": true,
+ "maxrss_kib": 101424,
+ "samples_kib": [
+ 101424,
+ 101572,
+ 101344,
+ 101352,
+ 101608
+ ],
+ "numpy": false
+ },
+ {
+ "group": "autobahn",
+ "label": "import autobahn.twisted.websocket",
+ "code": "import autobahn.twisted.websocket",
+ "available": true,
+ "maxrss_kib": 116580,
+ "samples_kib": [
+ 116580,
+ 116704,
+ 116456,
+ 116576,
+ 116584
+ ],
+ "numpy": false
+ },
+ {
+ "group": "daphne",
+ "label": "from daphne.server import Server",
+ "code": "from daphne.server import Server",
+ "available": true,
+ "maxrss_kib": 118356,
+ "samples_kib": [
+ 118412,
+ 118384,
+ 118320,
+ 118356,
+ 118296
+ ],
+ "numpy": false
+ }
+ ]
+}
diff --git a/scripts/memory-footprint/run.sh b/scripts/memory-footprint/run.sh
new file mode 100755
index 000000000..ada71e05c
--- /dev/null
+++ b/scripts/memory-footprint/run.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+###############################################################################
+#
+# The MIT License (MIT)
+#
+# Copyright (c) typedef int GmbH
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+#
+# Measure the import memory footprint of autobahn for one Python environment, for
+# each autobahn variant, in fresh throwaway venvs, and write the results as JSON to
+# scripts/memory-footprint/results/-.json (see docs/memory-footprint.rst).
+#
+# usage: scripts/memory-footprint/run.sh []
+#
+# env cpy314 | pypy311 (same interpreters as the justfile)
+# local-label column label for this working tree (default: the current git branch)
+#
+# Variants measured:
+# 25.10.2 last release with FlatBuffers as an optional extra (from PyPI)
+# 26.7.1 latest release, with the vendored FlatBuffers runtime (from PyPI)
+# this working tree
+#
+# Every venv gets numpy installed (on PyPy numpy<2.5: 2.5 ships no PyPy wheels), so
+# the tables show whether autobahn loads it. crossbar is measured in a separate venv
+# (crossbar 26.7.1 from PyPI), and not for 25.10.2, which no crossbar release supports.
+
+set -euo pipefail
+
+ENV_NAME="${1:?usage: run.sh []}"
+HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO="$(cd "${HERE}/../.." && pwd)"
+LOCAL_LABEL="${2:-$(git -C "${REPO}" rev-parse --abbrev-ref HEAD)}"
+RUNS="${MEMORY_FOOTPRINT_RUNS:-5}"
+CROSSBAR_VERSION="26.7.1"
+
+case "${ENV_NAME}" in
+ cpy314) PYTHON_SPEC="cpython-3.14"; NUMPY="numpy" ;;
+ pypy311) PYTHON_SPEC="pypy-3.11.15"; NUMPY="numpy<2.5" ;;
+ *) echo "unknown env: ${ENV_NAME}" >&2; exit 1 ;;
+esac
+
+WORKDIR="$(mktemp -d -t autobahn-memory-footprint-XXXXXX)"
+trap 'rm -rf "${WORKDIR}"' EXIT
+mkdir -p "${HERE}/results"
+
+measure() {
+ # measure