GithubHelp home page GithubHelp logo

adsr / phpspy Goto Github PK

View Code? Open in Web Editor NEW
1.4K 54.0 106.0 404 KB

low-overhead sampling profiler for PHP 7+

License: MIT License

Makefile 1.05% C 91.18% GDB 1.54% Shell 4.93% Perl 1.22% Dockerfile 0.08%
profiler performance php hacktoberfest

phpspy's Introduction

phpspy

phpspy is a low-overhead sampling profiler for PHP. It works with non-ZTS PHP 7.0+ with CLI, Apache, and FPM SAPIs on 64-bit Linux 3.2+.

Build Status

Demos

You can profile PHP scripts:

child

You can attach to running PHP processes:

attach cli

attach httpd

It has a top-like mode:

top

It can collect request info, memory usage, and variables:

advanced

You can also use it to make flamegraphs like this:

FlameGraph example

All with no changes to your application and minimal overhead.

Synopsis

$ git clone https://github.com/adsr/phpspy.git
Cloning into 'phpspy'...
...
$ cd phpspy
$ make
...
$ sudo ./phpspy --limit=1000 --pid=$(pgrep -n httpd) >traces
...
$ ./stackcollapse-phpspy.pl <traces | ./vendor/flamegraph.pl >flame.svg
$ google-chrome flame.svg # View flame.svg in browser

Build options

$ make                   # Use built-in structs
$ # or
$ USE_ZEND=1 make ...    # Use Zend structs (requires PHP development headers)

Usage

$ ./phpspy -h
Usage:
  phpspy [options] -p <pid>
  phpspy [options] -P <pgrep-args>
  phpspy [options] [--] <cmd>

Options:
  -h, --help                         Show this help
  -p, --pid=<pid>                    Trace PHP process at `pid`
  -P, --pgrep=<args>                 Concurrently trace processes that
                                       match pgrep `args` (see also `-T`)
  -T, --threads=<num>                Set number of threads to use with `-P`
                                       (default: 16)
  -s, --sleep-ns=<ns>                Sleep `ns` nanoseconds between traces
                                       (see also `-H`) (default: 10101010)
  -H, --rate-hz=<hz>                 Trace `hz` times per second
                                       (see also `-s`) (default: 99)
  -V, --php-version=<ver>            Set PHP version
                                       (default: auto;
                                       supported: 70 71 72 73 74 80 81 82)
  -l, --limit=<num>                  Limit total number of traces to capture
                                       (approximate limit in pgrep mode)
                                       (default: 0; 0=unlimited)
  -i, --time-limit-ms=<ms>           Stop tracing after `ms` milliseconds
                                       (second granularity in pgrep mode)
                                       (default: 0; 0=unlimited)
  -n, --max-depth=<max>              Set max stack trace depth
                                       (default: -1; -1=unlimited)
  -r, --request-info=<opts>          Set request info parts to capture
                                       (q=query c=cookie u=uri p=path
                                       capital=negation)
                                       (default: QCUP; none)
  -m, --memory-usage                 Capture peak and current memory usage
                                       with each trace (requires target PHP
                                       process to have debug symbols)
  -o, --output=<path>                Write phpspy output to `path`
                                       (default: -; -=stdout)
  -O, --child-stdout=<path>          Write child stdout to `path`
                                       (default: phpspy.%d.out)
  -E, --child-stderr=<path>          Write child stderr to `path`
                                       (default: phpspy.%d.err)
  -x, --addr-executor-globals=<hex>  Set address of executor_globals in hex
                                       (default: 0; 0=find dynamically)
  -a, --addr-sapi-globals=<hex>      Set address of sapi_globals in hex
                                       (default: 0; 0=find dynamically)
  -1, --single-line                  Output in single-line mode
  -b, --buffer-size=<size>           Set output buffer size to `size`.
                                       Note: In `-P` mode, setting this
                                       above PIPE_BUF (4096) may lead to
                                       interlaced writes across threads
                                       unless `-J m` is specified.
                                       (default: 4096)
  -f, --filter=<regex>               Filter output by POSIX regex
                                       (default: none)
  -F, --filter-negate=<regex>        Same as `-f` except negated
  -d, --verbose-fields=<opts>        Set verbose output fields
                                       (p=pid t=timestamp
                                       capital=negation)
                                       (default: PT; none)
  -c, --continue-on-error            Attempt to continue tracing after
                                       encountering an error
  -#, --comment=<any>                Ignored; intended for self-documenting
                                       commands
  -@, --nothing                      Ignored
  -v, --version                      Print phpspy version and exit

Experimental options:
  -j, --event-handler=<handler>      Set event handler (fout, callgrind)
                                       (default: fout)
  -J, --event-handler-opts=<opts>    Set event handler options
                                       (fout: m=use mutex to prevent
                                       interlaced writes on stdout in `-P`
                                       mode)
  -S, --pause-process                Pause process while reading stacktrace
                                       (unsafe for production!)
  -e, --peek-var=<varspec>           Peek at the contents of the var located
                                       at `varspec`, which has the format:
                                       <varname>@<path>:<lineno>
                                       <varname>@<path>:<start>-<end>
                                       e.g., xyz@/path/to.php:10-20
  -g, --peek-global=<glospec>        Peek at the contents of a global var
                                       located at `glospec`, which has
                                       the format: <global>.<key>
                                       where <global> is one of:
                                       post|get|cookie|server|files|globals
                                       e.g., server.REQUEST_TIME
  -t, --top                          Show dynamic top-like output

Example (variable peek)

$ sudo ./phpspy -e 'i@/var/www/test/lib/test.php:12' -p $(pgrep -n httpd) | grep varpeek
# varpeek i@/var/www/test/lib/test.php:12 = 42
# varpeek i@/var/www/test/lib/test.php:12 = 42
# varpeek i@/var/www/test/lib/test.php:12 = 43
# varpeek i@/var/www/test/lib/test.php:12 = 44
...

