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
33 changes: 20 additions & 13 deletions scripts/new
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@

set -eu

# Force the C locale so the [a-z0-9._-] range below means ASCII — under
# some locales the collation order folds uppercase into [a-z].
export LC_ALL=C

# Resolve the template relative to this script (which lives in scripts/),
# so it works from any working directory.
SRC="$(cd "$(dirname "$0")/.." && pwd)/template"
Expand All @@ -20,13 +16,15 @@ if [ -z "$DEST" ]; then
fi
NAME="${2:-$(basename "$DEST")}"

# npm package names: lowercase letters, digits, dash, underscore, dot.
case "$NAME" in
*[!a-z0-9._-]*)
echo "error: invalid project name '$NAME' (use [a-z0-9._-])" >&2
exit 1
;;
esac
# npm package names: lowercase letters, digits, dash, underscore, dot. The
# check runs in the C locale so the [a-z0-9._-] range means ASCII — under
# some locales the collation order folds uppercase into [a-z]. Only the check:
# the rest of the script (and the make it calls) keeps the user's locale so
# the output marks match the build's.
if printf '%s' "$NAME" | LC_ALL=C grep -q '[^a-z0-9._-]'; then
echo "error: invalid project name '$NAME' (use [a-z0-9._-])" >&2
exit 1
fi

if [ ! -d "$SRC" ]; then
echo "error: template/ not found — nothing to copy" >&2
Expand Down Expand Up @@ -57,6 +55,15 @@ done

# Finalize via the project's Makefile (git init with the vendored git). The
# generation logic lives in `make postgen`, not here — this just calls it.
make -C "$DEST" postgen || echo "warning: 'make postgen' failed" >&2
make --no-print-directory -C "$DEST" postgen || echo "warning: 'make postgen' failed" >&2

echo "created '$NAME' in $DEST — cd there, run 'yeet build', then 'yeet run'"
# Match the project's build output (build/ui.mk): a row with a tag column,
# marks in UTF-8 locales only, color only on a terminal.
if [ -t 2 ] && [ -z "${NO_COLOR:-}" ] && [ "${TERM:-}" != dumb ]; then
fmt='\033[1;32m%s\033[0m \033[1;36m%-10s\033[0m %s\n'
else
fmt='%s %-10s %s\n'
fi
case "$(locale charmap 2>/dev/null)" in UTF-8) ok=✔ ;; *) ok=+ ;; esac
printf "$fmt" "$ok" created "$NAME $DEST" >&2
printf "$fmt" ' ' next "cd $DEST && make && yeet run ." >&2
9 changes: 5 additions & 4 deletions template/.github/workflows/kernel-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,18 @@ jobs:
make bpf

# Resolve the vendored static veristat the same way build/toolchain.mk
# does, and stage it into bin/ so the VM finds it under /host. It is
# fully static, so it runs in any kernel image's rootfs.
# does, and stage it into .kmatrix/ (the matrix workspace, not bin/,
# which holds only build products) so the VM finds it under /host.
# It is fully static, so it runs in any kernel image's rootfs.
. build/toolchain.lock
arch="$(uname -m)"; [ "$arch" = arm64 ] && arch=aarch64
cache="${XDG_CACHE_HOME:-$HOME/.cache}/yeet/toolchain/v${TOOLCHAIN_VERSION}/${arch}"
if [ ! -x "$cache/veristat" ]; then
echo "::error::veristat is not in the pinned toolchain (v${TOOLCHAIN_VERSION}). Bump build/toolchain.lock to a toolchain release that ships veristat."
exit 1
fi
install -Dm755 "$cache/veristat" bin/veristat
file bin/veristat bin/probe.bpf.o
install -Dm755 "$cache/veristat" .kmatrix/veristat
file .kmatrix/veristat bin/probe.bpf.o

- name: Verify on kernel ${{ matrix.kernel }}
uses: cilium/little-vm-helper@v0.0.30
Expand Down
7 changes: 3 additions & 4 deletions template/.gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
# build artifacts (the esbuild bundle lands in dist/index.jsx)
# build artifacts (the esbuild bundle lands in dist/index.js)
/node_modules/
/dist/
/.build/

