Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/interop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,63 @@ jobs:
if: always()
run: docker compose -f docker-compose.cross.yml down -v

perf-interop:
name: perf-interop (hs <-> nim, tcp+noise+yamux)
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# nim-libp2p pinned to the commit unified-testing's perf/images.yaml
# registers for nim-v1.15 (single source of truth: NIM_LIBP2P_COMMIT
# in interop/Makefile); docker-compose.perf-cross.yml builds its
# interop/perf/Dockerfile. The unified-testing go perf image is a
# placeholder that opens no perf streams, so nim is the partner that
# actually exercises the /perf/1.0.0 wire format.
- name: Clone nim-libp2p (pinned)
run: make -C interop nim-libp2p

- name: Build libp2p-hs perf image
uses: docker/build-push-action@v6
with:
context: .
file: interop/perf/Dockerfile
load: true
tags: libp2p-hs-perf:latest
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Self-test — hs listener, hs dialer
run: >
docker compose -f docker-compose.perf.yml up
--exit-code-from hs-perf-dialer
redis hs-perf-listener hs-perf-dialer

- name: Reset after self-test
run: docker compose -f docker-compose.perf.yml down -v

- name: Direction 1 — nim listener, hs dialer
run: >
docker compose -f docker-compose.perf-cross.yml up
--exit-code-from hs-perf-dialer
redis nim-perf-listener hs-perf-dialer

- name: Reset between directions
run: docker compose -f docker-compose.perf-cross.yml down

- name: Direction 2 — hs listener, nim dialer
run: >
docker compose -f docker-compose.perf-cross.yml up
--exit-code-from nim-perf-dialer
redis hs-perf-listener nim-perf-dialer

- name: Tear down
if: always()
run: docker compose -f docker-compose.perf-cross.yml down -v

kad-dht-interop:
name: kad-dht-interop (hs bootstrap → provider → querier)
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ cabal.project.local~
.playwright-mcp/
cabal.project.freeze
go-libp2p/
nim-libp2p/
interop/rust-peer/target/
110 changes: 110 additions & 0 deletions docker-compose.perf-cross.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Cross-implementation perf test: libp2p-hs <-> nim-libp2p.
#
# nim-libp2p is the reference perf test app for the unified-testing
# submission (perf/images.yaml: nim-v1.15) and implements the canonical
# /perf/1.0.0 wire protocol from libp2p/specs. The unified-testing go
# image (images/go/v0.45) is a placeholder that opens no perf streams,
# so nim is the cross-implementation partner that actually exercises
# the wire format.
#
# Prerequisites: `make -C interop nim-libp2p` clones nim-libp2p at the
# commit perf/images.yaml pins for nim-v1.15 (NIM_LIBP2P_COMMIT in
# interop/Makefile is the single source of truth).
#
# Both sides speak the modern perf contract (uppercase env vars, Redis
# SET/GET on `{TEST_KEY}_listener_multiaddr`), so no key-translation
# shims are needed. Directions use distinct TEST_KEYs so a lingering
# SET from one direction can never satisfy the other's poll.
#
# Test 1: nim listener, hs dialer
# docker compose -f docker-compose.perf-cross.yml up --build --exit-code-from hs-perf-dialer redis nim-perf-listener hs-perf-dialer
#
# Test 2: hs listener, nim dialer
# docker compose -f docker-compose.perf-cross.yml up --build --exit-code-from nim-perf-dialer redis hs-perf-listener nim-perf-dialer
x-hs-dial-key: &hs-dial-key "cafe0131"
x-nim-dial-key: &nim-dial-key "cafe0132"

x-perf-sizes: &perf-sizes
UPLOAD_BYTES: "10485760"
DOWNLOAD_BYTES: "10485760"
UPLOAD_ITERATIONS: "3"
DOWNLOAD_ITERATIONS: "3"
LATENCY_ITERATIONS: "10"