Example (pgrep daemon mode)

$ sudo ./phpspy -H1 -T4 -P '-x php'
0 proc_open <internal>:-1
1 system_with_timeout /home/adam/php-src/run-tests.php:1137
2 run_test /home/adam/php-src/run-tests.php:1937
3 run_all_tests /home/adam/php-src/run-tests.php:1215
4 <main> /home/adam/php-src/run-tests.php:986
# - - - - -
...
^C
main_pgrep finished gracefully

Example (httpd)

$ sudo ./phpspy -p $(pgrep -n httpd)
0 Memcached::get <internal>:-1
1 Cache_MemcachedToggleable::get /foo/bar/lib/Cache/MemcachedToggleable.php:26
2 Cache_Memcached::get /foo/bar/lib/Cache/Memcached.php:251
3 IpDb_CacheBase::getFromCache /foo/bar/lib/IpDb/CacheBase.php:165
4 IpDb_CacheBase::get /foo/bar/lib/IpDb/CacheBase.php:107
5 IpDb_CacheBase::contains /foo/bar/lib/IpDb/CacheBase.php:70
6 IpDb_Botnet::has /foo/bar/lib/IpDb/Botnet.php:32
7 Security_Rule_IpAddr::__construct /foo/bar/lib/Security/Rule/IpAddr.php:53
8 Security_Rule_HttpRequestContext::initVariables /foo/bar/lib/Security/Rule/HttpRequestContext.php:22
9 Security_Rule_Context::__construct /foo/bar/lib/Security/Rule/Context.php:44
10 Security_Rule_Engine::getContextByName /foo/bar/lib/Security/Rule/Engine.php:225
11 Security_Rule_Engine::getContextByLocation /foo/bar/lib/Security/Rule/Engine.php:210
12 Security_Rule_Engine::evaluateActionRules /foo/bar/lib/Security/Rule/Engine.php:116
13 <main> /foo/bar/lib/bootstrap/api.php:49
14 <main> /foo/bar/htdocs/v3/public.php:5
# - - - - -
...

Example (cli child)

$ ./phpspy -- php -r 'usleep(100000);'
0 usleep <internal>:-1
1 <main> <internal>:-1

0 usleep <internal>:-1
1 <main> <internal>:-1

0 usleep <internal>:-1
1 <main> <internal>:-1

0 usleep <internal>:-1
1 <main> <internal>:-1

0 usleep <internal>:-1
1 <main> <internal>:-1

0 usleep <internal>:-1
1 <main> <internal>:-1

process_vm_readv: No such process

Example (cli attach)

$ php -r 'sleep(10);' &
[1] 28586
$ sudo ./phpspy -p 28586
0 sleep <internal>:-1
1 <main> <internal>:-1
...

Example (docker)

$ docker build . -t phpspy
$ docker run -it --cap-add SYS_PTRACE phpspy:latest ./phpspy/phpspy -V73 -r -- php -r 'sleep(1);'
0 sleep <internal>:-1
1 <main> <internal>:-1
...

