Skip to content

Add -N/--max-depth-outer to keep the outermost frames - #160

Open
rlerdorf wants to merge 1 commit into
adsr:masterfrom
rlerdorf:pr5-max-depth-outer
Open

Add -N/--max-depth-outer to keep the outermost frames#160
rlerdorf wants to merge 1 commit into
adsr:masterfrom
rlerdorf:pr5-max-depth-outer

Conversation

@rlerdorf

Copy link
Copy Markdown

-n keeps the innermost frames, which is the wrong end for attributing a sample to a phase of the program.

With -n 30 on a 100-deep recursion you get the leaf detail and lose the ability to say whether the sample is under parseFile or analyzeFile — which, profiling a long multi-stage job, was the question I actually needed answered. -N keeps the outermost frames instead, and the two compose:

$ phpspy -n 2 -N 2 -- php -r 'function f($n){ if($n) f($n-1); else usleep(2000000); } f(20);'
0 usleep <internal>:-1
1 f Command line code:1
2 <elided:19> <elided>:-1
21 f Command line code:1
22 <main> <internal>:-1
flags kept
neither all (unchanged)
-n N innermost N (unchanged)
-N M outermost M
-n N -N M both ends, middle elided

Implementation notes

Keeping the outermost frames means knowing the depth before emitting anything, so trace_stack gains a counting pre-pass. It copies only the prev_execute_data pointer rather than each frame, and is skipped entirely unless -N is given, so nothing changes for existing invocations.

It is deliberately kept inline rather than factored into a helper, so phpspy_trace_tpl.c needs no new #define/#undef pair across its twelve instantiations.

The emitted sequence always begins at depth 0. When the innermost frames are themselves elided, the marker takes depth 0. stackcollapse-phpspy.pl flushes its accumulated stack on $depth == 0, so without this every trace in a file would silently merge into a single stack — I hit exactly that in a first draft. The marker's function token is whitespace-free (<elided:19>) because that script and top.c both split frame lines on spaces; a #-prefixed marker was not an option for the same reason.

The stack can change between the two passes. That is inherent to sampling a running process. If it shrank, the walk ends early and the outer frames are simply missing. If it grew, the number of outer frames emitted is capped by count rather than by the boundary computed earlier, so -N remains a bound on output size.

Both walks are now bounded by PHPSPY_MAX_WALK. That also covers a latent issue in the existing single-pass walk: a torn read of prev_execute_data on a running target can currently loop forever.

-N composes usefully with -b — capping the stack is the cheapest way to keep deep traces inside the per-trace output budget.

Testing

make test passes (15/15) under both make and USE_ZEND=1 make. tests/test_max_depth_outer.sh covers each flag combination, that a stack shallower than the cap is not elided at all, and — as a regression guard for the depth-0 trap above — that a stackcollapse-phpspy.pl round-trip still accounts for every sample.

Independent of #156#159.

-n keeps the innermost frames, which is the wrong end for attributing a sample
to a phase of the program. With -n 30 on a 100-deep recursion you get the leaf
detail and lose the ability to say whether the sample is under parseFile or
analyzeFile -- which, on a long multi-stage job, is usually the question worth
answering. -N keeps the outermost frames instead, and the two compose: -n 2
-N 2 keeps both ends and elides the middle.

Keeping the outermost frames means knowing the depth before emitting anything,
so trace_stack gains a counting pre-pass. It copies only the
prev_execute_data pointer rather than each frame, and is skipped entirely
unless -N is given, so nothing changes for existing invocations. It is kept
inline rather than factored into a helper so that phpspy_trace_tpl.c needs no
new #define/#undef pair across its twelve instantiations.

The emitted sequence always begins at depth 0: when the innermost frames are
themselves elided, the marker takes depth 0. stackcollapse-phpspy.pl flushes
its accumulated stack on depth 0, so without that every trace in a file would
silently merge into one. The marker's function token is whitespace-free
(`<elided:19>`) because both that script and top.c split frame lines on
spaces, and `#`-prefixed lines were not an option for the same reason.

The stack can change between the two passes, which is inherent to sampling a
running process. If it shrank, the walk ends early and the outer frames are
simply missing; if it grew, the number of outer frames emitted is capped by
count rather than by the boundary computed earlier, so -N stays a bound on
output size. Both walks are also bounded by PHPSPY_MAX_WALK, since a torn read
of prev_execute_data could otherwise loop forever -- a latent issue in the
existing single-pass walk too.

-N composes usefully with -b: capping the stack is the cheapest way to keep
deep traces inside the per-trace output budget.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant