Skip to content

feat: implemented the perf protocol#1176

Merged
acul71 merged 48 commits intolibp2p:mainfrom
ItshMoh:perfexamples
Feb 28, 2026
Merged

feat: implemented the perf protocol#1176
acul71 merged 48 commits intolibp2p:mainfrom
ItshMoh:perfexamples

Conversation

@ItshMoh
Copy link
Copy Markdown
Contributor

@ItshMoh ItshMoh commented Jan 29, 2026

Issue #1169

Description

This PR implements the libp2p perf protocol as specified in the libp2p specs. The perf protocol enables measuring transfer performance (throughput) between libp2p nodes, which is useful for benchmarking and comparing implementations.

Protocol Summary

  • Client sends an 8-byte big-endian u64 specifying how many bytes the server should send back
  • Client uploads the specified number of bytes
  • Server reads all data, then sends back the requested number of bytes
  • Both sides measure time to calculate throughput

Usage

# Terminal 1 - Start server
python3 examples/perf/perf_example.py -p 8000
# Terminal 2 - Run client
python3 examples/perf/perf_example.py -d /ip4/127.0.0.1/tcp/8000/p2p/<PEER_ID>

Tested locally with py-libp2p nodes communicating with each other. Example output:
Screenshot from 2026-01-30 11-08-20

Cute Animal Picture

image

@ItshMoh
Copy link
Copy Markdown
Contributor Author

ItshMoh commented Jan 29, 2026

cc: @lla-dane

@seetadev
Copy link
Copy Markdown
Contributor

seetadev commented Jan 29, 2026

@ItshMoh : Hi Mohan.

This is great progress — thanks a lot for pushing this forward. 👏

The implementation looks solid, the structure is clean, and the example + protocol summary make it very easy to understand and try out. This is a meaningful step toward better benchmarking and comparison across libp2p implementations.

One small but important organizational point: could you please move this PR to the test-plans repository instead? The perf protocol implementation and examples fit much better there, especially since it’s focused on performance measurement, benchmarking, and experimentation rather than core protocol behavior.

Link to the repository: https://github.com/libp2p/test-plans. Please also visit https://github.com/libp2p/test-plans/blob/master/docs/write-a-perf-test-app.md

Once it’s in the test-plans repo, we can:

  • iterate more freely on the protocol and examples,
  • extend it with additional scenarios and tooling,
  • and later decide if / how pieces should graduate into core repos.

Everything you’ve done so far — including the module layout, example, and local testing — will carry over nicely. So this is more about placing it in the right home than changing the direction of the work.

Really nice momentum here. Looking forward to the updated PR, and happy to review again once it’s up in test-plans.
Wish to share that we can keep a minified PoC here if you wish to solve something specific to py-libp2p.

Thanks again for the effort and execution 🙌

CCing @dhuseby and @acul71

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Jan 30, 2026

@ItshMoh

Trying the example I can see this failure for big data

