chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:29:49 +08:00
commit 505bac6b53
1470 changed files with 457757 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
#!/usr/bin/env bash
# Fold changelog.d/ fragments into CHANGELOG.md's Unreleased section.
#
# scripts/changelog-merge.sh
#
# Each changelog.d/<slug>.md fragment is tagged on its first line
# (feature: / improvement: / fix: — see changelog.d/README.md). Bullets are
# appended to the END of the matching "### ..." section under
# "## Unreleased", preserving everything already there. Missing sections
# are created in canonical order (New Features, Improvements, Bug Fixes)
# before "### Contributors"; a missing "## Unreleased" block is created
# above the newest release heading. Merged fragments are deleted;
# changelog.d/README.md is never touched.
#
# Fails loudly on unknown tags or malformed fragments — a silently dropped
# changelog entry is worse than a broken merge.
set -u
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$repo_root" || exit 1
changelog="CHANGELOG.md"
fragments_dir="changelog.d"
[ -f "$changelog" ] || { echo "changelog-merge: $changelog not found" >&2; exit 1; }
[ -d "$fragments_dir" ] || { echo "changelog-merge: $fragments_dir/ not found" >&2; exit 1; }
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
: > "$tmp_dir/features"
: > "$tmp_dir/improvements"
: > "$tmp_dir/fixes"
section_file_for_tag() {
case "$1" in
feature) echo "$tmp_dir/features" ;;
improvement) echo "$tmp_dir/improvements" ;;
fix) echo "$tmp_dir/fixes" ;;
*) return 1 ;;
esac
}
merged_fragments=()
for fragment in "$fragments_dir"/*.md; do
[ -e "$fragment" ] || continue
[ "$(basename "$fragment")" = "README.md" ] && continue
first_line=""
rest_started=false
out=""
while IFS= read -r line || [ -n "$line" ]; do
if [ -z "$first_line" ] && ! $rest_started; then
[ -z "$line" ] && continue
first_line="$line"
continue
fi
rest_started=true
[ -z "$line" ] && continue
case "$line" in
"- "*|" "*|$'\t'*) out="$out$line"$'\n' ;;
*) out="$out- $line"$'\n' ;;
esac
done < "$fragment"
tag="${first_line%%:*}"
body="${first_line#*:}"
body="${body# }"
section_file="$(section_file_for_tag "$tag")" || {
echo "changelog-merge: $fragment: unknown tag '${tag}:' (expected feature:, improvement:, or fix:)" >&2
exit 1
}
if [ "$first_line" = "$tag" ] || [ -z "$body" ]; then
echo "changelog-merge: $fragment: first line must be '<tag>: <bullet text>'" >&2
exit 1
fi
case "$body" in
"- "*) printf '%s\n' "$body" >> "$section_file" ;;
*) printf -- '- %s\n' "$body" >> "$section_file" ;;
esac
[ -n "$out" ] && printf '%s' "$out" >> "$section_file"
merged_fragments+=("$fragment")
done
if [ "${#merged_fragments[@]}" -eq 0 ]; then
echo "changelog-merge: no fragments to merge"
exit 0
fi
awk -v features_file="$tmp_dir/features" \
-v improvements_file="$tmp_dir/improvements" \
-v fixes_file="$tmp_dir/fixes" '
function load(file, line, out) {
out = ""
while ((getline line < file) > 0) out = out line "\n"
close(file)
return out
}
function emit_pending_blanks( i) {
for (i = 0; i < blanks; i++) print ""
blanks = 0
}
# Append the current section pending bullets at the end of its existing
# content (held blank lines are emitted afterwards, so spacing before the
# next heading is preserved).
function close_section() {
if (cur != "" && pending[cur] != "") {
printf "%s", pending[cur]
pending[cur] = ""
}
cur = ""
}
# Emit any sections that had no heading yet, in canonical order.
function emit_missing_sections( i, name) {
for (i = 1; i <= 3; i++) {
name = order[i]
if (pending[name] != "") {
print "### " name
print ""
printf "%s", pending[name]
print ""
pending[name] = ""
}
}
}
BEGIN {
pending["New Features"] = load(features_file)
pending["Improvements"] = load(improvements_file)
pending["Bug Fixes"] = load(fixes_file)
order[1] = "New Features"; order[2] = "Improvements"; order[3] = "Bug Fixes"
in_unreleased = 0
seen_unreleased = 0
cur = ""
blanks = 0
}
/^## / {
if (in_unreleased) {
close_section()
emit_pending_blanks()
emit_missing_sections()
in_unreleased = 0
} else if (!seen_unreleased && $0 != "## Unreleased") {
# No Unreleased block exists; create one above the newest release.
emit_pending_blanks()
print "## Unreleased"
print ""
emit_missing_sections()
seen_unreleased = 1
} else {
emit_pending_blanks()
}
if ($0 == "## Unreleased") { in_unreleased = 1; seen_unreleased = 1 }
print
next
}
/^### / {
if (in_unreleased) {
close_section()
emit_pending_blanks()
if ($0 == "### Contributors") emit_missing_sections()
sub(/^### /, "")
if ($0 in pending) cur = $0
print "### " $0
next
}
emit_pending_blanks()
print
next
}
/^[[:space:]]*$/ { blanks++; next }
{
emit_pending_blanks()
print
next
}
END {
if (in_unreleased) {
close_section()
emit_missing_sections()
} else if (!seen_unreleased) {
emit_pending_blanks()
print "## Unreleased"
print ""
emit_missing_sections()
blanks = 0
}
emit_pending_blanks()
}
' "$changelog" > "$tmp_dir/changelog.new" || { echo "changelog-merge: awk pass failed" >&2; exit 1; }
mv "$tmp_dir/changelog.new" "$changelog"
rm -f "${merged_fragments[@]}"
echo "changelog-merge: merged ${#merged_fragments[@]} fragment(s) into $changelog"
for fragment in "${merged_fragments[@]}"; do
echo " - $fragment"
done
+364
View File
@@ -0,0 +1,364 @@
#!/usr/bin/env bash
# Tiered local gate for the Native SDK.
#
# scripts/gate.sh fast [base-ref] # affected-only: what your diff touches
# scripts/gate.sh full [base-ref] [--all] [--perf] # everything CI-shaped that runs locally
#
# fast — root `zig build test` + `zig build validate`, plus the suites for
# the examples AFFECTED by your diff against base-ref (default: main). The
# diff is `git diff --name-only` against `git merge-base base-ref HEAD`,
# plus untracked files, so uncommitted work counts. Framework diffs on
# macOS additionally run the render-benchmark budget ratchet (see below).
#
# Path -> step mapping (fast tier):
# src/**, build.zig, build.zig.zon, build/**,
# tools/**, tests/**, assets/** -> framework change: root suites
# + ALL example suites
# (frontends, native, mobile)
# src/platform/macos/** -> additionally cef-host-link:
# build the WebView example
# with -Dweb-engine=chromium so
# cef_host.mm actually compiles
# and links (macOS only; skipped
# with a loud warning when the
# CEF layout is absent — prime
# with `zig build &&
# ./zig-out/bin/native cef install`)
# examples/<name>/** -> that example's suite only
# (test-example-<name>; the
# mobile projects map to
# test-examples-mobile; hello/
# webview/browser run their
# in-dir `zig build test`)
# docs/** -> docs `pnpm check`
# anything else (README, .github, packages,
# scripts, skills, changelog.d, ...) -> root suites only
# A docs-ONLY diff runs only the docs check. The docs check is path-gated
# in both tiers: it never runs unless docs/ changed (or --all in full).
#
# full — root test + validate, every example suite (frontends, native incl.
# canvas-preview, mobile), the Chromium host link check (cef-host-link),
# the macOS automation smokes (gpu-surface,
# gpu-dashboard, gpu-components, canvas-preview, writeback; skipped off-macOS), a
# markup check over every example markup file, and the docs check if docs/ changed
# vs base-ref or --all was passed. --perf additionally runs the percentile
# GPU perf check (test-gpu-dashboard-perf; macOS only, slow, load-sensitive —
# opt-in so a busy dev box doesn't fail the gate on noise).
#
# bench-check — the render-benchmark budget ratchet
# (`zig build bench-render -Doptimize=ReleaseFast -- --check
# tools/bench-render-budgets.txt`): median e2e p50 of three deterministic
# suite passes per scenario vs committed budgets with ~1.3x+ headroom, so
# it trips on order-of-magnitude regressions (a reintroduced O(n^2)
# planner scan, an extra emission per input), not machine noise. Unlike
# the --perf harness it is headless, in-process, and seconds-long once the
# ReleaseFast build is cached — that is why it runs by DEFAULT (fast tier:
# framework diffs only, since engine perf cannot regress from an
# example-only or docs diff; full tier: always) instead of opt-in like
# --perf: a ratchet that only runs when someone remembers to ask is not a
# ratchet. macOS-only because the budgets are calibrated on Apple Silicon.
# Set NATIVE_SDK_SKIP_BENCH_CHECK=1 to skip it on a box that is too busy
# even for the generous budgets.
#
# Deliberately NOT `set -e`: every step runs even after a failure so the
# summary shows the whole picture; the exit code is non-zero if any step
# failed. Step output streams through; each step is timed.
set -u
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$repo_root" || exit 1
usage() {
echo "usage: scripts/gate.sh <fast|full> [base-ref] [--all] [--perf]" >&2
exit 2
}
tier="${1:-}"
case "$tier" in fast|full) ;; *) usage ;; esac
shift
base_ref="main"
run_all=false
run_perf=false
for arg in "$@"; do
case "$arg" in
--all) run_all=true ;;
--perf) run_perf=true ;;
-*) usage ;;
*) base_ref="$arg" ;;
esac
done
# ---- diff classification --------------------------------------------------
base_commit="$(git merge-base "$base_ref" HEAD 2>/dev/null)"
if [ -z "$base_commit" ]; then
echo "gate: cannot resolve merge-base of '$base_ref' and HEAD" >&2
exit 2
fi
changed_files="$( (git diff --name-only "$base_commit"; git ls-files --others --exclude-standard) | sort -u)"
framework_changed=false
macos_platform_changed=false
docs_changed=false
meta_changed=false
affected_examples="" # space-separated example dir names
mobile_affected=false
# Examples with a root `test-example-<name>` step, derived from build.zig's
# `addExampleTestStep(...)` registrations so this script is never a second
# registry that drifts (it did, twice in one day — soundboard/calculator/
# notes were silently skipped). Layout/file-contains checks register other
# `test-example-*` names via different helpers and are deliberately not
# example suites, so the match is keyed on the helper name.
registered_examples="$(sed -n 's/.*addExampleTestStep([^"]*"test-example-\([A-Za-z0-9-]*\)".*/\1/p' build.zig | sort -u | tr '\n' ' ')"
if [ -z "$registered_examples" ]; then
echo "gate: derived no registered examples from build.zig (addExampleTestStep pattern matched nothing — helper renamed?)" >&2
exit 2
fi
# Examples whose suite is their own in-dir `zig build test`.
indir_examples="browser hello webview"
# Mobile example projects, covered as a group by test-examples-mobile.
mobile_examples="android ios mobile-canvas mobile-shell"
note_example() {
case " $affected_examples " in *" $1 "*) ;; *) affected_examples="$affected_examples $1" ;; esac
}
while IFS= read -r file; do
[ -n "$file" ] || continue
case "$file" in
docs/*) docs_changed=true ;;
src/platform/macos/*) framework_changed=true; macos_platform_changed=true ;;
src/*|build.zig|build.zig.zon|build/*|tools/*|tests/*|assets/*) framework_changed=true ;;
examples/*/*)
example="${file#examples/}"
example="${example%%/*}"
case " $mobile_examples " in
*" $example "*) mobile_affected=true ;;
*) note_example "$example" ;;
esac
;;
*) meta_changed=true ;;
esac
done <<EOF_FILES
$changed_files
EOF_FILES
# ---- step machinery -------------------------------------------------------
step_names=""
step_status=""
step_secs=""
failures=0
gate_start=$(date +%s)
record() { # name status seconds
step_names="$step_names$1|"
step_status="$step_status$2|"
step_secs="$step_secs$3|"
}
run_step() { # name command...
name="$1"; shift
echo ""
echo "==> $name: $*"
start=$(date +%s)
"$@"
rc=$?
secs=$(( $(date +%s) - start ))
if [ "$rc" -eq 0 ]; then
record "$name" PASS "$secs"
else
record "$name" FAIL "$secs"
failures=$((failures + 1))
echo "==> $name FAILED (exit $rc)" >&2
fi
}
skip_step() { # name reason
echo ""
echo "==> $1: skipped ($2)"
record "$1" SKIP 0
}
docs_check() {
run_step "docs-install" pnpm --dir docs install --frozen-lockfile
# Build into a gate-owned dist dir so the check never corrupts a running
# dev server's .next cache.
run_step "docs-check" env NEXT_DIST_DIR=.next-gate pnpm --dir docs check
}
is_macos=false
[ "$(uname -s)" = "Darwin" ] && is_macos=true
bench_check() {
zig build bench-render -Doptimize=ReleaseFast -- --check "$repo_root/tools/bench-render-budgets.txt"
}
run_bench_check_step() { # runs (or explains skipping) the render-benchmark ratchet
if ! $is_macos; then
skip_step "bench-check" "budgets calibrated on Apple Silicon macOS"
elif [ -n "${NATIVE_SDK_SKIP_BENCH_CHECK:-}" ]; then
skip_step "bench-check" "NATIVE_SDK_SKIP_BENCH_CHECK set"
else
run_step "bench-check" bench_check
fi
}
# The Chromium (CEF) host, src/platform/macos/cef_host.mm, is only compiled
# by Chromium-engine app builds — never by `zig build test`/`validate` — so
# without this step an edit that keeps it merely grep-identical to the
# AppKit host can land without ever seeing a compiler. test-webview-cef-link
# builds (and links) the WebView example against the host for real.
cef_layout_ready() {
[ -f third_party/cef/macos/include/cef_app.h ] &&
[ -d "third_party/cef/macos/Release/Chromium Embedded Framework.framework" ] &&
[ -f third_party/cef/macos/libcef_dll_wrapper/libcef_dll_wrapper.a ]
}
cef_host_step() {
if ! $is_macos; then
skip_step "cef-host-link" "macOS only"
return
fi
if ! cef_layout_ready; then
echo "" >&2
echo "==> cef-host-link: WARNING — src/platform/macos changed but the CEF layout is absent," >&2
echo " so the Chromium host was NOT compiled. Blind cef_host.mm edits will not be caught." >&2
echo " Prime it once (downloads the prepared CEF runtime into third_party/cef/macos):" >&2
echo " zig build && ./zig-out/bin/native cef install" >&2
skip_step "cef-host-link" "CEF layout absent at third_party/cef/macos (see warning)"
return
fi
run_step "cef-host-link" zig build test-webview-cef-link
}
# ---- tiers ----------------------------------------------------------------
if [ "$tier" = "fast" ]; then
non_docs_change=false
{ $framework_changed || $meta_changed || $mobile_affected || [ -n "$affected_examples" ]; } && non_docs_change=true
if $non_docs_change || ! $docs_changed; then
# Root suites run for any non-docs diff, and also for an empty diff
# (a clean tree still deserves a baseline check).
run_step "zig-test" zig build test
run_step "zig-validate" zig build validate
else
skip_step "zig-test" "docs-only diff"
skip_step "zig-validate" "docs-only diff"
fi
if $framework_changed; then
run_step "examples-frontends" zig build test-examples-frontends
run_step "examples-native" zig build test-examples-native
run_step "examples-mobile" zig build test-examples-mobile
# Engine perf can only regress through framework code, so the ratchet
# rides the same trigger as the full example sweep.
run_bench_check_step
else
for example in $affected_examples; do
case " $registered_examples " in
*" $example "*) run_step "example-$example" zig build "test-example-$example" ;;
*)
case " $indir_examples " in
*" $example "*) run_step "example-$example" sh -c "cd 'examples/$example' && zig build test -Dplatform=null" ;;
*) skip_step "example-$example" "no test suite registered for examples/$example" ;;
esac
;;
esac
done
$mobile_affected && run_step "examples-mobile" zig build test-examples-mobile
fi
if $macos_platform_changed; then
cef_host_step
else
skip_step "cef-host-link" "src/platform/macos unchanged vs $base_ref"
fi
if $docs_changed; then
docs_check
else
skip_step "docs-check" "docs/ unchanged vs $base_ref"
fi
else # full
run_step "zig-test" zig build test
run_step "zig-validate" zig build validate
run_step "examples-frontends" zig build test-examples-frontends
run_step "examples-native" zig build test-examples-native
run_step "examples-mobile" zig build test-examples-mobile
cef_host_step
if $is_macos; then
run_step "smoke-gpu-surface" zig build test-gpu-surface-smoke
run_step "smoke-gpu-dashboard" zig build test-gpu-dashboard-smoke
run_step "smoke-gpu-components" zig build test-gpu-components-smoke
run_step "smoke-webview" zig build test-webview-smoke
run_step "smoke-native-shell" zig build test-native-shell-smoke
run_step "smoke-canvas-preview" zig build test-canvas-preview-smoke
run_step "smoke-writeback" zig build test-writeback-smoke
else
skip_step "smoke-gpu-surface" "macOS only"
skip_step "smoke-gpu-dashboard" "macOS only"
skip_step "smoke-gpu-components" "macOS only"
skip_step "smoke-webview" "macOS only"
skip_step "smoke-native-shell" "macOS only"
skip_step "smoke-canvas-preview" "macOS only"
skip_step "smoke-writeback" "macOS only"
fi
run_bench_check_step
if $run_perf; then
if $is_macos; then
run_step "perf-gpu-dashboard" zig build test-gpu-dashboard-perf
else
skip_step "perf-gpu-dashboard" "macOS only"
fi
else
skip_step "perf-gpu-dashboard" "opt-in: pass --perf (slow, load-sensitive)"
fi
markup_check() {
zig build || return 1
# shellcheck disable=SC2046
# -type f: zero-config app builds leave a .native/ cache DIRECTORY in
# example dirs, which the name pattern would otherwise match.
./zig-out/bin/native markup check $(find examples -name '*.native' -type f | sort)
}
run_step "markup-check" markup_check
if $docs_changed || $run_all; then
docs_check
else
skip_step "docs-check" "docs/ unchanged vs $base_ref (pass --all to force)"
fi
fi
# ---- summary --------------------------------------------------------------
total_secs=$(( $(date +%s) - gate_start ))
echo ""
echo "==================== gate $tier summary (base: $base_ref) ===================="
old_ifs="$IFS"; IFS='|'
set -- $step_names
names=("$@")
set -- $step_status
statuses=("$@")
set -- $step_secs
secs=("$@")
IFS="$old_ifs"
i=0
while [ "$i" -lt "${#names[@]}" ]; do
printf ' %-22s %-4s %4ss\n' "${names[$i]}" "${statuses[$i]}" "${secs[$i]}"
i=$((i + 1))
done
printf ' %-22s %-4s %4ss\n' "total" "$([ "$failures" -eq 0 ] && echo PASS || echo FAIL)" "$total_secs"
if [ "$failures" -gt 0 ]; then
echo "gate $tier: $failures step(s) failed" >&2
exit 1
fi
echo "gate $tier: all steps green"
+209
View File
@@ -0,0 +1,209 @@
#!/bin/sh
# Percentile GPU performance check for examples/gpu-dashboard.
#
# The single-launch smoke (test-gpu-dashboard-smoke) proves correctness with one
# load-tolerant latency sample; this harness measures a distribution:
#
# 1. Cold start: launches the automation build N times (NATIVE_SDK_PERF_LAUNCHES,
# default 5), recording gpu_first_frame_latency_ns from the automation
# snapshot for each launch.
# 2. Steady state: on the final (warm) launch, drives M "Auto refresh" switch
# clicks (NATIVE_SDK_PERF_INTERACTIONS, default 5), recording gpu_input_latency_ns
# (input receipt -> first present) per interaction.
# 3. Prints every sample plus min/median/p90/max per series, and asserts each
# series' p90 under its budget:
# NATIVE_SDK_PERF_BUDGET_MS first-frame p90 budget, default 300
# NATIVE_SDK_PERF_INPUT_BUDGET_MS input-latency p90 budget, default 100
# Defaults are deliberately generous (2x+ the smoke's 150 ms first-frame
# budget): this check exists to catch step-function regressions on shared
# CI/dev machines, not 10% drift on an idle box.
#
# p90 is nearest-rank: sorted[ceil(0.9 * n)]. At the default n=5 that IS the
# maximum of the five samples — with five samples the nearest-rank 90th
# percentile estimator selects the max, so "max of 5 under a generous budget"
# is not a shortcut but the estimator itself; raising NATIVE_SDK_PERF_LAUNCHES past 9
# starts discarding the worst outlier as expected.
#
# Usage: scripts/perf-gpu-dashboard.sh <native-sdk-cli>
# Expects examples/gpu-dashboard already built with -Dautomation=true
# (`zig build test-gpu-dashboard-perf` wires the build + this script).
set -eu
repo_root="$(cd "$(dirname "$0")/.." && pwd)"
cli="${1:?usage: scripts/perf-gpu-dashboard.sh <native-sdk-cli>}"
case "$cli" in /*) ;; *) cli="$repo_root/$cli" ;; esac
cd "$repo_root/examples/gpu-dashboard"
app="zig-out/bin/gpu-dashboard"
[ -x "$app" ] || { echo "perf: $app is missing; build examples/gpu-dashboard with -Dautomation=true first" >&2; exit 1; }
automation_dir=".zig-cache/native-sdk-automation"
mkdir -p "$automation_dir"
log=".zig-cache/native-sdk-gpu-dashboard-perf.log"
require_positive_int() { # name value
case "$2" in ''|*[!0-9]*) echo "perf: $1 must be a positive integer: $2" >&2; exit 1 ;; esac
[ "$2" -gt 0 ] || { echo "perf: $1 must be a positive integer: $2" >&2; exit 1; }
}
launches="${NATIVE_SDK_PERF_LAUNCHES:-5}"
interactions="${NATIVE_SDK_PERF_INTERACTIONS:-5}"
first_frame_budget_ms="${NATIVE_SDK_PERF_BUDGET_MS:-300}"
input_budget_ms="${NATIVE_SDK_PERF_INPUT_BUDGET_MS:-100}"
require_positive_int NATIVE_SDK_PERF_LAUNCHES "$launches"
require_positive_int NATIVE_SDK_PERF_INTERACTIONS "$interactions"
require_positive_int NATIVE_SDK_PERF_BUDGET_MS "$first_frame_budget_ms"
require_positive_int NATIVE_SDK_PERF_INPUT_BUDGET_MS "$input_budget_ms"
first_frame_budget_ns=$((first_frame_budget_ms * 1000000))
input_budget_ns=$((input_budget_ms * 1000000))
dump_diagnostics() { # failure forensics for shared CI runners: app log + canvas line
echo "---- app log ($log) ----" >&2
cat "$log" >&2 2>/dev/null || true
echo "---- dashboard-canvas snapshot line ----" >&2
read_snapshot
printf '%s\n' "$snapshot" | grep 'view @w1/dashboard-canvas' >&2 || echo "(no dashboard-canvas line in snapshot)" >&2
}
pid=""
trap 'kill "$pid" >/dev/null 2>&1 || true; wait "$pid" >/dev/null 2>&1 || true' EXIT
stop_app() {
kill "$pid" >/dev/null 2>&1 || true
wait "$pid" >/dev/null 2>&1 || true
pid=""
}
read_snapshot() {
snapshot="$(cat "$automation_dir/snapshot.txt" 2>/dev/null || true)"
}
canvas_field() { # field-name -> value of gpu_<field>=N on the dashboard-canvas line, or empty
printf '%s\n' "$snapshot" | sed -n "s/.*view @w1\\/dashboard-canvas kind=gpu_surface.* $1=\\([0-9][0-9]*\\).*/\\1/p"
}
ns_to_ms() { # ns -> ms with one decimal
awk "BEGIN { printf \"%.1f\", $1 / 1000000 }"
}
launch_and_measure_first_frame() {
rm -f "$automation_dir/snapshot.txt" "$automation_dir/accessibility.txt" "$automation_dir/windows.txt" "$automation_dir/command.txt"
"$app" > "$log" 2>&1 &
pid=$!
ready="$("$cli" automate wait 2>&1)"
case "$ready" in *"ready=true"*) ;; *) echo "perf: gpu-dashboard automation snapshot was not ready" >&2; dump_diagnostics; exit 1 ;; esac
first_frame_latency=""
attempts=0
while [ "$attempts" -lt 100 ]; do
read_snapshot
case "$snapshot" in
*'view @w1/dashboard-canvas kind=gpu_surface'*'gpu_nonblank=true'*)
first_frame_latency="$(canvas_field gpu_first_frame_latency_ns)"
case "$first_frame_latency" in ''|*[!0-9]*|0) ;; *) return 0 ;; esac
;;
esac
attempts=$((attempts + 1))
sleep 0.1
done
echo "perf: dashboard GPU first frame latency was not recorded within 10s" >&2
dump_diagnostics
exit 1
}
# ---- series bookkeeping -----------------------------------------------------
first_frame_samples=""
input_samples=""
append_sample() { # list-var-name value
eval "current=\$$1"
if [ -z "$current" ]; then eval "$1=\$2"; else eval "$1=\"\$current
\$2\""; fi
}
p90_rank() { # n -> nearest-rank index for p90: ceil(0.9 * n)
echo $(( (9 * $1 + 9) / 10 ))
}
series_stat() { # newline-list rank -> value at 1-based rank of ascending sort
printf '%s\n' "$1" | sort -n | sed -n "${2}p"
}
report_series() { # label list budget_ns budget_ms -> prints summary, returns 1 on p90 overrun
label="$1"; list="$2"; budget_ns="$3"; budget_ms="$4"
n="$(printf '%s\n' "$list" | wc -l | tr -d ' ')"
rank="$(p90_rank "$n")"
min="$(series_stat "$list" 1)"
median="$(series_stat "$list" $(( (n + 1) / 2 )))"
p90="$(series_stat "$list" "$rank")"
max="$(series_stat "$list" "$n")"
echo "perf: $label summary over $n samples: min $(ns_to_ms "$min") ms, median $(ns_to_ms "$median") ms, p90 $(ns_to_ms "$p90") ms, max $(ns_to_ms "$max") ms (budget $budget_ms ms)"
if [ "$p90" -gt "$budget_ns" ]; then
echo "perf: $label p90 exceeded the $budget_ms ms budget: $p90 ns ($(ns_to_ms "$p90") ms)" >&2
return 1
fi
return 0
}
# ---- cold start: N launches, first-frame latency ----------------------------
i=1
while [ "$i" -le "$launches" ]; do
launch_and_measure_first_frame
append_sample first_frame_samples "$first_frame_latency"
echo "perf: first-frame sample $i/$launches: $first_frame_latency ns ($(ns_to_ms "$first_frame_latency") ms)"
if [ "$i" -lt "$launches" ]; then stop_app; fi
i=$((i + 1))
done
# ---- steady state: M widget clicks on the final (warm) launch ---------------
# Each click of the "Auto refresh" switch requests a GPU frame; the runtime
# records gpu_input_latency_ns = input receipt -> first present that consumed
# it. A sample is ready once the snapshot shows this click's input timestamp
# AND a frame timestamp at/after it (that frame is what recorded the latency).
read_snapshot
prev_input_ts="$(canvas_field gpu_input_timestamp_ns)"
case "$prev_input_ts" in ''|*[!0-9]*) prev_input_ts=0 ;; esac
i=1
while [ "$i" -le "$interactions" ]; do
read_snapshot
switch_id="$(printf '%s\n' "$snapshot" | sed -n 's/.*widget @w1\/dashboard-canvas#\([0-9][0-9]*\) role=switch name="Auto refresh".*/\1/p' | head -1)"
case "$switch_id" in ''|*[!0-9]*) echo "perf: dashboard auto refresh switch id was missing from snapshot" >&2; exit 1 ;; esac
"$cli" automate widget-click dashboard-canvas "$switch_id" >/dev/null 2>&1
input_latency=""
attempts=0
# 200 x 50ms nominal; each iteration also spawns several subprocesses, so
# the real window is 2-4x longer on a loaded shared runner. That headroom
# only delays a genuine failure — a healthy click lands in a few frames.
while [ "$attempts" -lt 200 ]; do
read_snapshot
input_ts="$(canvas_field gpu_input_timestamp_ns)"
frame_ts="$(canvas_field gpu_timestamp_ns)"
case "$input_ts" in ''|*[!0-9]*) input_ts=0 ;; esac
case "$frame_ts" in ''|*[!0-9]*) frame_ts=0 ;; esac
if [ "$input_ts" -gt "$prev_input_ts" ] && [ "$frame_ts" -ge "$input_ts" ]; then
input_latency="$(canvas_field gpu_input_latency_ns)"
case "$input_latency" in ''|*[!0-9]*|0) input_latency="" ;; *) break ;; esac
fi
attempts=$((attempts + 1))
sleep 0.05
done
if [ -z "$input_latency" ]; then
echo "perf: widget-click $i did not produce a presented frame with recorded input latency within the wait window" >&2
dump_diagnostics
exit 1
fi
prev_input_ts="$input_ts"
append_sample input_samples "$input_latency"
echo "perf: input-latency sample $i/$interactions: $input_latency ns ($(ns_to_ms "$input_latency") ms)"
i=$((i + 1))
done
stop_app
# ---- summary + assertions ---------------------------------------------------
failures=0
report_series "first-frame latency" "$first_frame_samples" "$first_frame_budget_ns" "$first_frame_budget_ms" || failures=$((failures + 1))
report_series "input latency" "$input_samples" "$input_budget_ns" "$input_budget_ms" || failures=$((failures + 1))
[ "$failures" -eq 0 ] || exit 1
echo "gpu-dashboard perf ok"