Skip to content

Commit 67a1104

Browse files
committed
Add event-driven block benchmark evidence
1 parent 875207c commit 67a1104

10 files changed

Lines changed: 3070 additions & 92 deletions

File tree

.github/workflows/phase2-packet-capture.yml

Lines changed: 155 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on:
1010
required: true
1111
default: "static-spawn"
1212
type: choice
13-
options: ["static-spawn", "visibility-return"]
13+
options: ["static-spawn", "visibility-return", "visibility-itemdisplay-return", "visibility-textdisplay-return"]
1414
runs:
1515
description: "Restart-isolated runs (4, 8, or formal 12)"
1616
required: true
@@ -53,17 +53,22 @@ jobs:
5353
if: >-
5454
github.event_name == 'workflow_dispatch' ||
5555
github.event.action != 'labeled' ||
56+
github.event.label.name == 'phase2-packet-visibility-smoke' ||
5657
github.event.label.name == 'phase2-packet-static-formal' ||
57-
github.event.label.name == 'phase2-packet-visibility-formal'
58+
github.event.label.name == 'phase2-packet-visibility-formal' ||
59+
github.event.label.name == 'phase2-packet-visibility-itemdisplay-smoke' ||
60+
github.event.label.name == 'phase2-packet-visibility-itemdisplay-formal' ||
61+
github.event.label.name == 'phase2-packet-visibility-textdisplay-smoke' ||
62+
github.event.label.name == 'phase2-packet-visibility-textdisplay-formal'
5863
runs-on: ubuntu-latest
5964
timeout-minutes: 100
6065
env:
61-
CAMPAIGN_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.scenario || github.event.label.name == 'phase2-packet-visibility-formal' && 'visibility-return' || 'static-spawn' }}
62-
CAMPAIGN_RUNS: ${{ github.event_name == 'workflow_dispatch' && inputs.runs || github.event.action == 'labeled' && '12' || '4' }}
63-
CAMPAIGN_ITEMS: ${{ github.event_name == 'workflow_dispatch' && inputs.items || github.event.action == 'labeled' && '4096' || '1024' }}
64-
CAMPAIGN_WARMUP_SECONDS: ${{ github.event_name == 'workflow_dispatch' && inputs.warmup_seconds || github.event.action == 'labeled' && '60' || '10' }}
65-
CAMPAIGN_SETTLE_SECONDS: ${{ github.event_name == 'workflow_dispatch' && inputs.settle_seconds || github.event.action == 'labeled' && '10' || '5' }}
66-
CAMPAIGN_MEASURE_SECONDS: ${{ github.event_name == 'workflow_dispatch' && inputs.measure_seconds || github.event.action == 'labeled' && '10' || '5' }}
66+
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' }}
67+
CAMPAIGN_RUNS: ${{ github.event_name == 'workflow_dispatch' && inputs.runs || contains(github.event.label.name, '-formal') && '12' || '4' }}
68+
CAMPAIGN_ITEMS: ${{ github.event_name == 'workflow_dispatch' && inputs.items || contains(github.event.label.name, '-formal') && '4096' || '1024' }}
69+
CAMPAIGN_WARMUP_SECONDS: ${{ github.event_name == 'workflow_dispatch' && inputs.warmup_seconds || contains(github.event.label.name, '-formal') && '60' || '10' }}
70+
CAMPAIGN_SETTLE_SECONDS: ${{ github.event_name == 'workflow_dispatch' && inputs.settle_seconds || contains(github.event.label.name, '-formal') && '10' || '5' }}
71+
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' }}
6772
CAMPAIGN_SNAPLEN: ${{ github.event_name == 'workflow_dispatch' && inputs.snaplen || '128' }}
6873

6974
steps:
@@ -100,6 +105,8 @@ jobs:
100105
bash -n tools/perf/prepare-phase2-protocol-client.sh
101106
bash -n tools/perf/run-phase2-runtime-once.sh
102107
node --check tools/perf/phase2-protocol-client.js
108+
node --check tools/perf/analyze-phase2-protocol-trace.js
109+
node tools/perf/analyze-phase2-protocol-trace.js --self-test
103110
pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 -SelfTest
104111
pwsh -NoProfile -File tools/perf/analyze-phase2-pcap.ps1 -SelfTest
105112
@@ -124,6 +131,7 @@ jobs:
124131
run: bash tools/perf/prepare-phase2-protocol-client.sh phase2-dependencies/protocol-client
125132

126133
- name: Run restart-isolated packet ABBA campaign
134+
id: packet_campaign
127135
env:
128136
SCENARIO: ${{ env.CAMPAIGN_SCENARIO }}
129137
RUNS: ${{ env.CAMPAIGN_RUNS }}
@@ -134,29 +142,39 @@ jobs:
134142
SNAPLEN: ${{ env.CAMPAIGN_SNAPLEN }}
135143
run: |
136144
set -euo pipefail
137-
[[ "$SCENARIO" == static-spawn || "$SCENARIO" == visibility-return ]]
145+
[[ "$SCENARIO" == static-spawn || "$SCENARIO" == visibility-return || \
146+
"$SCENARIO" == visibility-itemdisplay-return || "$SCENARIO" == visibility-textdisplay-return ]]
138147
[[ "$RUNS" =~ ^(4|8|12)$ ]]
139148
[[ "$ITEMS" =~ ^[0-9]+$ ]] && (( ITEMS >= 1 && ITEMS <= 8192 ))
140149
[[ "$WARMUP_SECONDS" =~ ^[0-9]+$ ]] && (( WARMUP_SECONDS >= 10 ))
141150
[[ "$SETTLE_SECONDS" =~ ^[0-9]+$ ]] && (( SETTLE_SECONDS >= 5 ))
142151
[[ "$MEASURE_SECONDS" =~ ^[0-9]+$ ]] && (( MEASURE_SECONDS >= 5 ))
143152
[[ "$SNAPLEN" == 0 || "$SNAPLEN" == 128 ]]
144-
if [[ "$SCENARIO" == visibility-return ]]; then
153+
if [[ "$SCENARIO" == visibility-* ]]; then
145154
(( MEASURE_SECONDS >= 10 ))
146155
fi
147156
148157
PLUGIN_JAR=$(find build/libs -maxdepth 1 -type f -name 'InteractionVisualizer-*.jar' ! -name '*-sources.jar' ! -name '*-benchmark.jar' -print -quit)
149158
test -n "$PLUGIN_JAR"
150159
mkdir -p phase2-results/packet
151160
MANIFEST=phase2-results/packet/abba-manifest.csv
161+
PROTOCOL_MANIFEST=phase2-results/packet/protocol-trace-abba-manifest.csv
152162
printf 'Scenario,Block,Position,Variant,RunId,StackSha256,ArtifactSha256,CaptureMethod,SourcePath\n' > "$MANIFEST"
163+
printf 'Scenario,Block,Position,Variant,RunId,StackSha256,ArtifactSha256,CaptureMethod,SourcePath\n' > "$PROTOCOL_MANIFEST"
153164
ARTIFACT_SHA=$(sha256sum "$PLUGIN_JAR" | awk '{print $1}')
154165
STACK_SHA=$(
155166
{
156167
sha256sum phase2-dependencies/paper.jar
157168
sha256sum phase2-dependencies/protocol-client/client-build-manifest.json
169+
sha256sum tools/perf/phase2-protocol-client.js
170+
sha256sum tools/perf/analyze-phase2-protocol-trace.js
171+
sha256sum tools/perf/analyze-phase2-pcap.ps1
172+
sha256sum tools/perf/analyze-phase2-abba.ps1
173+
sha256sum tools/perf/run-phase2-runtime-once.sh
158174
java -version 2>&1
159-
printf '%s\n' 'java=-Xms2G -Xmx2G -XX:+UseG1GC -XX:+AlwaysPreTouch' "scenario=$SCENARIO" "snaplen=$SNAPLEN"
175+
printf '%s\n' \
176+
'java=-Xms2G -Xmx2G -XX:+UseG1GC -XX:+AlwaysPreTouch -Dinteractionvisualizer.performance.allowBlockScene=true' \
177+
"scenario=$SCENARIO" "snaplen=$SNAPLEN" 'protocolTrace=semantic-lifecycle-v1'
160178
} | sha256sum | awk '{print $1}'
161179
)
162180
@@ -180,11 +198,15 @@ jobs:
180198
PHASE2_MEASURE_SECONDS="$MEASURE_SECONDS" \
181199
PHASE2_CAPTURE_ENABLED=1 \
182200
PHASE2_CAPTURE_SNAPLEN="$SNAPLEN" \
201+
PHASE2_PROTOCOL_TRACE_ENABLED=1 \
183202
bash tools/perf/run-phase2-runtime-once.sh
184203
185204
printf '%s,%d,%d,%s,%s,%s,%s,tcpdump-lo-s%s,%s/%s.pcap-analysis.json\n' \
186205
"$SCENARIO" "$block" "$position" "$variant" "$run_id" "$STACK_SHA" "$ARTIFACT_SHA" \
187206
"$SNAPLEN" "$run_id" "$run_id" >> "$MANIFEST"
207+
printf '%s,%d,%d,%s,%s,%s,%s,protocol-client-memory,%s/%s.protocol-trace-analysis.json\n' \
208+
"$SCENARIO" "$block" "$position" "$variant" "$run_id" "$STACK_SHA" "$ARTIFACT_SHA" \
209+
"$run_id" "$run_id" >> "$PROTOCOL_MANIFEST"
188210
done
189211
190212
minimum_seconds=$(( MEASURE_SECONDS - 1 ))
@@ -201,11 +223,132 @@ jobs:
201223
-OutputJson "phase2-results/packet/$safe_metric.analysis.json" -Overwrite
202224
done
203225
226+
# These semantic lifecycle counts are generated from the same
227+
# explicit half-open window as the pcap evidence. They supplement,
228+
# and never replace, the transport-level byte and retransmit gates.
229+
for metric in traceCoverage.windowEventCount identity.spawn.observations spawnPeaks.epochAligned50ms.spawnedEntityIdCount spawnPeaks.epochAligned1s.spawnedEntityIdCount; do
230+
safe_metric=${metric//./_}
231+
pwsh -NoProfile -File tools/perf/analyze-phase2-abba.ps1 "$PROTOCOL_MANIFEST" \
232+
-Scenario "$SCENARIO" -Metric "$metric" -Direction LowerIsBetter \
233+
"${incomplete[@]}" \
234+
-OutputJson "phase2-results/packet/protocol_$safe_metric.analysis.json" -Overwrite
235+
done
236+
237+
- name: Summarize semantic protocol evidence
238+
if: always()
239+
env:
240+
SCENARIO: ${{ env.CAMPAIGN_SCENARIO }}
241+
EXPECTED_RUNS: ${{ env.CAMPAIGN_RUNS }}
242+
CAMPAIGN_OUTCOME: ${{ steps.packet_campaign.outcome }}
243+
run: |
244+
python3 - <<'PY'
245+
import json
246+
import os
247+
import re
248+
from pathlib import Path
249+
250+
results_root = Path("phase2-results/packet")
251+
results_root.mkdir(parents=True, exist_ok=True)
252+
suffix = ".protocol-trace-analysis.json"
253+
records = []
254+
errors = []
255+
for analysis_path in sorted(results_root.glob(f"*/*{suffix}")):
256+
run_id = analysis_path.name[:-len(suffix)]
257+
variant_match = re.search(r"_([AB])_[0-9]+$", run_id)
258+
try:
259+
analysis = json.loads(analysis_path.read_text(encoding="utf-8"))
260+
status = analysis.get("status", {})
261+
identity = analysis.get("identity", {})
262+
spawn = identity.get("spawn", {})
263+
destroy = identity.get("destroy", {})
264+
peaks = analysis.get("spawnPeaks", {})
265+
records.append({
266+
"runId": run_id,
267+
"variant": variant_match.group(1) if variant_match else None,
268+
"sourcePath": analysis_path.as_posix(),
269+
"formalEvidenceReady": status.get("formalEvidenceReady") is True,
270+
"windowCovered": status.get("windowCovered") is True,
271+
"windowDurationMs": analysis.get("window", {}).get("durationMs"),
272+
"windowEventCount": analysis.get("traceCoverage", {}).get("windowEventCount"),
273+
"spawnObservations": spawn.get("observations"),
274+
"uniqueSpawnIds": spawn.get("uniqueIdCount"),
275+
"destroyObservations": destroy.get("observations"),
276+
"peak50msSpawnIds": peaks.get("epochAligned50ms", {}).get("spawnedEntityIdCount"),
277+
"peak1sSpawnIds": peaks.get("epochAligned1s", {}).get("spawnedEntityIdCount"),
278+
"duplicateLiveSpawnObservations": identity.get("duplicateLiveSpawnObservations"),
279+
"destroyWithoutKnownLiveObservations": identity.get("destroyWithoutKnownLiveObservations"),
280+
})
281+
except Exception as error:
282+
errors.append({"sourcePath": analysis_path.as_posix(), "error": str(error)})
283+
284+
expected_runs = int(os.environ["EXPECTED_RUNS"])
285+
run_ids = [record["runId"] for record in records]
286+
unique_run_ids = len(run_ids) == len(set(run_ids))
287+
complete_run_set = len(records) == expected_runs and unique_run_ids and not errors
288+
formal_evidence_ready = complete_run_set and all(
289+
record["formalEvidenceReady"] for record in records
290+
)
291+
summary = {
292+
"schemaVersion": 1,
293+
"scenario": os.environ["SCENARIO"],
294+
"campaignOutcome": os.environ.get("CAMPAIGN_OUTCOME", "unknown"),
295+
"requestedRuns": expected_runs,
296+
"observedRuns": len(records),
297+
"formalEvidenceReady": formal_evidence_ready,
298+
"formalCampaignReady": (
299+
formal_evidence_ready
300+
and expected_runs == 12
301+
and os.environ.get("CAMPAIGN_OUTCOME") == "success"
302+
),
303+
"errors": errors,
304+
"runs": records,
305+
}
306+
summary_path = results_root / "protocol-trace-summary.json"
307+
summary_path.write_text(
308+
json.dumps(summary, ensure_ascii=False, indent=2) + "\n",
309+
encoding="utf-8",
310+
)
311+
312+
step_summary = os.environ.get("GITHUB_STEP_SUMMARY")
313+
if step_summary:
314+
lines = [
315+
"### Phase 2 semantic protocol trace",
316+
"",
317+
f"- Scenario: `{summary['scenario']}`",
318+
f"- Explicit-window runs: `{summary['observedRuns']}/{summary['requestedRuns']}`",
319+
f"- Per-run `formalEvidenceReady`: `{'ready' if formal_evidence_ready else 'not-ready'}`",
320+
f"- Formal 12-run ABBA campaign: `{'ready' if summary['formalCampaignReady'] else 'incomplete'}`",
321+
"",
322+
"| Run | Variant | Ready | Window events | Spawn IDs | Unique IDs | Peak 50 ms | Peak 1 s |",
323+
"|---|---:|---:|---:|---:|---:|---:|---:|",
324+
]
325+
for record in records:
326+
lines.append(
327+
"| {runId} | {variant} | {ready} | {events} | {spawn} | {unique} | {peak50} | {peak1s} |".format(
328+
runId=record["runId"],
329+
variant=record["variant"] or "?",
330+
ready="yes" if record["formalEvidenceReady"] else "no",
331+
events=record["windowEventCount"],
332+
spawn=record["spawnObservations"],
333+
unique=record["uniqueSpawnIds"],
334+
peak50=record["peak50msSpawnIds"],
335+
peak1s=record["peak1sSpawnIds"],
336+
)
337+
)
338+
if errors:
339+
lines.extend(["", f"Trace summary errors: `{len(errors)}`"])
340+
with open(step_summary, "a", encoding="utf-8", newline="\n") as stream:
341+
stream.write("\n".join(lines) + "\n")
342+
343+
if summary["campaignOutcome"] == "success" and not formal_evidence_ready:
344+
raise SystemExit("successful campaign is missing complete formal-ready protocol trace evidence")
345+
PY
346+
204347
- name: Publish packet evidence
205348
if: always()
206349
uses: actions/upload-artifact@v4
207350
with:
208-
name: phase2-packet-${{ inputs.scenario }}-${{ github.sha }}-${{ github.run_id }}
351+
name: phase2-packet-${{ env.CAMPAIGN_SCENARIO }}-${{ github.sha }}-${{ github.run_id }}
209352
path: |
210353
phase2-results/packet
211354
phase2-dependencies/protocol-client/client-build-manifest.json

0 commit comments

Comments
 (0)