Known bugs

  • phpspy may not work with a chrooted mod_php process whose binary lives inside overlayfs. (See #109.)

See also

TODO

phpspy's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

phpspy's Issues

can't compile on OSX, missing libpthread

With a freshly updated repo, I try to make phpspy :

phpspy$ make
Makefile:22: *** Need libpthread.  Stop.

libpthread is installed, available in /usr/lib/libpthread.dylib
$LDFLAGS is empty

a direct call to ld gives this (based on the Makefile that leads to the failure) :

$ ld -lpthread 
ld: warning: No version-min specified on command line
ld: warning: -arch not specified
ld: warning: -macosx_version_min not specified, assuming 10.11
Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for inferred architecture x86_64

How can I fix this ?

Find segfault in libdw address finder

Able to reproduce consistently with sudo ./phpspy -V73 -r qcup -P '-x php' and then running make test in the php-src repo, which spawns a lot of php procs.

Unsure if the problem is in phpspy code or somewhere in libdw. I've seen it manifest in a few ways:

Could all be the same underlying problem.

Support multiple versions of PHP

Currently the built-in structs reflect a single PHP API (7.1 or 7.2, I forget). The memory layout of PHP 7.3 is slightly different for example. Users should be able to specify version at runtime or at least compile time. Ideally we'd be able to detect version at runtime and use the correct structs.

Build Stuff

  1. Very cool ...
  2. What do you think about adopting cmake/autotools ?
  3. I've got a sort of fuzzy idea to use php-config to extract struct layout from headers in a given build environment, any thoughts on that ?

Compile fails on CentOS 6.9

Enviroment:

# cat /etc/redhat-release
CentOS release 6.9 (Final)
# python --version
Python 2.6.6
# pip --version
pip 7.1.0 from /usr/lib/python2.6/site-packages (python 2.6)
# rpm -q glibc
glibc-2.12-1.209.el6_9.2.x86_64
# php -v
PHP 7.2.5 (cli) (built: Apr 24 2018 19:29:16) ( NTS )

Step to reproduce:

git clone https://github.com/adsr/phpspy.git /opt/phpspy
cd /opt/phpspy && make

Error output:

cd vendor/termbox && ./waf configure && ./waf --targets=termbox_static
Setting top to                           : /opt/phpspy/vendor/termbox
Setting out to                           : /opt/phpspy/vendor/termbox/build
Checking for 'gcc' (C compiler)          : /usr/bin/gcc
'configure' finished successfully (0.494s)
Waf: Entering directory `/opt/phpspy/vendor/termbox/build'
[1/3] Compiling src/utf8.c
[2/3] Compiling src/termbox.c
[3/3] Linking build/src/libtermbox.a
Waf: Leaving directory `/opt/phpspy/vendor/termbox/build'
'build' finished successfully (0.266s)
cc -std=c99 -Wall -Wextra -pedantic -g -O3  -I. -I./vendor -Ivendor/termbox/src -Ivendor/termbox/src/  phpspy.c pgrep.c top.c addr_objdump.c event_fout.c -o phpspy  -pthread  -Wl,-Bstatic -Lvendor/termbox/build/src/ -ltermbox -Wl,-Bdynamic
phpspy.c: In function ‘copy_proc_mem’:
phpspy.c:577: warning: implicit declaration of function ‘process_vm_readv’
/tmp/cclVq5gw.o: In function `copy_proc_mem':
/opt/phpspy/phpspy.c:577: undefined reference to `process_vm_readv'
/tmp/cclVq5gw.o: In function `clock_get':
/opt/phpspy/phpspy.c:451: undefined reference to `clock_gettime'
/opt/phpspy/phpspy.c:451: undefined reference to `clock_gettime'
/opt/phpspy/phpspy.c:451: undefined reference to `clock_gettime'
/tmp/ccAr982C.o: In function `wait_for_turn':
/opt/phpspy/pgrep.c:62: undefined reference to `clock_gettime'
/tmp/ccAr982C.o: In function `pgrep_for_pids':
/opt/phpspy/pgrep.c:105: undefined reference to `clock_gettime'
/tmp/ccAr982C.o:/opt/phpspy/pgrep.c:62: more undefined references to `clock_gettime' follow
collect2: ld returned 1 exit status
make: *** [phpspy_static] Error 1

how i can fix it?

USE_ZEND

I don't think this can work: phpspy seems to reference private structs like zend_alloc_globals ... which are misnamed as they aren't global :)

Propose that this option is dropped from the build: It's possible to get some of the headers from the build environment (which I would suppose most people don't have installed), but phpspy must still define those private structs. In addition the ability of a single binary to interact with many versions is quite valuable, and you would loose that in a USE_ZEND build. Lastly, anything that makes the source more easily understandable is a win.

Failed to copy zfunc

Was looking at Wordpress both with PHP 7.1 and 7.2 and I got a bunch of these:

0 get_option /var/www/wordpress/wp-includes/option.php:93
1 <main> <internal>:-1
copy_proc_mem: Bad address; raddr=0x100000001 size=128
dump_trace: Failed to copy zfunc
0 translate /var/www/wordpress/wp-includes/l10n.php:134
copy_proc_mem: Bad address; raddr=0x100000006 size=128
dump_trace: Failed to copy zfunc
0 date_i18n /var/www/wordpress/wp-includes/functions.php:103
1 <main> <internal>:-1
2 <main> <internal>:-1
copy_proc_mem: Bad address; raddr=0x40808820000005a size=128
dump_trace: Failed to copy zfunc

Less of them with opcache off, but still some:

0 mysqli_fetch_object <internal>:-1
1 get_object_vars <internal>:-1
2 wp_cache_add /var/www/wordpress/wp-includes/cache.php:30
3 maybe_unserialize /var/www/wordpress/wp-includes/functions.php:330
4 get_metadata /var/www/wordpress/wp-includes/meta.php:510
copy_proc_mem: Bad address; raddr=0x100000001 size=128
dump_trace: Failed to copy zfunc

Unable to build on FreeBSD

Trying to build on FreeBSD 12.0-RELEASE using the provided instructions results in:

make: "/usr/home/Development/phpspy/Makefile" line 22: Need an operator
make: "/usr/home/Development/phpspy/Makefile" line 24: Need an operator
make: "/usr/home/Development/phpspy/Makefile" line 25: Missing dependency operator
make: "/usr/home/Development/phpspy/Makefile" line 28: Need an operator
make: Fatal errors encountered -- cannot continue
make: stopped in /usr/home/Development/phpspy

get_php_bin_path failing to find path to PHP binary

I'm running phpspy on one system where it fails to find the PHP path. It blows up with the error:

popen_read_line: No stdout; cmd=grep ' 0$' /proc/5694/maps | head -n1
get_php_base_addr: Failed to get start_addr

Tracking this back it's because get_php_bin_path returns 0.

Looking at /proc/5694/maps shows that the first line doesn't contain the path to the PHP binary, so awk 'NR==1{path=$NF} /libphp7/{path=$NF} END{print path}' /proc/%d/maps fails to find it.

I've attached the memory map for the process. I couldn't come up with a way to find the path reliably or I would've submitted a PR, but hopefully you can. If not, I wondered if it would make sense to allow specifying the path via a command line option? As a quick hack I've temporarily hardcoded the path.

If you do go down the route of adding a command line option I think it would be worth changing the error message to make it clear what the issue is, as currently it takes a bit of digging to get from get_php_base_addr: Failed to get start_addr to working out it can't find the binary. It would be straightforward enough to add a check in get_php_bin_path to see if path did exist.

Docker: Failed to copy executor_globals; Operation not permitted raddr

When I try to use phpspy inside docker container I got error:

copy_proc_mem: Failed to copy executor_globals; err=Operation not permitted raddr=0x7f30b3b0e240 size=496

Step to reporduce:

docker run -p 8181:80 -d --name=apache php:apache
docker exec  -it apache bash

inside container:

#add index file
echo "<?php phpinfo();" > index.php

# install requried libs
apt-get update
apt-get install git python gcc

cd ..
git clone https://github.com/adsr/phpspy.git
cd phpspy/
make
./phpspy -p 1

I tried on other images (centos+fpm) and I have the same error.

Add option/example of how to generate reversed flame graphs?

This has been useful for me to identify helper functions that are frequently called and have slow performance (E.g. total time spent directly within the scope of a function, excluding helper functions it calls, would be at the bottom of the graph)

e.g. if 20% of the total execution time is spent in my_helper(), but my_helper() has 100 callers, it'd be hard to tell that my_helper() is making the whole program slower from a regular flame graph, but easy with a reversed flame graph.

What I'm currently using (adding -r flag to perl script might be better):

» diff stackcollapse-phpspy.pl stackcollapse-phpspy-reversed.pl 
68c68
<         $stacks{join(';', reverse @frames)} += 1 if @frames;
---
>         $stacks{join(';', @frames)} += 1 if @frames;
74c74
< $stacks{join(';', reverse @frames)} += 1 if @frames;
---
> $stacks{join(';', @frames)} += 1 if @frames;

License information

I created a packaging script for archlinux on the AUR for easier installation and the package should include license information, didn't found any license file on the repo so was wondering what license will this project be licensed as :)

For the moment I just added the 'unknown' license placeholder. Thanks for sharing such a nice application!

Filter out invalid characters in stackcollapse (or phpspy) by default?

Preferably replace invalid bytes (or codepoints) with a placeholder character or encoding such as \x04/%04 so that the svg can be rendered even if the method name was invalid or read incorrectly.

https://en.wikipedia.org/wiki/Valid_characters_in_XML#XML_1.0 mentions the valid character values for xml 1.0 - filtering out invalid bytes would be an easy start

Newlines might also make sense to remove if the filtering was done in phpspy, but I haven't seen those


When analyzing a long running stack trace in php 7.3.8-dev, I saw the following error displayed in Google Chrome

(for the decimal byte values 4 and 16)

This page contains the following errors:

error on line 13591 at column 10: PCDATA invalid Char value 4

Below is a rendering of the page up to the first error.

The raw logs had various snippets such as the following, with the raw bytes 4 and 16. Some samples of what they looked like:

0 Phan\Language\UnionType::hasTemplateTypeRecursive /path/to/phan/src/Phan/Language/UnionType.php:881
1 ::� <internal>:-1

0 ::� <internal>:-1
1 Phan\BlockAnalysisVisitor::visitStmtList /path/to/phan/src/Phan/BlockAnalysisVisitor.php:208
2 Phan\BlockAnalysisVisitor::analyzeAndGetUpdatedContext /path/to/phan/src/Phan/BlockAnalysisVisitor.php:472
3 Phan\BlockAnalysisVisitor::visitIf /path/to/phan/src/Phan/BlockAnalysisVisitor.php:1357
4 Phan\BlockAnalysisVisitor::analyzeAndGetUpdatedContext /path/to/phan/src/Phan/BlockAnalysisVisitor.php:472
5 Phan\BlockAnalysisVisitor::visitStmtList /path/to/phan/src/Phan/BlockAnalysisVisitor.php:208
6 Phan\BlockAnalysisVisitor::analyzeAndGetUpdatedContext /path/to/phan/src/Phan/BlockAnalysisVisitor.php:472
7 Phan\BlockAnalysisVisitor::visitForeach /path/to/phan/src/Phan/BlockAnalysisVisitor.php:756
8 Phan\BlockAnalysisVisitor::analyzeAndGetUpdatedContext /path/to/phan/src/Phan/BlockAnalysisVisitor.php:472
9 Phan\BlockAnalysisVisitor::visitStmtList /path/to/phan/src/Phan/BlockAnalysisVisitor.php:208
10 Phan\BlockAnalysisVisitor::analyzeAndGetUpdatedContext /path/to/phan/src/Phan/BlockAnalysisVisitor.php:472
11 Phan\BlockAnalysisVisitor::visitMethod /path/to/phan/src/Phan/BlockAnalysisVisitor.php:2065
12 Phan\BlockAnalysisVisitor::analyzeAndGetUpdatedContext /path/to/phan/src/Phan/BlockAnalysisVisitor.php:472
13 Phan\BlockAnalysisVisitor::visitStmtList /path/to/phan/src/Phan/BlockAnalysisVisitor.php:208
14 Phan\BlockAnalysisVisitor::analyzeAndGetUpdatedContext /path/to/phan/src/Phan/BlockAnalysisVisitor.php:472
15 Phan\BlockAnalysisVisitor::visitClosedContext /path/to/phan/src/Phan/BlockAnalysisVisitor.php:1135
16 Phan\BlockAnalysisVisitor::visitClass /path/to/phan/src/Phan/BlockAnalysisVisitor.php:2051
17 Phan\BlockAnalysisVisitor::analyzeAndGetUpdatedContext /path/to/phan/src/Phan/BlockAnalysisVisitor.php:472
18 Phan\BlockAnalysisVisitor::visitStmtList /path/to/phan/src/Phan/BlockAnalysisVisitor.php:208
19 Phan\AST\Visitor\KindVisitorImplementation::__invoke /path/to/phan/src/Phan/AST/Visitor/KindVisitorImplementation.php:29
20 Phan\Analysis::analyzeFile /path/to/phan/src/Phan/Analysis.php:473
21 Phan\Phan::Phan\{closure} /path/to/phan/src/Phan/Phan.php:401
22 Phan\Phan::finishAnalyzingRemainingStatements /path/to/phan/src/Phan/Phan.php:316
23 Phan\Phan::analyzeFileList /path/to/phan/src/Phan/Phan.php:122
24 <main> /path/to/phan/src/phan.php:1
25 <main> /path/to/phan/phan:1

test breaks on php 7.2

nice work on setting up a test, I noticed that it fails on PHP 7.2 here

expected[req_path ]='^# path = Standard input code'

the equivalent trace on PHP 7.2 looks like this:

0 usleep <internal>:-1
1 <main> <internal>:-1
# uri = -
# path = -
# qstring = -
# cookie = -
# ts = 1540393892.910322

Support range of line numbers in varpeek

We currently support varname@file:lineno. It would be nice to support varname@file:start-end as well. This will require some refactoring as the current impl relies on a hash lookup via a key formatted as file:lineno. This will need to change to a hash lookup via file and then some sort of efficient search to see if lineno falls in any start-end ranges.

Docker: not displaying anything when profiling a PHP script

Hello,

I can't profile a php script inside docker. It is pending and it displays nothing.
I can strace the PHP script without any problem.

docker run --privileged --cap-add=SYS_PTRACE --rm akeneo/php php -r "sleep(100);"

I execute phpspy with strace to better know what happens:

sudo strace ./phpspy -p 20493

And here is what I get:

...
clock_gettime(CLOCK_MONOTONIC_RAW, {tv_sec=117355, tv_nsec=921478423}) = 0
process_vm_readv(20493, [{iov_base="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., iov_len=496}], 1, [{iov_base=0x5562e9485940, iov_len=496}], 1, 0) = 496
clock_gettime(CLOCK_MONOTONIC_RAW, {tv_sec=117355, tv_nsec=921772631}) = 0
nanosleep({tv_sec=0, tv_nsec=9806802}, NULL) = 0
clock_gettime(CLOCK_MONOTONIC_RAW, {tv_sec=117355, tv_nsec=931932920}) = 0
process_vm_readv(20493, [{iov_base="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., iov_len=496}], 1, [{iov_base=0x5562e9485940, iov_len=496}], 1, 0) = 496
clock_gettime(CLOCK_MONOTONIC_RAW, {tv_sec=117355, tv_nsec=932174327}) = 0
nanosleep({tv_sec=0, tv_nsec=9859603}, NULL) = 0
clock_gettime(CLOCK_MONOTONIC_RAW, {tv_sec=117355, tv_nsec=942321317}) = 0
process_vm_readv(20493, [{iov_base="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., iov_len=496}], 1, [{iov_base=0x5562e9485940, iov_len=496}], 1, 0) = 496
clock_gettime(CLOCK_MONOTONIC_RAW, {tv_sec=117355, tv_nsec=942508791}) = 0
nanosleep({tv_sec=0, tv_nsec=9913536}, NULL) = 0
...

Also, I tried with the official PHP image:
docker run --privileged --cap-add=SYS_PTRACE --rm php php -r "sleep(100);"

but I got another error, which is not related to this issue I suppose.

popen_read_line: No stdout; cmd=awk '/libphp7/{print $NF; exit 0} END{exit 1}' /proc/21207/maps || readlink -e /proc/21207/exe
get_php_bin_path: Failed

Am I missing something?

Thanks.

add long options

As the list of options grows, it would be helpful for new users to have a more descriptive set of long arguments

example:

Usage: phpspy [options] [--] <php_command>

-h, --help        Show help
-p, --pid <pid>   Trace PHP process at pid
-s, --nanoseconds <ns>    Sleep this many nanoseconds between traces (default: 10000000, 10ms)
-H, --hertz <hz>    The frequency at which traces are recorded (default: 100hz)
-n, --max-depth <max>   Set max stack trace depth to `max` (default: -1, unlimited)
...

Make extensible

This was @krakjoe 's idea.

I think we have a head start on this as the tracing code is already decoupled with the event handling code. See:

phpspy/phpspy.c

Line 262 in e9927c8

context.event_handler = event_handler_fout; /* TODO set based on option */

int event_handler_fout(struct trace_context_s *context, int event_type) {

Maybe we write an event_lua.c that is end-user scriptable?

Fails to run with message "unexpected token `deleted'"

./phpspy -V72 -p 127743
sh: -c: line 0: syntax error near unexpected token `deleted'
sh: -c: line 0: `objdump -p /proc/127743/root//usr/bin/php;5cde72be (deleted) | awk '/LOAD/{print $5; exit}''
popen_read_line: No stdout; cmd=objdump -p /proc/127743/root//usr/bin/php;5cde72be (deleted) | awk '/LOAD/{print $5; exit}'
get_php_base_addr: Failed to get virt_addr

For some reason the binary seems to be having ;5cde72be (deleted) appended which causes this to fail.

Running the command without this suffix gives me an address back.

objdump -p /proc/127743/root//usr/bin/php | awk '/LOAD/{print $5; exit}'
0x0000000000000000

This is on CentOS using Webtatic php72w.

Thank you!

Really really thank you, this tool just saved our production environment! I was able to find a infinite loop in a couple of minutes which was completely impossible to find, even with NewRelic, Blackfire, log analysis and days of search. The right tool, at the right moment.

Can you add a "buy me a beer" link in the README.md? 😄

Argument "…" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 810.

Tried to run it just with Laravels artisan:

$ php -v
PHP 7.1.20-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Jul 25 2018 10:07:09) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies

$ ./artisan --version
Laravel Framework 5.6.31

There are other messages too like copy_proc_mem: Bad address; raddr=(nil) size=128 or dump_trace: Failed to copy zfunc but since I'm totally new to this, I've no idea if this is ok or not.

How I used it:

~phpspy $ sudo ./phpspy -- php /path/to/laravel/artisan |  ./stackcollapse-phpspy.pl| ./flamegraph.pl > flame.svg
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=0x100000000 size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=0x100000004 size=16
dump_trace: Failed to copy zce
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=0x1 size=32
dump_trace: Failed to copy function_name
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=0x300000c08 size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
copy_proc_mem: Bad address; raddr=(nil) size=128
dump_trace: Failed to copy zfunc
Argument "Available" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 810.
Argument "If" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 927.
Argument "the" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 928.
Argument "This" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 930.
Argument "The" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 935.
Argument "Can" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 950.
Argument "This" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 952.
Argument "Only" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 953.
Argument "production" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 954.
Argument "Depending" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 956.
Argument "on" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 957.
Argument "A" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 960.
Argument "NOT" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 961.
Argument "It's" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 963.
Argument "applicable" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 964.
Argument "However" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 966.
Argument "a)" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 967.
Argument "b)" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 968.
Argument "In" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 970.
Argument "disk" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 971.
Argument "The" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 977.
Argument "records." isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 978.
Argument "receive" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 979.
Argument "Setting" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 982.
Argument "-" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 983.
Argument "-" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 985.
Argument "After" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 988.
Argument "Note:" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 991.
Argument "during" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 992.
Argument "operation" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 993.
Argument "Note:" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 996.
Argument "during" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 997.
Argument "operation" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 998.
Argument "Note:" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 1009.
Argument "\\Autoload\\includeFile" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 1017.
Argument "By" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 1089.
Argument "-" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 1090.
Argument "-" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 1091.
Argument "It's" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 1094.
Argument "Currently" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 1096.
Argument "-" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 1097.
process_vm_readv: No such process
Argument "ommand\\ListCommand::execute" isn't numeric in numeric eq (==) at ./stackcollapse-phpspy.pl line 73, <> line 1119.

add ability to print any `$_VAR` super global

now that we find the php_core_globals address and can print a basic representation of zend_array with print_array_recursive — we can easily grab any of the following info

https://github.com/php/php-src/blob/029a8c9882c70bf1326f8bf1b9446bc11867a80c/main/php_globals.h#L39-L45

#define TRACK_VARS_POST		0
#define TRACK_VARS_GET		1
#define TRACK_VARS_COOKIE	2
#define TRACK_VARS_SERVER	3
#define TRACK_VARS_ENV		4
#define TRACK_VARS_FILES	5
#define TRACK_VARS_REQUEST	6

Add more output format options

It would be useful to have support for various output formats along with the raw data (e.g. Callgrind and summary stats.) Flamegraph formatted output could be incorporated into the profiler as another output format option.

Users could then pass a -f option to specify the type of output.

Another option would be to have separate scripts like stackcollapse-phpspy that would transform the raw stacks into various other formats post hoc.

Test with php-fpm SAPI

Rasmus notes it mostly works, however...

...it seems to think it is spending most of its time on line 1 of wp-settings.php which doesn't make much sense

Memory option "-m" fails with popen_read_line: No stdout [...] get_symbol_offset: Failed

Hey,

I'm trying to use the -m flag on the following test script:

<?php

$i = 0;
while (true) {
    $i++;
    file_put_contents("/tmp/foo.txt", "hello$i\n", FILE_APPEND);
    sleep(1);
}

running as a background process but I get the following error:

popen_read_line: No stdout; cmd=objdump -Tt /usr/bin/php7.3 | awk '/ alloc_globals$/{print $1; exit}'
get_symbol_offset: Failed

Full steps (running as root):

# php /var/www/current/test.php &
[1] 311
# ~/phpspy/phpspy -p 311 -m
popen_read_line: No stdout; cmd=objdump -Tt /usr/bin/php7.3 | awk '/ alloc_globals$/{print $1; exit}'
get_symbol_offset: Failed
# ps aux | grep test.php
root       311  0.0  0.6 331260 27604 pts/1    S    10:18   0:00 php /var/www/current/test.php
root       328  0.0  0.0  14512  1076 pts/1    S+   10:19   0:00 grep --color=auto test.php

Using https://github.com/phusion/baseimage-docker to test

$ docker build -t my-phpspy -<<'EOF'
FROM phusion/baseimage:latest

RUN apt-get update -yqq && apt-get install -yqq software-properties-common && \
    add-apt-repository -y ppa:ondrej/php \
;

RUN apt-get update -yqq && apt-get install -yqq \
    git \
    build-essential \
    python \
    php7.3-cli \
;

RUN cd ~ \
 && git clone --recursive https://github.com/adsr/phpspy.git \
 && cd phpspy \
 && make \
;
EOF

$ docker run -d my-phpspy

Build failure on Ubuntu 18.04

Attempting to build the tool on WSL Ubuntu 18.04; get's almost done but throws some errors at me.

  ...
  CCLD     libebl_i386.so
  CCLD     libebl_sh.so
  CCLD     libebl_x86_64.so
  CCLD     libebl_ia64.so
  CCLD     libebl_alpha.so
  CCLD     libebl_arm.so
  CCLD     libebl_aarch64.so
  CCLD     libebl_sparc.so
  CCLD     libebl_ppc.so
  CCLD     libebl_ppc64.so
  CCLD     libebl_s390.so
  CCLD     libebl_tilegx.so
  CCLD     libebl_m68k.so
  CCLD     libebl_bpf.so
  CCLD     libebl_riscv.so
rm sparc_initreg.o ppc_cfi.o m68k_cfi.o riscv_initreg.o x86_64_symbol.o ppc_auxv.o alpha_symbol.o arm_auxv.o m68k_symbol.o riscv_corenote.o i386_unwind.o sparc_symbol.o x86_64_syscall.o aarch64_init.o ppc_initreg.o i386_retval.o sparc_corenote.o m68k_initreg.o sparc_cfi.o ppc_regs.o ppc_init.o s390_init.o ppc_syscall.o tilegx_symbol.o ia64_symbol.o i386_corenote.o m68k_corenote.o s390_cfi.o sh_regs.o i386_init.o ppc_corenote.o alpha_init.o i386_auxv.o m68k_init.o x32_corenote.o s390_initreg.o arm_attrs.o ppc64_resolve_sym.o x86_64_initreg.o ia64_init.o arm_initreg.o ppc64_retval.o x86_64_retval.o ia64_retval.o ppc64_unwind.o tilegx_init.o arm_cfi.o aarch64_initreg.o sparc_attrs.o i386_syscall.o arm_retval.o bpf_symbol.o arm_symbol.o x86_64_corenote.o aarch64_symbol.o s390_unwind.o aarch64_corenote.o alpha_corenote.o sparc64_corenote.o x86_64_cfi.o sh_symbol.o x86_64_init.o i386_regs.o ppc64_corenote.o s390_retval.o ppc_retval.o bpf_regs.o bpf_init.o ia64_regs.o alpha_retval.o arm_corenote.o aarch64_cfi.o aarch64_unwind.o sparc_init.o i386_initreg.o x86_64_unwind.o aarch64_retval.o s390_corenote.o riscv_regs.o sparc_auxv.o riscv_symbol.o s390_symbol.o sparc_retval.o s390x_corenote.o riscv_init.o x86_64_regs.o sh_retval.o sh_corenote.o i386_cfi.o alpha_regs.o ppc64_symbol.o m68k_retval.o arm_regs.o aarch64_regs.o sparc_regs.o i386_symbol.o m68k_regs.o alpha_auxv.o ppc_symbol.o arm_init.o s390_regs.o tilegx_retval.o riscv_cfi.o ppc64_init.o ppc_attrs.o tilegx_regs.o tilegx_corenote.o sh_init.o
make[1]: Leaving directory '/home/ubuntu/phpspy/vendor/elfutils'
cd vendor/termbox && ./waf configure && ./waf --targets=termbox_static
Setting top to                           : /home/ubuntu/phpspy/vendor/termbox
Setting out to                           : /home/ubuntu/phpspy/vendor/termbox/build
Checking for 'gcc' (C compiler)          : /usr/bin/gcc
'configure' finished successfully (0.094s)
Waf: Entering directory `/home/ubuntu/phpspy/vendor/termbox/build'
[1/3] c: src/termbox.c -> build/src/termbox.c.2.o
[2/3] c: src/utf8.c -> build/src/utf8.c.2.o
../src/termbox.c: In function ‘sigwinch_handler’:
../src/termbox.c:582:2: warning: ignoring return value of ‘write’, declared with attribute warn_unused_result [-Wunused-result]
  write(winch_fds[1], &zzz, sizeof(int));
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from ../src/termbox.c:19:0:
../src/bytebuffer.inl: In function ‘bytebuffer_flush’:
../src/bytebuffer.inl:58:2: warning: ignoring return value of ‘write’, declared with attribute warn_unused_result [-Wunused-result]
  write(fd, b->buf, b->len);
  ^~~~~~~~~~~~~~~~~~~~~~~~~
../src/termbox.c: In function ‘wait_fill_event’:
../src/termbox.c:672:4: warning: ignoring return value of ‘read’, declared with attribute warn_unused_result [-Wunused-result]
    read(winch_fds[0], &zzz, sizeof(int));
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[3/3] cstlib: build/src/termbox.c.2.o build/src/utf8.c.2.o -> build/src/libtermbox.a
Waf: Leaving directory `/home/ubuntu/phpspy/vendor/termbox/build'
'build' finished successfully (0.669s)
cc -std=c99 -Wall -Wextra -pedantic -g -Ofast -pthread  -I. -I./vendor -Ivendor/termbox/src -Ivendor/elfutils/lib -Ivendor/elfutils/libelf -Ivendor/elfutils/libebl -Ivendor/elfutils/libdwelf -Ivendor/elfutils/libdwfl -Ivendor/elfutils/libdw -Ivendor/elfutils/libcpu -Ivendor/elfutils/backends -Ivendor/symlinks/libdwfl -Ivendor/termbox/src/ -DUSE_TERMBOX=1 phpspy.c pgrep.c top.c addr_libdw.c event_fout.c -o phpspy  -Lvendor/elfutils/libdw -Lvendor/elfutils/libelf -Lvendor/elfutils/libdwfl -Lvendor/elfutils/libebl -Lvendor/elfutils/libdwelf  -Wl,-Bstatic -ldw -lelf -ldwfl -lebl -ldwelf -Lvendor/elfutils/lib -leu -Lvendor/elfutils/backends -lebl_x86_64_pic -Wl,-Bdynamic -ldl -lz -llzma -lbz2 -Wl,-Bstatic -Lvendor/termbox/build/src/ -ltermbox -Wl,-Bdynamic
/usr/bin/ld: cannot find -llzma
/usr/bin/ld: cannot find -lbz2
collect2: error: ld returned 1 exit status
Makefile:46: recipe for target 'phpspy_static' failed
make: *** [phpspy_static] Error 1
ubuntu@CPX-U6PE5MO3CD9:~/phpspy$
ubuntu@CPX-U6PE5MO3CD9:~/phpspy$ make phpspy_dynamic
Makefile:49: *** Need libdw. Hint: try `make phpspy_static`.  Stop.
ubuntu@CPX-U6PE5MO3CD9:~/phpspy$ make phpspy_static
cc -std=c99 -Wall -Wextra -pedantic -g -Ofast -pthread  -I. -I./vendor -Ivendor/termbox/src -Ivendor/elfutils/lib -Ivendor/elfutils/libelf -Ivendor/elfutils/libebl -Ivendor/elfutils/libdwelf -Ivendor/elfutils/libdwfl -Ivendor/elfutils/libdw -Ivendor/elfutils/libcpu -Ivendor/elfutils/backends -Ivendor/symlinks/libdwfl -Ivendor/termbox/src/ -DUSE_TERMBOX=1 phpspy.c pgrep.c top.c addr_libdw.c event_fout.c -o phpspy  -Lvendor/elfutils/libdw -Lvendor/elfutils/libelf -Lvendor/elfutils/libdwfl -Lvendor/elfutils/libebl -Lvendor/elfutils/libdwelf  -Wl,-Bstatic -ldw -lelf -ldwfl -lebl -ldwelf -Lvendor/elfutils/lib -leu -Lvendor/elfutils/backends -lebl_x86_64_pic -Wl,-Bdynamic -ldl -lz -llzma -lbz2 -Wl,-Bstatic -Lvendor/termbox/build/src/ -ltermbox -Wl,-Bdynamic
/usr/bin/ld: cannot find -llzma
/usr/bin/ld: cannot find -lbz2
collect2: error: ld returned 1 exit status
Makefile:46: recipe for target 'phpspy_static' failed
make: *** [phpspy_static] Error 1
ubuntu@CPX-U6PE5MO3CD9:~/phpspy$