(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ python perf_example.py 

Perf server ready, listening on:
  /ip4/127.0.0.1/tcp/55857/p2p/12D3KooWMFBZfDKd3pxKmRJCVsvHqp9hUo3WUb9mqTHgP1ejSA2J

Protocol: /perf/1.0.0

Run client with:
  python perf_example.py -d /ip4/127.0.0.1/tcp/55857/p2p/12D3KooWMFBZfDKd3pxKmRJCVsvHqp9hUo3WUb9mqTHgP1ejSA2J

Waiting for incoming perf requests...


#################################


(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ python perf_example.py -d /ip4/127.0.0.1/tcp/53573/p2p/12D3KooWPcmQezXgRAB6wDCgDmj8d2J8RuFvXu39G8iKa6cP8QEy -u 100 -D 1000

Connecting to 12D3KooWPcmQezXgRAB6wDCgDmj8d2J8RuFvXu39G8iKa6cP8QEy...
Failed to open TCP stream: all attempts to connect to 127.0.0.1:53573 failed
Failed to open TCP stream: all attempts to connect to 127.0.0.1:53573 failed
Failed to open TCP stream: all attempts to connect to 127.0.0.1:53573 failed
Failed to open TCP stream: all attempts to connect to 127.0.0.1:53573 failed
  + Exception Group Traceback (most recent call last):
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf/perf_example.py", line 170, in <module>
  |     main()
  |     ~~~~^^
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf/perf_example.py", line 164, in main
  |     trio.run(run, args.port, args.destination, args.upload, args.download)
  |     ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/venv/lib/python3.13/site-packages/trio/_core/_run.py", line 2549, in run
  |     raise runner.main_task_outcome.error
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf/perf_example.py", line 119, in run
  |     async with host.run(listen_addrs=listen_addrs):
  |                ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "/home/luca/.local/share/uv/python/cpython-3.13.11-linux-x86_64-gnu/lib/python3.13/contextlib.py", line 235, in __aexit__
  |     await self.gen.athrow(value)
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/host/basic_host.py", line 357, in _run
  |     async with background_trio_service(network):
  |                ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
  |   File "/home/luca/.local/share/uv/python/cpython-3.13.11-linux-x86_64-gnu/lib/python3.13/contextlib.py", line 235, in __aexit__
  |     await self.gen.athrow(value)
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/tools/async_service/trio_service.py", line 460, in background_trio_service
  |     async with trio.open_nursery() as nursery:
  |                ~~~~~~~~~~~~~~~~~^^
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/venv/lib/python3.13/site-packages/trio/_core/_run.py", line 1125, in __aexit__
  |     raise combined_error_from_nursery
  | ExceptionGroup: Exceptions from Trio nursery (1 sub-exception)
  +-+---------------- 1 ----------------
    | libp2p.exceptions.MultiError: Error 1: fail to open connection to peer 12D3KooWPcmQezXgRAB6wDCgDmj8d2J8RuFvXu39G8iKa6cP8QEy
    | 
    | The above exception was the direct cause of the following exception:
    | 
    | Traceback (most recent call last):
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/tools/async_service/trio_service.py", line 465, in background_trio_service
    |     yield manager
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/host/basic_host.py", line 374, in _run
    |     yield
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf/perf_example.py", line 122, in run
    |     await run_client(
    |     ...<5 lines>...
    |     )
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf/perf_example.py", line 59, in run_client
    |     await host.connect(info)
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/host/basic_host.py", line 675, in connect
    |     connections = await self._network.dial_peer(peer_info.peer_id)
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/network/swarm.py", line 275, in dial_peer
    |     raise SwarmException(
    |     ...<2 lines>...
    |     ) from MultiError(exceptions)
    | libp2p.network.exceptions.SwarmException: unable to connect to 12D3KooWPcmQezXgRAB6wDCgDmj8d2J8RuFvXu39G8iKa6cP8QEy, no addresses established a successful connection (with exceptions)
    +------------------------------------
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ 
(venv) luca@r17:~/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf$ python perf_example.py -d /ip4/127.0.0.1/tcp/53573/p2p/12D3KooWPcmQezXgRAB6wDCgDmj8d2J8RuFvXu39G8iKa6cP8QEy -u 1000 -D 100

Connecting to 12D3KooWPcmQezXgRAB6wDCgDmj8d2J8RuFvXu39G8iKa6cP8QEy...
Failed to open TCP stream: all attempts to connect to 127.0.0.1:53573 failed
Failed to open TCP stream: all attempts to connect to 127.0.0.1:53573 failed
Failed to open TCP stream: all attempts to connect to 127.0.0.1:53573 failed
Failed to open TCP stream: all attempts to connect to 127.0.0.1:53573 failed
  + Exception Group Traceback (most recent call last):
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf/perf_example.py", line 170, in <module>
  |     main()
  |     ~~~~^^
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf/perf_example.py", line 164, in main
  |     trio.run(run, args.port, args.destination, args.upload, args.download)
  |     ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/venv/lib/python3.13/site-packages/trio/_core/_run.py", line 2549, in run
  |     raise runner.main_task_outcome.error
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf/perf_example.py", line 119, in run
  |     async with host.run(listen_addrs=listen_addrs):
  |                ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |   File "/home/luca/.local/share/uv/python/cpython-3.13.11-linux-x86_64-gnu/lib/python3.13/contextlib.py", line 235, in __aexit__
  |     await self.gen.athrow(value)
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/host/basic_host.py", line 357, in _run
  |     async with background_trio_service(network):
  |                ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
  |   File "/home/luca/.local/share/uv/python/cpython-3.13.11-linux-x86_64-gnu/lib/python3.13/contextlib.py", line 235, in __aexit__
  |     await self.gen.athrow(value)
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/tools/async_service/trio_service.py", line 460, in background_trio_service
  |     async with trio.open_nursery() as nursery:
  |                ~~~~~~~~~~~~~~~~~^^
  |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/venv/lib/python3.13/site-packages/trio/_core/_run.py", line 1125, in __aexit__
  |     raise combined_error_from_nursery
  | ExceptionGroup: Exceptions from Trio nursery (1 sub-exception)
  +-+---------------- 1 ----------------
    | libp2p.exceptions.MultiError: Error 1: fail to open connection to peer 12D3KooWPcmQezXgRAB6wDCgDmj8d2J8RuFvXu39G8iKa6cP8QEy
    | 
    | The above exception was the direct cause of the following exception:
    | 
    | Traceback (most recent call last):
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/tools/async_service/trio_service.py", line 465, in background_trio_service
    |     yield manager
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/host/basic_host.py", line 374, in _run
    |     yield
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf/perf_example.py", line 122, in run
    |     await run_client(
    |     ...<5 lines>...
    |     )
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/examples/perf/perf_example.py", line 59, in run_client
    |     await host.connect(info)
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/host/basic_host.py", line 675, in connect
    |     connections = await self._network.dial_peer(peer_info.peer_id)
    |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |   File "/home/luca/Informatica/Learning/PNL_Launchpad_Curriculum/Libp2p/py-libp2p/libp2p/network/swarm.py", line 275, in dial_peer
    |     raise SwarmException(
    |     ...<2 lines>...
    |     ) from MultiError(exceptions)
    | libp2p.network.exceptions.SwarmException: unable to connect to 12D3KooWPcmQezXgRAB6wDCgDmj8d2J8RuFvXu39G8iKa6cP8QEy, no addresses established a successful connection (with exceptions)
    +------------------------------------

@ItshMoh
Copy link
Copy Markdown
Contributor Author

ItshMoh commented Jan 31, 2026

hey @acul71 thanks for pointing it out .
I forgot to reduce the WRITE_BLOCK_SIZE for adjusting the noise AEAD and Yamux header, due to which large data sizes are not being sent.

Now it is working , You can try.

@lla-dane
Copy link
Copy Markdown
Contributor

lla-dane commented Feb 1, 2026

Update the docs/examples.rst file to include examples.perf and create an example.perf.rst file in the same way the other example files are created.

Also include libp2p.perf in libp2p.rst and create a libp2p.perf.rst similar with the .rst files of other modules.

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Feb 1, 2026

@lla-dane @ItshMoh @seetadev

First version of python perf interop script in interop/perf

Summary of what’s in the report:

libp2p/test-plans#808

Contents:

  1. Tests passed (short list)

    • 3 baselines: https×https, quic-go×quic-go, iperf×iperf
    • 24 main tests: all rust×rust (9), all rust×python (9), 2 python×rust (tcp/ws + tls + mplex), 4 python×python (tcp/ws + noise/tls + mplex)
  2. Each of the 12 failed tests has:

    • Reason in one sentence
    • Relevant log excerpts that show why it failed

Failure categories:

  • Python dialer × Rust listener (yamux, 4 tests): Rust closes with KeepAliveTimeout during upload; Python sees Stream is closed / StreamReset. Likely Rust yamux keepalive vs Python not sending it (or sending it differently).
  • Python dialer × Rust listener (mplex, 2 tests): Uploads finish; Rust closes with KeepAliveTimeout before download; Python gets “Expected to receive 1073741824 bytes, but received 0”.
  • Python × Rust (quic-v1, 1 test): Protocol mismatch — Python tries /ipfs/id/1.0.0, Rust returns “protocol not supported”; stream is reset, then “cannot call write() after reset()”.
  • Python × Python (yamux, 4 tests): Runner timeout after 300s; Python yamux throughput is low, so 20×1 GB runs don’t finish in time.
  • Python × Python (quic-v1, 1 test): Listener error “Cannot send data on unknown peer-initiated stream”; download gets 0 bytes → “Expected to receive 1073741824 bytes, but received 0”.

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Feb 1, 2026

@ItshMoh We could modify the example like interop/perf to use with conf parameters different transport, secureChannels, muxers combinations (one at the time it's enough for testing I believe, eventually we could write a bash script that runs the test in auto with all combinations, but it's not required)
or do it in scripts if don't want to mess up the example

transports: [quic-v1, tcp, ws]
    secureChannels: [noise, tls]
    muxers: [yamux, mplex]

to see if they work, and also understand where we can optimize for throughput (yamux is awfully slow)

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Feb 1, 2026

Eventually I (we can) lint this PR and merge but opening a new issue for enhancing

@ItshMoh
Copy link
Copy Markdown
Contributor Author

ItshMoh commented Feb 2, 2026

hey @acul71 thanks for the perf_test.py .

@ItshMoh
Copy link
Copy Markdown
Contributor Author

ItshMoh commented Feb 2, 2026

Eventually I (we can) lint this PR and merge but opening a new issue for enhancing

Yeah we can merge this pr after i move the interface definitions in the abc.py file . We can eventually support the different transports and muxers in different issue.

@acul71 Thanks again for the help.

@ItshMoh
Copy link
Copy Markdown
Contributor Author

ItshMoh commented Feb 2, 2026

Update the docs/examples.rst file to include examples.perf and create an example.perf.rst file in the same way the other example files are created.

Also include libp2p.perf in libp2p.rst and create a libp2p.perf.rst similar with the .rst files of other modules.

@lla-dane Done.

@ItshMoh ItshMoh requested a review from lla-dane February 2, 2026 21:15
@ItshMoh
Copy link
Copy Markdown
Contributor Author

ItshMoh commented Feb 2, 2026

@seetadev @acul71 I think the PR is ready. you can review it finally and we can merge it.
Then we can enhance the perf-example file by including various transports, muxers etc.
we can create different issue one by one for those enhancements.
What are your thoughts?

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Feb 3, 2026

@seetadev @acul71 I think the PR is ready. you can review it finally and we can merge it. Then we can enhance the perf-example file by including various transports, muxers etc. we can create different issue one by one for those enhancements. What are your thoughts?

@ItshMoh Thanks for you contribution.
Eventually we could do like you are suggesting.

Before there are some issues to fix and decision to make like @seetadev perf module question about keeping it here in py-libp2p or make it an external lib (if possible)

Read this review and then we go from there.

AI Pull Request Review: #1176 — feat: implemented the perf protocol

Review version: 1
PR: libp2p/py-libp2p#1176
Branch: perfexamples → main
Issue: #1169 (Implement Perf Protocol for py-libp2p)


1. Summary of Changes

This PR implements the libp2p perf protocol (/perf/1.0.0) as specified in the libp2p specs. It addresses Issue #1169, which requests standardized performance benchmarking between libp2p implementations (connection establishment, upload/download throughput).

What changed:

  • New module libp2p/perf/:
    • constants.py — Protocol ID, write block size, stream limits.
    • types.py — TypedDicts (PerfOutput, PerfInit, PerfOptions, PerfComponents).
    • perf_service.pyPerfService implementing IPerf (server handler + client measure_performance).
    • __init__.py — Public API exports.
  • New interface: IPerf in libp2p/abc.py with start(), stop(), is_started(), and measure_performance(...).
  • New example: examples/perf/perf_example.py — Server and client with argparse, configurable upload/download sizes.
  • New interop: interop/perf/ — Dockerfile, perf_test.py, and pyproject.toml for perf interop testing.
  • Docs: docs/libp2p.perf.rst, docs/examples.perf.rst, and updates to docs/libp2p.rst and docs/examples.rst.

Protocol flow (aligned with spec):

  1. Client opens stream with /perf/1.0.0, sends 8-byte big-endian u64 (bytes to receive back).
  2. Client uploads data, then half-closes write (if close_write available).
  3. Server reads until EOF, parses u64 from first 8 bytes, sends requested bytes, closes stream.
  4. Client measures time and yields intermediary/final PerfOutput.

Architecture: Fits alongside other protocol modules (identify, ping, etc.). No breaking changes or deprecations. The PR body references Issue #1169 but does not use "Fixes #1169" or "Closes #1169".


2. Branch Sync Status and Merge Conflicts

Branch Sync Status

  • Status: ℹ️ Ahead of origin/main
  • Details: branch_sync_status.txt shows 0 4 (0 behind, 4 ahead). The PR branch has 4 commits not in main.

Merge Conflict Analysis

  • Conflicts detected:No conflicts
  • Details: merge_conflict_check.log shows "Already up to date" and "=== NO MERGE CONFLICTS DETECTED ===". The PR branch can be merged cleanly into origin/main.

3. Strengths

  • Spec alignment: Protocol steps match the perf spec (client u64, upload, half-close, server read-until-EOF, send N bytes). Protocol ID /perf/1.0.0 and big-endian u64 are correct.
  • Structure: Clear split between constants, types, interface (IPerf), and service; IPerf allows alternative implementations.
  • API design: measure_performance as an async generator yielding PerfOutput (intermediary + final) is flexible and observable.
  • Example and interop: perf_example.py shows server/client usage; interop/perf/ provides Docker-based perf test app aligned with test-plans.
  • Docstrings: NumPy-style in PerfService and measure_performance; spec link in module docstrings.
  • Resource handling: Stream close/reset in try/except/finally and after errors is consistent.
  • Documentation: libp2p.perf is included in docs/libp2p.rst toctree; API and example docs are present.

4. Issues Found

Critical

  • File: newsfragments/
  • Line(s): N/A
  • Issue: No newsfragment for issue Implement Perf Protocol for py-libp2p #1169. Project policy requires a newsfragment per issue (e.g. 1169.feature.rst).
  • Suggestion: Add newsfragments/1169.feature.rst with a short ReST, user-facing description (e.g. "Implemented the libp2p perf protocol (/perf/1.0.0) for measuring transfer performance between nodes.") and ensure the file ends with a newline.

Major

  • File: libp2p/perf/perf_service.py

  • Line(s): 51–62, 156

  • Issue: TypedDict/None handling: (1) init = {} assigns a plain dict to PerfInit | None; (2) after if init is None: init = {}, the type checker still considers init possibly None on the following .get() calls; (3) same pattern for options in measure_performance. Pyrefly reports 7 errors (bad-assignment, missing-attribute).

  • Suggestion: Use a local variable with an explicit type, e.g. init_opts: PerfInit = init if init is not None else {}, then use init_opts for all .get() calls. Same for options: opts: PerfOptions = options if options is not None else {}.

  • File: libp2p/perf/perf_service.py

  • Line(s): 95–108

  • Issue: Server only sets bytes_to_send_back when a single read returns ≥ 8 bytes. If the client sends the 8-byte header in a small write or TCP delivers the header in two segments, the first read can be < 8 bytes and the header is never parsed.

  • Suggestion: Accumulate data until 8 bytes are available, then parse the u64 (e.g. buffer fragments, then bytes_to_send_back = struct.unpack(">Q", buffer[:8])[0] and continue reading until EOF before sending response).

  • File: (none — missing)

  • Issue: No dedicated unit tests for libp2p.perf (no tests/core/perf/ or similar). Coverage relies on the full test suite and the example/interop.

  • Suggestion: Add tests (e.g. in tests/core/perf/) for: server parsing of the 8-byte header and response length; client upload/download with small sizes; error paths (stream reset, incomplete read).

Minor (Lint — must fix for CI)

  • File: examples/perf/perf_example.py

  • Line(s): 72, 77

  • Issue: Ruff E501 — lines over 88 characters (104 and 108 chars).

  • Suggestion: Break the f-strings (e.g. split onto two lines or assign to a variable then print(...)).

  • File: libp2p/perf/perf_service.py

  • Line(s): 217

  • Issue: Ruff E501 — comment line too long (91 > 88).

  • Suggestion: Shorten or wrap (e.g. "close_write() is on NetStream, not on INetStream").

  • File: interop/perf/perf_test.py

  • Line(s): 117

  • Issue: Ruff F841 — local variable n is assigned but never used.

  • Suggestion: Remove the assignment or use n (e.g. in percentile logic); if unused, delete the line.

  • File: interop/perf/perf_test.py

  • Line(s): 201, 206, 540, 561, 583, 597, 643, 659

  • Issue: Ruff E501 — multiple lines over 88 characters.

  • Suggestion: Break long lines (f-strings, comments) to satisfy E501.

  • File: interop/perf/perf_test.py

  • Line(s): 609

  • Issue: Ruff E741 — ambiguous variable name l.

  • Suggestion: Rename to e.g. latency_stats or lat to avoid single-letter l.


5. Security Review

  • Spec guidance: The perf spec states the protocol "SHOULD NOT be enabled by default" and advises against enabling it on publicly reachable nodes. This PR does not enable perf by default; users opt in by instantiating PerfService and calling start(). No change needed for default-off.
  • Input validation: The 8-byte u64 from the client is used as "bytes to send back." A malicious client could request a very large value (e.g. 2^64−1) and cause high memory/CPU/network usage.
    • Risk: Medium if perf is exposed on public interfaces without a cap.
    • Impact: High resource consumption.
    • Mitigation: Add a configurable max response size and reject/cap the client-requested value; document that perf should not be exposed on public nodes.
  • Sensitive data: No keys or secrets in logs; peer IDs and byte counts are acceptable for debugging.

6. Documentation and Examples

  • Docstrings: Module and class/method docstrings are present and reference the spec; measure_performance documents parameters and yielded PerfOutput.
  • Example: examples/perf/perf_example.py documents server/client usage in the module docstring and via --help.
  • API docs: libp2p.perf is in the toctree in docs/libp2p.rst; generated API docs and example docs build successfully.
  • Gaps: None for this PR; optional high-level perf tutorial can be a follow-up.

7. Newsfragment Requirement


8. Tests and Validation

Linting (make lint)

  • Result: Failed (exit code 1).
  • Ruff: 13 errors:
    • examples/perf/perf_example.py:72 — E501 line too long (104 > 88).
    • examples/perf/perf_example.py:77 — E501 line too long (108 > 88).
    • interop/perf/perf_test.py:117 — F841 unused variable n.
    • interop/perf/perf_test.py:201, 206 — E501 line too long (89 > 88).
    • interop/perf/perf_test.py:540, 561, 583, 597, 643, 659 — E501 line too long.
    • interop/perf/perf_test.py:609 — E741 ambiguous variable name l.
    • libp2p/perf/perf_service.py:217 — E501 line too long (91 > 88).
  • Mypy: 2 errors in files not in this PR:
    • libp2p/security/tls/autotls/broker.py:13 — Library stubs not installed for "requests".
    • libp2p/security/tls/autotls/acme.py:16 — Library stubs not installed for "requests".
  • Pyrefly: 7 errors in libp2p/perf/perf_service.py:
    • 51:20–22 — dict not assignable to PerfInit | None.
    • 55–61 — NoneType has no attribute get (after init = {}).
    • 156:23–25 — dict not assignable to PerfOptions | None; subsequent options.get on possibly None.
  • Other hooks (check yaml/toml, fix end of files, trim trailing whitespace, pyupgrade, ruff format, mdformat) passed.

Type checking (make typecheck)

  • Result: Failed (exit code 1).
  • Mypy: Same 2 errors in autotls (broker.py, acme.py) — pre-existing, not introduced by this PR.
  • Pyrefly: Same 7 errors in perf_service.py (TypedDict/None handling) — introduced by this PR and must be fixed.

Test suite (make test)

  • Result: Passed (exit code 0).
  • Summary: 1908 passed, 15 skipped, 25 warnings in ~91.71 s.
  • Warnings: PytestUnknownMarkWarning (integration), RuntimeWarning (coroutine never awaited in test_multistream).
  • Perf: No tests in the suite specifically target libp2p.perf; the new code is exercised indirectly or via the example/interop.

Documentation build (make linux-docs)

  • Result: Passed (exit code 0).
  • Details: Sphinx build succeeded; doctests passed (109 tests). libp2p.perf is included in the toctree in docs/libp2p.rst.

9. Recommendations for Improvement

  1. Newsfragment: Add newsfragments/1169.feature.rst and, if desired, "Fixes Implement Perf Protocol for py-libp2p #1169" in the PR description.
  2. TypedDict/None: In perf_service.py, use init_opts: PerfInit = init if init is not None else {} and opts: PerfOptions = options if options is not None else {}, then use these in all .get() calls so pyrefly passes.
  3. Line length and style: Fix all Ruff E501 (line length) and F841/E741 in examples/perf/perf_example.py, libp2p/perf/perf_service.py, and interop/perf/perf_test.py.
  4. Server header parsing: In _handle_message, accumulate the first 8 bytes across reads before parsing the u64, so the server is robust to small first reads.
  5. Tests: Add a small test module for perf (e.g. server parsing, client measure_performance with mocks or in-process streams) to guard regressions.
  6. Security: Consider a configurable maximum "bytes to send back" and document that perf should not be enabled on publicly reachable nodes.

10. Questions for the Author

  1. Would you consider adding a maximum response size (e.g. in constants or PerfInit) to limit abuse when perf is enabled?
  2. Are you planning to add unit/integration tests for PerfService in this PR or a follow-up?
  3. A reviewer (seetadev) suggested moving the perf implementation to the libp2p/test-plans repo and optionally keeping a minimal PoC here. Is the intent to keep this implementation in py-libp2p for now?
  4. The issue proposed result = await perf_client.measure_performance(...) returning a single result, whereas this PR uses an async generator. Was the generator design chosen for progress reporting, and do you want to document that as the intended public API?

11. Overall Assessment

  • Quality rating: Good — protocol and structure are solid; fixes needed for lint (including interop), typecheck (pyrefly), and policy (newsfragment).
  • Security impact: Low if perf is used as intended (not on public nodes); Medium if exposed without a cap on requested bytes — recommend adding a max response size.
  • Merge readiness: Needs fixes — block on newsfragment and on lint/typecheck (newsfragment, Ruff in PR files, pyrefly in perf_service.py). Mypy failures are pre-existing (autotls). Recommend addressing server header parsing and adding tests.
  • Confidence: High.

Review generated using the py-libp2p AI Pull Request Review Prompt. Logs: branch_sync_status.txt, merge_conflict_check.log, lint_output.log, typecheck_output.log, test_output.log, docs_output.log in downloads/AI-PR-REVIEWS/1176/.

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Feb 15, 2026

I didn't add the max_size_cap as the perf protocol is for the high stress testing , I didn't want to change the original perf spec .

what do you think?

Ok leave it like that

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Feb 15, 2026

@ItshMoh
Please reply to @pacrob questions. Or you remove the unused parameters or implement them properly with tests.
Also you requested @lla-dane review, is that a block if no review by him is done?

@ItshMoh
Copy link
Copy Markdown
Contributor Author

ItshMoh commented Feb 16, 2026

@ItshMoh Please reply to @pacrob questions. Or you remove the unused parameters or implement them properly with tests. Also you requested @lla-dane review, is that a block if no review by him is done?

@acul71 I have replied to the @pacrob questions.
I have requested the @lla-dane review , when he has asked me to move some files. Here is the link to the review. #1176 (comment)

it will not be a blocker if no review by him is done.

@ItshMoh
Copy link
Copy Markdown
Contributor Author

ItshMoh commented Feb 16, 2026

@acul71 Should i file this issue for the system-wide implementation of stop method.
You can get more information here. #1176 (comment)

@ItshMoh ItshMoh requested a review from pacrob February 16, 2026 20:12
@ItshMoh ItshMoh requested a review from pacrob February 17, 2026 05:12
@ItshMoh
Copy link
Copy Markdown
Contributor Author

ItshMoh commented Feb 17, 2026

@pacrob Fixed the changes as you have said.

Thanks, for the review.

@pacrob
Copy link
Copy Markdown
Member

pacrob commented Feb 18, 2026

Just noticed that some tests have gone in the top-level interop folder. Nothing there will get run by CI, only files within the top-level tests folder (so tests/interop for this code).

Looking at the pyproject.toml, maybe the interop/perf code should go over the the test-plans repo? I imagine it would go as an open PR for now, then merge one the rest of this PR is merged and released. I'm not following test-plans as closely - @seetadev @acul71 what do you think?

I've noted a couple other small things. Once those are fixed and we determine where the interop tests should go (I'm not even sure why we have a top-level interop folder?), this should be good to go.

@acul71
Copy link
Copy Markdown
Contributor

acul71 commented Feb 25, 2026

Looking at the pyproject.toml, maybe the interop/perf code should go over the the test-plans repo? I imagine it would go as an open PR for now, then merge one the rest of this PR is merged and released. I'm not following test-plans as closely - @seetadev @acul71 what do you think?

This is a convention of unified-test (new test-plans), that the "material"

  • Docker image
  • python unified-test script
  • pyproject.toml

needed for building a Docker image with the configured python test script can live in a implementation repo directory .
We choose /interop/[TEST-TYPE] for py-libp2p sometime ago.

The unified-test, allow also this code to live (only) in unified-test repo itself.
But I liked the idea to the code to live in py-libp2p, it was easier to in the begin to test it with local other implementation testing.
Also I thought it could be kind a nice "example" for how to setup Docker with py-libp2p in some test of protocols.

This is the current state of perf test (python x python)

Result summary

  • Total perf tests: 9
  • Passed: 3
  • Failed: 6

Passing cases

  • python-v0.x x python-v0.x (tcp, noise, mplex)
  • python-v0.x x python-v0.x (tcp, tls, mplex)
  • python-v0.x x python-v0.x (ws, noise, mplex)

Failing cases

  • python-v0.x x python-v0.x (tcp, noise, yamux) → timeout
  • python-v0.x x python-v0.x (tcp, tls, yamux) → timeout
  • python-v0.x x python-v0.x (ws, noise, yamux) → timeout
  • python-v0.x x python-v0.x (ws, tls, yamux) → timeout
  • python-v0.x x python-v0.x (ws, tls, mplex) → timeout
  • python-v0.x x python-v0.x (quic-v1) → runtime exception

Direct failure reason

  • The test runner enforces a hard 300s timeout (run-single-test.sh), and 5 scenarios hit that timeout (other then mplex are too slow)
  • QUIC fails with:
    • ValueError: Expected to receive 1073741824 bytes, but received 0

So this is a genuine test-execution failure (timeouts + one QUIC runtime error), not an artifact upload/workflow issue.

acul71 and others added 3 commits February 25, 2026 04:01
Use multiaddr items when rebuilding loopback-rewritten addresses in interop perf
so protocol/value pairs are handled correctly, and align perf write block size
default to 64KiB for js-libp2p parity.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@acul71 acul71 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM ! Thanks

@acul71 acul71 merged commit 08d25de into libp2p:main Feb 28, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants