From a6e3147a18f2f295cfe1e9d4fdfe65831fcc51f6 Mon Sep 17 00:00:00 2001 From: acul71 Date: Tue, 7 Apr 2026 03:17:22 +0200 Subject: [PATCH 1/2] perf(py-libp2p): add v0.6 implementation and runner wiring Add perf/impl/py-libp2p/v0.6 (Makefile, perf wrapper, perf_cli.py, README) with CLI/JSON contract matching other impls. Register py-libp2p in perf/impl/Makefile, runner versionsInput.json and versions.ts, and extend --test-filter in runner/src/index.ts. Update perf/README.md. Refs: https://github.com/libp2p/test-plans/issues/845 Made-with: Cursor --- perf/README.md | 3 +- perf/impl/Makefile | 12 +- perf/impl/py-libp2p/v0.6/.gitignore | 3 + perf/impl/py-libp2p/v0.6/Makefile | 31 +++ perf/impl/py-libp2p/v0.6/README.md | 33 +++ perf/impl/py-libp2p/v0.6/perf | 4 + perf/impl/py-libp2p/v0.6/perf_cli.py | 382 +++++++++++++++++++++++++++ perf/runner/src/index.ts | 2 +- perf/runner/src/versions.ts | 2 +- perf/runner/versionsInput.json | 8 + 10 files changed, 474 insertions(+), 6 deletions(-) create mode 100644 perf/impl/py-libp2p/v0.6/.gitignore create mode 100644 perf/impl/py-libp2p/v0.6/Makefile create mode 100644 perf/impl/py-libp2p/v0.6/README.md create mode 100755 perf/impl/py-libp2p/v0.6/perf create mode 100644 perf/impl/py-libp2p/v0.6/perf_cli.py diff --git a/perf/README.md b/perf/README.md index 64b57c5de..c73d8ba51 100644 --- a/perf/README.md +++ b/perf/README.md @@ -108,4 +108,5 @@ Given you have provisioned your infrastructure, you can now build and run the li } ``` 2. For a new implementation, in [`impl/Makefile` include your implementation in the `all` target.](./impl/Makefile#L7) -3. For a new version, reference version in [`runner/src/versions.ts`](./runner/src/versions.ts#L7-L43). +3. For a new version, add an entry to [`runner/versionsInput.json`](./runner/versionsInput.json) and extend the `implementation` union in [`runner/src/versions.ts`](./runner/src/versions.ts) if needed. +4. When adding a **new implementation name** (a new top-level folder under `impl/`), add that name to the `choices` list for `--test-filter` in [`runner/src/index.ts`](./runner/src/index.ts). The runner uses yargs with a fixed allow-list; omitting this causes `--test-filter ` to fail even if the implementation appears in `versionsInput.json`. diff --git a/perf/impl/Makefile b/perf/impl/Makefile index c4d4f7e01..f653bb4c6 100644 --- a/perf/impl/Makefile +++ b/perf/impl/Makefile @@ -3,8 +3,9 @@ RUST_SUBDIRS := $(wildcard rust-libp2p/*/.) HTTPS_SUBDIRS := $(wildcard https/*/.) QUIC_GO_SUBDIRS := $(wildcard quic-go/*/.) JS_SUBDIRS := $(wildcard js-libp2p/*/.) +PY_SUBDIRS := $(wildcard py-libp2p/*/.) -all: $(RUST_SUBDIRS) $(GO_SUBDIRS) $(HTTPS_SUBDIRS) $(QUIC_GO_SUBDIRS) $(JS_SUBDIRS) +all: $(RUST_SUBDIRS) $(GO_SUBDIRS) $(HTTPS_SUBDIRS) $(QUIC_GO_SUBDIRS) $(JS_SUBDIRS) $(PY_SUBDIRS) $(RUST_SUBDIRS): $(MAKE) -C $@ @@ -21,6 +22,9 @@ $(QUIC_GO_SUBDIRS): $(JS_SUBDIRS): $(MAKE) -C $@ +$(PY_SUBDIRS): + $(MAKE) -C $@ + go-libp2p: $(GO_SUBDIRS) rust-libp2p: $(RUST_SUBDIRS) @@ -31,9 +35,11 @@ quic-go: $(QUIC_GO_SUBDIRS) js-libp2p: $(JS_SUBDIRS) -clean: $(RUST_SUBDIRS:%=%clean) $(GO_SUBDIRS:%=%clean) $(HTTPS_SUBDIRS:%=%clean) $(QUIC_GO_SUBDIRS:%=%clean) $(JS_SUBDIRS:%=%clean) +py-libp2p: $(PY_SUBDIRS) + +clean: $(RUST_SUBDIRS:%=%clean) $(GO_SUBDIRS:%=%clean) $(HTTPS_SUBDIRS:%=%clean) $(QUIC_GO_SUBDIRS:%=%clean) $(JS_SUBDIRS:%=%clean) $(PY_SUBDIRS:%=%clean) %clean: $(MAKE) -C $* clean -.PHONY: $(RUST_SUBDIRS) $(GO_SUBDIRS) $(HTTPS_SUBDIRS) $(QUIC_GO_SUBDIRS) $(JS_SUBDIRS) all clean +.PHONY: $(RUST_SUBDIRS) $(GO_SUBDIRS) $(HTTPS_SUBDIRS) $(QUIC_GO_SUBDIRS) $(JS_SUBDIRS) $(PY_SUBDIRS) all clean diff --git a/perf/impl/py-libp2p/v0.6/.gitignore b/perf/impl/py-libp2p/v0.6/.gitignore new file mode 100644 index 000000000..b052132ba --- /dev/null +++ b/perf/impl/py-libp2p/v0.6/.gitignore @@ -0,0 +1,3 @@ +.venv/ +py-libp2p-*.zip +py-libp2p-*/ diff --git a/perf/impl/py-libp2p/v0.6/Makefile b/perf/impl/py-libp2p/v0.6/Makefile new file mode 100644 index 000000000..422e05af6 --- /dev/null +++ b/perf/impl/py-libp2p/v0.6/Makefile @@ -0,0 +1,31 @@ +# Pinned py-libp2p tree (same idea as transport-interop Python image). +# Includes yamux window + perf fixes from https://github.com/libp2p/py-libp2p/pull/1258 (tip at pin time); update when merged to main. +commitSha := 731c3c787d3e98e380263943132d301c1be8dcd5 + +# Use 3.11–3.12 for libp2p deps (e.g. coincurve wheels); avoid 3.14+ where builds fail. +PYTHON_FOR_VENV ?= python3.12 + +LIBP2P_DIR := py-libp2p-$(commitSha) +VENV := .venv +PYTHON := $(VENV)/bin/python +PIP := $(VENV)/bin/pip + +all: $(PYTHON) perf_cli.py + chmod +x perf + +$(LIBP2P_DIR).zip: + wget -O $@ "https://github.com/libp2p/py-libp2p/archive/$(commitSha).zip" + +$(LIBP2P_DIR): $(LIBP2P_DIR).zip + unzip -o $< + touch $@ + +$(PYTHON): $(LIBP2P_DIR) + $(PYTHON_FOR_VENV) -m venv $(VENV) + $(PIP) install --upgrade pip + $(PIP) install "./$(LIBP2P_DIR)/" + +clean: + rm -rf $(VENV) $(LIBP2P_DIR).zip $(LIBP2P_DIR) + +.PHONY: all clean diff --git a/perf/impl/py-libp2p/v0.6/README.md b/perf/impl/py-libp2p/v0.6/README.md new file mode 100644 index 000000000..18946030d --- /dev/null +++ b/perf/impl/py-libp2p/v0.6/README.md @@ -0,0 +1,33 @@ +# py-libp2p perf (test-plans) + +This directory implements the `perf` executable used by the perf benchmark runner (CLI flags + JSON on stdout; see [`perf/README.md`](../../README.md)). It is **not** the unified-testing harness in the py-libp2p repo ([`interop/perf/perf_test.py`](https://github.com/libp2p/py-libp2p/blob/main/interop/perf/perf_test.py), Redis/YAML): that flow does not match this runner’s contract. + +## Build + +```bash +make # downloads pinned py-libp2p, creates .venv, installs libp2p +``` + +Requires `python3.12` (or set `PYTHON_FOR_VENV`, e.g. `python3.11`) so dependencies such as `coincurve` install cleanly. + +## Pinned `libp2p` revision + +The `commitSha` in [`Makefile`](./Makefile) selects the GitHub archive used for `pip install`. It is currently aligned with [PR #1258](https://github.com/libp2p/py-libp2p/pull/1258) (yamux receive-window and related perf fixes). After that PR merges into `main`, update the pin to a stable `main` commit. + +## Implementation notes + +- **`perf_cli.py`** uses `PerfService(..., {"write_block_size": 65500})` so writes stay under Noise’s **65535-byte** frame limit; the library default block size can break runner-style upload throughput. +- **Server:** listens on **TCP** (Noise + Yamux) and **QUIC** on the same port pattern (two internal listeners), with the same deterministic peer id as the Go reference perf binary. +- **QUIC client — half-close:** `NetStream.close_write()` in py-libp2p calls `muxed_stream.close()`, which on `QUICStream` closes *both* halves of the stream; the client then cannot read the download phase. `perf_cli.py` patches `NetStream.close_write` to call `close_write()` on the muxed stream when available (upstream: `libp2p/network/stream/net_stream.py`). +- **QUIC client — identify:** `BasicHost` schedules background identify only for QUIC dialers, which opens a second stream on the same connection as perf. The peerstore is seeded with a cached “safe” protocol so identify is skipped (same effect as `BasicHost._has_cached_protocols`). +- **QUIC — aioquic flow limits:** aioquic defaults `QuicConfiguration.max_data` / `max_stream_data` to **1 MiB**. py-libp2p’s `QUICTransportConfig.CONNECTION_FLOW_CONTROL_WINDOW` / `STREAM_FLOW_CONTROL_WINDOW` are not applied to those fields. `perf_cli.py` wraps `create_server_config_from_base` / `create_client_config_from_base` in **both** `libp2p.transport.quic.utils` and `libp2p.transport.quic.transport` (the transport module re-imports the names, so patching only `utils` is not enough) and sets `max_data` / `max_stream_data` from the transport config. **`QUICTransportConfig`** is also passed into `new_host` for QUIC server and client with larger windows and interop-style timeouts. +- **Throughput asymmetry:** With the above, **QUIC upload** in long `timeout …` runs is typically in the same ballpark as TCP on loopback; **QUIC download** (server → client) may still be much lower than TCP in local tests — remaining limits likely involve py-libp2p/aioquic receive scheduling rather than the 1 MiB defaults alone (bumping `max_data` further did not change download totals in quick A/B tests). +- **Runner matrix:** [`runner/versionsInput.json`](../../runner/versionsInput.json) includes **`tcp`** and **`quic-v1`** for this implementation. + +## Smoke (local) + +```bash +./perf --run-server --server-address 127.0.0.1:4001 & +./perf --server-address 127.0.0.1:4001 --transport tcp --upload-bytes 4096 --download-bytes 4096 +./perf --server-address 127.0.0.1:4001 --transport quic-v1 --upload-bytes 4096 --download-bytes 4096 +``` diff --git a/perf/impl/py-libp2p/v0.6/perf b/perf/impl/py-libp2p/v0.6/perf new file mode 100755 index 000000000..4daacad05 --- /dev/null +++ b/perf/impl/py-libp2p/v0.6/perf @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "$DIR/.venv/bin/python" "$DIR/perf_cli.py" "$@" diff --git a/perf/impl/py-libp2p/v0.6/perf_cli.py b/perf/impl/py-libp2p/v0.6/perf_cli.py new file mode 100644 index 000000000..0e65be9c9 --- /dev/null +++ b/perf/impl/py-libp2p/v0.6/perf_cli.py @@ -0,0 +1,382 @@ +#!/usr/bin/env python3 +""" +libp2p perf CLI for test-plans (same contract as perf/impl/go-libp2p). + +Spec: https://github.com/libp2p/specs/blob/master/perf/perf.md +""" + +from __future__ import annotations + +import argparse +import ipaddress +import json +import logging +import sys +from typing import Any + +import multiaddr +import trio + +from libp2p import create_yamux_muxer_option, generate_peer_id_from, new_host +from libp2p.crypto.ed25519 import create_new_key_pair +from libp2p.crypto.x25519 import create_new_key_pair as create_new_x25519_key_pair +from libp2p.custom_types import TProtocol +from libp2p.host.ping import ID as PING_PROTOCOL_ID +from libp2p.network.stream.exceptions import StreamError +from libp2p.network.stream.net_stream import NetStream, StreamState +from libp2p.peer.id import ID as PeerID +from libp2p.perf import PerfService +from libp2p.perf.types import PerfInit, PerfOutput +from libp2p.security.noise.transport import ( + PROTOCOL_ID as NOISE_PROTOCOL_ID, + Transport as NoiseTransport, +) +from libp2p.transport.quic.config import QUICTransportConfig +import libp2p.transport.quic.transport as _quic_transport +import libp2p.transport.quic.utils as _quic_utils + +# Under Noise frame limit (65535). Pinned lib defaults WRITE_BLOCK_SIZE=65536 which +# breaks large uploads over TCP; interop uses 65500 — see interop/perf/perf_test.py +# and https://github.com/libp2p/py-libp2p/pull/1258 +PERF_SERVICE_INIT: PerfInit = {"write_block_size": 65500} + +# Matches go-libp2p perf simpleReader{seed:0} Ed25519 identity (see perf/impl/go-libp2p/v0.42/main.go). +GO_PERF_PEER_ID = "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN" + +_QUIC_AIOQUIC_FLOW_PATCH_INSTALLED = False + +logger = logging.getLogger("perf_cli") + + +def _json_line(out: PerfOutput) -> str: + # test-plans runner expects camelCase keys (Go/JS reference) + return json.dumps( + { + "type": out["type"], + "timeSeconds": out["time_seconds"], + "uploadBytes": out["upload_bytes"], + "downloadBytes": out["download_bytes"], + } + ) + + +def _parse_host_port(server_address: str) -> tuple[str, int]: + if server_address.startswith("["): + # [::1]:4001 + bracket_end = server_address.index("]") + host = server_address[1:bracket_end] + port = int(server_address[bracket_end + 2 :]) + return host, port + if server_address.count(":") > 1: + # IPv6 without brackets — avoid splitting on port + raise ValueError( + "IPv6 server-address must use brackets, e.g. [::1]:4001" + ) + host, port_s = server_address.rsplit(":", 1) + return host, int(port_s) + + +def _ip_prefix(host: str) -> str: + ip = ipaddress.ip_address(host) + if ip.version == 4: + return f"/ip4/{host}" + return f"/ip6/{host}" + + +def _ensure_quic_aioquic_flow_patch() -> None: + """ + QUICTransportConfig exposes CONNECTION_FLOW_CONTROL_WINDOW / STREAM_FLOW_CONTROL_WINDOW, + but libp2p's QUIC transport does not copy them onto aioquic's QuicConfiguration, which + defaults max_data and max_stream_data to 1MB — crushing large server→client perf + downloads. Patch the factory once so our _make_quic_transport_opt() takes effect in + aioquic (upstream should map these fields in transport._setup_quic_configurations). + """ + global _QUIC_AIOQUIC_FLOW_PATCH_INSTALLED + if _QUIC_AIOQUIC_FLOW_PATCH_INSTALLED: + return + _orig_s = _quic_utils.create_server_config_from_base + _orig_c = _quic_utils.create_client_config_from_base + + def _apply(cfg: Any, tc: Any) -> None: + if tc is None: + return + md = getattr(tc, "CONNECTION_FLOW_CONTROL_WINDOW", None) + ms = getattr(tc, "STREAM_FLOW_CONTROL_WINDOW", None) + if md is not None: + cfg.max_data = md + if ms is not None: + cfg.max_stream_data = ms + + def _wrap_s( + base: Any, security_manager: Any, transport_config: Any = None + ) -> Any: + out = _orig_s(base, security_manager, transport_config) + _apply(out, transport_config) + return out + + def _wrap_c( + base: Any, security_manager: Any, transport_config: Any = None + ) -> Any: + out = _orig_c(base, security_manager, transport_config) + _apply(out, transport_config) + return out + + # utils and transport each bind these names at import time — patch both. + _quic_utils.create_server_config_from_base = _wrap_s + _quic_utils.create_client_config_from_base = _wrap_c + _quic_transport.create_server_config_from_base = _wrap_s + _quic_transport.create_client_config_from_base = _wrap_c + _QUIC_AIOQUIC_FLOW_PATCH_INSTALLED = True + + +def _make_quic_transport_opt() -> QUICTransportConfig: + """ + Defaults keep connection-level flow control very small (~1.5MB), which + throttles large server→client perf downloads. Match interop-style timeouts + and raise stream/connection windows for throughput benchmarks. + """ + return QUICTransportConfig( + connection_timeout=30.0, + idle_timeout=120.0, + dial_timeout=30.0, + inbound_upgrade_timeout=30.0, + outbound_upgrade_timeout=30.0, + outbound_stream_protocol_negotiation_timeout=30.0, + inbound_stream_protocol_negotiation_timeout=30.0, + NEGOTIATE_TIMEOUT=30.0, + STREAM_FLOW_CONTROL_WINDOW=16 * 1024 * 1024, + CONNECTION_FLOW_CONTROL_WINDOW=64 * 1024 * 1024, + MAX_STREAM_RECEIVE_BUFFER=16 * 1024 * 1024, + ) + + +def _skip_quic_identify_for_perf_client(host: Any, dial_maddr: multiaddr.Multiaddr) -> None: + """ + BasicHost schedules background identify only for QUIC dialers. That opens + an extra stream on the same connection as perf and competes for negotiation + and QUIC stream resources (hurts large downloads). Seeding a safe cached + protocol skips identify — see BasicHost._schedule_identify. + """ + pid = PeerID.from_base58(GO_PERF_PEER_ID) + host.peerstore.add_addrs(pid, [dial_maddr], 120) + host.peerstore.add_protocols(pid, [str(PING_PROTOCOL_ID)]) + + +def _patch_netstream_close_write_half_close() -> None: + """ + NetStream.close_write() incorrectly calls muxed_stream.close(). For QUIC, + QUICStream.close() closes *both* read and write (see close_read in close()), + so the perf client never reads the download phase (0 bytes). Other muxers + may tolerate close(); QUIC needs half-close: muxed_stream.close_write(). + """ + if getattr(NetStream.close_write, "_perf_cli_quic_patched", False): + return + + async def close_write_fixed(self: NetStream) -> None: + async with self._state_lock: + if self._state == StreamState.ERROR: + raise StreamError( + "Cannot close write on stream; stream is in error state" + ) + ms = self.muxed_stream + if hasattr(ms, "close_write"): + await ms.close_write() + else: + await ms.close() + async with self._state_lock: + if self._state == StreamState.OPEN: + self._state = StreamState.CLOSE_WRITE + elif self._state == StreamState.CLOSE_READ: + self._state = StreamState.CLOSE_BOTH + await self.remove() + + setattr(close_write_fixed, "_perf_cli_quic_patched", True) + NetStream.close_write = close_write_fixed # type: ignore[method-assign] + + +def _dial_multiaddr(host: str, port: int, transport: str) -> multiaddr.Multiaddr: + pfx = _ip_prefix(host) + if transport == "tcp": + base = multiaddr.Multiaddr(f"{pfx}/tcp/{port}") + elif transport == "quic-v1": + base = multiaddr.Multiaddr(f"{pfx}/udp/{port}/quic-v1") + else: + raise ValueError(f"Invalid transport {transport!r}") + return base.encapsulate(multiaddr.Multiaddr(f"/p2p/{GO_PERF_PEER_ID}")) + + +def _server_identity() -> Any: + kp = create_new_key_pair(seed=bytes(32)) + pid = generate_peer_id_from(kp).to_base58() + if pid != GO_PERF_PEER_ID: + raise RuntimeError( + f"Deterministic peer id mismatch: got {pid}, want {GO_PERF_PEER_ID}" + ) + return kp + + +def _noise_sec_opt(key_pair: Any) -> dict[TProtocol, Any]: + noise_kp = create_new_x25519_key_pair() + return { + NOISE_PROTOCOL_ID: NoiseTransport( + libp2p_keypair=key_pair, + noise_privkey=noise_kp.private_key, + early_data=None, + ) + } + + +def _print_listen_multiaddrs(hosts: dict[str, Any]) -> None: + """Emit one line per listen addr (/p2p/…), same shape as go-libp2p perf server.""" + peer_id = next(iter(hosts.values())).get_id().to_base58() + seen: set[str] = set() + for h in hosts.values(): + for addr in h.get_addrs(): + s = str(addr) + if f"/p2p/{peer_id}" not in s: + s = str( + addr.encapsulate(multiaddr.Multiaddr(f"/p2p/{peer_id}")) + ) + if s not in seen: + seen.add(s) + print(s, flush=True) + + +async def _run_server(listen_host: str, listen_port: int) -> None: + _ensure_quic_aioquic_flow_patch() + # py-libp2p Swarm uses a single base transport; TCP+QUIC on one host requires + # two listeners (same identity), matching go-libp2p's dual listen. + key_pair = _server_identity() + pfx = _ip_prefix(listen_host) + m_tcp = multiaddr.Multiaddr(f"{pfx}/tcp/{listen_port}") + m_quic = multiaddr.Multiaddr(f"{pfx}/udp/{listen_port}/quic-v1") + + sec_opt = _noise_sec_opt(key_pair) + muxer_opt = create_yamux_muxer_option() + + hosts: dict[str, Any] = {} + print_lock = trio.Lock() + printed = False + ready = 0 + + async def maybe_print() -> None: + nonlocal printed, ready + async with print_lock: + ready += 1 + if printed or ready < 2: + return + _print_listen_multiaddrs(hosts) + printed = True + + async def tcp_stack() -> None: + h = new_host( + key_pair=key_pair, + sec_opt=sec_opt, + muxer_opt=muxer_opt, + listen_addrs=[m_tcp], + enable_quic=False, + ) + perf = PerfService(h, PERF_SERVICE_INIT) + await perf.start() + async with h.run(listen_addrs=[m_tcp]): + async with print_lock: + hosts["tcp"] = h + await maybe_print() + await trio.sleep_forever() + + async def quic_stack() -> None: + h = new_host( + key_pair=key_pair, + listen_addrs=[m_quic], + enable_quic=True, + quic_transport_opt=_make_quic_transport_opt(), + negotiate_timeout=30, + ) + perf = PerfService(h, PERF_SERVICE_INIT) + await perf.start() + async with h.run(listen_addrs=[m_quic]): + async with print_lock: + hosts["quic"] = h + await maybe_print() + await trio.sleep_forever() + + async with trio.open_nursery() as nursery: + nursery.start_soon(tcp_stack) + nursery.start_soon(quic_stack) + + +async def _run_client( + server_host: str, + server_port: int, + transport: str, + upload_bytes: int, + download_bytes: int, +) -> None: + dial_maddr = _dial_multiaddr(server_host, server_port, transport) + + if transport == "tcp": + key_pair = create_new_key_pair() + host = new_host( + key_pair=key_pair, + sec_opt=_noise_sec_opt(key_pair), + muxer_opt=create_yamux_muxer_option(), + listen_addrs=None, + enable_quic=False, + ) + else: + _ensure_quic_aioquic_flow_patch() + host = new_host( + key_pair=create_new_key_pair(), + listen_addrs=None, + enable_quic=True, + quic_transport_opt=_make_quic_transport_opt(), + negotiate_timeout=30, + ) + _skip_quic_identify_for_perf_client(host, dial_maddr) + _patch_netstream_close_write_half_close() + + perf = PerfService(host, PERF_SERVICE_INIT) + await perf.start() + + async with host.run(listen_addrs=[]): + async for out in perf.measure_performance( + dial_maddr, upload_bytes, download_bytes + ): + print(_json_line(out), flush=True) + + +def main() -> None: + logging.basicConfig(level=logging.WARNING, stream=sys.stderr) + + p = argparse.ArgumentParser() + p.add_argument("--run-server", action="store_true") + p.add_argument("--server-address", required=True) + p.add_argument("--transport", default="tcp") + p.add_argument("--upload-bytes", type=int, default=0) + p.add_argument("--download-bytes", type=int, default=0) + args = p.parse_args() + + host, port = _parse_host_port(args.server_address) + + if args.transport not in ("tcp", "quic-v1"): + print( + "Invalid transport. Accepted values: 'tcp' or 'quic-v1'", + file=sys.stderr, + ) + sys.exit(2) + + if args.run_server: + trio.run(_run_server, host, port) + else: + trio.run( + _run_client, + host, + port, + args.transport, + args.upload_bytes, + args.download_bytes, + ) + + +if __name__ == "__main__": + main() diff --git a/perf/runner/src/index.ts b/perf/runner/src/index.ts index 4042b5632..5d857560d 100644 --- a/perf/runner/src/index.ts +++ b/perf/runner/src/index.ts @@ -263,7 +263,7 @@ const argv = yargs 'test-filter': { type: 'string', array: true, - choices: ['js-libp2p', 'rust-libp2p', 'go-libp2p', 'https', 'quic-go', 'all'], + choices: ['js-libp2p', 'py-libp2p', 'rust-libp2p', 'go-libp2p', 'https', 'quic-go', 'all'], description: 'Filter tests to run, only the implementations here will be run. It defaults to all.', demandOption: false, default: 'all' diff --git a/perf/runner/src/versions.ts b/perf/runner/src/versions.ts index a52cf74c3..b4f6c8335 100644 --- a/perf/runner/src/versions.ts +++ b/perf/runner/src/versions.ts @@ -3,7 +3,7 @@ import path from 'path'; export type Version = { id: string, - implementation: "go-libp2p" | "js-libp2p" | "nim-libp2p" | "rust-libp2p" | "zig-libp2p" | "https" | "quic-go", + implementation: "go-libp2p" | "js-libp2p" | "nim-libp2p" | "py-libp2p" | "rust-libp2p" | "zig-libp2p" | "https" | "quic-go", transportStacks: string[], } diff --git a/perf/runner/versionsInput.json b/perf/runner/versionsInput.json index 186daffa7..82fbbb88a 100644 --- a/perf/runner/versionsInput.json +++ b/perf/runner/versionsInput.json @@ -43,5 +43,13 @@ "transportStacks": [ "tcp" ] + }, + { + "id": "v0.6", + "implementation": "py-libp2p", + "transportStacks": [ + "tcp", + "quic-v1" + ] } ] From a0882403e104739051dcce41cc16438d9761bda5 Mon Sep 17 00:00:00 2001 From: acul71 Date: Tue, 7 Apr 2026 04:16:51 +0200 Subject: [PATCH 2/2] chore: ci Made-with: Cursor