Skip to content

Commit 628e852

Browse files
committed
Add isolated Phase 2 runtime evidence
1 parent 8c1f24c commit 628e852

9 files changed

Lines changed: 1351 additions & 21 deletions

File tree

.github/workflows/build.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,13 @@ jobs:
7575
throw 'Phase 2 pcap analyzer self-test did not report passed=true.'
7676
}
7777
78+
- name: Validate runtime harness syntax
79+
shell: bash
80+
run: |
81+
bash -n tools/perf/prepare-phase2-protocol-client.sh
82+
bash -n tools/perf/run-phase2-runtime-once.sh
83+
node --check tools/perf/phase2-protocol-client.js
84+
7885
- name: Run checks and build production jar
7986
run: ./gradlew clean check shadowJar --no-daemon --no-build-cache --rerun-tasks
8087

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
name: Phase 2 Packet Capture A-B
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
scenario:
7+
description: "Independent network scenario"
8+
required: true
9+
default: "static-spawn"
10+
type: choice
11+
options: ["static-spawn", "visibility-return"]
12+
runs:
13+
description: "Restart-isolated runs (4, 8, or formal 12)"
14+
required: true
15+
default: "12"
16+
type: choice
17+
options: ["4", "8", "12"]
18+
items:
19+
description: "Logical items"
20+
required: true
21+
default: "4096"
22+
type: string
23+
warmup_seconds:
24+
description: "Warmup after the real TCP client is ready"
25+
required: true
26+
default: "60"
27+
type: string
28+
settle_seconds:
29+
description: "Pre-window settling time"
30+
required: true
31+
default: "10"
32+
type: string
33+
measure_seconds:
34+
description: "Packet capture window"
35+
required: true
36+
default: "10"
37+
type: string
38+
snaplen:
39+
description: "tcpdump snapshot length; 128 for transport metrics, 0 for full compatibility capture"
40+
required: true
41+
default: "128"
42+
type: choice
43+
options: ["128", "0"]
44+
45+
permissions:
46+
contents: read
47+
48+
jobs:
49+
packet-capture-ab:
50+
name: Paper 26.1.2 packet ABBA (${{ inputs.scenario }})
51+
runs-on: ubuntu-latest
52+
timeout-minutes: 100
53+
54+
steps:
55+
- name: Check out immutable test source
56+
uses: actions/checkout@v4
57+
with:
58+
persist-credentials: false
59+
60+
- name: Set up Java 25
61+
uses: actions/setup-java@v4
62+
with:
63+
distribution: temurin
64+
java-version: "25"
65+
66+
- name: Set up Node 24 for the isolated protocol peer
67+
uses: actions/setup-node@v4
68+
with:
69+
node-version: "24"
70+
71+
- name: Set up Gradle
72+
uses: gradle/actions/setup-gradle@v4
73+
74+
- name: Install capture readers
75+
env:
76+
DEBIAN_FRONTEND: noninteractive
77+
run: |
78+
sudo apt-get update
79+
sudo apt-get install --yes --no-install-recommends tcpdump tshark
80+
tcpdump --version
81+
tshark --version
82+
83+
- name: Validate harness source
84+
run: |
85+
bash -n tools/perf/prepare-phase2-protocol-client.sh
86+
bash -n tools/perf/run-phase2-runtime-once.sh
87+
node --check tools/perf/phase2-protocol-client.js
88+
pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 -SelfTest
89+
pwsh -NoProfile -File tools/perf/analyze-phase2-pcap.ps1 -SelfTest
90+
91+
- name: Build and test the production plugin
92+
run: ./gradlew clean check shadowJar --no-daemon --no-build-cache --rerun-tasks
93+
94+
- name: Download stable Paper 26.1.2
95+
env:
96+
PAPER_USER_AGENT: InteractionVisualizer-Phase2/1.0 (https://github.com/EllanServer/InteractionVisualizer)
97+
run: |
98+
mkdir -p phase2-dependencies
99+
BUILDS=$(curl --fail --silent --show-error \
100+
-H "User-Agent: $PAPER_USER_AGENT" \
101+
https://fill.papermc.io/v3/projects/paper/versions/26.1.2/builds)
102+
PAPER_URL=$(echo "$BUILDS" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // empty')
103+
test -n "$PAPER_URL"
104+
curl --fail --location --show-error \
105+
-H "User-Agent: $PAPER_USER_AGENT" \
106+
--output phase2-dependencies/paper.jar "$PAPER_URL"
107+
108+
- name: Prepare immutable protocol client artifact
109+
run: bash tools/perf/prepare-phase2-protocol-client.sh phase2-dependencies/protocol-client
110+
111+
- name: Run restart-isolated packet ABBA campaign
112+
env:
113+
SCENARIO: ${{ inputs.scenario }}
114+
RUNS: ${{ inputs.runs }}
115+
ITEMS: ${{ inputs.items }}
116+
WARMUP_SECONDS: ${{ inputs.warmup_seconds }}
117+
SETTLE_SECONDS: ${{ inputs.settle_seconds }}
118+
MEASURE_SECONDS: ${{ inputs.measure_seconds }}
119+
SNAPLEN: ${{ inputs.snaplen }}
120+
run: |
121+
set -euo pipefail
122+
[[ "$SCENARIO" == static-spawn || "$SCENARIO" == visibility-return ]]
123+
[[ "$RUNS" =~ ^(4|8|12)$ ]]
124+
[[ "$ITEMS" =~ ^[0-9]+$ ]] && (( ITEMS >= 1 && ITEMS <= 8192 ))
125+
[[ "$WARMUP_SECONDS" =~ ^[0-9]+$ ]] && (( WARMUP_SECONDS >= 10 ))
126+
[[ "$SETTLE_SECONDS" =~ ^[0-9]+$ ]] && (( SETTLE_SECONDS >= 5 ))
127+
[[ "$MEASURE_SECONDS" =~ ^[0-9]+$ ]] && (( MEASURE_SECONDS >= 5 ))
128+
[[ "$SNAPLEN" == 0 || "$SNAPLEN" == 128 ]]
129+
if [[ "$SCENARIO" == visibility-return ]]; then
130+
(( MEASURE_SECONDS >= 10 ))
131+
fi
132+
133+
PLUGIN_JAR=$(find build/libs -maxdepth 1 -type f -name 'InteractionVisualizer-*.jar' ! -name '*-sources.jar' ! -name '*-benchmark.jar' -print -quit)
134+
test -n "$PLUGIN_JAR"
135+
mkdir -p phase2-results/packet
136+
MANIFEST=phase2-results/packet/abba-manifest.csv
137+
printf 'Scenario,Block,Position,Variant,RunId,StackSha256,ArtifactSha256,CaptureMethod,SourcePath\n' > "$MANIFEST"
138+
ARTIFACT_SHA=$(sha256sum "$PLUGIN_JAR" | awk '{print $1}')
139+
STACK_SHA=$(
140+
{
141+
sha256sum phase2-dependencies/paper.jar
142+
sha256sum phase2-dependencies/protocol-client/client-build-manifest.json
143+
java -version 2>&1
144+
printf '%s\n' 'java=-Xms2G -Xmx2G -XX:+UseG1GC -XX:+AlwaysPreTouch' "scenario=$SCENARIO" "snaplen=$SNAPLEN"
145+
} | sha256sum | awk '{print $1}'
146+
)
147+
148+
for run_number in $(seq 1 "$RUNS"); do
149+
block=$(( (run_number - 1) / 4 + 1 ))
150+
position=$(( (run_number - 1) % 4 + 1 ))
151+
if (( block % 2 == 1 )); then pattern=ABBA; else pattern=BAAB; fi
152+
variant=${pattern:$((position - 1)):1}
153+
run_id=$(printf '%s_%s_%02d' "${SCENARIO//-/_}" "$variant" "$run_number")
154+
155+
PHASE2_PLUGIN_JAR="$PLUGIN_JAR" \
156+
PHASE2_PAPER_JAR=phase2-dependencies/paper.jar \
157+
PHASE2_CLIENT_ROOT=phase2-dependencies/protocol-client \
158+
PHASE2_OUTPUT_ROOT=phase2-results/packet \
159+
PHASE2_RUN_ID="$run_id" \
160+
PHASE2_SCENARIO="$SCENARIO" \
161+
PHASE2_VARIANT="$variant" \
162+
PHASE2_ITEM_COUNT="$ITEMS" \
163+
PHASE2_WARMUP_SECONDS="$WARMUP_SECONDS" \
164+
PHASE2_SETTLE_SECONDS="$SETTLE_SECONDS" \
165+
PHASE2_MEASURE_SECONDS="$MEASURE_SECONDS" \
166+
PHASE2_CAPTURE_ENABLED=1 \
167+
PHASE2_CAPTURE_SNAPLEN="$SNAPLEN" \
168+
bash tools/perf/run-phase2-runtime-once.sh
169+
170+
printf '%s,%d,%d,%s,%s,%s,%s,tcpdump-lo-s%s,%s/%s.pcap-analysis.json\n' \
171+
"$SCENARIO" "$block" "$position" "$variant" "$run_id" "$STACK_SHA" "$ARTIFACT_SHA" \
172+
"$SNAPLEN" "$run_id" "$run_id" >> "$MANIFEST"
173+
done
174+
175+
minimum_seconds=$(( MEASURE_SECONDS - 1 ))
176+
incomplete=()
177+
if [[ "$RUNS" != 12 ]]; then incomplete=(-AllowIncomplete); fi
178+
# Loopback capture preserves TCP payload volume/timing for paired
179+
# regression checks, but its synthetic link headers and offload
180+
# behavior are not evidence of physical on-wire frame bytes.
181+
for metric in downstream.tcpPayloadBytes downstream.peak50ms.tcpPayloadBytes downstream.peak1s.tcpPayloadBytes; do
182+
safe_metric=${metric//./_}
183+
pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 "$MANIFEST" \
184+
-Scenario "$SCENARIO" -Metric "$metric" -Direction LowerIsBetter \
185+
-MinimumSeconds "$minimum_seconds" "${incomplete[@]}" \
186+
-OutputJson "phase2-results/packet/$safe_metric.analysis.json" -Overwrite
187+
done
188+
189+
- name: Publish packet evidence
190+
if: always()
191+
uses: actions/upload-artifact@v4
192+
with:
193+
name: phase2-packet-${{ inputs.scenario }}-${{ github.sha }}-${{ github.run_id }}
194+
path: |
195+
phase2-results/packet
196+
phase2-dependencies/protocol-client/client-build-manifest.json
197+
phase2-dependencies/protocol-client/client-files.sha256
198+
phase2-dependencies/protocol-client/node-minecraft-protocol/package-lock.json
199+
phase2-dependencies/protocol-client/node-minecraft-protocol/dependency-tree.json
200+
if-no-files-found: warn
201+
retention-days: 30
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: Phase 2 Clean Runtime A-B
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
runs:
7+
description: "Restart-isolated runs (4, 8, or formal 12)"
8+
required: true
9+
default: "12"
10+
type: choice
11+
options: ["4", "8", "12"]
12+
items:
13+
description: "Static logical items"
14+
required: true
15+
default: "4096"
16+
type: string
17+
warmup_seconds:
18+
description: "Warmup after the real TCP client is ready"
19+
required: true
20+
default: "120"
21+
type: string
22+
settle_seconds:
23+
description: "Scene settling time before sampling"
24+
required: true
25+
default: "20"
26+
type: string
27+
measure_seconds:
28+
description: "Clean MSPT/TPS sampling window"
29+
required: true
30+
default: "180"
31+
type: string
32+
33+
permissions:
34+
contents: read
35+
36+
jobs:
37+
clean-runtime-ab:
38+
name: Paper 26.1.2 clean TPS-MSPT ABBA
39+
runs-on: ubuntu-latest
40+
timeout-minutes: 130
41+
42+
steps:
43+
- name: Check out immutable test source
44+
uses: actions/checkout@v4
45+
with:
46+
persist-credentials: false
47+
48+
- name: Set up Java 25
49+
uses: actions/setup-java@v4
50+
with:
51+
distribution: temurin
52+
java-version: "25"
53+
54+
- name: Set up Node 24 for the isolated protocol peer
55+
uses: actions/setup-node@v4
56+
with:
57+
node-version: "24"
58+
59+
- name: Set up Gradle
60+
uses: gradle/actions/setup-gradle@v4
61+
62+
- name: Validate harness source
63+
run: |
64+
bash -n tools/perf/prepare-phase2-protocol-client.sh
65+
bash -n tools/perf/run-phase2-runtime-once.sh
66+
node --check tools/perf/phase2-protocol-client.js
67+
pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 -SelfTest
68+
pwsh -NoProfile -File tools/perf/analyze-phase2-pcap.ps1 -SelfTest
69+
70+
- name: Build and test the production plugin
71+
run: ./gradlew clean check shadowJar --no-daemon --no-build-cache --rerun-tasks
72+
73+
- name: Download stable Paper 26.1.2
74+
env:
75+
PAPER_USER_AGENT: InteractionVisualizer-Phase2/1.0 (https://github.com/EllanServer/InteractionVisualizer)
76+
run: |
77+
mkdir -p phase2-dependencies
78+
BUILDS=$(curl --fail --silent --show-error \
79+
-H "User-Agent: $PAPER_USER_AGENT" \
80+
https://fill.papermc.io/v3/projects/paper/versions/26.1.2/builds)
81+
PAPER_URL=$(echo "$BUILDS" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // empty')
82+
test -n "$PAPER_URL"
83+
curl --fail --location --show-error \
84+
-H "User-Agent: $PAPER_USER_AGENT" \
85+
--output phase2-dependencies/paper.jar "$PAPER_URL"
86+
87+
- name: Prepare immutable protocol client artifact
88+
run: bash tools/perf/prepare-phase2-protocol-client.sh phase2-dependencies/protocol-client
89+
90+
- name: Run restart-isolated clean ABBA campaign
91+
env:
92+
RUNS: ${{ inputs.runs }}
93+
ITEMS: ${{ inputs.items }}
94+
WARMUP_SECONDS: ${{ inputs.warmup_seconds }}
95+
SETTLE_SECONDS: ${{ inputs.settle_seconds }}
96+
MEASURE_SECONDS: ${{ inputs.measure_seconds }}
97+
run: |
98+
set -euo pipefail
99+
[[ "$RUNS" =~ ^(4|8|12)$ ]]
100+
[[ "$ITEMS" =~ ^[0-9]+$ ]] && (( ITEMS >= 1 && ITEMS <= 8192 ))
101+
[[ "$WARMUP_SECONDS" =~ ^[0-9]+$ ]] && (( WARMUP_SECONDS >= 10 ))
102+
[[ "$SETTLE_SECONDS" =~ ^[0-9]+$ ]] && (( SETTLE_SECONDS >= 5 ))
103+
[[ "$MEASURE_SECONDS" =~ ^[0-9]+$ ]] && (( MEASURE_SECONDS >= 10 ))
104+
105+
PLUGIN_JAR=$(find build/libs -maxdepth 1 -type f -name 'InteractionVisualizer-*.jar' ! -name '*-sources.jar' ! -name '*-benchmark.jar' -print -quit)
106+
test -n "$PLUGIN_JAR"
107+
mkdir -p phase2-results/clean
108+
MANIFEST=phase2-results/clean/abba-manifest.csv
109+
printf 'Scenario,Block,Position,Variant,RunId,StackSha256,ArtifactSha256,CaptureMethod,SourcePath\n' > "$MANIFEST"
110+
ARTIFACT_SHA=$(sha256sum "$PLUGIN_JAR" | awk '{print $1}')
111+
STACK_SHA=$(
112+
{
113+
sha256sum phase2-dependencies/paper.jar
114+
sha256sum phase2-dependencies/protocol-client/client-build-manifest.json
115+
java -version 2>&1
116+
printf '%s\n' 'java=-Xms2G -Xmx2G -XX:+UseG1GC -XX:+AlwaysPreTouch' 'scenario=static-steady'
117+
} | sha256sum | awk '{print $1}'
118+
)
119+
120+
for run_number in $(seq 1 "$RUNS"); do
121+
block=$(( (run_number - 1) / 4 + 1 ))
122+
position=$(( (run_number - 1) % 4 + 1 ))
123+
if (( block % 2 == 1 )); then pattern=ABBA; else pattern=BAAB; fi
124+
variant=${pattern:$((position - 1)):1}
125+
run_id=$(printf 'static_steady_%s_%02d' "$variant" "$run_number")
126+
127+
PHASE2_PLUGIN_JAR="$PLUGIN_JAR" \
128+
PHASE2_PAPER_JAR=phase2-dependencies/paper.jar \
129+
PHASE2_CLIENT_ROOT=phase2-dependencies/protocol-client \
130+
PHASE2_OUTPUT_ROOT=phase2-results/clean \
131+
PHASE2_RUN_ID="$run_id" \
132+
PHASE2_SCENARIO=static-steady \
133+
PHASE2_VARIANT="$variant" \
134+
PHASE2_ITEM_COUNT="$ITEMS" \
135+
PHASE2_WARMUP_SECONDS="$WARMUP_SECONDS" \
136+
PHASE2_SETTLE_SECONDS="$SETTLE_SECONDS" \
137+
PHASE2_MEASURE_SECONDS="$MEASURE_SECONDS" \
138+
PHASE2_CAPTURE_ENABLED=0 \
139+
bash tools/perf/run-phase2-runtime-once.sh
140+
141+
printf 'static-steady,%d,%d,%s,%s,%s,%s,none,%s/iv-perf.json\n' \
142+
"$block" "$position" "$variant" "$run_id" "$STACK_SHA" "$ARTIFACT_SHA" "$run_id" \
143+
>> "$MANIFEST"
144+
done
145+
146+
minimum_seconds=$(( MEASURE_SECONDS - 2 ))
147+
incomplete=()
148+
if [[ "$RUNS" != 12 ]]; then incomplete=(-AllowIncomplete); fi
149+
for metric in msptP95 msptP99 msptMean; do
150+
pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 "$MANIFEST" \
151+
-Scenario static-steady -Metric "$metric" -Direction LowerIsBetter \
152+
-MinimumSeconds "$minimum_seconds" "${incomplete[@]}" \
153+
-OutputJson "phase2-results/clean/$metric.analysis.json" -Overwrite
154+
done
155+
# At a healthy 20 TPS cap this is only an overload/non-regression
156+
# diagnostic. MSPT remains the optimization effect-size metric.
157+
pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 "$MANIFEST" \
158+
-Scenario static-steady -Metric observedTps -Direction HigherIsBetter \
159+
-MinimumSeconds "$minimum_seconds" "${incomplete[@]}" \
160+
-OutputJson phase2-results/clean/observedTps.analysis.json -Overwrite
161+
162+
- name: Publish clean runtime evidence
163+
if: always()
164+
uses: actions/upload-artifact@v4
165+
with:
166+
name: phase2-clean-runtime-${{ github.sha }}-${{ github.run_id }}
167+
path: |
168+
phase2-results/clean
169+
phase2-dependencies/protocol-client/client-build-manifest.json
170+
phase2-dependencies/protocol-client/client-files.sha256
171+
phase2-dependencies/protocol-client/node-minecraft-protocol/package-lock.json
172+
phase2-dependencies/protocol-client/node-minecraft-protocol/dependency-tree.json
173+
if-no-files-found: warn
174+
retention-days: 30

0 commit comments

Comments
 (0)