diff --git a/CLAUDE.md b/CLAUDE.md index c04d980..a7da284 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -97,6 +97,24 @@ suffice to get out of it, and it must stay so. `bin/offline-kit.sh` assembles the equivalent package to carry, fingerprints included. +`bin/demo-kit.sh` assembles a different one, and the difference is the point: the offline kit +carries the tool towards an application one already has, the demo kit carries **an +application too** — `sample-app.jar`, its dependencies and its sources — so somebody can +replay the published campaign with a JDK and nothing else. Two decisions in it were made by +running it rather than by reading it, and both would have shipped a package that demonstrates +the opposite of what it is for: + +- **`--classes` names `sample-app.jar` alone.** Adding `libs/commons-lang3.jar`, which the + application really does reach and which the published demo really did pass, puts its 231 + classes into the coverage — none with its source in the package. The report then opened on + *"231 measured classes out of 258 have no source"*: the exact failure mode this tool exists + to explain, on the first screen of a demonstration. +- **`--attach-after` is computed from the workload.** Values are captured by attaching to the + live JVM, and the default 8 s suits the full run only. Shortening the campaign — which the + README offers — made the application finish before the attachment, and a third of what the + report shows disappeared with a warning nobody would connect to the `ITERATIONS=` they had + just typed. + ## When the report does not show what was expected This is the tool's most costly failure mode, because it is **silent**: an empty code panel diff --git a/README.md b/README.md index 093dec3..b9b9094 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,26 @@ async-profiler and Arthas (Apache-2.0) — that is what sets them apart from the which redistributes nothing. See [THIRD-PARTY.md](THIRD-PARTY.md). They are not published on Maven Central: the 170 KB jar stays the normal artefact. +#### The demonstration package, to try the tool without building it + +[`bin/demo-kit.sh`](bin/demo-kit.sh) assembles a package that carries the tool **and the +application it observes**, so the campaign published above can be replayed on any machine +with a JDK and nothing else: + +```bash +bin/demo-kit.sh # → target/runtime-xray-demo.zip (~18 MB) + +# then, wherever it is unzipped +./demo.sh # Linux / macOS demo.cmd on Windows +``` + +It holds the complete edition, `sample-app.jar` with its two dependencies, the sample's +sources — without them the report opens on "Source unavailable", which is the failure the +tool exists to explain rather than to demonstrate — and the three runs of the published +report, with their own root methods and filters. Under Windows the call tree is absent, and +that is the platform and not the package: async-profiler publishes no binary for it, and the +report now says so where the tree would be. + #### The kit, if one wants to see what is being carried [`bin/offline-kit.sh`](bin/offline-kit.sh) assembles the package to carry — the jar, the three diff --git a/bin/demo-kit.sh b/bin/demo-kit.sh new file mode 100755 index 0000000..ac05bcb --- /dev/null +++ b/bin/demo-kit.sh @@ -0,0 +1,262 @@ +#!/usr/bin/env bash +# Assembles the package that reproduces the published demonstration, locally, on a machine +# that has neither this repository nor a network. +# +# The offline kit next door answers a different question: it carries the tool towards an +# application one already has. This one carries an application TOO — the one the demo is +# made of — so that somebody can run the campaign, open the report, and compare it with +# https://beennnn.github.io/runtime-xray/multi/ without having anything to build. +# +# The three runs are the demo's own: same names, same root methods, same filters. Only the +# iteration counts are overridable, because they decide how long one waits and nothing else. +# +# bin/demo-kit.sh +set -uo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +KIT="$REPO_DIR/target/demo-kit/runtime-xray-demo" +ZIP="$REPO_DIR/target/runtime-xray-demo.zip" +VERSION="$(grep -m1 '' "$REPO_DIR/pom.xml" | sed 's/.*\(.*\)<\/version>.*/\1/')" + +echo "Demo kit — runtime-xray $VERSION" + +# The COMPLETE edition, and not the ordinary jar: the point of this package is that one +# unzips it and it runs. The ordinary jar would go looking for its three components on a +# Maven repository at the first launch, which is exactly the machine one does not have. +(cd "$REPO_DIR" && mvn -q -Pcomplet -DskipTests package) \ + || { echo " ❌ building the complete edition" >&2; exit 1; } + +APP_JAR="$REPO_DIR/sample-app/target/sample-app.jar" +TOOL_JAR="$REPO_DIR/orchestrator/target/runtime-xray-complet.jar" +for f in "$APP_JAR" "$TOOL_JAR"; do + [ -f "$f" ] || { echo " ❌ missing: $f" >&2; exit 1; } +done + +rm -rf "$REPO_DIR/target/demo-kit" +mkdir -p "$KIT/libs" "$KIT/src" + +cp "$TOOL_JAR" "$KIT/runtime-xray.jar" +cp "$APP_JAR" "$KIT/sample-app.jar" +# sample-app.jar names them in its manifest's Class-Path: without them it does not start. +cp "$REPO_DIR"/sample-app/target/libs/*.jar "$KIT/libs/" +# The sources are what the code panel displays. Without them the report opens, measures +# everything, and shows "Source unavailable" on every class — which is the very failure +# this tool exists to explain, and a poor thing to demonstrate. +cp -r "$REPO_DIR/sample-app/src/main/java/." "$KIT/src/" + +printf ' %-28s%s\n' "runtime-xray.jar" "$(( $(wc -c < "$KIT/runtime-xray.jar") / 1024 / 1024 )) MB, components embedded" +printf ' %-28s%s\n' "sample-app.jar + libs/" "the observed application" +printf ' %-28s%s\n' "src/" "$(find "$KIT/src" -name '*.java' | wc -l | tr -d ' ') source files" + +# ------------------------------------------------------------------ the campaign +# +# Written twice rather than once in a portable form: the two shells share no syntax worth +# the trouble, and a reader on either system must be able to read what is about to run. + +cat > "$KIT/demo.sh" <<'EOF' +#!/usr/bin/env bash +# Reproduces the published demonstration: three runs of the same application, observed +# differently, accumulated into one report. +# +# ./demo.sh the three runs, some two minutes +# WORKLOAD=2000000 ./demo.sh shorter, on a slower machine +# OUT=elsewhere ./demo.sh +set -euo pipefail +cd "$(dirname "$0")" + +OUT="${OUT:-report}" + +# The workload is not the same for the three runs, and the reason is not the scenario: +# capturing values means tracing EVERY invocation of the root method, and the two roots are +# not called at the same rate. Measured here, all runs observed identically: +# +# RoutePlanner::travelTimeMinutes 2 M → 5 s Terrain::slowdownFactor 1 M → 4 s +# RoutePlanner::travelTimeMinutes 8 M → 37 s Terrain::slowdownFactor 2 M → 5 s +# RoutePlanner::travelTimeMinutes 24 M → 206 s Terrain::slowdownFactor 4 M → 24 s +# Terrain::slowdownFactor 24 M → cut +# off at the 600 s guard rail +# +# The published demonstration gives all three runs 24 M, and pays 11 minutes for it — on the +# machine it was recorded on, where the middle run took 498 s. On a slower one that run +# crosses the guard rail and the tool stops it: a demonstration package whose middle run +# gets killed demonstrates the guard rail. So the full scenario carries the workload and the +# two others carry half of it. +# +# Note what the figures also say: past 4 M, the terrain run buys almost no extra sample. The +# time goes into the tracing, which is not in "lab/sample/terrain/*" and therefore does not +# count — a narrow filter is exactly what that run is there to show. +WORKLOAD="${WORKLOAD:-8000000}" +HALF=$(( WORKLOAD / 2 )) + +# The application's own classes, and them alone. Adding libs/commons-lang3.jar here — which +# the application does reach — puts its 231 classes into the coverage, none of which has its +# source in this package: the report then opens on "Source unavailable" 231 times over, +# which is the very failure this tool exists to explain and a poor thing to demonstrate. +COMMON=(--sources src --classes sample-app.jar --out "$OUT") + +# Values are captured by attaching to the live JVM: attach after it has ended and there is +# nothing to inspect. The default of 8 s suits the full workload; a shortened run needs a +# shortened delay, or one silently loses a third of what the report shows. +attach_for(){ local s=$(( $1 / 3000000 )); [ "$s" -lt 2 ] && s=2; [ "$s" -gt 8 ] && s=8; echo "$s"; } + +run(){ # name root method filter iterations + echo + echo "▶ $1" + java -jar runtime-xray.jar \ + --java "java -jar sample-app.jar --iterations $4" \ + --attach-after "$(attach_for "$4")" \ + --name "$1" --root "$2" --filter "$3" "${COMMON[@]}" +} + +run "Scenario 1" "lab.sample.RoutePlanner::travelTimeMinutes" "lab/sample/*" "$HALF" +run "Scenario 2" "lab.sample.terrain.Terrain::slowdownFactor" "lab/sample/terrain/*" "$HALF" +run "Full scenario" "lab.sample.RoutePlanner::travelTimeMinutes" "lab/sample/*" "$WORKLOAD" + +echo +echo "Report: $OUT/index.html" +echo "Compare with https://beennnn.github.io/runtime-xray/multi/" +EOF +chmod +x "$KIT/demo.sh" + +cat > "$KIT/demo.cmd" <<'EOF' +@echo off +rem Reproduces the published demonstration: three runs of the same application, observed +rem differently, accumulated into one report. +rem +rem demo.cmd the three runs +rem set ITERATIONS=4000000 & demo.cmd +setlocal +cd /d "%~dp0" + +if "%OUT%"=="" set OUT=report + +rem The first two runs carry half the workload: capturing values means tracing every +rem invocation of the root method, and Terrain::slowdownFactor is called far more often +rem than RoutePlanner::travelTimeMinutes. See demo.sh for the measured figures. +if "%WORKLOAD%"=="" set WORKLOAD=8000000 +set /a HALF=%WORKLOAD%/2 + +rem The application's own classes and them alone: adding libs\commons-lang3.jar would put +rem its 231 source-less classes into the coverage of a package meant to demonstrate. +set COMMON=--sources src --classes sample-app.jar --out "%OUT%" + +rem Values are captured by attaching to the live JVM. The default delay of 8 s suits the +rem full workload; a shortened run needs a shortened delay or the values are lost. +set /a ATTACH=%WORKLOAD%/3000000 +if %ATTACH% LSS 2 set ATTACH=2 +if %ATTACH% GTR 8 set ATTACH=8 + +echo. +echo ^> Scenario 1 +java -jar runtime-xray.jar --java "java -jar sample-app.jar --iterations %HALF%" ^ + --attach-after 2 ^ + --name "Scenario 1" --root "lab.sample.RoutePlanner::travelTimeMinutes" ^ + --filter "lab/sample/*" %COMMON% || exit /b 1 + +echo. +echo ^> Scenario 2 +java -jar runtime-xray.jar --java "java -jar sample-app.jar --iterations %HALF%" ^ + --attach-after 2 ^ + --name "Scenario 2" --root "lab.sample.terrain.Terrain::slowdownFactor" ^ + --filter "lab/sample/terrain/*" %COMMON% || exit /b 1 + +echo. +echo ^> Full scenario +java -jar runtime-xray.jar --java "java -jar sample-app.jar --iterations %WORKLOAD%" ^ + --attach-after %ATTACH% ^ + --name "Full scenario" --root "lab.sample.RoutePlanner::travelTimeMinutes" ^ + --filter "lab/sample/*" %COMMON% || exit /b 1 + +echo. +echo Report: %OUT%\index.html +echo Compare with https://beennnn.github.io/runtime-xray/multi/ +EOF + +# ---------------------------------------------------------------- instructions + +cat > "$KIT/README.txt" < SHA256 +EOF + +(cd "$KIT" && find . -type f ! -name SHA256SUMS.txt -print0 | sort -z \ + | xargs -0 sha256sum > SHA256SUMS.txt) + +# ------------------------------------------------------------------- archive + +rm -f "$ZIP" +if command -v zip >/dev/null 2>&1; then + (cd "$REPO_DIR/target/demo-kit" && zip -qr "$ZIP" runtime-xray-demo) +else + (cd "$REPO_DIR/target/demo-kit" && jar --create --file "$ZIP" --no-manifest runtime-xray-demo) +fi +[ -s "$ZIP" ] || { echo " ❌ archive not produced" >&2; exit 1; } + +echo +echo " ✅ $ZIP ($(( $(wc -c < "$ZIP") / 1024 / 1024 )) MB)"