Skip to content

Commit 66d03d2

Browse files
JohnMcLearclaude
andauthored
cpu-profile: invoke node directly, bypass pnpm/cross-env wrappers (#111)
The previous workflow exported NODE_OPTIONS=--cpu-prof and ran `pnpm run prod`, producing 5 cpuprofile files (one per Node process in the pnpm → cross-env → tsx chain). The pgrep-based SUT pid capture matched the tsx wrapper rather than the actual server.ts process — analysis of run 25956220071 showed pid 2636 (pnpm) blocked in spawnSync for 113s while the recorded sut pid 2684 (tsx) was 99.8% idle. Invoke node directly with the same args the prod script uses (`node --require tsx/cjs node/server.ts`) under --cpu-prof, set NODE_ENV inline, and record the SUT pid via `$!` so there's exactly one process to profile and stop. The earlier --cpu-prof-name pid-suffix fix from PR #110 is retained. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent fe39657 commit 66d03d2

1 file changed

Lines changed: 34 additions & 18 deletions

File tree

.github/workflows/cpu-profile.yml

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -104,30 +104,39 @@ jobs:
104104
*) echo "unknown lever"; exit 1 ;;
105105
esac
106106
107-
- name: Start Etherpad with --cpu-prof
107+
- name: Start Etherpad with --cpu-prof (bypassing pnpm/cross-env wrappers)
108108
working-directory: ./etherpad
109109
run: |
110110
mkdir -p /tmp/cpuprof
111-
# --cpu-prof writes a separate profile per Node process when the
112-
# process exits OR receives SIGUSR2. We deliberately omit
113-
# --cpu-prof-name so each process gets a unique pid-suffixed
114-
# filename (CPU.<date>.<pid>.<seq>.<n>.cpuprofile). pnpm's prod
115-
# script chains pnpm → cross-env → node → tsx → server.ts;
116-
# NODE_OPTIONS is inherited by all of them, so we expect 2-4
117-
# separate cpuprofile files in /tmp/cpuprof — we'll keep the
118-
# largest one (the long-running SUT) when analysing.
119-
export NODE_OPTIONS="--cpu-prof --cpu-prof-dir=/tmp/cpuprof"
120-
(cd src && pnpm run prod >../ep.log 2>&1 &)
111+
# Previous attempt (PR #110) wrapped `pnpm run prod` with NODE_OPTIONS=--cpu-prof.
112+
# That produced 5 cpuprofile files — one for each Node in the chain
113+
# (pnpm, cross-env wrapper, tsx) — and the SUT process was the
114+
# newest match for `node.*server\.ts` (tsx loader) but came back
115+
# 99.8% idle while another process pegged CPU. The wrapper chain
116+
# made it impossible to know which file held the actual server
117+
# samples.
118+
#
119+
# Fix: invoke node DIRECTLY (same command the prod script runs,
120+
# minus pnpm + cross-env), so there's exactly one Node process
121+
# under --cpu-prof and it IS the SUT. NODE_ENV is set inline
122+
# instead of via cross-env.
123+
cd src
124+
NODE_ENV=production node \
125+
--cpu-prof --cpu-prof-dir=/tmp/cpuprof \
126+
--require tsx/cjs \
127+
node/server.ts >../ep.log 2>&1 &
128+
SUT_PID=$!
129+
echo "$SUT_PID" > /tmp/cpuprof/sut.pid
130+
echo "SUT pid: $SUT_PID"
131+
cd ..
121132
for i in $(seq 1 90); do
122133
curl -sf http://127.0.0.1:9001/ >/dev/null && break
134+
# Bail out early if the SUT died during boot.
135+
kill -0 "$SUT_PID" 2>/dev/null || { echo "SUT exited during boot"; tail -n 100 ep.log; exit 1; }
123136
sleep 1
124137
done
125138
curl -sf http://127.0.0.1:9001/ >/dev/null || { echo "Etherpad failed to start"; tail -n 100 ep.log; exit 1; }
126-
echo "Etherpad up with cpu-prof; NODE_OPTIONS=$NODE_OPTIONS"
127-
# Record the SUT pid so the upload step can prefer it later if
128-
# multiple cpuprofiles land.
129-
pgrep -f 'node.*server\.ts' -n > /tmp/cpuprof/sut.pid || true
130-
cat /tmp/cpuprof/sut.pid 2>/dev/null || echo "(could not capture sut pid)"
139+
echo "Etherpad up; SUT pid $SUT_PID under --cpu-prof"
131140
132141
- name: Run sweep
133142
working-directory: ./loadtest
@@ -143,9 +152,16 @@ jobs:
143152
- name: Stop Etherpad cleanly (flushes the cpuprofile to disk)
144153
if: always()
145154
run: |
146-
# SIGTERM the node process running server.ts so Node writes the
155+
# SIGTERM the SUT pid we recorded at boot so Node writes the
147156
# .cpuprofile during clean shutdown. SIGKILL would lose it.
148-
pkill -SIGTERM -f 'node.*server\.ts' || true
157+
SUT_PID=$(cat /tmp/cpuprof/sut.pid 2>/dev/null || true)
158+
if [ -n "$SUT_PID" ] && kill -0 "$SUT_PID" 2>/dev/null; then
159+
echo "SIGTERM SUT pid $SUT_PID"
160+
kill -SIGTERM "$SUT_PID"
161+
else
162+
echo "SUT pid $SUT_PID not alive; trying pkill fallback"
163+
pkill -SIGTERM -f 'node.*server\.ts' || true
164+
fi
149165
# Give Node up to 20s to write the profile.
150166
for i in $(seq 1 20); do
151167
ls /tmp/cpuprof/*.cpuprofile 2>/dev/null && break

0 commit comments

Comments
 (0)