# compiled BPF objects + generated CO-RE header
/bin/*
!/bin/.gitkeep
# compiled BPF objects (bin/ exists only once built) + generated CO-RE header
/bin/
/src/bpf/include/vmlinux.h

# kernel-matrix run output (build/kernel-matrix.sh)
Expand Down
28 changes: 13 additions & 15 deletions template/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,17 @@ include build/bpf.mk
BUILD_START := $(shell date +%s%N 2>/dev/null)

all: bpf bundle
@end=$$(date +%s%N 2>/dev/null); t=""; \
case "$(BUILD_START)$$end" in \
""|*[!0-9]*) ;; \
*) t=" in $$(awk "BEGIN { printf \"%.2fs\", ($$end - $(BUILD_START)) / 1e9 }")" ;; \
esac; \
printf '$(STATUS_FMT)' 'Finished' "build$$t" >&2
$(call done,$(notdir $(CURDIR)))
$(call artifact,$(BPF_OUT))
$(call artifact,dist/index.js)

# Bundle the entry with the vendored esbuild. esbuild honors tsconfig `paths`
# (so `@/` resolves at bundle time), while `yeet:*` builtins and `*.bpf.o`
# objects stay external. The bundle is written to dist/index.jsx: the entry
# objects stay external. The bundle is written to dist/index.js: the entry
# ladder tries dist/ before src/, so once built, that is what runs — and the
# generated file stays out of the source tree (dist/ is gitignored).
# The .jsx extension keeps the bundle eligible for component auto-mount.
# generated file stays out of the source tree (dist/ is gitignored). Plain
# .js is fine: JSX is compiled away by esbuild, and src/main.jsx mounts its
# root component explicitly, so the bundle needs no auto-mount.
# Compiled BPF objects in bin/ are loaded by path at runtime, never imported,
# so they are not bundled.
#
Expand All @@ -53,11 +51,10 @@ all: bpf bundle
ESBUILD_FLAGS := --bundle --format=esm --platform=neutral \
--main-fields=module,main --conditions=import,module \
--define:import.meta.main=false \
--outfile=dist/index.jsx --jsx=automatic --jsx-import-source=yeet:tui
--outfile=dist/index.js --jsx=automatic --jsx-import-source=yeet:tui

bundle: | toolchain
$(Q)$(ESBUILD) src/main.jsx $(ESBUILD_FLAGS) --log-level=warning '--external:yeet:*' '--external:*.bpf.o'
@printf '$(STATUS_FMT)' 'Bundling' "src/main.jsx -> dist/index.jsx ($$(wc -c < dist/index.jsx | awk '{ printf "%.1fkb", $$1 / 1024 }'))" >&2
$(call step,bundle,dist/index.js,$(ESBUILD) src/main.jsx $(ESBUILD_FLAGS) --log-level=warning '--external:yeet:*' '--external:*.bpf.o')

# Post-generation finalize: initialize a git repository with the vendored git
# (fetched via `vendored-git`). Idempotent — skipped if this is already a repo.
Expand All @@ -66,16 +63,17 @@ bundle: | toolchain
postgen: | vendored-git
@g="$(GIT)"; $$g --version >/dev/null 2>&1 || g="$$(command -v git 2>/dev/null || true)"; \
if [ -e .git ]; then \
printf '$(STATUS_FMT)' 'Skipping' 'git init (already a repository)' >&2; \
printf '$(STATUS_FMT)' 'git' 'already a repository' >&2; \
elif [ -n "$$g" ]; then \
printf '$(STATUS_FMT)' 'Initializing' 'git repository' >&2; \
printf '$(STATUS_FMT)' 'git' 'initialized repository' >&2; \
$$g -c init.templateDir= init -q . || printf '$(WARN_FMT)' 'git init failed' >&2; \
else \
printf '$(WARN_FMT)' 'no git available (vendored or host); skipping git init' >&2; \
fi

clean: clean-bpf
$(call status,Cleaning,node_modules dist)
$(call status,clean,node_modules)
$(call status,clean,dist)
$(Q)rm -rf node_modules dist

.PHONY: all bundle clean postgen
6 changes: 3 additions & 3 deletions template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ src/components/*.jsx pure UI: titlebar, heatmap, detail, histogram, footer
src/lib/format.js pure render helpers (rate, duration, heat color)
src/bpf/cpusched.bpf.c sched_switch program + the runtime knob
src/bpf/runqlat.bpf.c wakeup→on-CPU latency histogram
bin/ the linked BPF object lands here
bin/ the linked BPF object lands here (created by the build)
dist/ the esbuild JS bundle lands here (gitignored)
.github/workflows/kernel-matrix.yml CI: verify the object across kernels
build/verify-kernel.sh the in-VM gate that workflow runs
Expand All @@ -65,12 +65,12 @@ the `@/` alias; `main.jsx` wires them together and owns input.
```sh
make # compile BPF (clang + bpftool) + bundle JS (esbuild)
make V=1 # same, showing every command behind the status lines
yeet run . # runs the bundled dist/index.jsx (needs root for BPF)
yeet run . # runs the bundled dist/index.js (needs root for BPF)
```

`make` runs two independent compilers: **clang + bpftool** compile
`src/bpf/*.bpf.c` and link them into one loadable object `bin/probe.bpf.o`;
**esbuild** bundles `src/main.jsx` into `dist/index.jsx`, resolving the `@/`
**esbuild** bundles `src/main.jsx` into `dist/index.js`, resolving the `@/`
alias (and inlining any npm/jsr deps you add) and leaving `yeet:*` builtins
external. esbuild is vendored by the toolchain, so the build needs no
node/npm.
Expand Down
Empty file removed template/bin/.gitkeep
Empty file.
26 changes: 13 additions & 13 deletions template/build/bpf.mk
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,29 @@ bpf: $(BPF_OUT)
# the cache before any rule shells out to them, without forcing rebuilds.
$(VMLINUX): | toolchain
@command -v $(firstword $(BPFTOOL)) >/dev/null 2>&1 || { $(call fail,bpftool not found — install bpftool / linux-tools); }
$(call status,Generating,$@)
$(Q)sh build/gen-vmlinux.sh "$(BPFTOOL)" $@
$(call step,vmlinux.h,$@,sh build/gen-vmlinux.sh "$(BPFTOOL)" $@)

# Compile each unit to an intermediate object.
.build/bpf/%.bpf.o: src/bpf/%.bpf.c $(VMLINUX) | toolchain
@command -v $(firstword $(CLANG)) >/dev/null 2>&1 || { $(call fail,clang not found — install clang); }
$(call status,Compiling,$(notdir $<))
$(Q)mkdir -p $(dir $@)
$(Q)$(CLANG) $(BPF_CFLAGS) -c $< -o $@
$(call step,$(basename $(basename $(notdir $<))),$@,$(CLANG) $(BPF_CFLAGS) -c $< -o $@)

# Statically link every unit into the single loadable object.
$(BPF_OUT): $(BPF_OBJS) | bin toolchain
@command -v $(firstword $(BPFTOOL)) >/dev/null 2>&1 || { $(call fail,bpftool not found — install bpftool / linux-tools); }
$(call status,Linking,$@)
$(Q)$(BPFTOOL) gen object $@ $(BPF_OBJS)
$(call step,link,$@,$(BPFTOOL) gen object $@ $(BPF_OBJS))

bin:
mkdir -p bin
$(Q)mkdir -p bin

# bin/ is a build product: it holds the linked object (and whatever the kernel
# matrix stages beside it), is not tracked, and the `bin` rule recreates it.
clean-bpf:
$(call status,Cleaning,$(BPF_OUT) .build $(VMLINUX))
$(Q)rm -rf $(BPF_OUT) .build $(VMLINUX)
$(call status,clean,bin)
$(call status,clean,.build)
$(call status,clean,$(VMLINUX))
$(Q)rm -rf bin .build $(VMLINUX)

# Load the linked object with veristat to confirm THIS kernel's verifier
# accepts every program, and to see per-program complexity (insns/states) — a
Expand All @@ -79,16 +80,15 @@ clean-bpf:
.PHONY: veristat
veristat: $(BPF_OUT) | toolchain
@command -v $(firstword $(VERISTAT)) >/dev/null 2>&1 || { $(call fail,veristat not found ($(VERISTAT)) — bump build/toolchain.lock to a toolchain that ships veristat, or install veristat on PATH); }
$(call status,Verifying,$(BPF_OUT))
$(Q)$(VERISTAT) $(BPF_OUT)
$(call step,verify,$(BPF_OUT),$(VERISTAT) $(BPF_OUT))

# Run the same verifier check across a matrix of kernels locally (Linux + KVM),
# the local counterpart to .github/workflows/kernel-matrix.yml. Boots
# quay.io/lvh-images/kind images with cilium's lvh + QEMU; pass kernels as
# KERNELS="6.6-main bpf-next-main" or rely on the script's default spread.
.PHONY: veristat-matrix
veristat-matrix: $(BPF_OUT) | toolchain
$(call status,Verifying,$(BPF_OUT) across kernels)
$(call status,verify,$(BPF_OUT) across kernels)
$(Q)VERISTAT="$(VERISTAT)" TOOLCHAIN_DIR="$(TOOLCHAIN_DIR)" sh build/kernel-matrix.sh $(KERNELS)

# Write a local .clangd so the editor resolves vmlinux.h, the libbpf SDK
Expand All @@ -111,6 +111,6 @@ clangd:
' - -D__TARGET_ARCH_$(ARCH)' \
' - -D__BPF_TRACING__' \
> .clangd
$(call status,Writing,.clangd (libbpf headers: $(if $(BPF_SYSINCLUDE),$(BPF_SYSINCLUDE),<host include path>)))
$(call status,clangd,.clangd (libbpf headers: $(if $(BPF_SYSINCLUDE),$(BPF_SYSINCLUDE),<host include path>)))

.PHONY: bpf clean-bpf
8 changes: 4 additions & 4 deletions template/build/fetch-toolchain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ set -eu

# Status lines in the build's format (STATUS_FMT comes from the Makefile; the
# default matches it uncolored). curl shows a progress bar only on a terminal.
status() { printf "${STATUS_FMT:-%12s %s\n}" "$1" "$2" >&2; }
status() { printf "${STATUS_FMT:-* %-10s %s\n}" "$1" "$2" >&2; }
if [ -t 2 ]; then CURL_PROGRESS=--progress-bar; else CURL_PROGRESS=-s; fi

DIR="${1:?usage: fetch-toolchain.sh <dest-dir> <arch> <lock> [tool...]}"
Expand Down Expand Up @@ -71,7 +71,7 @@ fetch_bin() {
name="$1"
want "$name" || return 0
[ -x "$DIR/$name" ] && return 0
status Downloading "${name} (${ARCH}, v${TOOLCHAIN_VERSION})"
status download "${name} (${ARCH}, v${TOOLCHAIN_VERSION})"
tmp="$DIR/.${name}.$$"
curl -fSL $CURL_PROGRESS --retry 3 -o "$tmp" "${REL}/${name}-${ARCH}"
verify "$tmp" "$(get_sha "$(echo "$name" | tr a-z A-Z)")" "$name" || { rm -f "$tmp"; exit 1; }
Expand All @@ -91,7 +91,7 @@ fetch_bin gh
# per-arch tool dirs ($key/include/bpf/*.h).
INC="$(dirname "$DIR")/include"
if want headers && [ ! -e "$INC/bpf/bpf_helpers.h" ]; then
status Downloading "libbpf headers (v${TOOLCHAIN_VERSION})"
status download "libbpf headers (v${TOOLCHAIN_VERSION})"
td="$(mktemp -d)"
curl -fSL $CURL_PROGRESS --retry 3 -o "$td/h.tgz" "${REL}/libbpf-headers.tar.gz"
verify "$td/h.tgz" "${LIBBPF_HEADERS_SHA256:-}" "libbpf-headers" || { rm -rf "$td"; exit 1; }
Expand All @@ -100,4 +100,4 @@ if want headers && [ ! -e "$INC/bpf/bpf_helpers.h" ]; then
rm -rf "$td"
fi

status Toolchain "$DIR${FILTER:+ ($FILTER)}"
status toolchain "$DIR${FILTER:+ ($FILTER)}"
6 changes: 4 additions & 2 deletions template/build/kernel-matrix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,22 @@ fi
[ -e /dev/kvm ] && ACCEL="--cpu-kind host" || { ACCEL="--no-hw-accel"; echo "note: /dev/kvm absent — running under TCG emulation (slow)"; }

# --- build the object and stage the vendored static veristat ------------------
# veristat goes into .kmatrix/ (the run's workspace, gitignored), not bin/,
# which holds only build products and is removed by `make clean`.
echo ">> building $OBJ"
make bpf >/dev/null
OUTDIR="$PWD/.kmatrix"; rm -rf "$OUTDIR"; mkdir -p "$OUTDIR"
VERISTAT="${VERISTAT:-}"
if [ -z "$VERISTAT" ] && [ -f build/toolchain.lock ]; then
. ./build/toolchain.lock
VERISTAT="${XDG_CACHE_HOME:-$HOME/.cache}/yeet/toolchain/v${TOOLCHAIN_VERSION}/${ARCH}/veristat"
fi
[ -n "$VERISTAT" ] && [ -x "$VERISTAT" ] || VERISTAT="$(command -v veristat || true)"
[ -n "$VERISTAT" ] && [ -x "$VERISTAT" ] || { echo "error: veristat not found — bump build/toolchain.lock to a toolchain that ships it, or install veristat" >&2; exit 1; }
install -Dm755 "$VERISTAT" bin/veristat
install -Dm755 "$VERISTAT" "$OUTDIR/veristat"

# --- per-kernel: pull image, boot, verify, stop -------------------------------
WORK="$(mktemp -d)"
OUTDIR="$PWD/.kmatrix"; rm -rf "$OUTDIR"; mkdir -p "$OUTDIR"
overall=0

stop_vm() { printf 'quit\n' | { nc -N 127.0.0.1 "$MON_PORT" 2>/dev/null || nc 127.0.0.1 "$MON_PORT" 2>/dev/null; } || true; sleep 1; }
Expand Down
10 changes: 5 additions & 5 deletions template/build/toolchain.mk
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@
# Status-line formats, normally set by the project's build/ui.mk (included
# before this file); these fallbacks keep the glue usable on its own. Exported
# so build/fetch-toolchain.sh prints the same way.
STATUS_FMT ?= %12s %s\n
WARN_FMT ?= warning: %s\n
ERROR_FMT ?= error: %s\n
STATUS_FMT ?= * %-10s %s\n
WARN_FMT ?= ! %s\n
ERROR_FMT ?= x %s\n
export STATUS_FMT WARN_FMT ERROR_FMT

UNAME_S := $(shell uname -s)
Expand Down Expand Up @@ -108,10 +108,10 @@ GH ?= gh
toolchain:
ifneq ($(YEET),)
@$(YEET_EXEC) clang -print-prog-name=clang >/dev/null 2>&1 || { \
printf '$(STATUS_FMT)' 'Installing' 'yeet toolchain $(TOOLCHAIN_WANT) (none installed yet)' >&2; \
printf '$(STATUS_FMT)' 'install' 'yeet toolchain $(TOOLCHAIN_WANT) (none installed yet)' >&2; \
$(YEET) toolchain install $(TOOLCHAIN_WANT) --default; }
@d="$$($(YEET_EXEC) clang -print-prog-name=clang)"; d="$${d%/*}"; v="$${d##*/}"; \
printf '$(STATUS_FMT)' 'Toolchain' "yeet $$v ($$d)" >&2; \
printf '$(STATUS_FMT)' 'toolchain' "$$v ~$${d#$$HOME}" >&2; \
if [ -n "$(TOOLCHAIN_KEY)" ] && [ "$$v" != "$(TOOLCHAIN_KEY)" ]; then \
printf '$(WARN_FMT)' "yeet's default toolchain is $$v but build/toolchain.lock pins $(TOOLCHAIN_KEY) — 'yeet toolchain default $(TOOLCHAIN_KEY)' to match it, or 'make YEET=' to build from the pinned cache" >&2; \
fi
Expand Down
Loading
Loading