Files
bun-src/scripts/generate-perf-trace-events.sh
2026-08-27 21:09:14 +00:00

112 lines
4.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Regenerate the perf-trace event list from Rust call sites.
#
# Scans for both call shapes:
# - bun_core::perf::trace("Event.name") (string-literal form)
# - bun_perf::trace(PerfEvent::VariantName) (enum form, any qualifier)
#
# and emits:
# - src/jsc/bindings/generated_perf_trace_events.h (X-macro, sorted, 0-indexed)
# - src/perf/generated_perf_trace_events.rs (#[repr(i32)] PerfEvent enum)
#
# Not run during the build; re-run manually when adding new trace() calls.
set -euo pipefail
cd "$(dirname "$0")/.."
# rg exits 1 on zero matches; each form is allowed to be empty so long as the
# combined set isn't. -I/--no-filename: rg prints path prefixes when searching
# a directory and we only want the captured group.
LITERAL_EVENTS=$(rg 'bun_core::perf::trace\("([^"]+)"\)' -t rust -I -o -r '$1' src/ | sort -u || true)
# Enum form: accept any of PerfEvent::X, bun_perf::PerfEvent::X, ::bun_perf::PerfEvent::X.
ENUM_VARIANTS=$(rg '(?:::)?bun_perf::trace\((?:::)?(?:bun_perf::)?PerfEvent::([A-Za-z0-9_]+)\)' \
-t rust -I -o -r '$1' src/ | sort -u || true)
# Build "dotted\tvariant" pairs. For literal-form callers the variant name is
# derived from the literal (non-alnum stripped, first of each segment
# uppercased). For enum-form callers the variant is what the caller wrote and
# the dotted name is looked up in the current as_cstr() table so re-running
# preserves existing names; rustfmt wraps long arms across lines so match with -U.
PAIRS=""
while IFS= read -r ev; do
[ -z "$ev" ] && continue
variant=$(echo "$ev" | perl -pe 's/(^|[^A-Za-z0-9])([a-z])/$1\u$2/g; s/[^A-Za-z0-9]//g')
PAIRS="$PAIRS"$'\n'"$ev"$'\t'"$variant"
done <<< "$LITERAL_EVENTS"
for v in $ENUM_VARIANTS; do
name=$(rg -U "PerfEvent::${v}\b\s*=>\s*\{?\s*c\"([^\"]+)\"" \
src/perf/generated_perf_trace_events.rs -o -r '$1' || true)
if [ -z "$name" ]; then
# New variant without a cstr mapping yet: synthesize Foo.Bar from FooBar.
name=$(echo "$v" | sed -E 's/([a-z])([A-Z])/\1.\2/g; s/^_//')
fi
PAIRS="$PAIRS"$'\n'"$name"$'\t'"$v"
done
# Sort by dotted name (stable ids across runs); dedup by dotted name first,
# then by derived variant (two literals like "X.foo" and "X::foo" collapse).
# `awk NF` drops blank lines without the exit-1-on-no-match behaviour of grep.
PAIRS=$(echo "$PAIRS" | awk 'NF' | sort -t$'\t' -k1,1 -u | awk -F'\t' '!seen[$2]++')
if [ -z "$PAIRS" ]; then
echo "error: no perf::trace() call sites found under src/" >&2
exit 1
fi
H_OUT=src/jsc/bindings/generated_perf_trace_events.h
{
echo "// Generated with scripts/generate-perf-trace-events.sh"
echo "// clang-format off"
echo "#define FOR_EACH_TRACE_EVENT(macro) \\"
i=0
while IFS=$'\t' read -r dotted variant; do
echo " macro($dotted, $i) \\"
i=$((i + 1))
done <<< "$PAIRS"
echo " // end"
} > "$H_OUT"
echo "Generated $H_OUT"
RS_OUT=src/perf/generated_perf_trace_events.rs
{
echo "// Generated with scripts/generate-perf-trace-events.sh"
echo "//"
echo "// Discriminants match the ids in src/jsc/bindings/generated_perf_trace_events.h"
echo "// (the Darwin signpost path reads the event by integer id)."
echo
echo "#![allow(non_camel_case_types)]"
echo
echo "use core::ffi::CStr;"
echo
echo "#[repr(i32)]"
echo "#[derive(Copy, Clone, Eq, PartialEq)]"
echo "pub enum PerfEvent {"
i=0
while IFS=$'\t' read -r dotted variant; do
echo " $variant = $i,"
i=$((i + 1))
done <<< "$PAIRS"
echo "}"
echo
echo "impl PerfEvent {"
echo " pub fn as_cstr(self) -> &'static CStr {"
echo " match self {"
while IFS=$'\t' read -r dotted variant; do
echo " PerfEvent::$variant => c\"$dotted\","
done <<< "$PAIRS"
echo " }"
echo " }"
echo "}"
echo
echo "impl From<PerfEvent> for &'static str {"
echo " fn from(e: PerfEvent) -> &'static str {"
echo " e.as_cstr().to_str().unwrap()"
echo " }"
echo "}"
} > "$RS_OUT"
rustfmt "$RS_OUT" 2>/dev/null || true
echo "Generated $RS_OUT"