Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 48 additions & 20 deletions examples/demo-01-image-strategy/demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
#
# Image strategy is the lowest-hanging-fruit performance and security win in
# containerized C++. A multi-stage build leaves the toolchain (GCC, ld, headers,
# build deps) OUT of the runtime image — a ~26× size drop from the naive
# single-stage baseline to ubi-micro, and a matching cut in CVE surface — with
# build deps) OUT of the runtime image — a ~20-26× size drop from the naive
# single-stage baseline to ubi-micro (the script prints the exact multiplier
# for this build), and a matching cut in CVE surface — with
# NO measurable p50 penalty. LTO plus a representative PGO profile then buys a
# further few percent on the hot path, essentially for free once the pipeline
# is in place.
Expand Down Expand Up @@ -99,6 +100,8 @@ callout \
"Ports: bench containers on ${PORT_BASE}+ (one per variant)" \
"Load: hey -n 5000 -c 50 per variant → p50/p95/p99" \
"PGO: $([[ $DO_PGO -eq 1 ]] && echo 'enabled (two-pass build + training run)' || echo 'skipped (--no-pgo)')"
callout "" "The service itself is one file — the SAME binary in every variant:"
code_ref "src/main.cpp" 65 "httplib routing (/, /echo, /healthz, /metrics) — constexpr-lean, zero-alloc startup"
pause "Ready to build? Press Enter"

# ── Step 1: Build the image variants ────────────────────────────────────────
Expand All @@ -107,29 +110,37 @@ callout "First run compiles from source; later runs hit the podman layer cache."
"Never proceed to measurement after a failed build — a broken image would" \
"just show up as a bogus benchmark row."

log_step "Building UBI multi-stage (LTO on, no PGO)"
if ! podman build -f Containerfile.ubi-multistage -t "${IMG_PREFIX}:ubi-multistage" .; then
log_err "ubi-multistage build failed — stopping (nothing valid to measure)."
exit 1
if should_build "${IMG_PREFIX}:ubi-multistage"; then
log_step "Building UBI multi-stage (LTO on, no PGO)"
if ! podman build -f Containerfile.ubi-multistage -t "${IMG_PREFIX}:ubi-multistage" .; then
log_err "ubi-multistage build failed — stopping (nothing valid to measure)."
exit 1
fi
fi

log_step "Building UBI-micro (fully-static binary, production answer)"
if ! podman build -f Containerfile.ubi-micro -t "${IMG_PREFIX}:ubi-micro" .; then
log_err "ubi-micro build failed — stopping (nothing valid to measure)."
exit 1
if should_build "${IMG_PREFIX}:ubi-micro"; then
log_step "Building UBI-micro (fully-static binary, production answer)"
if ! podman build -f Containerfile.ubi-micro -t "${IMG_PREFIX}:ubi-micro" .; then
log_err "ubi-micro build failed — stopping (nothing valid to measure)."
exit 1
fi
fi

log_step "Building UBI-micro-glibc-mismatch (TEACHING REFERENCE — intentionally fails at runtime)"
if ! podman build -f Containerfile.ubi-micro-glibc-mismatch -t "${IMG_PREFIX}:ubi-micro-glibc-mismatch" .; then
log_err "ubi-micro-glibc-mismatch build failed — stopping (this variant must BUILD;"
log_err "it's only meant to fail at RUNTIME, which is the lesson)."
exit 1
if should_build "${IMG_PREFIX}:ubi-micro-glibc-mismatch"; then
log_step "Building UBI-micro-glibc-mismatch (TEACHING REFERENCE — intentionally fails at runtime)"
if ! podman build -f Containerfile.ubi-micro-glibc-mismatch -t "${IMG_PREFIX}:ubi-micro-glibc-mismatch" .; then
log_err "ubi-micro-glibc-mismatch build failed — stopping (this variant must BUILD;"
log_err "it's only meant to fail at RUNTIME, which is the lesson)."
exit 1
fi
fi

log_step "Building naive single-stage (anti-pattern)"
if ! podman build -f Containerfile.single-stage-naive -t "${IMG_PREFIX}:single-stage-naive" .; then
log_err "single-stage-naive build failed — stopping (nothing valid to measure)."
exit 1
if should_build "${IMG_PREFIX}:single-stage-naive"; then
log_step "Building naive single-stage (anti-pattern)"
if ! podman build -f Containerfile.single-stage-naive -t "${IMG_PREFIX}:single-stage-naive" .; then
log_err "single-stage-naive build failed — stopping (nothing valid to measure)."
exit 1
fi
fi

callout "" "Four images built. The single-stage one still ships GCC, ld, and the" \
Expand All @@ -139,6 +150,9 @@ pause

# ── Step 2: PGO two-pass build (optional) ────────────────────────────────────
if [[ $DO_PGO -eq 1 ]]; then
if [[ "${DEMO_NO_BUILD:-0}" == "1" ]] && image_exists "${IMG_PREFIX}:pgo"; then
log_info "Reusing existing ${IMG_PREFIX}:pgo (DEMO_NO_BUILD=1) — skipping the two-pass PGO build."
else
demo_step "PGO two-pass build: instrument → train → rebuild with the profile"
callout "GCC PGO is three phases run back-to-back: compile an instrumented" \
"binary, drive it with a representative workload to gather .gcda profile" \
Expand Down Expand Up @@ -200,6 +214,7 @@ if [[ $DO_PGO -eq 1 ]]; then
"below is PGO alone — nothing else changed."
fi
pause
fi
else
log_info "PGO skipped (--no-pgo)."
fi
Expand All @@ -217,7 +232,20 @@ podman images \
| sort -u \
| column -t \
|| true
callout "" "The naive single-stage image is ~26× the size of ubi-micro, and almost" \

# Compute the naive-vs-micro multiplier from the ACTUAL image bytes rather
# than hard-coding a number that drifts as base images change. inspect gives
# raw bytes; awk turns it into a clean "NN×".
naive_b=$(podman image inspect -f '{{.Size}}' "${IMG_PREFIX}:single-stage-naive" 2>/dev/null || echo 0)
micro_b=$(podman image inspect -f '{{.Size}}' "${IMG_PREFIX}:ubi-micro" 2>/dev/null || echo 0)
if [[ "${micro_b}" -gt 0 && "${naive_b}" -gt 0 ]]; then
RATIO=$(awk -v n="$naive_b" -v m="$micro_b" 'BEGIN{printf "%.0f", n/m}')
RATIO_TXT="~${RATIO}× the size of ubi-micro"
else
RATIO_TXT="many times the size of ubi-micro"
fi

callout "" "The naive single-stage image is ${RATIO_TXT}, and almost" \
"all of that gap is the toolchain sitting in production: GCC, ld, headers," \
"build deps — none needed at runtime, all of them CVE surface. Multi-stage" \
"drops them; ubi-micro also statically links libstdc++ for the smallest floor." \
Expand Down
12 changes: 8 additions & 4 deletions examples/demo-02-stl-layout/demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,20 @@ callout \
"Sizes: 64 · 1024 · 16384 · 262144 Ops: point lookup · iterate-sum" \
"Pressure: cgroup memory.max=$MEMORY_LIMIT, no swap" \
"Outputs: results-baseline.json · results-pressured.json + table"
callout "" "The four containers and both benchmarks live in one file:"
code_ref "src/main.cpp" 39 "container choices + BM_Lookup_Hit / BM_IterateAndSum (Google Benchmark)"

# ── Step 1: Build the bench image ───────────────────────────────────────────
demo_step "Build the benchmark image"
callout "First build ~3-5 min (Conan pulls boost + Google Benchmark)." \
"Subsequent runs hit the podman layer cache (~30s for both phases)."
if ! podman build -f Containerfile -t "$IMAGE" .; then
log_err "podman build failed — nothing to benchmark. Stopping here."
exit 1
if should_build "$IMAGE"; then
if ! podman build -f Containerfile -t "$IMAGE" .; then
log_err "podman build failed — nothing to benchmark. Stopping here."
exit 1
fi
log_ok "Image built: $IMAGE"
fi
log_ok "Image built: $IMAGE"
pause

# ── Step 2: Phase 1 — baseline (no memory limit) ────────────────────────────
Expand Down
48 changes: 40 additions & 8 deletions examples/demo-03-io-uring-grpc/demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,27 @@ while [[ $# -gt 0 ]]; do
case "$1" in
--keep) KEEP_UP=1; shift ;;
--clean) CLEAN_ONLY=1; shift ;;
--stack-down) CLEAN_ONLY=1; shift ;; # demo-03's clean is already image-preserving
--production) USE_PRODUCTION=1; shift ;;
--no-pause) export DEMO_NO_PAUSE=1; shift ;;
-h|--help) sed -n '2,45p' "$0"; exit 0 ;;
*) log_err "unknown arg: $1"; exit 2 ;;
esac
done

# The presentation cockpit can ask us to leave the stack up (DEMO_KEEP_STACK=1)
# so the next run of a stack demo skips bring-up. Same effect as --keep.
[[ "${DEMO_KEEP_STACK:-0}" == "1" ]] && KEEP_UP=1

require podman

OBS="$REPO_ROOT/observability/compose.yml"

# Absolute paths for the tutorial dashboard mounts (relative paths in the
# included observability/compose.yml would resolve against this demo's dir).
export OBS_DASHBOARDS_DIR="$REPO_ROOT/observability/grafana/dashboards"
export OBS_PROVIDER_FILE="$REPO_ROOT/observability/grafana/otel-provisioning/tutorial-dashboards.yaml"

if (( USE_PRODUCTION )); then
# Verify the one-time host setup is in place before bringing up.
# Each check is short; failures point at the corresponding
Expand Down Expand Up @@ -150,12 +160,21 @@ callout \
"io_uring direct: :9000 (raw liburing submission/completion ring)" \
"Asio io_uring: :9001 (same kernel calls, executor abstraction)" \
"Grafana: $GRAFANA_URL (anonymous viewer)"
callout "" "Three server heads, three source files worth opening:"
code_ref "src/grpc_async_server.cpp" 67 "async gRPC completion-queue worker loop (Proceed() state machine)"
code_ref "src/echo_uring.cpp" 1 "raw liburing submission/completion ring (multishot on kernels ≥6.0)"
code_ref "proto/echo.proto" 1 "the Echo service contract"

# ── Step 1: Build and bring up ─────────────────────────────────────────
demo_step "Build the demo image and bring up the stack + LGTM backend"
callout "First build is ~30-45 min (OTel + gRPC + asio compiled from source" \
"under the override profile). Warm rebuilds are ~2-3 min."
if ! "${COMPOSE[@]}" up -d --build; then
BUILD_FLAG="--build"
if [[ "${DEMO_NO_BUILD:-0}" == "1" ]] && image_exists "cpp-tut/demo-03:latest"; then
log_info "Reusing cpp-tut/demo-03:latest (DEMO_NO_BUILD=1) — starting without --build"
BUILD_FLAG=""
fi
if ! "${COMPOSE[@]}" up -d ${BUILD_FLAG}; then
log_err "compose up failed — not going any further (nothing to load)."
"${COMPOSE[@]}" logs --tail=40 demo-03-svc 2>&1 || true
exit 1
Expand Down Expand Up @@ -276,12 +295,18 @@ pause
# ── Step 5: Grafana — the gRPC instrumentation ─────────────────────────
demo_step "Inspect the gRPC instrumentation in Grafana"
grafana_callout "$GRAFANA_URL" \
"Explore → Prometheus / Tempo (no prebuilt demo-03 dashboard)" \
"demo3.grpc.latency — per-method latency histogram (Prometheus)" \
"demo3.grpc.requests — request counter, rolls up by status code" \
"demo3.tcp.iouring.connections — direct liburing connection gauge" \
"demo3.tcp.asio.connections — Asio backend connection gauge" \
"Traces (Tempo) — drill into individual RPC spans"
"'Demo 03 — io_uring + async gRPC' — Tutorial folder (Dashboards → Browse)" \
"gRPC request rate (stat) — demo3_grpc_requests_total" \
"gRPC latency p50/95/99 (timeseries) — demo3_grpc_latency_milliseconds histogram" \
"TCP conns/s (timeseries) — io_uring direct vs Asio" \
"Recent gRPC traces (table) — Tempo, click a row for the span tree" \
"Service logs (logs) — Loki"
callout "" "Prefer Explore? Paste these into a Prometheus Explore query:" \
" sum(rate(demo3_grpc_requests_total[1m]))" \
" histogram_quantile(0.99, sum(rate(demo3_grpc_latency_milliseconds_bucket[1m])) by (le))" \
" sum(rate(demo3_tcp_iouring_connections_total[1m]))" \
" sum(rate(demo3_tcp_asio_connections_total[1m]))" \
"…and in a Tempo Explore, TraceQL: { resource.service.name=\"demo-03-svc\" }"
callout "The TCP echo servers are deliberately un-instrumented (they're the" \
"'floor'); only the gRPC path carries OTel, so you can see what the" \
"semantics cost — in latency AND in observable surface area."
Expand All @@ -290,7 +315,14 @@ pause
# ── Teardown ────────────────────────────────────────────────────────────
echo
if (( KEEP_UP == 0 )); then
pause "Press Enter to tear down the stack (or Ctrl-C to leave it running)"
# Under the presentation cockpit, DON'T invite Ctrl-C here — a Ctrl-C at
# this prompt would SIGINT the whole orchestrated run. Tear down quietly
# (the EXIT trap does the actual work) and let the cockpit move on.
if [[ "${DEMO_ORCHESTRATED:-0}" == "1" ]]; then
log_info "Orchestrated run — tearing down demo-03 stack and continuing."
else
pause "Explore Grafana now if you like, then press Enter to tear down the stack"
fi
else
log_ok "Demo 03 complete — stack left up (--keep)."
fi
Expand Down
47 changes: 44 additions & 3 deletions examples/demo-04-observability/demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
# ./demo.sh full run (build, bring up, load, verify)
# ./demo.sh --workload-only skip build/bring-up; drive an already-up stack
# ./demo.sh --bpftrace also run the kernel-level bpftrace view (sudo)
# ./demo.sh --keep leave the stack up at the end (fast re-runs)
# ./demo.sh --no-pause never stop for Enter (unattended)
# ./demo.sh --clean tear the stack down and remove the image
# ============================================================================
Expand All @@ -42,30 +43,53 @@ source "$(cd ../../scripts/lib && pwd)/_helpers.sh"
OBS_COMPOSE="$(cd ../../observability && pwd)/compose.yml"
COMPOSE=(podman compose -f compose.yml -f "$OBS_COMPOSE")

# Absolute paths for the tutorial dashboard mounts. Relative paths in the
# included observability/compose.yml would resolve against THIS demo's dir
# (the compose project dir), so the dashboards must be passed in as absolutes.
OBS_DIR="$(dirname "$OBS_COMPOSE")"
export OBS_DASHBOARDS_DIR="$OBS_DIR/grafana/dashboards"
export OBS_PROVIDER_FILE="$OBS_DIR/grafana/otel-provisioning/tutorial-dashboards.yaml"

GRAFANA_URL="http://127.0.0.1:3000"
SVC_URL="http://127.0.0.1:18401"

WORKLOAD_ONLY=0
DO_BPFTRACE=0
DO_CLEAN=0
DO_STACK_DOWN=0
KEEP_UP=0
while [[ $# -gt 0 ]]; do
case "$1" in
--workload-only) WORKLOAD_ONLY=1; shift;;
--bpftrace) DO_BPFTRACE=1; shift;;
--keep) KEEP_UP=1; shift;;
--no-pause) export DEMO_NO_PAUSE=1; shift;;
--clean) DO_CLEAN=1; shift;;
--stack-down) DO_STACK_DOWN=1; shift;;
-h|--help) sed -n '2,33p' "$0"; exit 0;;
*) log_err "unknown arg: $1"; exit 2;;
esac
done

# The presentation cockpit can ask us to leave the stack up (DEMO_KEEP_STACK=1)
# so the next run skips bring-up. Same effect as --keep.
[[ "${DEMO_KEEP_STACK:-0}" == "1" ]] && KEEP_UP=1

if [[ $DO_CLEAN -eq 1 ]]; then
"${COMPOSE[@]}" down -v 2>/dev/null || true
podman rmi -f cpp-tut/demo-04:latest 2>/dev/null || true
log_ok "Cleaned."
exit 0
fi

# --stack-down: tear down the running stack but KEEP the image (so a re-run
# doesn't recompile). Used by the cockpit to free :3000 for the other stack demo.
if [[ $DO_STACK_DOWN -eq 1 ]]; then
"${COMPOSE[@]}" down -v 2>/dev/null || true
log_ok "Stack down (image kept)."
exit 0
fi

require podman
HAVE_HEY=1; command -v hey >/dev/null 2>&1 || HAVE_HEY=0
HAVE_JQ=1; command -v jq >/dev/null 2>&1 || HAVE_JQ=0
Expand All @@ -85,7 +109,12 @@ if [[ $WORKLOAD_ONLY -eq 0 ]]; then
demo_step "Build the service and bring up the LGTM stack"
callout "First run compiles opentelemetry-cpp from source (~10-20 min)." \
"Later runs hit the podman layer cache (~2-3 min)."
if ! "${COMPOSE[@]}" up -d --build; then
BUILD_FLAG="--build"
if [[ "${DEMO_NO_BUILD:-0}" == "1" ]] && image_exists "cpp-tut/demo-04:latest"; then
log_info "Reusing cpp-tut/demo-04:latest (DEMO_NO_BUILD=1) — starting without --build"
BUILD_FLAG=""
fi
if ! "${COMPOSE[@]}" up -d ${BUILD_FLAG}; then
log_err "compose up failed — not going any further (nothing to observe)."
"${COMPOSE[@]}" logs --tail=40 demo-04-svc 2>&1 || true
exit 1
Expand Down Expand Up @@ -121,6 +150,8 @@ callout "Every GET / does three things in ~40 lines of C++:" \
" • starts a span 'handle_request' with a child span 'compute' (TRACE)" \
" • increments demo.requests and records demo.request.duration (METRICS)" \
" • emits a 'request handled' log record (LOGS)"
code_ref "src/main.cpp" 162 "the GET / handler — StartSpan, counter->Add, hist->Record, EmitLogRecord"
code_ref "src/main.cpp" 153 "provider wiring — GetTracer/GetMeter/GetLogger, same as Java/Go"
echo
printf ' Priming a few requests: '
for _ in 1 2 3 4 5; do curl -sf "$SVC_URL/" >/dev/null 2>&1 && printf '.'; done
Expand Down Expand Up @@ -247,6 +278,16 @@ log_info " Service: $SVC_URL"

# ── Teardown ────────────────────────────────────────────────────────────────
echo
pause "Press Enter to tear down the stack (or Ctrl-C to leave it running)"
"${COMPOSE[@]}" down -v 2>/dev/null || true
if (( KEEP_UP == 1 )); then
log_ok "Stack left running (Grafana $GRAFANA_URL). Tear down with: ./demo.sh --clean"
else
# Under the presentation cockpit, don't invite Ctrl-C — it would SIGINT the
# whole orchestrated run. Tear down quietly and let the cockpit continue.
if [[ "${DEMO_ORCHESTRATED:-0}" == "1" ]]; then
log_info "Orchestrated run — tearing down demo-04 stack and continuing."
else
pause "Explore Grafana now if you like, then press Enter to tear down the stack"
fi
"${COMPOSE[@]}" down -v 2>/dev/null || true
fi
log_ok "Demo 04 complete."
18 changes: 12 additions & 6 deletions examples/demo-05-isolation/demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ callout \
"tenant-b: the 'noisy neighbor' — pegs CPU in a tight loop, no limits" \
"Images: $IMG_A · $IMG_B" \
"Knobs: cpu.weight (bound interference) · cpuset.cpus (dedicate CPUs)"
callout "" "Both tenants are the SAME binary — behaviour differs only by cgroup:"
code_ref "src/main.cpp" 84 "the request-path CPU work + tuned httplib thread pool (twin source)"

# ── Step 1: Check the host — cgroup v2 controller delegation ─────────────
demo_step "Check the host: cgroup v2 controller delegation"
Expand Down Expand Up @@ -133,13 +135,17 @@ pause
demo_step "Build both tenants"
callout "tenant-a is the HTTP service we probe; tenant-b is the CPU/memory" \
"hog. First run adds a Conan + Containerfile build (~2-3 min)."
if ! podman build --target tenant-a -t "$IMG_A" .; then
log_err "tenant-a build failed — cannot measure anything without it."
exit 1
if should_build "$IMG_A"; then
if ! podman build --target tenant-a -t "$IMG_A" .; then
log_err "tenant-a build failed — cannot measure anything without it."
exit 1
fi
fi
if ! podman build --target tenant-b -t "$IMG_B" .; then
log_err "tenant-b build failed — cannot run the noisy-neighbor scenarios."
exit 1
if should_build "$IMG_B"; then
if ! podman build --target tenant-b -t "$IMG_B" .; then
log_err "tenant-b build failed — cannot run the noisy-neighbor scenarios."
exit 1
fi
fi

# Detect NUMA topology so we can decide whether the 'pinned' scenario
Expand Down
Loading
Loading