Optimize Paper 26 display hot paths #47
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Phase 2 Packet Capture A-B | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, labeled] | |
| workflow_dispatch: | |
| inputs: | |
| scenario: | |
| description: "Independent network scenario" | |
| required: true | |
| default: "static-spawn" | |
| type: choice | |
| options: ["static-spawn", "visibility-return", "visibility-itemdisplay-return", "visibility-textdisplay-return"] | |
| runs: | |
| description: "Restart-isolated runs (4, 8, or formal 12)" | |
| required: true | |
| default: "12" | |
| type: choice | |
| options: ["4", "8", "12"] | |
| items: | |
| description: "Logical items" | |
| required: true | |
| default: "4096" | |
| type: string | |
| warmup_seconds: | |
| description: "Warmup after the real TCP client is ready" | |
| required: true | |
| default: "60" | |
| type: string | |
| settle_seconds: | |
| description: "Pre-window settling time" | |
| required: true | |
| default: "10" | |
| type: string | |
| measure_seconds: | |
| description: "Packet capture window" | |
| required: true | |
| default: "10" | |
| type: string | |
| snaplen: | |
| description: "tcpdump snapshot length; 128 for transport metrics, 0 for full compatibility capture" | |
| required: true | |
| default: "128" | |
| type: choice | |
| options: ["128", "0"] | |
| permissions: | |
| contents: read | |
| jobs: | |
| packet-capture-ab: | |
| name: Paper 26.1.2 packet ABBA | |
| if: >- | |
| github.event_name == 'workflow_dispatch' || | |
| github.event.action != 'labeled' || | |
| github.event.label.name == 'phase2-packet-visibility-smoke' || | |
| github.event.label.name == 'phase2-packet-static-formal' || | |
| github.event.label.name == 'phase2-packet-visibility-formal' || | |
| github.event.label.name == 'phase2-packet-visibility-itemdisplay-smoke' || | |
| github.event.label.name == 'phase2-packet-visibility-itemdisplay-formal' || | |
| github.event.label.name == 'phase2-packet-visibility-textdisplay-smoke' || | |
| github.event.label.name == 'phase2-packet-visibility-textdisplay-formal' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 100 | |
| env: | |
| CAMPAIGN_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.scenario || contains(github.event.label.name, 'visibility-itemdisplay') && 'visibility-itemdisplay-return' || contains(github.event.label.name, 'visibility-textdisplay') && 'visibility-textdisplay-return' || contains(github.event.label.name, 'visibility') && 'visibility-return' || 'static-spawn' }} | |
| CAMPAIGN_RUNS: ${{ github.event_name == 'workflow_dispatch' && inputs.runs || contains(github.event.label.name, '-formal') && '12' || '4' }} | |
| CAMPAIGN_ITEMS: ${{ github.event_name == 'workflow_dispatch' && inputs.items || contains(github.event.label.name, '-formal') && '4096' || '1024' }} | |
| CAMPAIGN_WARMUP_SECONDS: ${{ github.event_name == 'workflow_dispatch' && inputs.warmup_seconds || contains(github.event.label.name, '-formal') && '60' || '10' }} | |
| CAMPAIGN_SETTLE_SECONDS: ${{ github.event_name == 'workflow_dispatch' && inputs.settle_seconds || contains(github.event.label.name, '-formal') && '10' || '5' }} | |
| CAMPAIGN_MEASURE_SECONDS: ${{ github.event_name == 'workflow_dispatch' && inputs.measure_seconds || contains(github.event.label.name, '-formal') && '10' || contains(github.event.label.name, 'visibility') && '10' || '5' }} | |
| CAMPAIGN_SNAPLEN: ${{ github.event_name == 'workflow_dispatch' && inputs.snaplen || '128' }} | |
| steps: | |
| - name: Check out immutable test source | |
| uses: actions/checkout@v4 | |
| with: | |
| persist-credentials: false | |
| - name: Set up Java 25 | |
| uses: actions/setup-java@v4 | |
| with: | |
| distribution: temurin | |
| java-version: "25" | |
| - name: Set up Node 24 for the isolated protocol peer | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "24" | |
| - name: Set up Gradle | |
| uses: gradle/actions/setup-gradle@v4 | |
| - name: Install capture readers | |
| env: | |
| DEBIAN_FRONTEND: noninteractive | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install --yes --no-install-recommends tcpdump tshark | |
| tcpdump --version | |
| tshark --version | |
| - name: Validate harness source | |
| run: | | |
| bash -n tools/perf/prepare-phase2-protocol-client.sh | |
| bash -n tools/perf/run-phase2-runtime-once.sh | |
| node --check tools/perf/phase2-protocol-client.js | |
| node --check tools/perf/analyze-phase2-protocol-trace.js | |
| node tools/perf/analyze-phase2-protocol-trace.js --self-test | |
| pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 -SelfTest | |
| pwsh -NoProfile -File tools/perf/analyze-phase2-pcap.ps1 -SelfTest | |
| - name: Build and test the production plugin | |
| run: ./gradlew clean check shadowJar --no-daemon --no-build-cache --rerun-tasks | |
| - name: Download stable Paper 26.1.2 | |
| env: | |
| PAPER_USER_AGENT: InteractionVisualizer-Phase2/1.0 (https://github.com/EllanServer/InteractionVisualizer) | |
| run: | | |
| mkdir -p phase2-dependencies | |
| BUILDS=$(curl --fail --silent --show-error \ | |
| -H "User-Agent: $PAPER_USER_AGENT" \ | |
| https://fill.papermc.io/v3/projects/paper/versions/26.1.2/builds) | |
| PAPER_URL=$(echo "$BUILDS" | jq -r 'first(.[] | select(.channel == "STABLE") | .downloads."server:default".url) // empty') | |
| test -n "$PAPER_URL" | |
| curl --fail --location --show-error \ | |
| -H "User-Agent: $PAPER_USER_AGENT" \ | |
| --output phase2-dependencies/paper.jar "$PAPER_URL" | |
| - name: Prepare immutable protocol client artifact | |
| run: bash tools/perf/prepare-phase2-protocol-client.sh phase2-dependencies/protocol-client | |
| - name: Run restart-isolated packet ABBA campaign | |
| id: packet_campaign | |
| env: | |
| SCENARIO: ${{ env.CAMPAIGN_SCENARIO }} | |
| RUNS: ${{ env.CAMPAIGN_RUNS }} | |
| ITEMS: ${{ env.CAMPAIGN_ITEMS }} | |
| WARMUP_SECONDS: ${{ env.CAMPAIGN_WARMUP_SECONDS }} | |
| SETTLE_SECONDS: ${{ env.CAMPAIGN_SETTLE_SECONDS }} | |
| MEASURE_SECONDS: ${{ env.CAMPAIGN_MEASURE_SECONDS }} | |
| SNAPLEN: ${{ env.CAMPAIGN_SNAPLEN }} | |
| run: | | |
| set -euo pipefail | |
| [[ "$SCENARIO" == static-spawn || "$SCENARIO" == visibility-return || \ | |
| "$SCENARIO" == visibility-itemdisplay-return || "$SCENARIO" == visibility-textdisplay-return ]] | |
| [[ "$RUNS" =~ ^(4|8|12)$ ]] | |
| [[ "$ITEMS" =~ ^[0-9]+$ ]] && (( ITEMS >= 1 && ITEMS <= 8192 )) | |
| [[ "$WARMUP_SECONDS" =~ ^[0-9]+$ ]] && (( WARMUP_SECONDS >= 10 )) | |
| [[ "$SETTLE_SECONDS" =~ ^[0-9]+$ ]] && (( SETTLE_SECONDS >= 5 )) | |
| [[ "$MEASURE_SECONDS" =~ ^[0-9]+$ ]] && (( MEASURE_SECONDS >= 5 )) | |
| [[ "$SNAPLEN" == 0 || "$SNAPLEN" == 128 ]] | |
| if [[ "$SCENARIO" == visibility-* ]]; then | |
| (( MEASURE_SECONDS >= 10 )) | |
| fi | |
| PLUGIN_JAR=$(find build/libs -maxdepth 1 -type f -name 'InteractionVisualizer-*.jar' ! -name '*-sources.jar' ! -name '*-benchmark.jar' -print -quit) | |
| test -n "$PLUGIN_JAR" | |
| mkdir -p phase2-results/packet | |
| MANIFEST=phase2-results/packet/abba-manifest.csv | |
| PROTOCOL_MANIFEST=phase2-results/packet/protocol-trace-abba-manifest.csv | |
| printf 'Scenario,Block,Position,Variant,RunId,StackSha256,ArtifactSha256,CaptureMethod,SourcePath\n' > "$MANIFEST" | |
| printf 'Scenario,Block,Position,Variant,RunId,StackSha256,ArtifactSha256,CaptureMethod,SourcePath\n' > "$PROTOCOL_MANIFEST" | |
| ARTIFACT_SHA=$(sha256sum "$PLUGIN_JAR" | awk '{print $1}') | |
| STACK_SHA=$( | |
| { | |
| sha256sum phase2-dependencies/paper.jar | |
| sha256sum phase2-dependencies/protocol-client/client-build-manifest.json | |
| sha256sum tools/perf/phase2-protocol-client.js | |
| sha256sum tools/perf/analyze-phase2-protocol-trace.js | |
| sha256sum tools/perf/analyze-phase2-pcap.ps1 | |
| sha256sum tools/perf/analyze-phase2-abba.ps1 | |
| sha256sum tools/perf/run-phase2-runtime-once.sh | |
| java -version 2>&1 | |
| printf '%s\n' \ | |
| 'java=-Xms2G -Xmx2G -XX:+UseG1GC -XX:+AlwaysPreTouch -Dinteractionvisualizer.performance.allowBlockScene=true -Xlog:gc*=info,safepoint=info:file=jvm-gc-safepoint.log:time,uptime,level,tags:filecount=0 -Dfile.encoding=UTF-8' \ | |
| "scenario=$SCENARIO" "snaplen=$SNAPLEN" 'protocolTrace=semantic-lifecycle-v1' \ | |
| 'sparkProfile=none' | |
| } | sha256sum | awk '{print $1}' | |
| ) | |
| for run_number in $(seq 1 "$RUNS"); do | |
| block=$(( (run_number - 1) / 4 + 1 )) | |
| position=$(( (run_number - 1) % 4 + 1 )) | |
| if (( block % 2 == 1 )); then pattern=ABBA; else pattern=BAAB; fi | |
| variant=${pattern:$((position - 1)):1} | |
| run_id=$(printf '%s_%s_%02d' "${SCENARIO//-/_}" "$variant" "$run_number") | |
| PHASE2_PLUGIN_JAR="$PLUGIN_JAR" \ | |
| PHASE2_PAPER_JAR=phase2-dependencies/paper.jar \ | |
| PHASE2_CLIENT_ROOT=phase2-dependencies/protocol-client \ | |
| PHASE2_OUTPUT_ROOT=phase2-results/packet \ | |
| PHASE2_RUN_ID="$run_id" \ | |
| PHASE2_SCENARIO="$SCENARIO" \ | |
| PHASE2_VARIANT="$variant" \ | |
| PHASE2_ITEM_COUNT="$ITEMS" \ | |
| PHASE2_WARMUP_SECONDS="$WARMUP_SECONDS" \ | |
| PHASE2_SETTLE_SECONDS="$SETTLE_SECONDS" \ | |
| PHASE2_MEASURE_SECONDS="$MEASURE_SECONDS" \ | |
| PHASE2_CAPTURE_ENABLED=1 \ | |
| PHASE2_CAPTURE_SNAPLEN="$SNAPLEN" \ | |
| PHASE2_PROTOCOL_TRACE_ENABLED=1 \ | |
| PHASE2_SPARK_PROFILE_MODE=none \ | |
| bash tools/perf/run-phase2-runtime-once.sh | |
| printf '%s,%d,%d,%s,%s,%s,%s,tcpdump-lo-s%s,%s/%s.pcap-analysis.json\n' \ | |
| "$SCENARIO" "$block" "$position" "$variant" "$run_id" "$STACK_SHA" "$ARTIFACT_SHA" \ | |
| "$SNAPLEN" "$run_id" "$run_id" >> "$MANIFEST" | |
| printf '%s,%d,%d,%s,%s,%s,%s,protocol-client-memory,%s/%s.protocol-trace-analysis.json\n' \ | |
| "$SCENARIO" "$block" "$position" "$variant" "$run_id" "$STACK_SHA" "$ARTIFACT_SHA" \ | |
| "$run_id" "$run_id" >> "$PROTOCOL_MANIFEST" | |
| done | |
| minimum_seconds=$(( MEASURE_SECONDS - 1 )) | |
| incomplete=() | |
| if [[ "$RUNS" != 12 ]]; then incomplete=(-AllowIncomplete); fi | |
| # Loopback capture preserves TCP payload volume/timing for paired | |
| # regression checks, but its synthetic link headers and offload | |
| # behavior are not evidence of physical on-wire frame bytes. | |
| for metric in downstream.tcpPayloadBytes downstream.peak50ms.tcpPayloadBytes downstream.peak1s.tcpPayloadBytes; do | |
| safe_metric=${metric//./_} | |
| pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 "$MANIFEST" \ | |
| -Scenario "$SCENARIO" -Metric "$metric" -Direction LowerIsBetter \ | |
| -MinimumSeconds "$minimum_seconds" "${incomplete[@]}" \ | |
| -OutputJson "phase2-results/packet/$safe_metric.analysis.json" -Overwrite | |
| done | |
| # These semantic lifecycle counts are generated from the same | |
| # explicit half-open window as the pcap evidence. They supplement, | |
| # and never replace, the transport-level byte and retransmit gates. | |
| for metric in traceCoverage.windowEventCount identity.spawn.observations spawnPeaks.epochAligned50ms.spawnedEntityIdCount spawnPeaks.epochAligned1s.spawnedEntityIdCount; do | |
| safe_metric=${metric//./_} | |
| pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 "$PROTOCOL_MANIFEST" \ | |
| -Scenario "$SCENARIO" -Metric "$metric" -Direction LowerIsBetter \ | |
| "${incomplete[@]}" \ | |
| -OutputJson "phase2-results/packet/protocol_$safe_metric.analysis.json" -Overwrite | |
| done | |
| - name: Summarize semantic protocol evidence | |
| if: always() | |
| env: | |
| SCENARIO: ${{ env.CAMPAIGN_SCENARIO }} | |
| EXPECTED_RUNS: ${{ env.CAMPAIGN_RUNS }} | |
| CAMPAIGN_OUTCOME: ${{ steps.packet_campaign.outcome }} | |
| run: | | |
| python3 - <<'PY' | |
| import json | |
| import os | |
| import re | |
| from pathlib import Path | |
| results_root = Path("phase2-results/packet") | |
| results_root.mkdir(parents=True, exist_ok=True) | |
| suffix = ".protocol-trace-analysis.json" | |
| records = [] | |
| errors = [] | |
| for analysis_path in sorted(results_root.glob(f"*/*{suffix}")): | |
| run_id = analysis_path.name[:-len(suffix)] | |
| variant_match = re.search(r"_([AB])_[0-9]+$", run_id) | |
| try: | |
| analysis = json.loads(analysis_path.read_text(encoding="utf-8")) | |
| status = analysis.get("status", {}) | |
| identity = analysis.get("identity", {}) | |
| spawn = identity.get("spawn", {}) | |
| destroy = identity.get("destroy", {}) | |
| peaks = analysis.get("spawnPeaks", {}) | |
| records.append({ | |
| "runId": run_id, | |
| "variant": variant_match.group(1) if variant_match else None, | |
| "sourcePath": analysis_path.as_posix(), | |
| "formalEvidenceReady": status.get("formalEvidenceReady") is True, | |
| "windowCovered": status.get("windowCovered") is True, | |
| "windowDurationMs": analysis.get("window", {}).get("durationMs"), | |
| "windowEventCount": analysis.get("traceCoverage", {}).get("windowEventCount"), | |
| "spawnObservations": spawn.get("observations"), | |
| "uniqueSpawnIds": spawn.get("uniqueIdCount"), | |
| "destroyObservations": destroy.get("observations"), | |
| "peak50msSpawnIds": peaks.get("epochAligned50ms", {}).get("spawnedEntityIdCount"), | |
| "peak1sSpawnIds": peaks.get("epochAligned1s", {}).get("spawnedEntityIdCount"), | |
| "duplicateLiveSpawnObservations": identity.get("duplicateLiveSpawnObservations"), | |
| "destroyWithoutKnownLiveObservations": identity.get("destroyWithoutKnownLiveObservations"), | |
| }) | |
| except Exception as error: | |
| errors.append({"sourcePath": analysis_path.as_posix(), "error": str(error)}) | |
| expected_runs = int(os.environ["EXPECTED_RUNS"]) | |
| run_ids = [record["runId"] for record in records] | |
| unique_run_ids = len(run_ids) == len(set(run_ids)) | |
| complete_run_set = len(records) == expected_runs and unique_run_ids and not errors | |
| formal_evidence_ready = complete_run_set and all( | |
| record["formalEvidenceReady"] for record in records | |
| ) | |
| summary = { | |
| "schemaVersion": 1, | |
| "scenario": os.environ["SCENARIO"], | |
| "campaignOutcome": os.environ.get("CAMPAIGN_OUTCOME", "unknown"), | |
| "requestedRuns": expected_runs, | |
| "observedRuns": len(records), | |
| "formalEvidenceReady": formal_evidence_ready, | |
| "formalCampaignReady": ( | |
| formal_evidence_ready | |
| and expected_runs == 12 | |
| and os.environ.get("CAMPAIGN_OUTCOME") == "success" | |
| ), | |
| "errors": errors, | |
| "runs": records, | |
| } | |
| summary_path = results_root / "protocol-trace-summary.json" | |
| summary_path.write_text( | |
| json.dumps(summary, ensure_ascii=False, indent=2) + "\n", | |
| encoding="utf-8", | |
| ) | |
| step_summary = os.environ.get("GITHUB_STEP_SUMMARY") | |
| if step_summary: | |
| lines = [ | |
| "### Phase 2 semantic protocol trace", | |
| "", | |
| f"- Scenario: `{summary['scenario']}`", | |
| f"- Explicit-window runs: `{summary['observedRuns']}/{summary['requestedRuns']}`", | |
| f"- Per-run `formalEvidenceReady`: `{'ready' if formal_evidence_ready else 'not-ready'}`", | |
| f"- Formal 12-run ABBA campaign: `{'ready' if summary['formalCampaignReady'] else 'incomplete'}`", | |
| "", | |
| "| Run | Variant | Ready | Window events | Spawn IDs | Unique IDs | Peak 50 ms | Peak 1 s |", | |
| "|---|---:|---:|---:|---:|---:|---:|---:|", | |
| ] | |
| for record in records: | |
| lines.append( | |
| "| {runId} | {variant} | {ready} | {events} | {spawn} | {unique} | {peak50} | {peak1s} |".format( | |
| runId=record["runId"], | |
| variant=record["variant"] or "?", | |
| ready="yes" if record["formalEvidenceReady"] else "no", | |
| events=record["windowEventCount"], | |
| spawn=record["spawnObservations"], | |
| unique=record["uniqueSpawnIds"], | |
| peak50=record["peak50msSpawnIds"], | |
| peak1s=record["peak1sSpawnIds"], | |
| ) | |
| ) | |
| if errors: | |
| lines.extend(["", f"Trace summary errors: `{len(errors)}`"]) | |
| with open(step_summary, "a", encoding="utf-8", newline="\n") as stream: | |
| stream.write("\n".join(lines) + "\n") | |
| if summary["campaignOutcome"] == "success" and not formal_evidence_ready: | |
| raise SystemExit("successful campaign is missing complete formal-ready protocol trace evidence") | |
| PY | |
| - name: Publish packet evidence | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: phase2-packet-${{ env.CAMPAIGN_SCENARIO }}-${{ github.sha }}-${{ github.run_id }} | |
| path: | | |
| phase2-results/packet | |
| phase2-dependencies/protocol-client/client-build-manifest.json | |
| phase2-dependencies/protocol-client/client-files.sha256 | |
| phase2-dependencies/protocol-client/node-minecraft-protocol/package-lock.json | |
| phase2-dependencies/protocol-client/node-minecraft-protocol/production-lock-inventory.json | |
| if-no-files-found: warn | |
| retention-days: 30 |