x-perf-env: &perf-env
REDIS_ADDR: "redis:6379"
TRANSPORT: tcp
SECURE_CHANNEL: noise
MUXER: yamux
LISTENER_IP: "0.0.0.0"

services:
redis:
image: redis:7-alpine
# Disable persistence so a stale listener address never survives across runs.
command: ["redis-server", "--save", "", "--appendonly", "no"]
ports:
- "6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 1s
timeout: 3s
retries: 30

# --- Test 1: nim listener, hs dialer ---
nim-perf-listener:
build:
context: ./nim-libp2p
dockerfile: interop/perf/Dockerfile
image: nim-libp2p-perf:latest
depends_on:
redis:
condition: service_healthy
environment:
<<: *perf-env
TEST_KEY: *hs-dial-key
IS_DIALER: "false"

hs-perf-dialer:
build:
context: .
dockerfile: interop/perf/Dockerfile
image: libp2p-hs-perf:latest
depends_on:
redis:
condition: service_healthy
nim-perf-listener:
condition: service_started
environment:
<<: [*perf-env, *perf-sizes]
TEST_KEY: *hs-dial-key
IS_DIALER: "true"

# --- Test 2: hs listener, nim dialer ---
hs-perf-listener:
build:
context: .
dockerfile: interop/perf/Dockerfile
image: libp2p-hs-perf:latest
depends_on:
redis:
condition: service_healthy
environment:
<<: *perf-env
TEST_KEY: *nim-dial-key
IS_DIALER: "false"

nim-perf-dialer:
build:
context: ./nim-libp2p
dockerfile: interop/perf/Dockerfile
image: nim-libp2p-perf:latest
depends_on:
redis:
condition: service_healthy
hs-perf-listener:
condition: service_started
environment:
<<: [*perf-env, *perf-sizes]
TEST_KEY: *nim-dial-key
IS_DIALER: "true"
64 changes: 64 additions & 0 deletions docker-compose.perf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Perf self-test: libp2p-hs listener + libp2p-hs dialer.
#
# Speaks the unified-testing perf contract: the listener SETs
# `{TEST_KEY}_listener_multiaddr` in Redis and the dialer polls GET
# (the perf contract uses a plain string, unlike the transport
# contract's RPUSH/BLPOP list).
#
# Sizes are kept small so the self-test finishes quickly; the upstream
# perf harness supplies its own (much larger) values.
#
# docker compose -f docker-compose.perf.yml up --build --exit-code-from hs-perf-dialer redis hs-perf-listener hs-perf-dialer
x-test-key: &test-key "cafe0130"

x-perf-env: &perf-env
REDIS_ADDR: "redis:6379"
TEST_KEY: *test-key
TRANSPORT: tcp
SECURE_CHANNEL: noise
MUXER: yamux
LISTENER_IP: "0.0.0.0"

services:
redis:
image: redis:7-alpine
# Disable persistence so a stale listener address never survives across runs.
command: ["redis-server", "--save", "", "--appendonly", "no"]
ports:
- "6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 1s
timeout: 3s
retries: 30

hs-perf-listener:
build:
context: .
dockerfile: interop/perf/Dockerfile
image: libp2p-hs-perf:latest
depends_on:
redis:
condition: service_healthy
environment:
<<: *perf-env
IS_DIALER: "false"

hs-perf-dialer:
build:
context: .
dockerfile: interop/perf/Dockerfile
image: libp2p-hs-perf:latest
depends_on:
redis:
condition: service_healthy
hs-perf-listener:
condition: service_started
environment:
<<: *perf-env
IS_DIALER: "true"
UPLOAD_BYTES: "10485760"
DOWNLOAD_BYTES: "10485760"
UPLOAD_ITERATIONS: "3"
DOWNLOAD_ITERATIONS: "3"
LATENCY_ITERATIONS: "10"
26 changes: 25 additions & 1 deletion interop/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
IMAGE_NAME := libp2p-hs-interop

