Skip to content

pgrep mode: fail fast on bad args, and stop probing processes that are not PHP - #159

Open
rlerdorf wants to merge 1 commit into
adsr:masterfrom
rlerdorf:pr4-pgrep
Open

pgrep mode: fail fast on bad args, and stop probing processes that are not PHP#159
rlerdorf wants to merge 1 commit into
adsr:masterfrom
rlerdorf:pr4-pgrep

Conversation

@rlerdorf

Copy link
Copy Markdown

Three ways -P wastes effort or hides a mistake. All found while profiling on a shared host.

1. A pattern containing a space breaks silently, forever

$ phpspy -P '-f phan/phan -f files' -o out.txt
# on stderr, every poll, forever:
pgrep: only one pattern can be provided
Try `pgrep --help' for more information.

The argument string is interpolated into a shell command, so the pattern's second word becomes a second pgrep pattern. phpspy keeps running and writes an empty file. The exit status was never checked — not pclose's, and popen had no else branch at all, so a failure there just spun the loop quietly.

Status >= 2 is now fatal and says what to look at. Status 1 ("no match") stays the normal idle case, which test_pgrep_time_limit.sh depends on.

2. The pattern usually matches phpspy itself

The pattern text appears in phpspy's own command line, and in the shell or sudo that launched it:

popen_read_line: No stdout; cmd=objdump -Tt '/proc/272954/root//home/user/phpspy/phpspy' | ...
popen_read_line: No stdout; cmd=objdump -Tt '/proc/272362/root//usr/bin/bash' | ...
popen_read_line: No stdout; cmd=objdump -Tt '/proc/272951/root//usr/bin/sudo' | ...
get_symbol_offset: Failed            # x348

phpspy's own pid and its ancestors are now excluded. The ancestry walk reads PPid: from status(5) rather than field 4 of stat(5), whose comm field can contain spaces and parens.

3. Non-PHP matches are re-probed on every poll

A readlink of /proc/<pid>/exe, falling back to matching -w against the mappings for mod_php-style targets, now gates the expensive path in two syscalls instead of four popen'd shell commands.

This filter fails open at every ambiguity — an unreadable /proc means "try it", never "skip it" — so a real target is never silently dropped. That is deliberate, and it is also why the next part is needed.

4. …which is that unattachable pids come back every poll

Because the filter correctly fails open on another user's PHP process (unreadable /proc), find_addresses then fails, and the producer re-queues the same pid on the next poll. On a shared host that was 84 failed attach attempts per second against 11 such processes.

Pids that could not be attached to are now remembered for 30s. Two strikes before sidelining one, because address resolution legitimately fails for a process caught between fork and exec and a single miss should not cost it half a minute.

Together these take the same 3s run from roughly 250 failed attaches to 13.

Testing

make test passes (16/16) under both make and USE_ZEND=1 make. Two new tests: a multi-word -P must fail promptly with a non-zero status rather than hang, and -P '-f phpspy' must produce no objdump probing at all.

Independent of #156, #157 and #158.

…e not PHP

Three ways `-P` wastes effort or hides a mistake.

A pattern containing a space silently breaks, forever. The argument string is
interpolated into a shell command, so `-P '-f phan/phan -f files'` becomes two
pgrep patterns; pgrep then fails on every poll with "only one pattern can be
provided" while phpspy keeps running and produces an empty output file. The
exit status was never checked -- neither pclose's nor popen's, which had no
else branch at all. Status >= 2 is now fatal and says what to look at ("no
match" is status 1 and stays the normal idle case).

The pattern usually matches phpspy itself. The text appears in phpspy's own
command line, and in the shell or sudo that launched it, so phpspy attaches to
those and spends four popen'd shell commands per poll discovering that bash is
not a PHP process. phpspy's own pid and its ancestors are now excluded --
walking PPid from status(5), not field 4 of stat(5), whose comm field can
contain spaces and parens.

Non-PHP matches are re-probed on every poll forever. A readlink of
/proc/<pid>/exe, falling back to matching `-w` against the mappings for
mod_php-style targets, gates the expensive path in two syscalls. It fails open
at every ambiguity: an unreadable /proc means "try it", never "skip it", so a
real target is never silently dropped.

And pids that cannot be attached to are now remembered for 30s rather than
re-queued on every poll. On a shared host with other users' PHP processes --
where /proc is unreadable, so the filter above correctly fails open and
find_addresses then fails -- that was 84 failed attach attempts per second
against 11 processes. Two strikes before sidelining a pid, because address
resolution legitimately fails for a process caught between fork and exec.

Together these take the same 3s run from ~250 failed attaches to 13.
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