Any ideas concerning the collect2: error: ld returned 1 exit status ?

Interlaced output from multiple threads being traced

Occasionally phpspy will interlace output from multiple threads of tracing like so:

ternal>:-1
1 FooBarBaz::execute /var/foo/bar/phplib/Baz/DBBaz.php:104
2 FooBarBaz::bindAndExecuteStatement /var/foo/bar/Baz/Baz/Baz.php:1379
3 FooBarBaz::callFunc /var/foo/bar0 curl_exec <internal>:-1
1 Baz::execute /var/foo/bar/Bazify.php:152
2 FooBarBaz::singleCurlFetch /var/foo/bar/Baz/SuperBaz.php:503

An strace points to these fprintf calls happening multiple times on a single trace/with multiple threads they could be overlapping with each other.

@keyurdg suggested:

Instead of line buffering here maybe full buffer _IOFBF and fflush when completed

and fflush here

Document termbox dependency

Commit df75b6f added a dependency on termbox which means following the simple make instructions in the readme now blows up:

$ make
cc -Wall -Wextra -pedantic -g -Ofast -pthread  -I.  -DUSE_READELF=1 phpspy.c pgrep.c top.c addr_libdw.c addr_readelf.c -o phpspy
In file included from phpspy.c:1:0:
phpspy.h:24:21: fatal error: termbox.h: No such file or directory
 #include <termbox.h>
                     ^
compilation terminated.
In file included from pgrep.c:1:0:
phpspy.h:24:21: fatal error: termbox.h: No such file or directory
 #include <termbox.h>
                     ^
compilation terminated.
In file included from top.c:1:0:
phpspy.h:24:21: fatal error: termbox.h: No such file or directory
 #include <termbox.h>
                     ^
compilation terminated.
In file included from addr_libdw.c:1:0:
phpspy.h:24:21: fatal error: termbox.h: No such file or directory
 #include <termbox.h>
                     ^
compilation terminated.
In file included from addr_readelf.c:1:0:
phpspy.h:24:21: fatal error: termbox.h: No such file or directory
 #include <termbox.h>
                     ^
compilation terminated.
make: *** [phpspy] Error 1

Installing https://github.com/nsf/termbox allows make to complete successfully (albeit with a lot of warnings), but this could probably do with documenting.

Great tool by the way! I've already used it in anger a couple of times to work out what a stuck process was doing.

Some errors occurred when generate frame

PHP

PHP 7.1.26-1+ubuntu16.04.1+deb.sury.org+1 (cli) (built: Jan 11 2019 14:13:16) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.1.26-1+ubuntu16.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies
Linux admin 4.4.0-1072-aws #82-Ubuntu SMP Fri Nov 2 15:00:21 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

Command:

./phpspy -l 10000 -p 23465 | ./stackcollapse-phpspy.pl | ./vendor/flamegraph.pl > frame.svg

output:

copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00000003 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x100000000 size=132
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0xffffffff00001c07 size=25
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00000004 size=132
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0x6 size=25
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x800001c07 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00001406 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00000002 size=132
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0x6 size=25
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00000004 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00000c08 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00000003 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00001406 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00000c08 size=132
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0x4effe0000000007f size=25
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x563b00000003 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x4245445f444e455a size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00000c08 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0xa00000001 size=132
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0x7f4f002000 size=25
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0xffffffff00000006 size=25
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0xb42b97e08712010 size=25
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x73 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x563b00000000 size=132
copy_proc_mem: Not copying zfunc; raddr is NULL
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x61775f6c7173796d size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00000001 size=132
copy_proc_mem: Not copying zfunc; raddr is NULL
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0xffe0000000007f4f size=25
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0xe0000000007f4f00 size=25
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0xb42b977c3312010 size=25
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00001c07 size=132
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0xffe0000000007f4f size=25
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x3939646200000c08 size=132
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0xffffffff00000001 size=25
copy_proc_mem: Not copying zfunc; raddr is NULL
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x7f4e00001406 size=132
copy_proc_mem: Failed to copy zfunc; err=Bad address raddr=0x200000c08 size=132
copy_proc_mem: Failed to copy function_name; err=Bad address raddr=0x7f4effe000000000 size=25

Production readyness

Hello,

I'm planning to use phpspy in production and since it needs root access to do it's magic I would like to know how production-safe/ready you think this project is.
Also if there are any security audit's on the source to make my life easier.

My intentions are:

  1. peek php processes from time to time.
  2. keep it running all the time gathering profile-sampling information of production environments.

Im sure usecase 1. is ok.
On the 2nd usecase, xhprof is not compatible with php7, one can use tideways's fork. But tideways removed the sampling module in the portability effort.

So is it safe to be running constantly? Is that considered a common usecase?
Are there any known issues or missing things to take into consideration.

try_get_php_version

This may be improved ... version string can be read from (char*) basic_functions_module.version (there are no builds of PHP where this is not present).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.