.PHONY: build image self-test cross-test-go-listener cross-test-hs-listener \
cross-gossipsub-rust-listener cross-gossipsub-hs-listener cross-gossipsub kad-dht
cross-gossipsub-rust-listener cross-gossipsub-hs-listener cross-gossipsub kad-dht \
perf-self-test perf-cross-nim-listener perf-cross-hs-listener perf-cross nim-libp2p

# nim-libp2p commit pinned by unified-testing perf/images.yaml (nim-v1.15).
NIM_LIBP2P_COMMIT := 1bdf2f67971529e8bee01252230bdb00ab785ef7

image:
docker build -t $(IMAGE_NAME) ..
Expand Down Expand Up @@ -39,3 +43,23 @@ cross-gossipsub-hs-listener:

# GossipSub cross-test: both directions
cross-gossipsub: cross-gossipsub-rust-listener cross-gossipsub-hs-listener

# Perf self-test: hs listener + hs dialer
perf-self-test:
cd .. && docker compose -f docker-compose.perf.yml up --build --exit-code-from hs-perf-dialer redis hs-perf-listener hs-perf-dialer

# Clone nim-libp2p at the commit unified-testing pins for perf (nim-v1.15)
nim-libp2p:
cd .. && test -d nim-libp2p || git clone https://github.com/vacp2p/nim-libp2p.git nim-libp2p
cd ../nim-libp2p && git checkout $(NIM_LIBP2P_COMMIT)

# Perf cross-test: nim listener, hs dialer
perf-cross-nim-listener: nim-libp2p
cd .. && docker compose -f docker-compose.perf-cross.yml up --build --exit-code-from hs-perf-dialer redis nim-perf-listener hs-perf-dialer

# Perf cross-test: hs listener, nim dialer
perf-cross-hs-listener: nim-libp2p
cd .. && docker compose -f docker-compose.perf-cross.yml up --build --exit-code-from nim-perf-dialer redis hs-perf-listener nim-perf-dialer

# Perf cross-test: both directions
perf-cross: perf-cross-nim-listener perf-cross-hs-listener
47 changes: 47 additions & 0 deletions interop/perf/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Multi-stage build for the libp2p-hs perf test daemon.
# Used by the libp2p/unified-testing perf framework; build context is
# the repository root (perf/images.yaml: dockerfile: interop/perf/Dockerfile).

# Stage 1: Build with GHC 9.10
FROM haskell:9.10-slim-bookworm AS builder

WORKDIR /app

# Copy only the package description first, so the dependency-build layer below
# is cached and reused as long as libp2p-hs.cabal / cabal.project are unchanged.
COPY libp2p-hs.cabal cabal.project ./

# Fix ppad-sha256 ARM SHA2 intrinsic compilation on Docker (GCC 12).
# Only apply ARM-specific flags on aarch64; skip on x86_64.
# Must run before any build so the override is in effect.
RUN if [ "$(uname -m)" = "aarch64" ]; then \
echo 'package ppad-sha256' >> cabal.project && \
echo ' ghc-options: -optc-march=armv8-a+crypto' >> cabal.project; \
fi

# Pre-build the library's dependencies. This is the expensive layer (crypton,
# cacophony, tls, lens, ...) and is cached unless the cabal file changes, so
# source-only edits skip it. We target the library (not the executable) because
# the executable depends on the local libp2p-hs library, which cannot be built
# before its source is copied below.
RUN cabal update && cabal build --only-dependencies lib:libp2p-hs

# Copy sources and build the project itself.
COPY src/ src/
COPY interop/ interop/
RUN cabal build libp2p-perf \
&& cp "$(cabal list-bin libp2p-perf)" /app/libp2p-perf \
&& strip /app/libp2p-perf

# Stage 2: Minimal runtime image
FROM debian:bookworm-slim

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libgmp10 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/libp2p-perf /usr/local/bin/libp2p-perf

ENTRYPOINT ["libp2p-perf"]
Loading
Loading