mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
Add early macOS startup diagnostics (#4504)
This commit is contained in:
Executable
+366
@@ -0,0 +1,366 @@
|
||||
#!/usr/bin/env bash
|
||||
# Capture one-shot macOS launch diagnostics for a published Orca release.
|
||||
#
|
||||
# Usage:
|
||||
# ORCA_DIAGNOSTIC_TAG=v1.4.42-rc.1 bash config/scripts/macos-launch-diagnostics.sh
|
||||
# bash config/scripts/macos-launch-diagnostics.sh --tag v1.4.42-rc.1
|
||||
set -euo pipefail
|
||||
|
||||
REPO="${ORCA_DIAGNOSTIC_REPO:-stablyai/orca}"
|
||||
TAG="${ORCA_DIAGNOSTIC_TAG:-}"
|
||||
KEEP=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--tag)
|
||||
TAG="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--repo)
|
||||
REPO="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--keep)
|
||||
KEEP=1
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
cat <<'EOF'
|
||||
Capture one-shot macOS launch diagnostics for a published Orca release.
|
||||
|
||||
Usage:
|
||||
ORCA_DIAGNOSTIC_TAG=v1.4.42-rc.1 bash config/scripts/macos-launch-diagnostics.sh
|
||||
bash config/scripts/macos-launch-diagnostics.sh --tag v1.4.42-rc.1
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
echo "Usage: $0 --tag vX.Y.Z-rc.N [--repo owner/repo] [--keep]" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$(uname -s)" != "Darwin" ]]; then
|
||||
echo "This diagnostic script only supports macOS." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if [[ -z "$TAG" ]]; then
|
||||
echo "Set ORCA_DIAGNOSTIC_TAG or pass --tag vX.Y.Z-rc.N." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
ARCH="$(uname -m)"
|
||||
case "$ARCH" in
|
||||
arm64) ASSET="orca-macos-arm64.dmg" ;;
|
||||
x86_64) ASSET="orca-macos-x64.dmg" ;;
|
||||
*)
|
||||
echo "Unsupported macOS architecture: $ARCH" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
TIMESTAMP="$(date -u '+%Y%m%dT%H%M%SZ')"
|
||||
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/orca-launch-diagnostics.XXXXXXXX")"
|
||||
OUT_DIR="$WORK_DIR/output"
|
||||
MOUNT_DIR="$WORK_DIR/mount"
|
||||
APP_DIR="$WORK_DIR/Orca.app"
|
||||
DMG_PATH="$WORK_DIR/$ASSET"
|
||||
mkdir -p "$OUT_DIR" "$MOUNT_DIR"
|
||||
|
||||
if [[ -d "$HOME/Desktop" ]]; then
|
||||
ZIP_PATH="$HOME/Desktop/orca-launch-diagnostics-${TAG}-${TIMESTAMP}.zip"
|
||||
else
|
||||
ZIP_PATH="$PWD/orca-launch-diagnostics-${TAG}-${TIMESTAMP}.zip"
|
||||
fi
|
||||
|
||||
log() {
|
||||
printf '[orca-diagnostics] %s\n' "$*"
|
||||
}
|
||||
|
||||
run_capture() {
|
||||
local name="$1"
|
||||
shift
|
||||
{
|
||||
echo "\$ $*"
|
||||
"$@"
|
||||
} >"$OUT_DIR/$name.out" 2>"$OUT_DIR/$name.err" || {
|
||||
local code=$?
|
||||
echo "exit=$code" >>"$OUT_DIR/$name.err"
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if mount | grep -F " on $MOUNT_DIR " >/dev/null 2>&1; then
|
||||
hdiutil detach "$MOUNT_DIR" -quiet >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [[ "$KEEP" -eq 0 ]]; then
|
||||
rm -rf "$WORK_DIR"
|
||||
else
|
||||
log "kept working directory: $WORK_DIR"
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
write_environment_report() {
|
||||
{
|
||||
echo "tag=$TAG"
|
||||
echo "repo=$REPO"
|
||||
echo "asset=$ASSET"
|
||||
echo "timestamp_utc=$TIMESTAMP"
|
||||
echo
|
||||
echo "## sw_vers"
|
||||
sw_vers || true
|
||||
echo
|
||||
echo "## uname"
|
||||
uname -a || true
|
||||
echo
|
||||
echo "## shell"
|
||||
echo "$SHELL"
|
||||
echo
|
||||
echo "## current Orca processes before diagnostics"
|
||||
pgrep -fl 'Orca|orca' || true
|
||||
} >"$OUT_DIR/environment.txt"
|
||||
}
|
||||
|
||||
ensure_no_existing_orca() {
|
||||
local existing
|
||||
existing="$(pgrep -x Orca || true)"
|
||||
if [[ -z "$existing" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
{
|
||||
echo "An Orca process is already running. Close Orca and run this script again."
|
||||
echo
|
||||
ps -p "$(printf '%s' "$existing" | paste -sd, -)" -o pid=,ppid=,command= || true
|
||||
} | tee "$OUT_DIR/existing-orca-process.txt" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
download_and_copy_app() {
|
||||
local url="https://github.com/$REPO/releases/download/$TAG/$ASSET"
|
||||
log "downloading $url"
|
||||
curl -fL --retry 3 --retry-delay 2 -o "$DMG_PATH" "$url"
|
||||
shasum -a 256 "$DMG_PATH" >"$OUT_DIR/dmg.sha256" || true
|
||||
|
||||
log "mounting DMG"
|
||||
hdiutil attach "$DMG_PATH" -nobrowse -readonly -mountpoint "$MOUNT_DIR" >"$OUT_DIR/hdiutil-attach.txt"
|
||||
local source_app
|
||||
source_app="$(find "$MOUNT_DIR" -maxdepth 1 -name '*.app' -type d | head -n 1)"
|
||||
if [[ -z "$source_app" ]]; then
|
||||
echo "No .app bundle found in $DMG_PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "copying app to isolated temp path"
|
||||
ditto "$source_app" "$APP_DIR"
|
||||
hdiutil detach "$MOUNT_DIR" -quiet
|
||||
}
|
||||
|
||||
write_app_report() {
|
||||
{
|
||||
echo "app=$APP_DIR"
|
||||
echo
|
||||
echo "## Info.plist"
|
||||
plutil -p "$APP_DIR/Contents/Info.plist" || true
|
||||
echo
|
||||
echo "## spctl"
|
||||
spctl -a -vvv "$APP_DIR" || true
|
||||
echo
|
||||
echo "## codesign display"
|
||||
codesign -dv --verbose=4 "$APP_DIR" || true
|
||||
echo
|
||||
echo "## codesign verify"
|
||||
codesign --verify --strict --deep "$APP_DIR" || true
|
||||
echo
|
||||
echo "## xattr"
|
||||
xattr -lr "$APP_DIR" || true
|
||||
echo
|
||||
echo "## native modules"
|
||||
find "$APP_DIR/Contents/Resources" -name '*.node' -print || true
|
||||
} >"$OUT_DIR/app-report.txt" 2>&1
|
||||
}
|
||||
|
||||
start_log_stream() {
|
||||
local file="$1"
|
||||
local predicate='process == "Orca" OR eventMessage CONTAINS[c] "Orca" OR eventMessage CONTAINS[c] "com.stablyai.orca"'
|
||||
if command -v log >/dev/null 2>&1; then
|
||||
log stream --style compact --predicate "$predicate" >"$file" 2>&1 &
|
||||
echo "$!"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
stop_log_stream() {
|
||||
local pid="$1"
|
||||
if [[ -n "$pid" ]] && kill -0 "$pid" >/dev/null 2>&1; then
|
||||
kill "$pid" >/dev/null 2>&1 || true
|
||||
wait "$pid" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
latest_orca_pid_for_app() {
|
||||
pgrep -nf "$APP_DIR/Contents/MacOS/Orca" || true
|
||||
}
|
||||
|
||||
sample_process_once() {
|
||||
local pid="$1"
|
||||
local file="$2"
|
||||
if [[ -n "$pid" ]] && command -v sample >/dev/null 2>&1; then
|
||||
sample "$pid" 1 -file "$file" >"$file.command.out" 2>"$file.command.err" || true
|
||||
fi
|
||||
}
|
||||
|
||||
terminate_app_processes() {
|
||||
local pid
|
||||
pid="$(latest_orca_pid_for_app)"
|
||||
if [[ -n "$pid" ]]; then
|
||||
kill "$pid" >/dev/null 2>&1 || true
|
||||
sleep 1
|
||||
if kill -0 "$pid" >/dev/null 2>&1; then
|
||||
kill -9 "$pid" >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_probe() {
|
||||
local runner_pid="$1"
|
||||
local label="$2"
|
||||
local sample_file="$OUT_DIR/$label.sample.txt"
|
||||
local sampled=0
|
||||
local i
|
||||
|
||||
for i in $(seq 1 200); do
|
||||
local app_pid
|
||||
app_pid="$(latest_orca_pid_for_app)"
|
||||
if [[ "$sampled" -eq 0 && -n "$app_pid" ]]; then
|
||||
sampled=1
|
||||
echo "sampled_pid=$app_pid" >>"$OUT_DIR/$label.meta"
|
||||
sample_process_once "$app_pid" "$sample_file"
|
||||
fi
|
||||
|
||||
if ! kill -0 "$runner_pid" >/dev/null 2>&1; then
|
||||
set +e
|
||||
wait "$runner_pid"
|
||||
local runner_exit=$?
|
||||
set -e
|
||||
echo "runner_exit=$runner_exit" >>"$OUT_DIR/$label.meta"
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
echo "runner_timeout_after_seconds=20" >>"$OUT_DIR/$label.meta"
|
||||
terminate_app_processes
|
||||
wait "$runner_pid" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
run_launchservices_probe() {
|
||||
local label="$1"
|
||||
local extra_env_name="${2:-}"
|
||||
local extra_env_value="${3:-}"
|
||||
local stderr_file="$OUT_DIR/$label.stderr.log"
|
||||
local stdout_file="$OUT_DIR/$label.stdout.log"
|
||||
local bootstrap_file="$OUT_DIR/$label.bootstrap.log"
|
||||
local stream_file="$OUT_DIR/$label.system-stream.log"
|
||||
local stream_pid
|
||||
|
||||
log "running LaunchServices probe: $label"
|
||||
stream_pid="$(start_log_stream "$stream_file")"
|
||||
{
|
||||
echo "label=$label"
|
||||
echo "started_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
echo "bootstrap_file=$bootstrap_file"
|
||||
[[ -n "$extra_env_name" ]] && echo "$extra_env_name=$extra_env_value"
|
||||
} >"$OUT_DIR/$label.meta"
|
||||
|
||||
if [[ -n "$extra_env_name" ]]; then
|
||||
open -n -W \
|
||||
--stdout "$stdout_file" \
|
||||
--stderr "$stderr_file" \
|
||||
--env ELECTRON_ENABLE_LOGGING=1 \
|
||||
--env ORCA_STARTUP_DIAGNOSTICS=trace \
|
||||
--env ORCA_STARTUP_DIAGNOSTICS_TRACE_LIMIT=30000 \
|
||||
--env ORCA_STARTUP_DIAGNOSTICS_FILE="$bootstrap_file" \
|
||||
--env "$extra_env_name=$extra_env_value" \
|
||||
"$APP_DIR" &
|
||||
else
|
||||
open -n -W \
|
||||
--stdout "$stdout_file" \
|
||||
--stderr "$stderr_file" \
|
||||
--env ELECTRON_ENABLE_LOGGING=1 \
|
||||
--env ORCA_STARTUP_DIAGNOSTICS=trace \
|
||||
--env ORCA_STARTUP_DIAGNOSTICS_TRACE_LIMIT=30000 \
|
||||
--env ORCA_STARTUP_DIAGNOSTICS_FILE="$bootstrap_file" \
|
||||
"$APP_DIR" &
|
||||
fi
|
||||
local runner_pid="$!"
|
||||
wait_for_probe "$runner_pid" "$label"
|
||||
echo "ended_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >>"$OUT_DIR/$label.meta"
|
||||
stop_log_stream "$stream_pid"
|
||||
}
|
||||
|
||||
run_direct_exec_probe() {
|
||||
local label="direct-exec"
|
||||
local stderr_file="$OUT_DIR/$label.stderr.log"
|
||||
local stdout_file="$OUT_DIR/$label.stdout.log"
|
||||
local bootstrap_file="$OUT_DIR/$label.bootstrap.log"
|
||||
local stream_file="$OUT_DIR/$label.system-stream.log"
|
||||
local stream_pid
|
||||
|
||||
log "running direct exec probe"
|
||||
stream_pid="$(start_log_stream "$stream_file")"
|
||||
{
|
||||
echo "label=$label"
|
||||
echo "started_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
echo "bootstrap_file=$bootstrap_file"
|
||||
} >"$OUT_DIR/$label.meta"
|
||||
|
||||
ELECTRON_ENABLE_LOGGING=1 \
|
||||
ORCA_STARTUP_DIAGNOSTICS=trace \
|
||||
ORCA_STARTUP_DIAGNOSTICS_TRACE_LIMIT=30000 \
|
||||
ORCA_STARTUP_DIAGNOSTICS_FILE="$bootstrap_file" \
|
||||
"$APP_DIR/Contents/MacOS/Orca" >"$stdout_file" 2>"$stderr_file" &
|
||||
local runner_pid="$!"
|
||||
wait_for_probe "$runner_pid" "$label"
|
||||
echo "ended_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >>"$OUT_DIR/$label.meta"
|
||||
stop_log_stream "$stream_pid"
|
||||
}
|
||||
|
||||
write_system_log_snapshot() {
|
||||
local predicate='process == "Orca" OR eventMessage CONTAINS[c] "Orca" OR eventMessage CONTAINS[c] "com.stablyai.orca"'
|
||||
if command -v log >/dev/null 2>&1; then
|
||||
log "capturing recent unified log snapshot"
|
||||
log show --style syslog --last 10m --predicate "$predicate" >"$OUT_DIR/system-log-last-10m.log" 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
package_results() {
|
||||
{
|
||||
echo "Diagnostics completed."
|
||||
echo "Tag: $TAG"
|
||||
echo "Output zip: $ZIP_PATH"
|
||||
echo
|
||||
echo "Please attach this zip to the GitHub issue:"
|
||||
echo " $ZIP_PATH"
|
||||
} >"$OUT_DIR/README.txt"
|
||||
|
||||
log "creating zip"
|
||||
(cd "$OUT_DIR" && zip -qry "$ZIP_PATH" .)
|
||||
log "wrote $ZIP_PATH"
|
||||
}
|
||||
|
||||
write_environment_report
|
||||
ensure_no_existing_orca
|
||||
download_and_copy_app
|
||||
write_app_report
|
||||
run_launchservices_probe "launchservices-trace"
|
||||
run_launchservices_probe "launchservices-bypass-lock" "ORCA_BYPASS_SINGLE_INSTANCE_LOCK" "1"
|
||||
run_direct_exec_probe
|
||||
write_system_log_snapshot
|
||||
package_results
|
||||
+133
-1
@@ -33,6 +33,137 @@ const ORCA_DIAGNOSTICS_TOKEN_URL_LITERAL =
|
||||
? JSON.stringify(orcaDiagnosticsTokenUrl)
|
||||
: 'null'
|
||||
|
||||
function createStartupDiagnosticsBanner(chunkName: string): string {
|
||||
return `
|
||||
;(() => {
|
||||
const env = typeof process !== 'undefined' ? process.env : undefined
|
||||
const mode = env?.ORCA_STARTUP_DIAGNOSTICS
|
||||
if (mode !== '1' && mode !== 'trace') {
|
||||
return
|
||||
}
|
||||
const safeJson = (value) => {
|
||||
try {
|
||||
return JSON.stringify(value)
|
||||
} catch {
|
||||
return '"<unserializable>"'
|
||||
}
|
||||
}
|
||||
let closeSync
|
||||
let diagnosticFileDescriptor
|
||||
let openSync
|
||||
let writeSync
|
||||
try {
|
||||
const fs = require('node:fs')
|
||||
closeSync = fs.closeSync
|
||||
openSync = fs.openSync
|
||||
writeSync = fs.writeSync
|
||||
} catch {
|
||||
closeSync = undefined
|
||||
openSync = undefined
|
||||
writeSync = undefined
|
||||
}
|
||||
const diagnosticFile = env?.ORCA_STARTUP_DIAGNOSTICS_FILE
|
||||
if (typeof diagnosticFile === 'string' && diagnosticFile.length > 0 && typeof openSync === 'function') {
|
||||
try {
|
||||
diagnosticFileDescriptor = openSync(diagnosticFile, 'a', 0o600)
|
||||
} catch {
|
||||
diagnosticFileDescriptor = undefined
|
||||
}
|
||||
}
|
||||
const writeLine = (message) => {
|
||||
try {
|
||||
const line = message.endsWith('\\n') ? message : message + '\\n'
|
||||
if (typeof writeSync === 'function') {
|
||||
writeSync(2, line)
|
||||
if (typeof diagnosticFileDescriptor === 'number') {
|
||||
writeSync(diagnosticFileDescriptor, line)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Diagnostics must never affect startup.
|
||||
}
|
||||
}
|
||||
const chunkName = ${JSON.stringify(chunkName)}
|
||||
writeLine('[bootstrap] bundle-enter chunk=' + safeJson(chunkName) + ' pid=' + process.pid + ' ppid=' + process.ppid + ' execPath=' + safeJson(process.execPath) + ' argv=' + safeJson(process.argv) + ' electronRunAsNode=' + safeJson(env?.ELECTRON_RUN_AS_NODE ?? null))
|
||||
if (!globalThis.__ORCA_BOOTSTRAP_EXIT_LOG_INSTALLED__) {
|
||||
globalThis.__ORCA_BOOTSTRAP_EXIT_LOG_INSTALLED__ = true
|
||||
process.once('exit', (code) => {
|
||||
writeLine('[bootstrap] process-exit code=' + code)
|
||||
if (typeof closeSync === 'function' && typeof diagnosticFileDescriptor === 'number') {
|
||||
try {
|
||||
closeSync(diagnosticFileDescriptor)
|
||||
} catch {
|
||||
// Diagnostics must never affect shutdown.
|
||||
}
|
||||
}
|
||||
})
|
||||
process.on('uncaughtExceptionMonitor', (error, origin) => {
|
||||
const message = error && typeof error === 'object' && 'stack' in error ? error.stack : error
|
||||
writeLine('[bootstrap] uncaught-exception origin=' + safeJson(origin) + ' error=' + safeJson(String(message)))
|
||||
})
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
const message = reason && typeof reason === 'object' && 'stack' in reason ? reason.stack : reason
|
||||
writeLine('[bootstrap] unhandled-rejection error=' + safeJson(String(message)))
|
||||
})
|
||||
}
|
||||
if (mode === 'trace' && !globalThis.__ORCA_BOOTSTRAP_REQUIRE_TRACE_INSTALLED__) {
|
||||
globalThis.__ORCA_BOOTSTRAP_REQUIRE_TRACE_INSTALLED__ = true
|
||||
try {
|
||||
const Module = require('node:module')
|
||||
const originalLoad = Module._load
|
||||
const parsedTraceLimit = Number(env?.ORCA_STARTUP_DIAGNOSTICS_TRACE_LIMIT ?? 20000)
|
||||
const traceLimit = Number.isFinite(parsedTraceLimit) && parsedTraceLimit > 0 ? parsedTraceLimit : 20000
|
||||
let traceLineCount = 0
|
||||
let traceLimitReported = false
|
||||
const writeTraceLine = (message) => {
|
||||
if (traceLineCount >= traceLimit) {
|
||||
if (!traceLimitReported) {
|
||||
traceLimitReported = true
|
||||
writeLine('[bootstrap] require-trace-limit-reached limit=' + safeJson(traceLimit))
|
||||
}
|
||||
return
|
||||
}
|
||||
traceLineCount += 1
|
||||
writeLine(message)
|
||||
}
|
||||
Module._load = function (request, parent, isMain) {
|
||||
const parentName = parent && parent.filename ? parent.filename : null
|
||||
writeTraceLine('[bootstrap] require-start request=' + safeJson(request) + ' parent=' + safeJson(parentName) + ' isMain=' + safeJson(Boolean(isMain)))
|
||||
try {
|
||||
const result = Reflect.apply(originalLoad, this, arguments)
|
||||
writeTraceLine('[bootstrap] require-ok request=' + safeJson(request))
|
||||
return result
|
||||
} catch (error) {
|
||||
const message = error && typeof error === 'object' && 'stack' in error ? error.stack : error
|
||||
writeTraceLine('[bootstrap] require-error request=' + safeJson(request) + ' error=' + safeJson(String(message)))
|
||||
throw error
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
writeLine('[bootstrap] require-trace-install-error error=' + safeJson(String(error)))
|
||||
}
|
||||
}
|
||||
})();
|
||||
`
|
||||
}
|
||||
|
||||
function createStartupDiagnosticsBootstrapPlugin() {
|
||||
return {
|
||||
name: 'orca-startup-diagnostics-bootstrap',
|
||||
generateBundle(_options, bundle) {
|
||||
const mainChunk = bundle['index.js']
|
||||
if (!mainChunk || mainChunk.type !== 'chunk') {
|
||||
return
|
||||
}
|
||||
|
||||
// Why: source-level startup diagnostics run after Rollup's generated
|
||||
// prelude and require() list. Mutate the final emitted chunk so macOS
|
||||
// launch failures can identify the earliest JS boundary reached.
|
||||
mainChunk.code = createStartupDiagnosticsBanner(mainChunk.fileName) + mainChunk.code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
build: {
|
||||
@@ -54,7 +185,8 @@ export default defineConfig({
|
||||
'agent-hooks/managed-agent-hook-controls': resolve(
|
||||
'src/main/agent-hooks/managed-agent-hook-controls.ts'
|
||||
)
|
||||
}
|
||||
},
|
||||
plugins: [createStartupDiagnosticsBootstrapPlugin()]
|
||||
}
|
||||
},
|
||||
// Why: compile-time substitution for the telemetry gate. See the block
|
||||
|
||||
Reference in New Issue
Block a user