mirror of
https://github.com/warpdotdev/warp.git
synced 2026-06-10 08:44:56 +08:00
## Description Productionizes automatic jemalloc heap profiling on Linux so high-memory events upload a heap profile to Sentry, matching what macOS already does. The key difference from macOS: **the Linux profile is uploaded unsymbolized (raw pprof)**. It carries sample addresses + memory mappings + the GNU build-id, and is symbolized **offline** against the debug-info file (DIF) the release pipeline already uploads to Sentry — the same artifact used to symbolize panics, matched by build-id. This lets the shipped binary stay fully stripped (no symbol-table bloat) while still producing usable, symbolizable profiles. ### What changed - **`app/src/profiling.rs`**: on Linux, dump the gzipped pprof **in-process** via `jemalloc_pprof` (`dump_jemalloc_pprof_bytes`) — no external `pprof` binary, HTTP server, or fixed port (essential for the headless remote-server daemon). The dump is now **raw / unsymbolized**. The external-`pprof` path is kept for macOS, where in-process symbolization stays. - **`app/Cargo.toml`**: build `jemalloc_pprof` **without** the `symbolize` feature, so `dump_pprof()` returns a raw profile instead of symbolizing in-process (in-process symbolization would have required keeping the symbol table in the shipped binary). - **`Cargo.lock`**: bump `jemalloc_pprof` / `pprof_util` to `0.8.2`. This is required: `0.8.2` writes a usable mapping range (`memory_limit = u64::MAX`), whereas `0.8.1` wrote `memory_limit = 0`, leaving pprof unable to bind sample addresses to the binary — so profiles would be unsymbolizable even with the correct DIF. (See this PR: https://github.com/polarsignals/rust-jemalloc-pprof/pull/31) - **`script/linux/bundle`**: enable `jemalloc_pprof,heap_usage_tracking` for the `dev` and `preview` channels, and keep the **normal strip behavior** (`--strip-all` for non-dev builds). The shipped binary stays small; symbols live only in the uploaded DIF. ### Symbolizing a Linux heap profile (offline, via the Sentry DIF) The release pipeline already uploads each build's debug-info file to Sentry (`script/sentry_upload_dif.sh`), keyed by GNU build-id. To analyze a `heap-profile.pb` from an "Excessive memory usage detected" event: ```bash # 0. Use the standalone pprof (the Go-bundled `go tool pprof` misreads the # build-id on large DIFs): go install github.com/google/pprof@latest # 1. Read the main binary's build-id from the raw profile's mappings: BUILD_ID=$(pprof -raw heap-profile.pb \ | sed -n '/^Mappings/,/^Locations/p' \ | grep -E 'warp-(dev|preview)' | head -1 | awk '{print $NF}') # 2. Download the matching DIF from the channel's Sentry project # (a read-only token is sufficient; dev -> warp-client-dev, # preview -> warp-client-preview): ORG=warpdotdev; PROJECT=warp-client-dev ID=$(curl -s "https://us.sentry.io/api/0/projects/$ORG/$PROJECT/files/dsyms/?query=${BUILD_ID:0:20}" \ -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" | jq -r '.[0].id') mkdir -p difs curl -sL "https://us.sentry.io/api/0/projects/$ORG/$PROJECT/files/dsyms/?id=$ID" \ -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" -o "difs/${BUILD_ID}.debug" # 3. Symbolize + analyze (pprof matches the DIF to the profile by build-id): PPROF_BINARY_PATH=./difs pprof -http=: heap-profile.pb # flame graph / top / graph ``` The DIF carries `.symtab` + DWARF, so frames resolve to function names with file:line and inlined frames. (`addr2line -e difs/$BUILD_ID.debug <addr>` works as a lower-level alternative.) ### Notes / scope - No `pprof` binary is bundled on Linux (in-process raw dump). - `stable` Linux builds are unchanged (no profiling features) — enabling for `dev` + `preview` first. ## Linked Issue N/A — infrastructure/observability follow-up. ## Testing - `cargo check` + `cargo clippy -- -D warnings` for the app lib targeting Linux with `heap_usage_tracking` (exercises the new raw-dump branch); macOS host path also checked (validates the `pprof_binary_path` cfg-gate) — passes. - `./script/format` — no changes beyond the edited files. - End-to-end validated on a build sharing this exact code path - [ ] I have manually tested my changes locally with `./script/run` ## Agent Mode - [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode CHANGELOG-NONE --- 🤖 Generated with Warp Agent Mode Conversation: https://staging.warp.dev/conversation/e6ba6343-1bb8-4cce-a155-6f0b453bad40 Plan: https://staging.warp.dev/drive/notebook/359wXMjgAzXhtOZfnSWUlA
336 lines
10 KiB
Bash
Executable File
336 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Builds a Warp binary and bundles it up for distribution.
|
|
|
|
set -e
|
|
|
|
WORKSPACE_ROOT_DIR="$(pwd)"
|
|
CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-$WORKSPACE_ROOT_DIR/target}"
|
|
DIST_DIR="$CARGO_TARGET_DIR/dist"
|
|
|
|
cleanup() {
|
|
# Delete the directory that was used as a staging area during the bundling
|
|
# process.
|
|
if [ -d "$DIST_DIR" ]; then
|
|
rm -rf "$DIST_DIR"
|
|
fi
|
|
}
|
|
|
|
# Run cleanup() when the script terminates (whether it succeeded or failed).
|
|
trap cleanup EXIT
|
|
|
|
# By default we build dev bundles.
|
|
RELEASE_CHANNEL="dev"
|
|
FEATURES="release_bundle,crash_reporting"
|
|
EXTRA_FEATURES=""
|
|
PACKAGES=( appimage )
|
|
BUILD="true"
|
|
BUILD_ARCH="$(uname -m)"
|
|
DEBUG=false
|
|
ARTIFACT="app"
|
|
|
|
# Cache all params so we can pass them to downstream scripts.
|
|
ALL_PARAMS=$@
|
|
|
|
PARAMS=""
|
|
while (( "$#" )); do
|
|
case "$1" in
|
|
--debug)
|
|
DEBUG=true
|
|
shift
|
|
;;
|
|
--check-only)
|
|
echo 'Only running `cargo check` and not producing a bundle.'
|
|
CHECK_ONLY="true"
|
|
shift
|
|
;;
|
|
--skip-build)
|
|
BUILD="false"
|
|
shift
|
|
;;
|
|
--nouniversal)
|
|
# Discard the --nouniversal argument, which is only used for macOS
|
|
# bundles.
|
|
shift
|
|
;;
|
|
-c|--channel)
|
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
|
RELEASE_CHANNEL=$2
|
|
shift 2
|
|
else
|
|
echo "Error: Argument for $1 is missing" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
--release-tag)
|
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
|
echo "Setting release tag to $2"
|
|
export GIT_RELEASE_TAG=$2
|
|
shift 2
|
|
else
|
|
echo "Error: Argument for $1 is missing" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
--packages)
|
|
PACKAGES=( $(IFS=, ; echo $2) )
|
|
shift 2
|
|
;;
|
|
--features)
|
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
|
EXTRA_FEATURES="$2"
|
|
shift 2
|
|
else
|
|
echo "Error: Argument for $1 is missing" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
--artifact)
|
|
if [ -n "$2" ] && [ "${2:0:1}" != "-" ]; then
|
|
if [[ "$2" != "app" && "$2" != "cli" && "$2" != "warpctrl" ]]; then
|
|
echo "Error: --artifact must be 'app', 'cli', or 'warpctrl', got '$2'" >&2
|
|
exit 1
|
|
fi
|
|
ARTIFACT="$2"
|
|
shift 2
|
|
else
|
|
echo "Error: Argument for $1 is missing" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
--arch)
|
|
if [ -n "$2" ]; then
|
|
if [ "$2" = "aarch64" -o "$2" = "x86_64" ]; then
|
|
echo "Setting architecture to $2"
|
|
export BUILD_ARCH=$2
|
|
shift 2
|
|
else
|
|
echo "Error: Argument for $1 is invalid; got '$2' but expected 'aarch64' or 'x86_64'." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "Error: Argument for $1 is missing" >&2
|
|
exit 1
|
|
fi
|
|
;;
|
|
*) # preserve positional arguments
|
|
PARAMS="$PARAMS $1"
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
# set positional arguments in their proper place
|
|
eval set -- "$PARAMS"
|
|
|
|
if [[ $DEBUG = true ]]; then
|
|
CARGO_PROFILE="dev"
|
|
elif [[ $RELEASE_CHANNEL = "local" || $RELEASE_CHANNEL = "dev" ]]; then
|
|
# For dev bundles, we want to enable debug assertions to
|
|
# catch violations that would otherwise silently pass in
|
|
# a normal release build (e.g. in stable).
|
|
if [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then
|
|
CARGO_PROFILE="release-cli-debug_assertions"
|
|
else
|
|
CARGO_PROFILE="release-lto-debug_assertions"
|
|
fi
|
|
else
|
|
if [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then
|
|
CARGO_PROFILE="release-cli"
|
|
else
|
|
CARGO_PROFILE="release-lto"
|
|
fi
|
|
fi
|
|
|
|
if [[ "$CARGO_PROFILE" == "dev" ]]; then
|
|
CARGO_TARGET_OUTPUT_DIR="$CARGO_TARGET_DIR/debug"
|
|
else
|
|
CARGO_TARGET_OUTPUT_DIR="$CARGO_TARGET_DIR/$CARGO_PROFILE"
|
|
fi
|
|
# NOTE: if you change this path, update the "Clean stale bundle output"
|
|
# steps in .github/workflows/create_release.yml so they continue to wipe
|
|
# stale packages left in the shared Namespace runner cache.
|
|
OUT_DIR="$CARGO_TARGET_OUTPUT_DIR/bundle/linux"
|
|
mkdir -p "$OUT_DIR"
|
|
|
|
# Update parameters based on the target release channel.
|
|
#
|
|
# APP_NAME here must match the value used in Rust as the
|
|
# application name; see app/src/channel.rs.
|
|
#
|
|
# WARP_BIN is the name of the binary produced by cargo;
|
|
# BINARY_NAME is the desired name of the binary in the final package.
|
|
if [[ $RELEASE_CHANNEL = "local" ]]; then
|
|
WARP_BIN="warp"
|
|
BINARY_NAME="warp-local"
|
|
APP_NAME="WarpLocal"
|
|
FEATURES="$FEATURES,agent_mode_debug"
|
|
export HANDLE_MARKDOWN=1
|
|
elif [[ $RELEASE_CHANNEL = "dev" ]]; then
|
|
WARP_BIN="dev"
|
|
BINARY_NAME="warp-dev"
|
|
APP_NAME="WarpDev"
|
|
FEATURES="$FEATURES,agent_mode_debug"
|
|
# Enable heap usage tracking & profiling using jemalloc through pprof.
|
|
FEATURES="$FEATURES,jemalloc_pprof,heap_usage_tracking"
|
|
export HANDLE_MARKDOWN=1
|
|
elif [[ $RELEASE_CHANNEL = "preview" ]]; then
|
|
WARP_BIN="preview"
|
|
BINARY_NAME="warp-preview"
|
|
APP_NAME="WarpPreview"
|
|
FEATURES="$FEATURES,preview_channel"
|
|
# Enable heap usage tracking & profiling using jemalloc through pprof.
|
|
FEATURES="$FEATURES,jemalloc_pprof,heap_usage_tracking"
|
|
elif [[ $RELEASE_CHANNEL = "stable" ]]; then
|
|
WARP_BIN="stable"
|
|
BINARY_NAME="warp"
|
|
APP_NAME="Warp"
|
|
elif [[ $RELEASE_CHANNEL = "oss" ]]; then
|
|
WARP_BIN="warp-oss"
|
|
BINARY_NAME="warp-oss"
|
|
APP_NAME="WarpOss"
|
|
# The OSS channel does not ship Sentry, so drop the crash_reporting feature
|
|
# (which would otherwise pull in the Sentry SDK as a dependency).
|
|
FEATURES="release_bundle"
|
|
fi
|
|
|
|
# Artifact-specific binary naming
|
|
if [[ "$ARTIFACT" == "cli" ]]; then
|
|
# For CLI artifacts, use oz instead of warp as the binary name. The OSS
|
|
# channel is an exception: its CLI command name is `warp-oss`, matching
|
|
# `Channel::cli_command_name` in the Rust source.
|
|
if [[ $RELEASE_CHANNEL != "oss" ]]; then
|
|
BINARY_NAME="${BINARY_NAME/warp/oz}"
|
|
fi
|
|
elif [[ "$ARTIFACT" == "warpctrl" ]]; then
|
|
BINARY_NAME="warpctrl"
|
|
PACKAGES=()
|
|
fi
|
|
|
|
# Artifact-specific configuration
|
|
if [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then
|
|
FEATURES="$FEATURES,standalone"
|
|
if [[ "$ARTIFACT" == "warpctrl" ]]; then
|
|
FEATURES="$FEATURES,warp_control_cli"
|
|
fi
|
|
elif [[ "$ARTIFACT" == "app" ]]; then
|
|
# All channels ship the v3 classifier and v2 heuristic.
|
|
FEATURES="$FEATURES,gui,nld_classifier_v3,nld_heuristic_v2"
|
|
fi
|
|
if [[ -n "$EXTRA_FEATURES" ]]; then
|
|
FEATURES="$FEATURES,$EXTRA_FEATURES"
|
|
fi
|
|
|
|
BUNDLE_ID="dev.warp.$APP_NAME"
|
|
EXECUTABLE_PATH="$CARGO_TARGET_OUTPUT_DIR/$WARP_BIN"
|
|
DEBUG_EXECUTABLE_PATH="$EXECUTABLE_PATH.debug"
|
|
|
|
# Note that this variable must be set (and exported!) before we compile the
|
|
# binary, as it is read at compile time by Linux autoupdate logic (to know the
|
|
# expected name of the AppImage when downloading updates).
|
|
export APPIMAGE_NAME="$APP_NAME-$BUILD_ARCH.AppImage"
|
|
|
|
# If we only want to check that compilation will succeed, perform the checks
|
|
# then exit. We use this script to invoke `cargo check` to ensure that we are
|
|
# using the same feature flags and profile that we would be using in production.
|
|
if [[ "$CHECK_ONLY" == "true" ]]; then
|
|
cargo check -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES"
|
|
exit 0
|
|
fi
|
|
|
|
# Build the binary.
|
|
if [[ "$BUILD" == "true" ]]; then
|
|
echo "Building and bundling Warp for channel $RELEASE_CHANNEL and bundle id $BUNDLE_ID"
|
|
cargo build -p warp --profile "$CARGO_PROFILE" --bin "$WARP_BIN" --features "$FEATURES"
|
|
echo "Making debug copy of '$EXECUTABLE_PATH' at '$DEBUG_EXECUTABLE_PATH'"
|
|
cp "$EXECUTABLE_PATH" "$DEBUG_EXECUTABLE_PATH"
|
|
|
|
echo "Stripping debug symbols from '$EXECUTABLE_PATH'"
|
|
if [[ $RELEASE_CHANNEL = "dev" ]]; then
|
|
# For dev builds, only strip debug symbols, as we want to keep some
|
|
# symbols for profiling purposes.
|
|
strip --strip-debug "$EXECUTABLE_PATH"
|
|
else
|
|
# For production builds, strip all symbols, to keep the binary size
|
|
# smaller.
|
|
strip --strip-all "$EXECUTABLE_PATH"
|
|
fi
|
|
else
|
|
echo 'Skipping `cargo build` step due to --skip-build argument'
|
|
fi
|
|
|
|
if [[ "$ARTIFACT" == "warpctrl" ]]; then
|
|
echo "Copying control-mode binary into $OUT_DIR/$WARP_BIN"
|
|
cp "$EXECUTABLE_PATH" "$OUT_DIR/$WARP_BIN"
|
|
WARPCTRL_SCRIPT_PATH="$OUT_DIR/warpctrl"
|
|
echo "Creating warpctrl wrapper script at $WARPCTRL_SCRIPT_PATH"
|
|
cat > "$WARPCTRL_SCRIPT_PATH" << EOF
|
|
#!/usr/bin/env bash
|
|
script_dir="\$(cd "\$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
exec "\$script_dir/$WARP_BIN" --warpctrl "\$@"
|
|
EOF
|
|
chmod +x "$WARPCTRL_SCRIPT_PATH"
|
|
fi
|
|
BINARY_PATH="$EXECUTABLE_PATH"
|
|
if [[ "$ARTIFACT" == "warpctrl" ]]; then
|
|
BINARY_PATH="$WARPCTRL_SCRIPT_PATH"
|
|
fi
|
|
|
|
# Prepare bundled resources for CLI builds.
|
|
if [[ "$ARTIFACT" == "cli" || "$ARTIFACT" == "warpctrl" ]]; then
|
|
echo "Preparing CLI resources directory"
|
|
BUNDLED_RESOURCES_DIR="$OUT_DIR/resources"
|
|
"$WORKSPACE_ROOT_DIR/script/prepare_bundled_resources" "$BUNDLED_RESOURCES_DIR" "$RELEASE_CHANNEL" "$CARGO_PROFILE"
|
|
fi
|
|
|
|
|
|
# If this is being run within a GitHub action, set an output variable with the
|
|
# location of the binary so it can be referenced by subsequent actions, as well
|
|
# as the directory containing all built packages.
|
|
if [ "${GITHUB_ACTIONS}" == "true" ]; then
|
|
echo "::echo::on"
|
|
echo "executable_path=$BINARY_PATH" >> "$GITHUB_OUTPUT"
|
|
echo "debug_executable_path=$DEBUG_EXECUTABLE_PATH" >> "$GITHUB_OUTPUT"
|
|
echo "packages_dir=$OUT_DIR" >> "$GITHUB_OUTPUT"
|
|
echo "bundled_resources_dir=${BUNDLED_RESOURCES_DIR:-}" >> "$GITHUB_OUTPUT"
|
|
echo "::echo::off"
|
|
fi
|
|
|
|
# Make sure a variety of environment variables are available in downstream scripts.
|
|
export \
|
|
WORKSPACE_ROOT_DIR \
|
|
DIST_DIR \
|
|
CARGO_TARGET_OUTPUT_DIR \
|
|
OUT_DIR \
|
|
RELEASE_CHANNEL \
|
|
BUNDLE_ID \
|
|
EXECUTABLE_PATH \
|
|
BINARY_NAME \
|
|
APPIMAGE_NAME \
|
|
BUILD_ARCH \
|
|
ARTIFACT \
|
|
CARGO_PROFILE
|
|
|
|
# Build the AppImage bundle.
|
|
if [[ ${PACKAGES[@]} =~ "appimage" ]]; then
|
|
echo "Building AppImage..."
|
|
"$WORKSPACE_ROOT_DIR/script/linux/bundle_appimage"
|
|
fi
|
|
|
|
# Build the .deb package.
|
|
if [[ ${PACKAGES[@]} =~ "deb" ]]; then
|
|
echo "Building .deb package..."
|
|
"$WORKSPACE_ROOT_DIR/script/linux/bundle_deb"
|
|
fi
|
|
|
|
# Build the .rpm package.
|
|
if [[ ${PACKAGES[@]} =~ "rpm" ]]; then
|
|
echo "Building .rpm package..."
|
|
"$WORKSPACE_ROOT_DIR/script/linux/bundle_rpm"
|
|
fi
|
|
|
|
# Build the Arch Linux package.
|
|
if [[ ${PACKAGES[@]} =~ "arch" ]]; then
|
|
echo "Building Arch Linux package..."
|
|
"$WORKSPACE_ROOT_DIR/script/linux/bundle_arch"
|
|
fi
|