├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.yml │ └── bug_report.yml └── workflows │ ├── test │ ├── build-xdebug.sh │ ├── build-extension.sh │ ├── tests.sh │ └── build-php.sh │ ├── package.yml │ ├── package │ └── package.sh │ └── test.yml ├── assets └── qcachegrind.png ├── tests ├── memprof-version.phpt ├── 006.phpt ├── 005.phpt ├── common.php ├── zend_pass_function.phpt ├── 004.phpt ├── 007.phpt ├── 001.phpt ├── dump-pprof.phpt ├── 003.phpt ├── autodump-failure.phpt ├── dump-failure.phpt ├── autodump-disabled.phpt ├── autodump-format-failure.phpt ├── autodump.phpt ├── autodump-pprof.phpt ├── autodump-xdebug.phpt └── 002.phpt ├── .editorconfig ├── composer.json ├── memprof.stub.php ├── .gitignore ├── LICENSE ├── util.h ├── memprof_legacy_arginfo.h ├── memprof_arginfo.h ├── config.m4 ├── php_memprof.h ├── INTERNALS.md ├── util.c ├── package.xml ├── README.md └── memprof.c /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /assets/qcachegrind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arnaud-lb/php-memory-profiler/HEAD/assets/qcachegrind.png -------------------------------------------------------------------------------- /tests/memprof-version.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | memprof_version() 3 | --FILE-- 4 | 14 | bool(true) 15 | ["native"]=> 16 | bool(false) 17 | ["dump_on_limit"]=> 18 | bool(false) 19 | } 20 | -------------------------------------------------------------------------------- /tests/007.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Enable native profiling 3 | --SKIPIF-- 4 | 15 | bool(true) 16 | ["native"]=> 17 | bool(true) 18 | ["dump_on_limit"]=> 19 | bool(false) 20 | } 21 | -------------------------------------------------------------------------------- /tests/001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Enable/disable 3 | --FILE-- 4 | = 8.1.0" 8 | }, 9 | "php-ext": { 10 | "extension-name": "memprof", 11 | "support-zts": false, 12 | "configure-options": [ 13 | { 14 | "name": "with-judy-dir", 15 | "description": "Use system libjudy", 16 | "needs-value": true 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/dump-pprof.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | memprof_dump_pprof() 3 | --ENV-- 4 | MEMPROF_PROFILE=1 5 | --FILE-- 6 | /dev/null 2>&1; then 12 | sudo apt-get -y install libjudy-dev 13 | else 14 | brew install traildb/judy/judy 15 | fi 16 | 17 | if [ "$WERROR" = "1" ]; then 18 | PHP_EXT_CFLAGS="-Wall -Werror -Wno-deprecated-declarations" 19 | fi 20 | 21 | cd ext 22 | phpize 23 | CFLAGS="$PHP_EXT_CFLAGS" ./configure 24 | make 25 | 26 | echo "extension=$(pwd)/modules/memprof.so"|sudo tee "$HOME/php/etc/php/memprof.ini" >/dev/null 27 | -------------------------------------------------------------------------------- /.github/workflows/test/tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -xve 4 | 5 | export PATH="$HOME/php/bin:$PATH" 6 | hash -r 7 | 8 | cd ext 9 | 10 | if [ "$MEMORY_CHECK" = "1" ]; then 11 | echo "Enabling memory checking" 12 | showmem=--show-mem 13 | checkmem=-m 14 | fi 15 | 16 | # Hack to ensure that run-tests.php attempts to load xdebug as a zend_extension 17 | # This hack is necessary with PHP < 8.1 18 | sed -i "s/if (\$req_ext == 'opcache') {/if (\$req_ext == 'opcache' || \$req_ext == 'xdebug') {/" run-tests.php || true 19 | 20 | PHP=$(which php) 21 | REPORT_EXIT_STATUS=1 TEST_PHP_EXECUTABLE="$PHP" "$PHP" run-tests.php -q $checkmem --show-diff $showmem 22 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: 'Package' 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | package: 8 | name: 'Package' 9 | runs-on: 'ubuntu-22.04' 10 | steps: 11 | - name: 'Check out repository' 12 | uses: 'actions/checkout@v4' 13 | with: 14 | path: 'ext' 15 | 16 | - name: 'Install dependencies' 17 | run: 'sudo apt-get -y install php8.1-dev libjudy-dev' 18 | 19 | - name: 'Package and verify package' 20 | run: './ext/.github/workflows/package/package.sh' 21 | 22 | - name: 'Archive package' 23 | uses: 'actions/upload-artifact@v4' 24 | with: 25 | path: 'ext/memprof.tgz' 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | 3 | !.gitignore 4 | 5 | *.dep 6 | *.la 7 | *.lo 8 | .deps 9 | .libs 10 | Makefile 11 | Makefile.fragments 12 | Makefile.global 13 | Makefile.objects 14 | acinclude.m4 15 | aclocal.m4 16 | autom4te.cache 17 | build 18 | common.fn_flags 19 | config.guess 20 | config.h 21 | config.h.in 22 | config.h.in~ 23 | config.log 24 | config.nice 25 | config.status 26 | config.sub 27 | configure 28 | configure.ac 29 | configure.in 30 | install-sh 31 | libtool 32 | ltmain.sh 33 | memprof-*.tgz 34 | missing 35 | mkinstalldirs 36 | modules 37 | run-tests.php 38 | tests/*.diff 39 | tests/*.exp 40 | tests/*.log 41 | tests/*.out 42 | tests/*.php 43 | tests/*.sh 44 | tests/phpt.* 45 | tmp-php.ini 46 | -------------------------------------------------------------------------------- /tests/003.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | memprof_dump_callgrind() 3 | --FILE-- 4 | 26 | bool(true) 27 | ["native"]=> 28 | bool(false) 29 | ["dump_on_limit"]=> 30 | bool(true) 31 | } 32 | 33 | Fatal error: Allowed memory size of 15728640 bytes exhausted%S (tried to allocate %d bytes) (memprof failed dumping to %smemprof.callgrind%s, please check file permissions or disk capacity) in %s on line%a 34 | -------------------------------------------------------------------------------- /tests/dump-failure.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | memprof_dump_ failure 3 | --ENV-- 4 | MEMPROF_PROFILE=1 5 | --FILE-- 6 | getMessage(), "\n"; 21 | } 22 | 23 | try { 24 | memprof_dump_callgrind($fd); 25 | } catch (\Exception $e) { 26 | echo "Exception: ", $e->getMessage(), "\n"; 27 | } 28 | 29 | try { 30 | memprof_dump_pprof($fd); 31 | } catch (\Exception $e) { 32 | echo "Exception: ", $e->getMessage(), "\n"; 33 | } 34 | 35 | --EXPECTF-- 36 | Exception: memprof_dump_callgrind(): dump failed, please check file permissions or disk capacity 37 | Exception: memprof_dump_pprof(): dump failed, please check file permissions or disk capacity 38 | -------------------------------------------------------------------------------- /tests/autodump-disabled.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | autodump disabled 3 | --ENV-- 4 | MEMPROF_PROFILE=1 5 | --FILE-- 6 | 36 | bool(true) 37 | ["native"]=> 38 | bool(false) 39 | ["dump_on_limit"]=> 40 | bool(false) 41 | } 42 | 43 | Fatal error: Allowed memory size of 15728640 bytes exhausted%S (tried to allocate %d bytes) in %s on line%a 44 | array(2) { 45 | [0]=> 46 | string(1) "." 47 | [1]=> 48 | string(2) ".." 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/package/package.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | cd ext 6 | 7 | echo "Checking version consistency" 8 | 9 | CODE_VERSION="$(grep PHP_MEMPROF_VERSION php_memprof.h|cut -d'"' -f2)" 10 | PACKAGE_VERSION="$(grep -m 1 '' package.xml|cut -d'>' -f2|cut -d'<' -f1)" 11 | 12 | if ! [ "$CODE_VERSION" = "$PACKAGE_VERSION" ]; then 13 | printf "Version in php_memprof.h does not match version in package.xml: '%s' vs '%s'" "$CODE_VERSION" "$PACKAGE_VERSION" >&2 14 | exit 1 15 | fi 16 | 17 | echo "Packaging" 18 | 19 | pecl package 20 | 21 | echo "Installing package.xml" 22 | 23 | mv "./memprof-$PACKAGE_VERSION.tgz" memprof.tgz 24 | sudo pecl install ./memprof.tgz 25 | 26 | echo "Checking that all test files was included" 27 | 28 | sudo pecl list-files memprof|grep ^test|sed 's@.*/tests/@@'|sort > installed-test-files 29 | find tests/ -type f|sed 's@^tests/@@'|sort > repository-test-files 30 | 31 | if ! diff -u repository-test-files installed-test-files; then 32 | echo "Some test files are missing from package.xml (see diff above)" >&2 33 | exit 1 34 | fi 35 | -------------------------------------------------------------------------------- /tests/autodump-format-failure.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | autodump 3 | --ENV-- 4 | MEMPROF_PROFILE=dump_on_limit 5 | --FILE-- 6 | 37 | bool(true) 38 | ["native"]=> 39 | bool(false) 40 | ["dump_on_limit"]=> 41 | bool(true) 42 | } 43 | 44 | Fatal error: Invalid memprof.output_format setting. Should be "callgrind" or "pprof" in %s on line %a 45 | array(2) { 46 | [0]=> 47 | string(1) "." 48 | [1]=> 49 | string(2) ".." 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Arnaud Le Blanc 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /tests/autodump.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | autodump 3 | --ENV-- 4 | MEMPROF_PROFILE=dump_on_limit 5 | --FILE-- 6 | 36 | bool(true) 37 | ["native"]=> 38 | bool(false) 39 | ["dump_on_limit"]=> 40 | bool(true) 41 | } 42 | 43 | Fatal error: Allowed memory size of 15728640 bytes exhausted%S (tried to allocate %d bytes) (memprof dumped to %smemprof.callgrind%s) in %s on line%a 44 | array(3) { 45 | [0]=> 46 | string(1) "." 47 | [1]=> 48 | string(2) ".." 49 | [2]=> 50 | string(%d) "memprof.callgrind.%s" 51 | } 52 | -------------------------------------------------------------------------------- /tests/autodump-pprof.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | autodump 3 | --ENV-- 4 | MEMPROF_PROFILE=dump_on_limit 5 | --FILE-- 6 | 37 | bool(true) 38 | ["native"]=> 39 | bool(false) 40 | ["dump_on_limit"]=> 41 | bool(true) 42 | } 43 | 44 | Fatal error: Allowed memory size of 15728640 bytes exhausted%S (tried to allocate %d bytes) (memprof dumped to %smemprof.pprof%s) in %s on line%a 45 | array(3) { 46 | [0]=> 47 | string(1) "." 48 | [1]=> 49 | string(2) ".." 50 | [2]=> 51 | string(%d) "memprof.pprof.%s" 52 | } 53 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | Memprof | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2012-2013 Arnaud Le Blanc | 6 | +----------------------------------------------------------------------+ 7 | | Redistribution and use in source and binary forms, with or without | 8 | | modification, are permitted provided that the conditions mentioned | 9 | | in the accompanying LICENSE file are met. | 10 | +----------------------------------------------------------------------+ 11 | | Author: Arnaud Le Blanc | 12 | +----------------------------------------------------------------------+ 13 | */ 14 | 15 | zend_bool stream_printf(php_stream * stream, const char * format, ...); 16 | zend_bool stream_write_word(php_stream * stream, zend_uintptr_t word); 17 | 18 | size_t get_file_name(zend_execute_data * execute_data, char * buf, size_t buf_size); 19 | size_t get_function_name(zend_execute_data * execute_data, char * buf, size_t buf_size); 20 | 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a bug report 3 | labels: ["bug"] 4 | body: 5 | - type: textarea 6 | attributes: 7 | label: Description 8 | description: "Please provide a minimal way to reproduce the problem and describe what the expected vs actual behavior is." 9 | value: | 10 | The following code: 11 | 12 | ```php 13 | 43 | bool(true) 44 | 'native' => 45 | bool(false) 46 | 'dump_on_limit' => 47 | bool(true) 48 | } 49 | 50 | Fatal error: Allowed memory size of 15728640 bytes exhausted%S (tried to allocate %d bytes) (memprof dumped to %smemprof.callgrind%s) in %s on line%a 51 | 52 | Call Stack: 53 | %s 54 | %s 55 | %s 56 | 57 | %sautodump-xdebug.php:%d: 58 | array(3) { 59 | [0] => 60 | string(1) "." 61 | [1] => 62 | string(2) ".." 63 | [2] => 64 | string(%d) "memprof.callgrind.%s" 65 | } 66 | -------------------------------------------------------------------------------- /.github/workflows/test/build-php.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | if [ "$MEMORY_CHECK" = "1" ]; then 6 | if which apt-get >/dev/null 2>&1; then 7 | sudo apt-get -y install valgrind 8 | else 9 | break install valgrind 10 | fi 11 | fi 12 | 13 | PREFIX="$HOME/php" 14 | 15 | if ! [ -f "$HOME/build-cache/php/$PREFIX/bin/php" ]; then 16 | echo "PHP build is not cached" 17 | 18 | if [ -z "$PHP_TAG" ]; then 19 | wget https://secure.php.net/distributions/php-${PHP_VERSION}.tar.bz2 20 | tar xjf php-${PHP_VERSION}.tar.bz2 21 | cd php-${PHP_VERSION} 22 | else 23 | git clone --depth 1 --branch "$PHP_TAG" https://github.com/php/php-src.git 24 | cd php-src 25 | 26 | sudo apt-get -y install re2c 27 | fi 28 | 29 | if ! [ -f "configure" ]; then 30 | ./buildconf --force 31 | fi 32 | 33 | PHP_BUILD_FLAGS="--prefix=$PREFIX --disable-all --enable-cli --enable-cgi --with-config-file-scan-dir=$PREFIX/etc/php --with-zlib --enable-debug" 34 | 35 | if [ $MEMORY_CHECK -eq 1 ]; then 36 | PHP_BUILD_FLAGS="$PHP_BUILD_FLAGS --with-valgrind" 37 | fi 38 | 39 | ./configure $PHP_BUILD_FLAGS $PHP_BUILD_EXTRA_FLAGS 40 | make -j $(nproc) 41 | rm -rf "$HOME/build-cache/php" 42 | mkdir -p ~/build-cache/php 43 | sudo make install INSTALL_ROOT=$HOME/build-cache/php 44 | else 45 | echo "PHP build is cached" 46 | fi 47 | 48 | sudo rsync -av "$HOME/build-cache/php$PREFIX/" "$PREFIX/" 49 | sudo mkdir -p "$PREFIX/etc/php" 50 | -------------------------------------------------------------------------------- /tests/002.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | memprof_dump_array() 3 | --FILE-- 4 | 31 | int(0) 32 | ["blocks_count"]=> 33 | int(0) 34 | ["memory_size_inclusive"]=> 35 | int(0) 36 | ["blocks_count_inclusive"]=> 37 | int(0) 38 | ["calls"]=> 39 | int(1) 40 | ["called_functions"]=> 41 | array(0) { 42 | } 43 | } 44 | array(6) { 45 | ["memory_size"]=> 46 | int(3145760) 47 | ["blocks_count"]=> 48 | int(1) 49 | ["memory_size_inclusive"]=> 50 | int(11534400) 51 | ["blocks_count_inclusive"]=> 52 | int(2) 53 | ["calls"]=> 54 | int(1) 55 | ["called_functions"]=> 56 | array(1) { 57 | ["Eater::eat"]=> 58 | array(6) { 59 | ["memory_size"]=> 60 | int(8388640) 61 | ["blocks_count"]=> 62 | int(1) 63 | ["memory_size_inclusive"]=> 64 | int(8388640) 65 | ["blocks_count_inclusive"]=> 66 | int(1) 67 | ["calls"]=> 68 | int(1) 69 | ["called_functions"]=> 70 | array(0) { 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /memprof_legacy_arginfo.h: -------------------------------------------------------------------------------- 1 | /* This is a generated file, edit the .stub.php file instead. 2 | * Stub hash: afb43b76aec85442c7267aba0b45bfd00d5c8cf1 */ 3 | 4 | ZEND_BEGIN_ARG_INFO_EX(arginfo_memprof_enabled, 0, 0, 0) 5 | ZEND_END_ARG_INFO() 6 | 7 | #define arginfo_memprof_enabled_flags arginfo_memprof_enabled 8 | 9 | #define arginfo_memprof_enable arginfo_memprof_enabled 10 | 11 | #define arginfo_memprof_disable arginfo_memprof_enabled 12 | 13 | #define arginfo_memprof_dump_array arginfo_memprof_enabled 14 | 15 | ZEND_BEGIN_ARG_INFO_EX(arginfo_memprof_dump_callgrind, 0, 0, 1) 16 | ZEND_ARG_INFO(0, handle) 17 | ZEND_END_ARG_INFO() 18 | 19 | #define arginfo_memprof_dump_pprof arginfo_memprof_dump_callgrind 20 | 21 | #define arginfo_memprof_version arginfo_memprof_enabled 22 | 23 | 24 | ZEND_FUNCTION(memprof_enabled); 25 | ZEND_FUNCTION(memprof_enabled_flags); 26 | ZEND_FUNCTION(memprof_enable); 27 | ZEND_FUNCTION(memprof_disable); 28 | ZEND_FUNCTION(memprof_dump_array); 29 | ZEND_FUNCTION(memprof_dump_callgrind); 30 | ZEND_FUNCTION(memprof_dump_pprof); 31 | ZEND_FUNCTION(memprof_version); 32 | 33 | 34 | static const zend_function_entry ext_functions[] = { 35 | ZEND_FE(memprof_enabled, arginfo_memprof_enabled) 36 | ZEND_FE(memprof_enabled_flags, arginfo_memprof_enabled_flags) 37 | ZEND_FE(memprof_enable, arginfo_memprof_enable) 38 | ZEND_FE(memprof_disable, arginfo_memprof_disable) 39 | ZEND_FE(memprof_dump_array, arginfo_memprof_dump_array) 40 | ZEND_FE(memprof_dump_callgrind, arginfo_memprof_dump_callgrind) 41 | ZEND_FE(memprof_dump_pprof, arginfo_memprof_dump_pprof) 42 | ZEND_FE(memprof_version, arginfo_memprof_version) 43 | ZEND_FE_END 44 | }; 45 | -------------------------------------------------------------------------------- /memprof_arginfo.h: -------------------------------------------------------------------------------- 1 | /* This is a generated file, edit the .stub.php file instead. 2 | * Stub hash: afb43b76aec85442c7267aba0b45bfd00d5c8cf1 */ 3 | 4 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_memprof_enabled, 0, 0, _IS_BOOL, 0) 5 | ZEND_END_ARG_INFO() 6 | 7 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_memprof_enabled_flags, 0, 0, IS_ARRAY, 0) 8 | ZEND_END_ARG_INFO() 9 | 10 | #define arginfo_memprof_enable arginfo_memprof_enabled 11 | 12 | #define arginfo_memprof_disable arginfo_memprof_enabled 13 | 14 | #define arginfo_memprof_dump_array arginfo_memprof_enabled_flags 15 | 16 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_memprof_dump_callgrind, 0, 1, IS_VOID, 0) 17 | ZEND_ARG_INFO(0, handle) 18 | ZEND_END_ARG_INFO() 19 | 20 | #define arginfo_memprof_dump_pprof arginfo_memprof_dump_callgrind 21 | 22 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_memprof_version, 0, 0, IS_STRING, 0) 23 | ZEND_END_ARG_INFO() 24 | 25 | 26 | ZEND_FUNCTION(memprof_enabled); 27 | ZEND_FUNCTION(memprof_enabled_flags); 28 | ZEND_FUNCTION(memprof_enable); 29 | ZEND_FUNCTION(memprof_disable); 30 | ZEND_FUNCTION(memprof_dump_array); 31 | ZEND_FUNCTION(memprof_dump_callgrind); 32 | ZEND_FUNCTION(memprof_dump_pprof); 33 | ZEND_FUNCTION(memprof_version); 34 | 35 | 36 | static const zend_function_entry ext_functions[] = { 37 | ZEND_FE(memprof_enabled, arginfo_memprof_enabled) 38 | ZEND_FE(memprof_enabled_flags, arginfo_memprof_enabled_flags) 39 | ZEND_FE(memprof_enable, arginfo_memprof_enable) 40 | ZEND_FE(memprof_disable, arginfo_memprof_disable) 41 | ZEND_FE(memprof_dump_array, arginfo_memprof_dump_array) 42 | ZEND_FE(memprof_dump_callgrind, arginfo_memprof_dump_callgrind) 43 | ZEND_FE(memprof_dump_pprof, arginfo_memprof_dump_pprof) 44 | ZEND_FE(memprof_version, arginfo_memprof_version) 45 | ZEND_FE_END 46 | }; 47 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: 'Tests' 2 | 3 | on: 4 | push: 5 | branches: 6 | - v3 7 | pull_request: 8 | schedule: 9 | - cron: '30 8 * * 1' 10 | 11 | jobs: 12 | tests: 13 | name: 'Tests' 14 | strategy: 15 | matrix: 16 | include: 17 | - php: '8.4.0' 18 | os: 'ubuntu-24.04' 19 | werror: 1 20 | xdebug: '3.4.1' 21 | 22 | - php: '8.3.0' 23 | os: 'ubuntu-22.04' 24 | werror: 1 25 | xdebug: '3.3.0alpha3' 26 | 27 | - php: '8.2.0' 28 | os: 'ubuntu-22.04' 29 | werror: 1 30 | xdebug: '3.2.2' 31 | 32 | - php: '8.1.0' 33 | os: 'ubuntu-20.04' 34 | expect_native: 1 35 | werror: 1 36 | xdebug: '3.1.2' 37 | 38 | - php: '8.1.0' 39 | os: 'macos-13' 40 | xdebug: '3.1.2' 41 | 42 | - php: '8.0.0' 43 | os: 'ubuntu-20.04' 44 | expect_native: 1 45 | xdebug: '3.1.2' 46 | 47 | - php: '7.4.0' 48 | os: 'ubuntu-20.04' 49 | expect_native: 1 50 | xdebug: '3.1.2' 51 | 52 | - php: '7.3.0' 53 | os: 'ubuntu-20.04' 54 | expect_native: 1 55 | xdebug: '3.1.2' 56 | 57 | runs-on: ${{ matrix.os }} 58 | continue-on-error: ${{ !!matrix.experimental }} 59 | env: 60 | PHP_VERSION: ${{ matrix.php }} 61 | PHP_TAG: ${{ matrix.tag }} 62 | MEMORY_CHECK: ${{ matrix.memcheck }} 63 | WERROR: ${{ matrix.werror }} 64 | MEMPROF_EXPECT_NATIVE_TRACKING: ${{ matrix.expect_native }} 65 | XDEBUG_VERSION: ${{ matrix.xdebug }} 66 | steps: 67 | - name: 'Check out repository' 68 | uses: 'actions/checkout@v4' 69 | with: 70 | path: 'ext' 71 | 72 | - uses: 'actions/cache@v3' 73 | with: 74 | path: '~/build-cache/php' 75 | key: ${{ runner.os }}-${{ matrix.php }}-${{ matrix.memcheck }} 76 | 77 | - name: 'Build PHP' 78 | run: './ext/.github/workflows/test/build-php.sh' 79 | 80 | - name: 'Install xdebug' 81 | run: './ext/.github/workflows/test/build-xdebug.sh' 82 | 83 | - name: 'Build extension' 84 | run: './ext/.github/workflows/test/build-extension.sh' 85 | 86 | - name: 'Run tests' 87 | run: './ext/.github/workflows/test/tests.sh' 88 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 for extension memprof 2 | 3 | PHP_ARG_ENABLE(memprof, whether to enable memprof support, 4 | [ --enable-memprof Enable memprof support]) 5 | 6 | PHP_ARG_WITH(judy-dir, for judy lib, 7 | [ --with-judy-dir Specify judy dir]) 8 | 9 | AC_ARG_ENABLE(memprof-debug, 10 | [ --enable-memprof-debug Enable memprof debugging],[ 11 | PHP_MEMPROF_DEBUG=$enableval 12 | ],[ 13 | PHP_MEMPROF_DEBUG=no 14 | ]) 15 | 16 | if test "$PHP_MEMPROF" != "no"; then 17 | 18 | SEARCH_PATH="/usr/local /usr" 19 | SEARCH_FOR="/include/Judy.h" 20 | if test -r $PHP_JUDY_DIR/$SEARCH_FOR; then 21 | JUDY_DIR=$PHP_JUDY_DIR 22 | else 23 | AC_MSG_CHECKING([for include/Judy.h in $SEARCH_PATH]) 24 | for i in $SEARCH_PATH ; do 25 | if test -r $i/$SEARCH_FOR; then 26 | JUDY_DIR=$i 27 | AC_MSG_RESULT(found in $i) 28 | fi 29 | done 30 | fi 31 | 32 | if test -z "$JUDY_DIR"; then 33 | AC_MSG_RESULT([not found]) 34 | AC_MSG_ERROR([Please install lib judy]) 35 | fi 36 | 37 | PHP_ADD_INCLUDE($JUDY_DIR/include) 38 | 39 | LIBNAME=Judy 40 | LIBSYMBOL=Judy1Set 41 | 42 | PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, 43 | [ 44 | PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $JUDY_DIR/$PHP_LIBDIR, MEMPROF_SHARED_LIBADD) 45 | AC_DEFINE(HAVE_JUDYLIB,1,[ ]) 46 | ],[ 47 | AC_MSG_ERROR([wrong judy lib version or lib not found]) 48 | ],[ 49 | -L$JUDY_DIR/$PHP_LIBDIR -lJudy 50 | ]) 51 | dnl 52 | PHP_SUBST(MEMPROF_SHARED_LIBADD) 53 | 54 | ORIG_CFLAGS="$CFLAGS" 55 | CFLAGS="" 56 | 57 | AC_MSG_CHECKING(for malloc hooks) 58 | AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ 59 | #include 60 | extern void * malloc_hook(size_t size, const void *caller); 61 | extern void * realloc_hook(void *ptr, size_t size, const void *caller); 62 | extern void free_hook(void *ptr, const void *caller); 63 | extern void * memalign_hook(size_t alignment, size_t size, const void *caller); 64 | ]], [[ 65 | __malloc_hook = malloc_hook; 66 | __free_hook = free_hook; 67 | __realloc_hook = realloc_hook; 68 | __memalign_hook = memalign_hook; 69 | ]])],[ 70 | AC_DEFINE([HAVE_MALLOC_HOOKS], 1, [Define to 1 if libc supports malloc hooks]) 71 | AC_MSG_RESULT(yes) 72 | ],[ 73 | AC_DEFINE([HAVE_MALLOC_HOOKS], 0, [Define to 1 if libc supports malloc hooks]) 74 | AC_MSG_RESULT(no) 75 | ]) 76 | 77 | CFLAGS="$ORIG_CFLAGS" 78 | 79 | AC_DEFINE([MEMPROF_CONFIGURE_VERSION], 3, [Define configure version]) 80 | 81 | PHP_NEW_EXTENSION(memprof, memprof.c util.c, $ext_shared) 82 | fi 83 | 84 | if test "$PHP_MEMPROF_DEBUG" != "no"; then 85 | AC_DEFINE([MEMPROF_DEBUG], 1, [memprof debug]) 86 | else 87 | AC_DEFINE([MEMPROF_DEBUG], 0, [memprof debug]) 88 | fi 89 | 90 | -------------------------------------------------------------------------------- /php_memprof.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | Memprof | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2012-2013 Arnaud Le Blanc | 6 | +----------------------------------------------------------------------+ 7 | | Redistribution and use in source and binary forms, with or without | 8 | | modification, are permitted provided that the conditions mentioned | 9 | | in the accompanying LICENSE file are met. | 10 | +----------------------------------------------------------------------+ 11 | | Author: Arnaud Le Blanc | 12 | +----------------------------------------------------------------------+ 13 | */ 14 | 15 | #ifndef PHP_MEMPROF_H 16 | #define PHP_MEMPROF_H 17 | 18 | #define MEMPROF_NAME "memprof" 19 | #define PHP_MEMPROF_VERSION "3.1.0" 20 | 21 | extern zend_module_entry memprof_module_entry; 22 | #define phpext_memprof_ptr &memprof_module_entry 23 | 24 | #ifdef PHP_WIN32 25 | # define PHP_MEMPROF_API __declspec(dllexport) 26 | #elif defined(__GNUC__) && __GNUC__ >= 4 27 | # define PHP_MEMPROF_API __attribute__ ((visibility("default"))) 28 | #else 29 | # define PHP_MEMPROF_API 30 | #endif 31 | 32 | #ifdef ZTS 33 | #include "TSRM.h" 34 | #endif 35 | 36 | #ifndef PHP_FE_END 37 | # define PHP_FE_END { NULL, NULL, NULL, 0, 0 } 38 | #endif 39 | 40 | typedef enum { 41 | FORMAT_CALLGRIND = 0, 42 | FORMAT_PPROF = 1, 43 | } memprof_output_format; 44 | 45 | typedef struct _memprof_profile_flags { 46 | zend_bool enabled; 47 | zend_bool native; 48 | zend_bool dump_on_limit; 49 | } memprof_profile_flags; 50 | 51 | ZEND_BEGIN_MODULE_GLOBALS(memprof) 52 | const char * output_dir; 53 | memprof_output_format output_format; 54 | memprof_profile_flags profile_flags; 55 | ZEND_END_MODULE_GLOBALS(memprof) 56 | 57 | #define MEMPROF_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(memprof, v) 58 | 59 | #define MEMPROF_OUTPUT_FORMAT_CALLGRIND "callgrind" 60 | #define MEMPROF_OUTPUT_FORMAT_PPROF "pprof" 61 | 62 | PHP_MINIT_FUNCTION(memprof); 63 | PHP_MSHUTDOWN_FUNCTION(memprof); 64 | PHP_RINIT_FUNCTION(memprof); 65 | PHP_RSHUTDOWN_FUNCTION(memprof); 66 | PHP_MINFO_FUNCTION(memprof); 67 | PHP_GINIT_FUNCTION(memprof); 68 | 69 | PHP_FUNCTION(memprof_dump_callgrind); 70 | PHP_FUNCTION(memprof_dump_pprof); 71 | PHP_FUNCTION(memprof_dump_array); 72 | PHP_FUNCTION(memprof_memory_get_usage); 73 | PHP_FUNCTION(memprof_memory_get_peak_usage); 74 | PHP_FUNCTION(memprof_enable); 75 | PHP_FUNCTION(memprof_disable); 76 | PHP_FUNCTION(memprof_enabled); 77 | PHP_FUNCTION(memprof_enabled_flags); 78 | 79 | #endif /* PHP_MEMPROF_H */ 80 | -------------------------------------------------------------------------------- /INTERNALS.md: -------------------------------------------------------------------------------- 1 | # Memprof internals 2 | 3 | Memprof tracks the amount of memory allocated by PHP functions. It does so by hooking 4 | in the memory allocator in order to keep track of which function allocated a block. 5 | 6 | ## Data structures 7 | 8 | Three main structures allow to track the memory of the program: 9 | 10 | - `frame`: Allocation informations about one particular call path. 11 | - `allocs_set`: As the name doesn't suggest, this is a map from memory addresses to informations about one particular allocation. 12 | - `alloc`: A struct representing one particular allocation. It's a linked list item. 13 | 14 | ### Call frames 15 | 16 | We want to know how much memory was allocated by each call stacks. This structure represents exactly this information: 17 | 18 | /* a call frame */ 19 | typedef struct _frame { 20 | char * name; /* function name */ 21 | size_t name_len; 22 | struct _frame * prev; /* frame of the calling function (parent) */ 23 | size_t calls; /* number of times it has been called */ 24 | HashTable next_cache; /* called functions (children) */ 25 | alloc_list_head allocs; /* head of the allocations list */ 26 | } frame; 27 | 28 | Every time a function is called, we create a new `frame` struct, unless one already exists for this call path (we use `next_cache` to find existing `frame` structs). 29 | 30 | The frame contains a linked list of allocation informations (a list of `alloc` structs). This is a doubly linked list to make it possible to remove one item without knowing the related frame or list head. 31 | 32 | ### Allocation map 33 | 34 | We want to forget about allocated blocks when they are freed. The `allocs_set` struct is a map from memory addresses to `alloc` structs. It makes it easy and fast to find the `alloc` struct related to a memory address being freed, and to remove this struct from its doubly linked list (whose list head or related framne we don't need to know). 35 | 36 | ### Allocating allocation information 37 | 38 | In order to reduce the overhead of creating `alloc` structs to the minimum, we use a memory pool to allocate and recycle them. 39 | 40 | ## Hooking in ``malloc`` 41 | 42 | The GNU C library makes this very simple by [allowing it 43 | explicitly](https://www.gnu.org/software/libc/manual/html_node/Hooks-for-Malloc.html#Hooks-for-Malloc). 44 | 45 | These hooks are not thread safe, and are deprecated for this reason, however 46 | this doesn't have an impact on non-ZTS PHP builds (unless an extension or library creates threads internally). 47 | 48 | Hooking in ``malloc`` calls allow to track persistent allocations made by 49 | PHP (i.e. allocations that should persist after script execution and are 50 | not done through the Zend Memory Manager). 51 | 52 | ## Hooking in the Zend Memory Manager 53 | 54 | PHP uses the Zend Memory Manager for pretty much every single allocation, 55 | so hooking in the ZMM allows to track pretty much every allocation. 56 | 57 | Hooking is done by creating an alternate ZMM heap that proxies calls to the 58 | original ZMM heap. 59 | 60 | ## Hooking in function calls 61 | 62 | Hooking in function calls is done by proxying Zend Engine's ``zend_execute_fn`` 63 | and ``zend_execute_internal`` functions. The former is called when the 64 | engine executes a userland php function, and the later is called for internal 65 | php functions. 66 | 67 | These are function pointers meant to be changed by extensions. However, when 68 | they are not changed, PHP might emit opcodes that don't use them. So we have 69 | to change them during request init, before any file is compiled. 70 | -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | Memprof | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2012-2013 Arnaud Le Blanc | 6 | +----------------------------------------------------------------------+ 7 | | Redistribution and use in source and binary forms, with or without | 8 | | modification, are permitted provided that the conditions mentioned | 9 | | in the accompanying LICENSE file are met. | 10 | +----------------------------------------------------------------------+ 11 | | Author: Arnaud Le Blanc | 12 | +----------------------------------------------------------------------+ 13 | */ 14 | 15 | #include "php.h" 16 | #include 17 | 18 | zend_bool stream_printf(php_stream * stream, const char * format, ...) 19 | { 20 | char * buf; 21 | va_list ap; 22 | int len; 23 | zend_bool result; 24 | 25 | va_start(ap, format); 26 | len = vspprintf(&buf, 0, format, ap); 27 | va_end(ap); 28 | 29 | result = php_stream_write(stream, buf, len) == len; 30 | 31 | efree(buf); 32 | 33 | return result; 34 | } 35 | 36 | zend_bool stream_write_word(php_stream * stream, zend_uintptr_t word) 37 | { 38 | return php_stream_write(stream, (char*) &word, sizeof(word)) == sizeof(word); 39 | } 40 | 41 | size_t get_function_name(zend_execute_data * execute_data, char * buf, size_t buf_size) 42 | { 43 | const char * function_name = NULL; 44 | const char * call_type = NULL; 45 | const char * class_name = NULL; 46 | size_t len; 47 | zend_function * func; 48 | zend_string * zname; 49 | const char * include_type; 50 | const char * file_name; 51 | 52 | if (!execute_data) { 53 | return snprintf(buf, buf_size, "main"); 54 | } 55 | 56 | func = EG(current_execute_data)->func; 57 | 58 | if (func->type != ZEND_USER_FUNCTION && func->type != ZEND_INTERNAL_FUNCTION) { 59 | return snprintf(buf, buf_size, "main"); 60 | } 61 | 62 | if (&execute_data->func->internal_function == &zend_pass_function) { 63 | return snprintf(buf, buf_size, "zend_pass_function"); 64 | } 65 | 66 | zname = func->common.function_name; 67 | 68 | if (zname == NULL) { 69 | zend_execute_data * include_execute_data = execute_data; 70 | if (include_execute_data->opline->opcode != ZEND_INCLUDE_OR_EVAL && include_execute_data->prev_execute_data != NULL && include_execute_data->prev_execute_data->opline->opcode == ZEND_INCLUDE_OR_EVAL) { 71 | include_execute_data = execute_data->prev_execute_data; 72 | } 73 | switch (include_execute_data->opline->extended_value) { 74 | case ZEND_EVAL: 75 | include_type = "eval"; 76 | break; 77 | case ZEND_INCLUDE: 78 | include_type = "include"; 79 | break; 80 | case ZEND_INCLUDE_ONCE: 81 | include_type = "include_once"; 82 | break; 83 | case ZEND_REQUIRE: 84 | include_type = "require"; 85 | break; 86 | case ZEND_REQUIRE_ONCE: 87 | include_type = "require_once"; 88 | break; 89 | default: 90 | include_type = "main"; 91 | break; 92 | } 93 | 94 | if (func->type == ZEND_USER_FUNCTION) { 95 | file_name = func->op_array.filename->val; 96 | } else { 97 | file_name = NULL; 98 | } 99 | 100 | len = snprintf(buf, buf_size, "%s %s", include_type, file_name); 101 | 102 | return len >= buf_size ? buf_size-1 : len; 103 | } 104 | 105 | function_name = ZSTR_VAL(zname); 106 | class_name = get_active_class_name(&call_type); 107 | 108 | len = snprintf(buf, buf_size, "%s%s%s", class_name != NULL ? class_name : "", call_type != NULL ? call_type : "", function_name != NULL ? function_name : ""); 109 | 110 | return len >= buf_size ? buf_size-1 : len; 111 | } 112 | 113 | size_t get_file_name(zend_execute_data * execute_data, char * buf, size_t buf_size) 114 | { 115 | zend_function * func; 116 | 117 | if (!execute_data) { 118 | buf[0] = '\0'; 119 | return 0; 120 | } 121 | 122 | func = EG(current_execute_data)->func; 123 | 124 | switch (func->type) { 125 | case ZEND_USER_FUNCTION: 126 | return snprintf(buf, buf_size, "%s", func->op_array.filename->val); 127 | case ZEND_INTERNAL_FUNCTION: 128 | return snprintf(buf, buf_size, "php:internal"); 129 | default: 130 | buf[0] = '\0'; 131 | return 0; 132 | } 133 | } 134 | 135 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | memprof 4 | pecl.php.net 5 | Memory profiler 6 | Memprof is a fast and accurate memory profiler that can be used to find the cause of memory leaks in PHP applications. 7 | 8 | Arnaud Le Blanc 9 | lbarnaud 10 | lbarnaud@php.net 11 | yes 12 | 13 | 2025-02-24 14 | 15 | 16 | 3.1.0 17 | 1.0.0 18 | 19 | 20 | stable 21 | stable 22 | 23 | MIT 24 | 25 | ## Improvements 26 | - Use filename for script and binary instead of /todo.php (@samlitowitz, #92) 27 | - Allow output_format to be defined from ini setting (@gaelreyrol, #97) 28 | - Add units to Callgrind label for memory size and update `BlockCount` to `Call_Count` (@samlitowitz, #95) 29 | - Add support for PIE 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 7.1.0 70 | 71 | 72 | 1.10.0 73 | 74 | 75 | 76 | memprof 77 | 78 | 79 | 80 | 2022-01-07 81 | 82 | 83 | 3.0.2 84 | 1.0.0 85 | 86 | 87 | stable 88 | stable 89 | 90 | MIT 91 | 92 | ## Improvements 93 | - PHP 8.1 support (@remicollet) 94 | - Added parameter types and return types (@remicollet) 95 | - dump_on_limit: Append the profile filename to the "Allowed memory size of %d 96 | bytes exhausted" error message (@arnaud-lb) 97 | - Added memprof_version() function (@arnaud-lb) 98 | 99 | ## Bugfixes 100 | - Fixed compatibility issue with xdebug (@arnaud-lb) 101 | 102 | 103 | 104 | 2021-03-29 105 | 106 | 107 | 3.0.1 108 | 1.0.0 109 | 110 | 111 | stable 112 | stable 113 | 114 | BSD 115 | 116 | * Fix build 117 | 118 | 119 | 120 | 2021-03-29 121 | 122 | 123 | 3.0.0 124 | 1.0.0 125 | 126 | 127 | stable 128 | stable 129 | 130 | BSD 131 | 132 | * Added ability to dump profile when the memory limit is exceeded 133 | * Profiling of non-php allocations is disabled by default (can be enabled at runtime) 134 | 135 | 136 | 137 | 2020-11-19 138 | 139 | 140 | 2.1.2 141 | 1.0.0 142 | 143 | 144 | stable 145 | stable 146 | 147 | BSD 148 | 149 | * Updated minimal PHP version requirement to 7.1.0 150 | 151 | 152 | 153 | 2020-11-18 154 | 155 | 156 | 2.1.1 157 | 1.0.0 158 | 159 | 160 | stable 161 | stable 162 | 163 | BSD 164 | 165 | * Fix crash on zend_pass_function 166 | 167 | 168 | 169 | 2020-07-04 170 | 171 | 172 | 2.1.0 173 | 1.0.0 174 | 175 | 176 | stable 177 | stable 178 | 179 | BSD 180 | 181 | * Added a way to trigger profiling from the environment 182 | * Stability improvements 183 | 184 | 185 | 186 | 2017-01-28 187 | 188 | 189 | 2.0.0 190 | 1.0.0 191 | 192 | 193 | stable 194 | stable 195 | 196 | BSD 197 | 198 | * PHP 7 version 199 | 200 | 201 | 202 | 2013-05-21 203 | 204 | 205 | 1.0.0 206 | 1.0.0 207 | 208 | 209 | stable 210 | stable 211 | 212 | BSD 213 | 214 | PECL release 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | memprof logo 3 |

4 | 5 | # memprof 6 | 7 | ![Supported PHP versions: 7.1 ... 8.x](https://img.shields.io/badge/php-7.1%20...%208.x-blue.svg) 8 | 9 | php-memprof is a fast and accurate memory profiling extension for PHP that can be used to find the cause of memory leaks. 10 | 11 | ## Features 12 | 13 | The extension tracks the allocation and release of memory blocks to report the amount of memory leaked by every function, method, or file in a program. 14 | 15 | * Reports non-freed memory at arbitrary points in the program 16 | * Dumps profile in callgrind, pprof, or raw array formats 17 | * Can track memory allocated by PHP itself as well as native malloc 18 | 19 | ## Install 20 | 21 | ### Dependencies 22 | 23 | php-memprof depends on libjudy and sys/queue.h. 24 | 25 | On Debian-based distributions the dependencies can be installed with: 26 | 27 | # Debian or Ubuntu: 28 | apt install libjudy-dev 29 | 30 | On Alpine: 31 | 32 | # Alpine 33 | apk add judy-dev bsd-compat-headers 34 | 35 | On MacOS: 36 | 37 | # install libjudy dependency: 38 | brew install traildb/judy/judy 39 | 40 | ### Installing with [PIE](https://github.com/php/pie) 41 | 42 | Make sure to install [dependencies](#dependencies), and then: 43 | 44 | pie install arnaud-lb/memprof 45 | 46 | > **Note** If libjudy is installed in a non-standard path (not /usr or /usr/local), you need to specify it via the `--with-judy-dir` option. 47 | > 48 | > Example on MacOS: 49 | > ``` 50 | > pecl install memprof --with-judy-dir=$(brew --prefix traildb/judy/judy) 51 | > ``` 52 | 53 | ### Installing with PECL 54 | 55 | Make sure to install [dependencies](#dependencies), and then: 56 | 57 | pecl install memprof 58 | 59 | > **Note** If libjudy is installed in a non-standard path (not /usr or /usr/local), you need to specify it via the `JUDY_DEV` environment variable. 60 | > 61 | > Example on MacOS: 62 | > ``` 63 | > JUDY_DIR=$(brew --prefix traildb/judy/judy) pecl install memprof 64 | > ``` 65 | 66 | ### Installing manually 67 | 68 | Make sure to install [dependencies](#dependencies), and then: 69 | 70 | Download the source and run the following commands in the source directory: 71 | 72 | phpize 73 | ./configure 74 | make 75 | make install 76 | 77 | > **Note** If libjudy is installed in a non-standard path (not /usr or /usr/local), you can specify it like this: 78 | 79 | ./configure --with-judy-dir=/opt/homebrew/Cellar/judy/1.0.5 80 | 81 | ### Installing on Arch Linux 82 | 83 | Arch Linux users may prefer to install the unofficial php-memprof [package][8] 84 | with `makepkg` or their preferred AUR [helper][9]. If using `yay`, for example: 85 | 86 | yay -S php-memprof 87 | 88 | > ℹ️ Please report any issues with this package on its AUR [page][8]. 89 | 90 | ## Loading the extension 91 | 92 | The extension can be loaded on the command line, just for one script: 93 | 94 | php -dextension=memprof.so script.php 95 | 96 | Or permanently, in php.ini: 97 | 98 | extension=memprof.so 99 | 100 | The extension has no overhead when not profiling, so it can be loaded by default on dev environments. 101 | 102 | ## Usage example 103 | 104 | The simplest way to use `memprof` is to let it save the memory profile when the 105 | program's memory limit is exceeded. 106 | 107 | ### Step 1: Enable profiling in `dump_on_limit` mode 108 | 109 | Profiling in `dump_on_limit` mode is enabled at request startup when one 110 | of these is true: 111 | 112 | * The environment variable `MEMPROF_PROFILE` is equal to `dump_on_limit` 113 | * `$_GET["MEMPROF_PROFILE"]` is equal to `dump_on_limit` 114 | * `$_POST["MEMPROF_PROFILE"]` is equal to `dump_on_limit` 115 | 116 | For command line scripts, we can set the environment variable: 117 | 118 | ``` 119 | MEMPROF_PROFILE=dump_on_limit php test.php 120 | ``` 121 | 122 | For web scripts, we can set the `$_GET` variable: 123 | 124 | ``` 125 | curl http://127.0.0.1/test.php?MEMPROF_PROFILE=dump_on_limit 126 | ``` 127 | 128 | Or the `$_POST` variable: 129 | 130 | ``` 131 | curl -d MEMPROF_PROFILE=dump_on_limit http://127.0.0.1/test.php 132 | ``` 133 | 134 | > **Note** The `memprof_enabled_flags()` function can be called to 135 | > check whether profiling is currently enabled in `dump_on_limit` mode. 136 | 137 | ### Step 2: Dumping the profile 138 | 139 | In this mode, `memprof` will automatically save the profile if the program 140 | exceeds the memory limit (when PHP triggers an error like `Fatal error: Allowed 141 | memory size of 15728640 bytes exhausted (tried to allocate 1024 bytes)` error). 142 | 143 | By default, the profile is saved in a file named `memprof.callgrind.*` in `/tmp` 144 | or `C:\Windows\Temp`. 145 | 146 | ### Step 3: Visualizing the profile 147 | 148 | The recommended way to visualize the result is to use Kcachegrind (on Linux) or Qcachegrind (on MacOS, Windows). Google Perftools are also supported. See the documentation of ``memprof_dump_callgrind()`` and variants. 149 | 150 | #### Install Kcachegrind on Linux 151 | 152 | Most distributions have a `kcachegrind` package ready to be installed. 153 | 154 | For example, Ubuntu or Debian: 155 | 156 | ``` 157 | sudo apt install kcachegrind 158 | ``` 159 | 160 | Other distributions most likely have a package ready to be installed as well. 161 | 162 | #### Install Qcachegrind on MacOS 163 | 164 | Use Homebrew: https://formulae.brew.sh/formula/qcachegrind 165 | 166 | #### Install Qcachegrind on Windows 167 | 168 | Download it from https://sourceforge.net/projects/qcachegrindwin/ 169 | 170 | ## Advanced usage 171 | 172 | ### Profile trigger 173 | 174 | Profiling is enabled at request startup when one of these is true: 175 | 176 | * The environment variable `MEMPROF_PROFILE` is non-empty 177 | * `$_GET["MEMPROF_PROFILE"]` is non-empty 178 | * `$_POST["MEMPROF_PROFILE"]` is non-empty 179 | 180 | ### Profile flags 181 | 182 | The `MEMPROF_PROFILE` variable accepts a comma-separated list of flags. 183 | 184 | Examples of valid `MEMPROF_PROFILE` values: 185 | 186 | * `1`: non-empty: profiling is enabled 187 | * `dump_on_limit`: profiling is enabled, will dump on memory limit 188 | * `native`: profiling is enabled, will profile native allocations 189 | * `dump_on_limit,native`: profiling is enabled, will profile native allocations, will dump on memory limit 190 | 191 | List of valid flags: 192 | 193 | * `dump_on_limit`: Will dump the profile in callgrind format in `/tmp` or 194 | `C:\Windows\Temp`. The output directory can be changed with the 195 | `memprof.output_dir` ini setting. 196 | * `native`: Will profile native `malloc()` allocations, not only PHP's (This is 197 | not thread safe, see bellow). 198 | 199 | ### Profiling native allocations 200 | 201 | Memprof doesn't track native allocations by default, but this can be enabled 202 | by setting `MEMPROF_PROFILE` to `native`. 203 | 204 | Native allocations are the allocations made outside of PHP's own memory 205 | allocator. Typically, external libraries such as libxml2 (used in the DOM 206 | extension) make native allocations. PHP can also make native allocations for 207 | persistent resources. 208 | 209 | Enabling native allocation tracking will profile these allocations in addition 210 | to PHP's own allocations. 211 | 212 | Note that when native tracking is enabled, the program will crash if a native 213 | library uses threads, because the underlying hooks are not thread safe. 214 | 215 | ### Output format 216 | 217 | The output file format is defined with the `memprof.output_format` ini setting. The options are: 218 | - `callgrind` (default) 219 | - `pprof` 220 | 221 | ## Functions documentation 222 | 223 | ### memprof_enabled() 224 | 225 | Returns whether memory profiling is currently enabled (see above). 226 | 227 | ### memprof_enabled_flags() 228 | 229 | Returns whether memory profiling and which profiling features are enabled (see 230 | above). 231 | 232 | ### memprof_dump_callgrind(resource $stream) 233 | 234 | Dumps the current profile in callgrind format. The result can be visualized with tools such as 235 | [KCacheGrind](#install-kcachegrind-on-linux) or [QCacheGrind](#install-qcachegrind-on-macos). 236 | 237 | ``` php 238 | 298 | Example output 299 | 300 | Array 301 | ( 302 | [memory_size] => 11578 303 | [blocks_count] => 236 304 | [memory_size_inclusive] => 10497691 305 | [blocks_count_inclusive] => 244 306 | [calls] => 1 307 | [called_functions] => Array 308 | ( 309 | [main] => Array 310 | ( 311 | [memory_size] => 288 312 | [blocks_count] => 3 313 | [memory_size_inclusive] => 10486113 314 | [blocks_count_inclusive] => 8 315 | [calls] => 1 316 | [called_functions] => Array 317 | ( 318 | [a] => Array 319 | ( 320 | [memory_size] => 4 321 | [blocks_count] => 1 322 | [memory_size_inclusive] => 10485825 323 | [blocks_count_inclusive] => 5 324 | [calls] => 1 325 | [called_functions] => Array 326 | ( 327 | [b] => Array 328 | ( 329 | [memory_size] => 10485821 330 | [blocks_count] => 4 331 | [memory_size_inclusive] => 10485821 332 | [blocks_count_inclusive] => 4 333 | [calls] => 1 334 | [called_functions] => Array 335 | ( 336 | [str_repeat] => Array 337 | ( 338 | [memory_size] => 0 339 | [blocks_count] => 0 340 | [memory_size_inclusive] => 0 341 | [blocks_count_inclusive] => 0 342 | [calls] => 1 343 | [called_functions] => Array 344 | ( 345 | ) 346 | ) 347 | ) 348 | ) 349 | ) 350 | ) 351 | [memprof_dump_array] => Array 352 | ( 353 | [memory_size] => 0 354 | [blocks_count] => 0 355 | [memory_size_inclusive] => 0 356 | [blocks_count_inclusive] => 0 357 | [calls] => 1 358 | [called_functions] => Array 359 | ( 360 | ) 361 | ) 362 | ) 363 | ) 364 | ) 365 | ) 366 | 367 | 368 | ### memprof_version() 369 | 370 | Returns the version of the extension as a string. The version can be compared with [version_compare()](https://php.net/version_compare). 371 | 372 | ## Troubleshooting 373 | 374 | * The extensions may conflict with xdebug, blackfire, or other extensions. If that's the case for you, please report it. 375 | 376 | ## PHP versions 377 | 378 | The current branch supports PHP 7.1 to PHP 8. 379 | 380 | The php5 branch supports PHP 5. 381 | 382 | ## How it works 383 | 384 | See [INTERNALS.md][7] 385 | 386 | [1]: https://www.gnu.org/software/libc/manual/html_node/Hooks-for-Malloc.html#Hooks-for-Malloc 387 | [2]: https://kcachegrind.github.io/html/Home.html 388 | [3]: http://judy.sourceforge.net/index.html 389 | [5]: https://github.com/gperftools/gperftools 390 | [6]: https://www.google.com/search?q=qcachegrind 391 | [7]: https://github.com/arnaud-lb/php-memory-profiler/blob/master/INTERNALS.md 392 | [8]: https://aur.archlinux.org/packages/php-memprof/ 393 | [9]: https://wiki.archlinux.org/title/AUR_helpers 394 | -------------------------------------------------------------------------------- /memprof.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | Memprof | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) 2012-2013 Arnaud Le Blanc | 6 | +----------------------------------------------------------------------+ 7 | | Redistribution and use in source and binary forms, with or without | 8 | | modification, are permitted provided that the conditions mentioned | 9 | | in the accompanying LICENSE file are met. | 10 | +----------------------------------------------------------------------+ 11 | | Author: Arnaud Le Blanc | 12 | +----------------------------------------------------------------------+ 13 | */ 14 | 15 | #ifdef HAVE_CONFIG_H 16 | #include "config.h" 17 | #endif 18 | 19 | #include "php.h" 20 | #include "SAPI.h" 21 | #include "php_ini.h" 22 | #include "ext/standard/info.h" 23 | #include "php_memprof.h" 24 | #include "zend_extensions.h" 25 | #include "zend_exceptions.h" 26 | #include 27 | #include 28 | #include "util.h" 29 | #include 30 | #if MEMPROF_DEBUG 31 | # undef NDEBUG 32 | #endif 33 | #include 34 | 35 | #if PHP_VERSION_ID < 80000 36 | #include "memprof_legacy_arginfo.h" 37 | #else 38 | #include "memprof_arginfo.h" 39 | #endif 40 | 41 | #define MEMPROF_ENV_PROFILE "MEMPROF_PROFILE" 42 | #define MEMPROF_FLAG_NATIVE "native" 43 | #define MEMPROF_FLAG_DUMP_ON_LIMIT "dump_on_limit" 44 | 45 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 46 | 47 | #ifdef ZTS 48 | /* TODO: support ZTS builds (without malloc hooks) */ 49 | # error "ZTS build not supported (yet)" 50 | #endif 51 | 52 | #if MEMPROF_CONFIGURE_VERSION != 3 53 | # error Please rebuild configure (run phpize and reconfigure) 54 | #endif 55 | 56 | #if HAVE_MALLOC_HOOKS 57 | # include 58 | 59 | # if MEMPROF_DEBUG 60 | # define MALLOC_HOOK_CHECK_NOT_OWN() \ 61 | if (__malloc_hook == malloc_hook) { \ 62 | fprintf(stderr, "__malloc_hook == malloc_hook (set at %s:%d)", malloc_hook_file, malloc_hook_line); \ 63 | abort(); \ 64 | } \ 65 | 66 | # define MALLOC_HOOK_CHECK_OWN() \ 67 | if (__malloc_hook != malloc_hook) { \ 68 | fprintf(stderr, "__malloc_hook != malloc_hook"); \ 69 | abort(); \ 70 | } \ 71 | 72 | # define MALLOC_HOOK_SET_FILE_LINE() do { \ 73 | malloc_hook_file = __FILE__; \ 74 | malloc_hook_line = __LINE__; \ 75 | } while (0) 76 | 77 | # else /* MEMPROF_DEBUG */ 78 | # define MALLOC_HOOK_CHECK_NOT_OWN() 79 | # define MALLOC_HOOK_CHECK_OWN() 80 | # define MALLOC_HOOK_SET_FILE_LINE() 81 | # endif /* MEMPROF_DEBUG */ 82 | 83 | # define WITHOUT_MALLOC_HOOKS \ 84 | do { \ 85 | int ___malloc_hook_restored = 0; \ 86 | if (__malloc_hook == malloc_hook) { \ 87 | MALLOC_HOOK_RESTORE_OLD(); \ 88 | ___malloc_hook_restored = 1; \ 89 | } \ 90 | do \ 91 | 92 | # define END_WITHOUT_MALLOC_HOOKS \ 93 | while (0); \ 94 | if (___malloc_hook_restored) { \ 95 | MALLOC_HOOK_SAVE_OLD(); \ 96 | MALLOC_HOOK_SET_OWN(); \ 97 | } \ 98 | } while (0) 99 | 100 | # define MALLOC_HOOK_RESTORE_OLD() \ 101 | /* Restore all old hooks */ \ 102 | MALLOC_HOOK_CHECK_OWN(); \ 103 | __malloc_hook = old_malloc_hook; \ 104 | __free_hook = old_free_hook; \ 105 | __realloc_hook = old_realloc_hook; \ 106 | __memalign_hook = old_memalign_hook; \ 107 | 108 | # define MALLOC_HOOK_SAVE_OLD() \ 109 | /* Save underlying hooks */ \ 110 | MALLOC_HOOK_CHECK_NOT_OWN(); \ 111 | old_malloc_hook = __malloc_hook; \ 112 | old_free_hook = __free_hook; \ 113 | old_realloc_hook = __realloc_hook; \ 114 | old_memalign_hook = __memalign_hook; \ 115 | 116 | # define MALLOC_HOOK_SET_OWN() \ 117 | /* Restore our own hooks */ \ 118 | __malloc_hook = malloc_hook; \ 119 | __free_hook = free_hook; \ 120 | __realloc_hook = realloc_hook; \ 121 | __memalign_hook = memalign_hook; \ 122 | MALLOC_HOOK_SET_FILE_LINE(); 123 | 124 | #else /* HAVE_MALLOC_HOOKS */ 125 | 126 | # define MALLOC_HOOK_CHECK_NOT_OWN() 127 | # define MALLOC_HOOK_IS_SET() (__malloc_hook == malloc_hook) 128 | # define MALLOC_HOOK_RESTORE_OLD() 129 | # define MALLOC_HOOK_SAVE_OLD() 130 | # define MALLOC_HOOK_SET_OWN() 131 | # define WITHOUT_MALLOC_HOOKS \ 132 | do { \ 133 | do 134 | # define END_WITHOUT_MALLOC_HOOKS \ 135 | while (0); \ 136 | } while (0); 137 | 138 | #endif /* HAVE_MALLOC_HOOKS */ 139 | 140 | #define MEMORY_LIMIT_ERROR_PREFIX "Allowed memory size of" 141 | 142 | typedef LIST_HEAD(_alloc_list_head, _alloc) alloc_list_head; 143 | 144 | /* a call frame */ 145 | typedef struct _frame { 146 | char * fn_name; 147 | size_t fn_name_len; 148 | char * file_name; 149 | size_t file_name_len; 150 | struct _frame * prev; 151 | size_t calls; 152 | HashTable next_cache; 153 | alloc_list_head allocs; 154 | } frame; 155 | 156 | /* an allocated block's infos */ 157 | typedef struct _alloc { 158 | #if MEMPROF_DEBUG 159 | size_t canary_a; 160 | #endif 161 | LIST_ENTRY(_alloc) list; 162 | size_t size; 163 | #if MEMPROF_DEBUG 164 | size_t canary_b; 165 | #endif 166 | } alloc; 167 | 168 | typedef union _alloc_bucket_item { 169 | alloc alloc; 170 | union _alloc_bucket_item * next_free; 171 | } alloc_bucket_item; 172 | 173 | typedef struct _alloc_buckets { 174 | size_t growsize; 175 | size_t nbuckets; 176 | alloc_bucket_item * next_free; 177 | alloc_bucket_item ** buckets; 178 | } alloc_buckets; 179 | 180 | static zend_bool dump_callgrind(php_stream * stream); 181 | static zend_bool dump_pprof(php_stream * stream); 182 | 183 | static ZEND_DECLARE_MODULE_GLOBALS(memprof) 184 | 185 | #if HAVE_MALLOC_HOOKS 186 | static void * malloc_hook(size_t size, const void *caller); 187 | static void * realloc_hook(void *ptr, size_t size, const void *caller); 188 | static void free_hook(void *ptr, const void *caller); 189 | static void * memalign_hook(size_t alignment, size_t size, const void *caller); 190 | #if MEMPROF_DEBUG 191 | static int malloc_hook_line = 0; 192 | static const char * malloc_hook_file = NULL; 193 | #endif 194 | 195 | static void * (*old_malloc_hook) (size_t size, const void *caller) = NULL; 196 | static void * (*old_realloc_hook) (void *ptr, size_t size, const void *caller) = NULL; 197 | static void (*old_free_hook) (void *ptr, const void *caller) = NULL; 198 | static void * (*old_memalign_hook) (size_t alignment, size_t size, const void *caller) = NULL; 199 | #endif /* HAVE_MALLOC_HOOKS */ 200 | 201 | static void (*old_zend_execute)(zend_execute_data *execute_data); 202 | static void (*old_zend_execute_internal)(zend_execute_data *execute_data_ptr, zval *return_value); 203 | #define zend_execute_fn zend_execute_ex 204 | 205 | #if PHP_VERSION_ID < 70200 /* PHP 7.1 */ 206 | # define MEMPROF_ZEND_ERROR_CB_ARGS int type, const char *error_filename, const uint error_lineno, const char *format, va_list args 207 | # define MEMPROF_ZEND_ERROR_CB_ARGS_PASSTHRU type, error_filename, error_lineno, format, args 208 | #elif PHP_VERSION_ID < 80000 /* PHP 7.2 - 7.4 */ 209 | # define MEMPROF_ZEND_ERROR_CB_ARGS int type, const char *error_filename, const uint32_t error_lineno, const char *format, va_list args 210 | # define MEMPROF_ZEND_ERROR_CB_ARGS_PASSTHRU type, error_filename, error_lineno, format, args 211 | #elif PHP_VERSION_ID < 80100 /* PHP 8.0 */ 212 | # define MEMPROF_ZEND_ERROR_CB_ARGS int type, const char *error_filename, const uint32_t error_lineno, zend_string *message 213 | # define MEMPROF_ZEND_ERROR_CB_ARGS_PASSTHRU type, error_filename, error_lineno, message 214 | #else /* PHP 8.1 */ 215 | # define MEMPROF_ZEND_ERROR_CB_ARGS int type, zend_string *error_filename, const uint32_t error_lineno, zend_string *message 216 | # define MEMPROF_ZEND_ERROR_CB_ARGS_PASSTHRU type, error_filename, error_lineno, message 217 | #endif 218 | 219 | static void (*old_zend_error_cb)(MEMPROF_ZEND_ERROR_CB_ARGS); 220 | static void (*rinit_zend_error_cb)(MEMPROF_ZEND_ERROR_CB_ARGS); 221 | static zend_bool zend_error_cb_overridden; 222 | static void memprof_zend_error_cb(MEMPROF_ZEND_ERROR_CB_ARGS); 223 | 224 | static PHP_INI_MH((*origOnChangeMemoryLimit)) = NULL; 225 | 226 | static int memprof_dumped = 0; 227 | static int track_mallocs = 0; 228 | 229 | static frame root_frame; 230 | static frame * current_frame; 231 | static alloc_list_head * current_alloc_list; 232 | static alloc_buckets current_alloc_buckets; 233 | 234 | static Pvoid_t allocs_set = (Pvoid_t) NULL; 235 | 236 | static const size_t zend_mm_heap_size = 4096; 237 | static zend_mm_heap * zheap = NULL; 238 | static zend_mm_heap * orig_zheap = NULL; 239 | 240 | #define ALLOC_INIT(alloc, size) alloc_init(alloc, size) 241 | 242 | #define ALLOC_LIST_INSERT_HEAD(head, elem) alloc_list_insert_head(head, elem) 243 | #define ALLOC_LIST_REMOVE(elem) alloc_list_remove(elem) 244 | 245 | ZEND_NORETURN static void out_of_memory(void) { 246 | fprintf(stderr, "memprof: System out of memory, try lowering memory_limit\n"); 247 | exit(1); 248 | } 249 | 250 | static inline void * malloc_check(size_t size) { 251 | void * ptr = malloc(size); 252 | if (UNEXPECTED(ptr == NULL)) { 253 | out_of_memory(); 254 | } 255 | return ptr; 256 | } 257 | 258 | static inline void * realloc_check(void * ptr, size_t size) { 259 | void * newptr = realloc(ptr, size); 260 | if (UNEXPECTED(newptr == NULL)) { 261 | out_of_memory(); 262 | } 263 | return newptr; 264 | } 265 | 266 | ZEND_NORETURN static void int_overflow(void) { 267 | fprintf(stderr, "memprof: Integer overflow in memory allocation, try lowering memory_limit\n"); 268 | exit(1); 269 | } 270 | 271 | static inline size_t safe_size(size_t nmemb, size_t size, size_t offset) { 272 | size_t r = nmemb * size; 273 | if (UNEXPECTED(nmemb != 0 && r / nmemb != size)) { 274 | int_overflow(); 275 | } 276 | if (UNEXPECTED(SIZE_MAX - r < offset)) { 277 | int_overflow(); 278 | } 279 | return r + offset; 280 | } 281 | 282 | static inline void alloc_init(alloc * alloc, size_t size) { 283 | alloc->size = size; 284 | alloc->list.le_next = NULL; 285 | alloc->list.le_prev = NULL; 286 | #if MEMPROF_DEBUG 287 | alloc->canary_a = alloc->canary_b = size ^ 0x5a5a5a5a; 288 | #endif 289 | } 290 | 291 | static void alloc_list_insert_head(alloc_list_head * head, alloc * elem) { 292 | LIST_INSERT_HEAD(head, elem, list); 293 | } 294 | 295 | static void list_remove(alloc * elm) { 296 | LIST_REMOVE(elm, list); 297 | } 298 | 299 | static void alloc_list_remove(alloc * elem) { 300 | if (elem->list.le_prev || elem->list.le_next) { 301 | list_remove(elem); 302 | elem->list.le_next = NULL; 303 | elem->list.le_prev = NULL; 304 | } 305 | } 306 | 307 | #if MEMPROF_DEBUG 308 | 309 | static void alloc_check_single(alloc * alloc, const char * function, int line) { 310 | if (alloc->canary_a != (alloc->size ^ 0x5a5a5a5a) || alloc->canary_a != alloc->canary_b) { 311 | fprintf(stderr, "canary mismatch for %p at %s:%d\n", alloc, function, line); 312 | abort(); 313 | } 314 | } 315 | 316 | static void alloc_check(alloc * alloc, const char * function, int line) { 317 | /* fprintf(stderr, "checking %p at %s:%d\n", alloc, function, line); */ 318 | alloc_check_single(alloc, function, line); 319 | /* 320 | for (alloc = current_alloc_list->lh_first; alloc; alloc = alloc->list.le_next) { 321 | alloc_check_single(alloc, function, line); 322 | } 323 | */ 324 | } 325 | 326 | # define ALLOC_CHECK(alloc) alloc_check(alloc, __FUNCTION__, __LINE__); 327 | #else /* MEMPROF_DEBUG */ 328 | # define ALLOC_CHECK(alloc) 329 | #endif /* MEMPROF_DEBUG */ 330 | 331 | static void alloc_buckets_destroy(alloc_buckets * buckets) 332 | { 333 | size_t i; 334 | 335 | for (i = 0; i < buckets->nbuckets; ++i) { 336 | free(buckets->buckets[i]); 337 | } 338 | 339 | #if MEMPROF_DEBUG 340 | memset(buckets->buckets, 0x5a, buckets->nbuckets * sizeof(buckets->buckets[0])); 341 | #endif 342 | free(buckets->buckets); 343 | 344 | #if MEMPROF_DEBUG 345 | memset(buckets, 0x5a, sizeof(*buckets)); 346 | #endif 347 | } 348 | 349 | static void alloc_buckets_grow(alloc_buckets * buckets) 350 | { 351 | size_t i; 352 | alloc_bucket_item * bucket; 353 | 354 | buckets->nbuckets++; 355 | buckets->buckets = realloc_check(buckets->buckets, safe_size(buckets->nbuckets, sizeof(*buckets->buckets), 0)); 356 | 357 | buckets->growsize = safe_size(2, buckets->growsize, 0); 358 | bucket = malloc_check(safe_size(buckets->growsize, sizeof(*bucket), 0)); 359 | buckets->buckets[buckets->nbuckets-1] = bucket; 360 | 361 | for (i = 1; i < buckets->growsize; ++i) { 362 | bucket[i-1].next_free = &bucket[i]; 363 | } 364 | bucket[buckets->growsize-1].next_free = buckets->next_free; 365 | buckets->next_free = &bucket[0]; 366 | } 367 | 368 | static void alloc_buckets_init(alloc_buckets * buckets) 369 | { 370 | buckets->growsize = 128; 371 | buckets->nbuckets = 0; 372 | buckets->buckets = NULL; 373 | buckets->next_free = NULL; 374 | alloc_buckets_grow(buckets); 375 | } 376 | 377 | static alloc * alloc_buckets_alloc(alloc_buckets * buckets, size_t size) 378 | { 379 | alloc_bucket_item * item = buckets->next_free; 380 | 381 | if (item == NULL) { 382 | alloc_buckets_grow(buckets); 383 | item = buckets->next_free; 384 | } 385 | 386 | buckets->next_free = item->next_free; 387 | 388 | ALLOC_INIT(&item->alloc, size); 389 | 390 | return &item->alloc; 391 | } 392 | 393 | static void alloc_buckets_free(alloc_buckets * buckets, alloc * a) 394 | { 395 | alloc_bucket_item * item; 396 | item = (alloc_bucket_item*) a; 397 | item->next_free = buckets->next_free; 398 | buckets->next_free = item; 399 | } 400 | 401 | static void destroy_frame(frame * f) 402 | { 403 | alloc * a; 404 | 405 | #if MEMPROF_DEBUG 406 | memset(f->file_name, 0x5a, f->file_name_len); 407 | memset(f->fn_name, 0x5a, f->fn_name_len); 408 | #endif 409 | free(f->file_name); 410 | free(f->fn_name); 411 | 412 | while (f->allocs.lh_first) { 413 | a = f->allocs.lh_first; 414 | ALLOC_CHECK(a); 415 | ALLOC_LIST_REMOVE(a); 416 | } 417 | 418 | zend_hash_destroy(&f->next_cache); 419 | 420 | #if MEMPROF_DEBUG 421 | memset(f, 0x5a, sizeof(*f)); 422 | #endif 423 | } 424 | 425 | /* HashTable destructor */ 426 | static void frame_dtor(zval * pDest) 427 | { 428 | frame * f = Z_PTR_P(pDest); 429 | destroy_frame(f); 430 | free(f); 431 | } 432 | 433 | static void init_frame(frame * f, frame * prev, char *file_name, size_t file_name_len, char * fn_name, size_t fn_name_len) 434 | { 435 | zend_hash_init(&f->next_cache, 0, NULL, frame_dtor, 0); 436 | 437 | f->file_name = malloc_check(safe_size(1, file_name_len, 1)); 438 | memcpy(f->file_name, file_name, file_name_len+1); 439 | f->file_name_len = file_name_len; 440 | 441 | f->fn_name = malloc_check(safe_size(1, fn_name_len, 1)); 442 | memcpy(f->fn_name, fn_name, fn_name_len+1); 443 | f->fn_name_len = fn_name_len; 444 | 445 | f->calls = 0; 446 | f->prev = prev; 447 | LIST_INIT(&f->allocs); 448 | } 449 | 450 | static frame * new_frame(frame * prev, char *file_name, size_t file_name_len, char * fn_name, size_t fn_name_len) 451 | { 452 | frame * f = malloc_check(sizeof(*f)); 453 | init_frame(f, prev, file_name, file_name_len, fn_name, fn_name_len); 454 | return f; 455 | } 456 | 457 | static frame * get_or_create_frame(zend_execute_data * current_execute_data, frame * prev) 458 | { 459 | frame * f; 460 | 461 | char file_name[256]; 462 | char fn_name[256]; 463 | size_t file_name_len; 464 | size_t fn_name_len; 465 | 466 | file_name_len = get_file_name(current_execute_data, file_name, sizeof(file_name)); 467 | fn_name_len = get_function_name(current_execute_data, fn_name, sizeof(fn_name)); 468 | 469 | if (prev == NULL) { 470 | return new_frame(prev, file_name, file_name_len, fn_name, fn_name_len); 471 | } 472 | 473 | f = zend_hash_str_find_ptr(&prev->next_cache, fn_name, fn_name_len); 474 | if (f == NULL) { 475 | f = new_frame(prev, file_name, file_name_len, fn_name, fn_name_len); 476 | zend_hash_str_add_ptr(&prev->next_cache, fn_name, fn_name_len, f); 477 | } 478 | 479 | return f; 480 | } 481 | 482 | static size_t frame_alloc_size(const frame * f) 483 | { 484 | size_t size = 0; 485 | alloc * alloc; 486 | 487 | LIST_FOREACH(alloc, &f->allocs, list) { 488 | size += alloc->size; 489 | } 490 | 491 | return size; 492 | } 493 | 494 | static int frame_stack_depth(const frame * f) 495 | { 496 | const frame * prev; 497 | int depth = 0; 498 | 499 | for (prev = f; prev != &root_frame; prev = prev->prev) { 500 | depth ++; 501 | } 502 | 503 | return depth; 504 | } 505 | 506 | static void mark_own_alloc(Pvoid_t * set, void * ptr, alloc * a) 507 | { 508 | Word_t * p; 509 | JLI(p, *set, (Word_t)ptr); 510 | *p = (Word_t) a; 511 | } 512 | 513 | static void unmark_own_alloc(Pvoid_t * set, void * ptr) 514 | { 515 | int ret; 516 | 517 | MALLOC_HOOK_CHECK_NOT_OWN(); 518 | 519 | JLD(ret, *set, (Word_t)ptr); 520 | } 521 | 522 | alloc * is_own_alloc(Pvoid_t * set, void * ptr) 523 | { 524 | Word_t * p; 525 | 526 | MALLOC_HOOK_CHECK_NOT_OWN(); 527 | 528 | JLG(p, *set, (Word_t)ptr); 529 | if (p != NULL) { 530 | return (alloc*) *p; 531 | } else { 532 | return 0; 533 | } 534 | } 535 | 536 | #if HAVE_MALLOC_HOOKS 537 | 538 | static void * malloc_hook(size_t size, const void *caller) 539 | { 540 | void *result; 541 | 542 | WITHOUT_MALLOC_HOOKS { 543 | 544 | result = malloc_check(size); 545 | if (result != NULL) { 546 | alloc * a = alloc_buckets_alloc(¤t_alloc_buckets, size); 547 | if (track_mallocs) { 548 | ALLOC_LIST_INSERT_HEAD(current_alloc_list, a); 549 | } 550 | mark_own_alloc(&allocs_set, result, a); 551 | assert(is_own_alloc(&allocs_set, result)); 552 | } 553 | 554 | } END_WITHOUT_MALLOC_HOOKS; 555 | 556 | return result; 557 | } 558 | 559 | static void * realloc_hook(void *ptr, size_t size, const void *caller) 560 | { 561 | void *result; 562 | alloc *a; 563 | 564 | WITHOUT_MALLOC_HOOKS { 565 | 566 | if (ptr != NULL && !(a = is_own_alloc(&allocs_set, ptr))) { 567 | result = realloc(ptr, size); 568 | } else { 569 | /* ptr may be freed by realloc, so we must remove it from list now */ 570 | if (ptr != NULL) { 571 | ALLOC_CHECK(a); 572 | ALLOC_LIST_REMOVE(a); 573 | unmark_own_alloc(&allocs_set, ptr); 574 | alloc_buckets_free(¤t_alloc_buckets, a); 575 | } 576 | 577 | result = realloc(ptr, size); 578 | if (result != NULL) { 579 | /* succeeded; add result */ 580 | a = alloc_buckets_alloc(¤t_alloc_buckets, size); 581 | if (track_mallocs) { 582 | ALLOC_LIST_INSERT_HEAD(current_alloc_list, a); 583 | } 584 | mark_own_alloc(&allocs_set, result, a); 585 | } else if (ptr != NULL) { 586 | /* failed, re-add ptr, since it hasn't been freed */ 587 | a = alloc_buckets_alloc(¤t_alloc_buckets, size); 588 | if (track_mallocs) { 589 | ALLOC_LIST_INSERT_HEAD(current_alloc_list, a); 590 | } 591 | mark_own_alloc(&allocs_set, ptr, a); 592 | } 593 | } 594 | 595 | } END_WITHOUT_MALLOC_HOOKS; 596 | 597 | return result; 598 | } 599 | 600 | static void free_hook(void *ptr, const void *caller) 601 | { 602 | WITHOUT_MALLOC_HOOKS { 603 | 604 | if (ptr != NULL) { 605 | alloc * a; 606 | if ((a = is_own_alloc(&allocs_set, ptr))) { 607 | ALLOC_CHECK(a); 608 | ALLOC_LIST_REMOVE(a); 609 | free(ptr); 610 | unmark_own_alloc(&allocs_set, ptr); 611 | alloc_buckets_free(¤t_alloc_buckets, a); 612 | } else { 613 | free(ptr); 614 | } 615 | } 616 | 617 | } END_WITHOUT_MALLOC_HOOKS; 618 | } 619 | 620 | static void * memalign_hook(size_t alignment, size_t size, const void *caller) 621 | { 622 | void * result; 623 | 624 | WITHOUT_MALLOC_HOOKS { 625 | 626 | result = memalign(alignment, size); 627 | if (result != NULL) { 628 | alloc *a = alloc_buckets_alloc(¤t_alloc_buckets, size); 629 | if (track_mallocs) { 630 | ALLOC_LIST_INSERT_HEAD(current_alloc_list, a); 631 | } 632 | mark_own_alloc(&allocs_set, result, a); 633 | } 634 | 635 | } END_WITHOUT_MALLOC_HOOKS; 636 | 637 | return result; 638 | } 639 | #endif /* HAVE_MALLOC_HOOKS */ 640 | 641 | #define WITHOUT_MALLOC_TRACKING do { \ 642 | int ___old_track_mallocs = track_mallocs; \ 643 | track_mallocs = 0; \ 644 | do 645 | 646 | #define END_WITHOUT_MALLOC_TRACKING \ 647 | while (0); \ 648 | track_mallocs = ___old_track_mallocs; \ 649 | } while (0) 650 | 651 | #if PHP_VERSION_ID >= 80400 652 | # define MM_HANDLER_FILE_LINE_DC ZEND_FILE_LINE_DC 653 | # define MM_HANDLER_FILE_LINE_ORIG_DC ZEND_FILE_LINE_ORIG_DC 654 | #else 655 | # define MM_HANDLER_FILE_LINE_DC 656 | # define MM_HANDLER_FILE_LINE_ORIG_DC 657 | #endif 658 | 659 | static void * zend_malloc_handler(size_t size MM_HANDLER_FILE_LINE_DC MM_HANDLER_FILE_LINE_ORIG_DC) 660 | { 661 | void *result; 662 | 663 | assert(MEMPROF_G(profile_flags).enabled); 664 | 665 | WITHOUT_MALLOC_HOOKS { 666 | 667 | result = zend_mm_alloc(orig_zheap, size); 668 | if (result != NULL) { 669 | alloc * a = alloc_buckets_alloc(¤t_alloc_buckets, size); 670 | if (track_mallocs) { 671 | ALLOC_LIST_INSERT_HEAD(current_alloc_list, a); 672 | } 673 | mark_own_alloc(&allocs_set, result, a); 674 | assert(is_own_alloc(&allocs_set, result)); 675 | } 676 | 677 | } END_WITHOUT_MALLOC_HOOKS; 678 | 679 | return result; 680 | } 681 | 682 | static void zend_free_handler(void * ptr MM_HANDLER_FILE_LINE_DC MM_HANDLER_FILE_LINE_ORIG_DC) 683 | { 684 | assert(MEMPROF_G(profile_flags).enabled); 685 | 686 | WITHOUT_MALLOC_HOOKS { 687 | 688 | if (ptr != NULL) { 689 | alloc * a; 690 | if ((a = is_own_alloc(&allocs_set, ptr))) { 691 | ALLOC_CHECK(a); 692 | ALLOC_LIST_REMOVE(a); 693 | zend_mm_free(orig_zheap, ptr); 694 | unmark_own_alloc(&allocs_set, ptr); 695 | alloc_buckets_free(¤t_alloc_buckets, a); 696 | } else { 697 | zend_mm_free(orig_zheap, ptr); 698 | } 699 | } 700 | 701 | } END_WITHOUT_MALLOC_HOOKS; 702 | } 703 | 704 | static void * zend_realloc_handler(void * ptr, size_t size MM_HANDLER_FILE_LINE_DC MM_HANDLER_FILE_LINE_ORIG_DC) 705 | { 706 | void *result; 707 | alloc *a; 708 | 709 | assert(MEMPROF_G(profile_flags).enabled); 710 | 711 | WITHOUT_MALLOC_HOOKS { 712 | 713 | if (ptr != NULL && !(a = is_own_alloc(&allocs_set, ptr))) { 714 | result = zend_mm_realloc(orig_zheap, ptr, size); 715 | } else { 716 | /* ptr may be freed by realloc, so we must remove it from list now */ 717 | if (ptr != NULL) { 718 | ALLOC_CHECK(a); 719 | ALLOC_LIST_REMOVE(a); 720 | unmark_own_alloc(&allocs_set, ptr); 721 | alloc_buckets_free(¤t_alloc_buckets, a); 722 | } 723 | 724 | result = zend_mm_realloc(orig_zheap, ptr, size); 725 | if (result != NULL) { 726 | /* succeeded; add result */ 727 | a = alloc_buckets_alloc(¤t_alloc_buckets, size); 728 | if (track_mallocs) { 729 | ALLOC_LIST_INSERT_HEAD(current_alloc_list, a); 730 | } 731 | mark_own_alloc(&allocs_set, result, a); 732 | } else if (ptr != NULL) { 733 | /* failed, re-add ptr, since it hasn't been freed */ 734 | a = alloc_buckets_alloc(¤t_alloc_buckets, size); 735 | if (track_mallocs) { 736 | ALLOC_LIST_INSERT_HEAD(current_alloc_list, a); 737 | } 738 | mark_own_alloc(&allocs_set, ptr, a); 739 | } 740 | } 741 | 742 | } END_WITHOUT_MALLOC_HOOKS; 743 | 744 | return result; 745 | } 746 | 747 | // Some extensions override zend_error_cb and don't call the previous 748 | // zend_error_cb, so memprof needs to be the last to override it 749 | static void memprof_late_override_error_cb(void) { 750 | old_zend_error_cb = zend_error_cb; 751 | zend_error_cb = memprof_zend_error_cb; 752 | zend_error_cb_overridden = 1; 753 | } 754 | 755 | static void memprof_zend_execute(zend_execute_data *execute_data) 756 | { 757 | if (UNEXPECTED(!zend_error_cb_overridden)) { 758 | memprof_late_override_error_cb(); 759 | } 760 | 761 | WITHOUT_MALLOC_TRACKING { 762 | 763 | current_frame = get_or_create_frame(execute_data, current_frame); 764 | current_frame->calls++; 765 | current_alloc_list = ¤t_frame->allocs; 766 | 767 | } END_WITHOUT_MALLOC_TRACKING; 768 | 769 | old_zend_execute(execute_data); 770 | 771 | if (MEMPROF_G(profile_flags).enabled) { 772 | current_frame = current_frame->prev; 773 | current_alloc_list = ¤t_frame->allocs; 774 | } 775 | } 776 | 777 | static void memprof_zend_execute_internal(zend_execute_data *execute_data_ptr, zval *return_value) 778 | { 779 | int ignore = 0; 780 | 781 | if (UNEXPECTED(!zend_error_cb_overridden)) { 782 | memprof_late_override_error_cb(); 783 | } 784 | 785 | if (&execute_data_ptr->func->internal_function == &zend_pass_function) { 786 | ignore = 1; 787 | } else if (execute_data_ptr->func->common.function_name) { 788 | zend_string * name = execute_data_ptr->func->common.function_name; 789 | if (ZSTR_LEN(name) == sizeof("call_user_func")-1 790 | && 0 == memcmp(name, "call_user_func", sizeof("call_user_func"))) 791 | { 792 | ignore = 1; 793 | } else if (ZSTR_LEN(name) == sizeof("call_user_func_array")-1 794 | && 0 == memcmp(name, "call_user_func_array", sizeof("call_user_func_array"))) 795 | { 796 | ignore = 1; 797 | } 798 | } 799 | 800 | WITHOUT_MALLOC_TRACKING { 801 | 802 | if (!ignore) { 803 | current_frame = get_or_create_frame(execute_data_ptr, current_frame); 804 | current_frame->calls++; 805 | current_alloc_list = ¤t_frame->allocs; 806 | } 807 | 808 | } END_WITHOUT_MALLOC_TRACKING; 809 | 810 | if (!old_zend_execute_internal) { 811 | execute_internal(execute_data_ptr, return_value); 812 | } else { 813 | old_zend_execute_internal(execute_data_ptr, return_value); 814 | } 815 | 816 | if (!ignore && MEMPROF_G(profile_flags).enabled) { 817 | current_frame = current_frame->prev; 818 | current_alloc_list = ¤t_frame->allocs; 819 | } 820 | } 821 | 822 | static zend_bool should_autodump(int error_type, const char *message) { 823 | if (EXPECTED(error_type != E_ERROR)) { 824 | return 0; 825 | } 826 | 827 | if (EXPECTED(!MEMPROF_G(profile_flags).dump_on_limit)) { 828 | return 0; 829 | } 830 | 831 | if (EXPECTED(strncmp(MEMORY_LIMIT_ERROR_PREFIX, message, strlen(MEMORY_LIMIT_ERROR_PREFIX)) != 0)) { 832 | return 0; 833 | } 834 | 835 | return 1; 836 | } 837 | 838 | static char * generate_filename(const char * format) { 839 | char * filename; 840 | struct timeval tv; 841 | uint64_t ts; 842 | const char * output_dir = MEMPROF_G(output_dir); 843 | char slash[] = "\0"; 844 | 845 | gettimeofday(&tv, NULL); 846 | ts = ((uint64_t) tv.tv_sec) * 0x100000 + (((uint64_t) tv.tv_usec) % 0x100000); 847 | 848 | if (!IS_SLASH(output_dir[strlen(output_dir)-1])) { 849 | slash[0] = DEFAULT_SLASH; 850 | } 851 | 852 | spprintf(&filename, 0, "%s%smemprof.%s.%" PRIu64, output_dir, slash, format, ts); 853 | 854 | return filename; 855 | } 856 | 857 | static void memprof_zend_error_cb_dump(MEMPROF_ZEND_ERROR_CB_ARGS) 858 | { 859 | char * filename = NULL; 860 | php_stream * stream; 861 | zend_bool error = 0; 862 | #if PHP_VERSION_ID < 80000 863 | const char * message_chr = format; 864 | #else 865 | const char * message_chr = ZSTR_VAL(message); 866 | #endif 867 | zend_string * new_message = NULL; 868 | 869 | zend_mm_set_heap(orig_zheap); 870 | zend_set_memory_limit((size_t)Z_L(-1) >> (size_t)Z_L(1)); 871 | zend_mm_set_heap(zheap); 872 | 873 | WITHOUT_MALLOC_TRACKING { 874 | if (MEMPROF_G(output_format) == FORMAT_CALLGRIND) { 875 | filename = generate_filename(MEMPROF_OUTPUT_FORMAT_CALLGRIND); 876 | stream = php_stream_open_wrapper_ex(filename, "w", 0, NULL, NULL); 877 | if (stream != NULL) { 878 | error = !dump_callgrind(stream); 879 | php_stream_free(stream, PHP_STREAM_FREE_CLOSE); 880 | } else { 881 | error = 1; 882 | } 883 | } else if (MEMPROF_G(output_format) == FORMAT_PPROF) { 884 | filename = generate_filename(MEMPROF_OUTPUT_FORMAT_PPROF); 885 | stream = php_stream_open_wrapper_ex(filename, "w", 0, NULL, NULL); 886 | if (stream != NULL) { 887 | error = !dump_pprof(stream); 888 | php_stream_free(stream, PHP_STREAM_FREE_CLOSE); 889 | } else { 890 | error = 1; 891 | } 892 | } 893 | 894 | if (filename != NULL) { 895 | if (error == 0) { 896 | new_message = strpprintf(0, "%s (memprof dumped to %s)", message_chr, filename); 897 | } else { 898 | new_message = strpprintf(0, "%s (memprof failed dumping to %s, please check file permissions or disk capacity)", message_chr, filename); 899 | } 900 | efree(filename); 901 | } 902 | 903 | if (new_message != NULL) { 904 | #if PHP_VERSION_ID < 80000 905 | format = ZSTR_VAL(new_message); 906 | #else 907 | message = new_message; 908 | #endif 909 | } 910 | } END_WITHOUT_MALLOC_TRACKING; 911 | 912 | zend_mm_set_heap(orig_zheap); 913 | zend_set_memory_limit(PG(memory_limit)); 914 | zend_mm_set_heap(zheap); 915 | 916 | old_zend_error_cb(MEMPROF_ZEND_ERROR_CB_ARGS_PASSTHRU); 917 | 918 | WITHOUT_MALLOC_TRACKING { 919 | if (new_message != NULL) { 920 | zend_string_free(new_message); 921 | } 922 | } END_WITHOUT_MALLOC_TRACKING; 923 | 924 | } 925 | 926 | static void memprof_zend_error_cb(MEMPROF_ZEND_ERROR_CB_ARGS) 927 | { 928 | #if PHP_VERSION_ID < 80000 929 | const char * message_chr = format; 930 | #else 931 | const char * message_chr = ZSTR_VAL(message); 932 | #endif 933 | 934 | if (EXPECTED(!MEMPROF_G(profile_flags).enabled)) { 935 | old_zend_error_cb(MEMPROF_ZEND_ERROR_CB_ARGS_PASSTHRU); 936 | return; 937 | } 938 | 939 | if (EXPECTED(!should_autodump(type, message_chr))) { 940 | old_zend_error_cb(MEMPROF_ZEND_ERROR_CB_ARGS_PASSTHRU); 941 | return; 942 | } 943 | 944 | return memprof_zend_error_cb_dump(MEMPROF_ZEND_ERROR_CB_ARGS_PASSTHRU); 945 | } 946 | 947 | static PHP_INI_MH(OnChangeMemoryLimit) 948 | { 949 | int ret; 950 | 951 | if (!origOnChangeMemoryLimit) { 952 | return FAILURE; 953 | } 954 | 955 | ret = origOnChangeMemoryLimit(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); 956 | 957 | if (ret != SUCCESS) { 958 | return ret; 959 | } 960 | 961 | if (MEMPROF_G(profile_flags).enabled && orig_zheap) { 962 | zend_mm_set_heap(orig_zheap); 963 | zend_set_memory_limit(PG(memory_limit)); 964 | zend_mm_set_heap(zheap); 965 | } 966 | 967 | return SUCCESS; 968 | } 969 | 970 | static void memprof_enable(memprof_profile_flags * pf) 971 | { 972 | assert(pf->enabled); 973 | 974 | alloc_buckets_init(¤t_alloc_buckets); 975 | 976 | init_frame(&root_frame, &root_frame, "", 0, "root", sizeof("root")-1); 977 | root_frame.calls = 1; 978 | 979 | current_frame = &root_frame; 980 | current_alloc_list = &root_frame.allocs; 981 | 982 | if (pf->native) { 983 | MALLOC_HOOK_SAVE_OLD(); 984 | MALLOC_HOOK_SET_OWN(); 985 | } 986 | 987 | memprof_dumped = 0; 988 | 989 | if (is_zend_mm()) { 990 | /* There is no way to completely free a zend_mm_heap with custom 991 | * handlers, so we have to allocate it ourselves. We don't know the 992 | * actual size of a _zend_mm_heap struct, but this should be enough. */ 993 | zheap = malloc_check(zend_mm_heap_size); 994 | memset(zheap, 0, zend_mm_heap_size); 995 | zend_mm_set_custom_handlers(zheap, zend_malloc_handler, zend_free_handler, zend_realloc_handler); 996 | orig_zheap = zend_mm_set_heap(zheap); 997 | } else { 998 | zheap = NULL; 999 | orig_zheap = NULL; 1000 | } 1001 | 1002 | old_zend_execute = zend_execute_fn; 1003 | old_zend_execute_internal = zend_execute_internal; 1004 | zend_execute_fn = memprof_zend_execute; 1005 | zend_execute_internal = memprof_zend_execute_internal; 1006 | 1007 | track_mallocs = 1; 1008 | } 1009 | 1010 | static void memprof_disable(void) 1011 | { 1012 | track_mallocs = 0; 1013 | 1014 | zend_execute_fn = old_zend_execute; 1015 | zend_execute_internal = old_zend_execute_internal; 1016 | 1017 | if (zheap) { 1018 | zend_mm_set_heap(orig_zheap); 1019 | free(zheap); 1020 | } 1021 | 1022 | if (MEMPROF_G(profile_flags).native) { 1023 | MALLOC_HOOK_RESTORE_OLD(); 1024 | } 1025 | 1026 | MEMPROF_G(profile_flags).enabled = 0; 1027 | 1028 | destroy_frame(&root_frame); 1029 | 1030 | alloc_buckets_destroy(¤t_alloc_buckets); 1031 | 1032 | JudyLFreeArray(&allocs_set, PJE0); 1033 | allocs_set = (Pvoid_t) NULL; 1034 | 1035 | if (!memprof_dumped) { 1036 | // Calling this during RSHUTDOWN breaks zend_deactivate_modules(), which 1037 | // causes corruption of global state. 1038 | // zend_error(E_WARNING, "Memprof profiling was enabled, but no profile was dumped. Did you forget to call one of memprof_dump_callgrind(), memprof_dump_pprof(), or memprof_dump_array() ?"); 1039 | } 1040 | } 1041 | 1042 | static void disable_opcache(void) 1043 | { 1044 | zend_string *key = zend_string_init(ZEND_STRL("opcache.enable"), 0); 1045 | zend_alter_ini_entry_chars_ex( 1046 | key, 1047 | "0", 1048 | 1, 1049 | ZEND_INI_USER, 1050 | ZEND_INI_STAGE_ACTIVATE, 1051 | 0 1052 | ); 1053 | zend_string_release(key); 1054 | } 1055 | 1056 | static zend_string* read_env_get_post(char *name, size_t len) 1057 | { 1058 | zval *value; 1059 | 1060 | char *env = sapi_getenv(name, len); 1061 | if (env != NULL) { 1062 | zend_string *str = zend_string_init(env, strlen(env), 0); 1063 | efree(env); 1064 | return str; 1065 | } 1066 | 1067 | env = getenv(name); 1068 | if (env != NULL) { 1069 | return zend_string_init(env, strlen(env), 0); 1070 | } 1071 | 1072 | if (Z_ARR(PG(http_globals)[TRACK_VARS_GET]) != NULL) { 1073 | value = zend_hash_str_find(Z_ARR(PG(http_globals)[TRACK_VARS_GET]), name, len); 1074 | if (value != NULL) { 1075 | convert_to_string_ex(value); 1076 | zend_string_addref(Z_STR_P(value)); 1077 | return Z_STR_P(value); 1078 | } 1079 | } 1080 | 1081 | if (Z_ARR(PG(http_globals)[TRACK_VARS_POST]) != NULL) { 1082 | value = zend_hash_str_find(Z_ARR(PG(http_globals)[TRACK_VARS_POST]), name, len); 1083 | if (value != NULL) { 1084 | convert_to_string_ex(value); 1085 | zend_string_addref(Z_STR_P(value)); 1086 | return Z_STR_P(value); 1087 | } 1088 | } 1089 | 1090 | return NULL; 1091 | } 1092 | 1093 | static void parse_trigger(memprof_profile_flags * pf) 1094 | { 1095 | char *saveptr; 1096 | const char *delim = ","; 1097 | char *flag; 1098 | 1099 | zend_string *value = read_env_get_post(MEMPROF_ENV_PROFILE, strlen(MEMPROF_ENV_PROFILE)); 1100 | if (value == NULL) { 1101 | return; 1102 | } 1103 | 1104 | pf->enabled = ZSTR_LEN(value) > 0; 1105 | 1106 | for (flag = strtok_r(ZSTR_VAL(value), delim, &saveptr); flag != NULL; flag = strtok_r(NULL, delim, &saveptr)) { 1107 | if (HAVE_MALLOC_HOOKS && strcmp(MEMPROF_FLAG_NATIVE, flag) == 0) { 1108 | pf->native = 1; 1109 | } 1110 | if (strcmp(MEMPROF_FLAG_DUMP_ON_LIMIT, flag) == 0) { 1111 | pf->dump_on_limit = 1; 1112 | } 1113 | } 1114 | 1115 | zend_string_release(value); 1116 | } 1117 | 1118 | ZEND_DLEXPORT int memprof_zend_startup(zend_extension *extension) 1119 | { 1120 | return zend_startup_module(&memprof_module_entry); 1121 | } 1122 | 1123 | #ifndef ZEND_EXT_API 1124 | #define ZEND_EXT_API ZEND_DLEXPORT 1125 | #endif 1126 | ZEND_EXTENSION(); 1127 | 1128 | ZEND_DLEXPORT zend_extension zend_extension_entry = { 1129 | MEMPROF_NAME, 1130 | PHP_MEMPROF_VERSION, 1131 | "Arnaud Le Blanc", 1132 | "https://github.com/arnaud-lb/php-memory-profiler", 1133 | "Copyright (c) 2013", 1134 | memprof_zend_startup, 1135 | NULL, 1136 | NULL, /* activate_func_t */ 1137 | NULL, /* deactivate_func_t */ 1138 | NULL, /* message_handler_func_t */ 1139 | NULL, /* op_array_handler_func_t */ 1140 | NULL, /* statement_handler_func_t */ 1141 | NULL, /* fcall_begin_handler_func_t */ 1142 | NULL, /* fcall_end_handler_func_t */ 1143 | NULL, /* op_array_ctor_func_t */ 1144 | NULL, /* op_array_dtor_func_t */ 1145 | STANDARD_ZEND_EXTENSION_PROPERTIES 1146 | }; 1147 | 1148 | ZEND_BEGIN_ARG_INFO_EX(arginfo_memprof_memory_get_usage, 0, 0, 0) 1149 | ZEND_ARG_INFO(0, real) 1150 | ZEND_END_ARG_INFO() 1151 | 1152 | /* {{{ memprof_functions_overrides[] 1153 | */ 1154 | const zend_function_entry memprof_function_overrides[] = { 1155 | PHP_FALIAS(memory_get_peak_usage, memprof_memory_get_peak_usage, arginfo_memprof_memory_get_usage) 1156 | PHP_FALIAS(memory_get_usage, memprof_memory_get_usage, arginfo_memprof_memory_get_usage) 1157 | PHP_FE_END /* Must be the last line in memprof_function_overrides[] */ 1158 | }; 1159 | /* }}} */ 1160 | 1161 | /* {{{ memprof_module_entry 1162 | */ 1163 | zend_module_entry memprof_module_entry = { 1164 | STANDARD_MODULE_HEADER, 1165 | MEMPROF_NAME, 1166 | ext_functions, 1167 | PHP_MINIT(memprof), 1168 | PHP_MSHUTDOWN(memprof), 1169 | PHP_RINIT(memprof), 1170 | PHP_RSHUTDOWN(memprof), 1171 | PHP_MINFO(memprof), 1172 | PHP_MEMPROF_VERSION, 1173 | PHP_MODULE_GLOBALS(memprof), 1174 | PHP_GINIT(memprof), 1175 | NULL, 1176 | NULL, 1177 | STANDARD_MODULE_PROPERTIES_EX 1178 | }; 1179 | /* }}} */ 1180 | 1181 | #ifdef COMPILE_DL_MEMPROF 1182 | # ifdef ZTS 1183 | ZEND_TSRMLS_CACHE_DEFINE() 1184 | # endif 1185 | ZEND_GET_MODULE(memprof) 1186 | #endif 1187 | 1188 | #ifdef P_tmpdir 1189 | # define MEMPROF_TEMP_DIR P_tmpdir 1190 | #else 1191 | # ifdef PHP_WIN32 1192 | # define MEMPROF_TEMP_DIR "C:\\Windows\\Temp" 1193 | # else 1194 | # define MEMPROF_TEMP_DIR "/tmp" 1195 | # endif 1196 | #endif 1197 | 1198 | static ZEND_INI_MH(onUpdateOutputFormat) 1199 | { 1200 | if (zend_string_equals_literal_ci(new_value, MEMPROF_OUTPUT_FORMAT_CALLGRIND)) { 1201 | MEMPROF_G(output_format) = FORMAT_CALLGRIND; 1202 | } else if (zend_string_equals_literal_ci(new_value, MEMPROF_OUTPUT_FORMAT_PPROF)) { 1203 | MEMPROF_G(output_format) = FORMAT_PPROF; 1204 | } else { 1205 | zend_error_noreturn(E_ERROR, "Invalid memprof.output_format setting. Should be \"callgrind\" or \"pprof\""); 1206 | return FAILURE; 1207 | } 1208 | 1209 | return SUCCESS; 1210 | } 1211 | 1212 | /* {{{ PHP_INI_BEGIN 1213 | */ 1214 | PHP_INI_BEGIN() 1215 | STD_PHP_INI_ENTRY("memprof.output_dir", MEMPROF_TEMP_DIR, PHP_INI_ALL, OnUpdateStringUnempty, output_dir, zend_memprof_globals, memprof_globals) 1216 | STD_PHP_INI_ENTRY("memprof.output_format", MEMPROF_OUTPUT_FORMAT_CALLGRIND, PHP_INI_ALL, onUpdateOutputFormat, output_format, zend_memprof_globals, memprof_globals) 1217 | PHP_INI_END() 1218 | /* }}} */ 1219 | 1220 | /* {{{ PHP_MINIT_FUNCTION 1221 | */ 1222 | PHP_MINIT_FUNCTION(memprof) 1223 | { 1224 | zend_ini_entry * entry; 1225 | const zend_function_entry * fentry; 1226 | 1227 | REGISTER_INI_ENTRIES(); 1228 | 1229 | entry = zend_hash_str_find_ptr(EG(ini_directives), "memory_limit", sizeof("memory_limit")-1); 1230 | 1231 | if (entry == NULL) { 1232 | zend_error(E_CORE_ERROR, "memory_limit ini entry not found"); 1233 | return FAILURE; 1234 | } 1235 | 1236 | origOnChangeMemoryLimit = entry->on_modify; 1237 | entry->on_modify = OnChangeMemoryLimit; 1238 | 1239 | for (fentry = memprof_function_overrides; fentry->fname; fentry++) { 1240 | size_t name_len = strlen(fentry->fname); 1241 | zend_internal_function * orig = zend_hash_str_find_ptr(CG(function_table), fentry->fname, name_len); 1242 | if (orig != NULL && orig->type == ZEND_INTERNAL_FUNCTION) { 1243 | orig->handler = fentry->handler; 1244 | } else { 1245 | zend_error(E_WARNING, "memprof: Could not override %s(), return value from this function may be be accurate.", fentry->fname); 1246 | } 1247 | } 1248 | 1249 | return SUCCESS; 1250 | } 1251 | /* }}} */ 1252 | 1253 | /* {{{ PHP_MSHUTDOWN_FUNCTION 1254 | */ 1255 | PHP_MSHUTDOWN_FUNCTION(memprof) 1256 | { 1257 | if (origOnChangeMemoryLimit) { 1258 | zend_ini_entry * entry; 1259 | 1260 | entry = zend_hash_str_find_ptr(EG(ini_directives), "memory_limit", sizeof("memory_limit")-1); 1261 | 1262 | if (entry != NULL) { 1263 | entry->on_modify = origOnChangeMemoryLimit; 1264 | } 1265 | } 1266 | 1267 | return SUCCESS; 1268 | } 1269 | /* }}} */ 1270 | 1271 | /* {{{ PHP_RINIT_FUNCTION 1272 | */ 1273 | PHP_RINIT_FUNCTION(memprof) 1274 | { 1275 | #if defined(ZTS) && defined(COMPILE_DL_FOO) 1276 | ZEND_TSRMLS_CACHE_UPDATE(); 1277 | #endif 1278 | 1279 | parse_trigger(&MEMPROF_G(profile_flags)); 1280 | 1281 | if (MEMPROF_G(profile_flags).enabled) { 1282 | disable_opcache(); 1283 | memprof_enable(&MEMPROF_G(profile_flags)); 1284 | } 1285 | 1286 | rinit_zend_error_cb = zend_error_cb; 1287 | zend_error_cb_overridden = 0; 1288 | 1289 | return SUCCESS; 1290 | } 1291 | /* }}} */ 1292 | 1293 | /* {{{ PHP_RSHUTDOWN_FUNCTION 1294 | */ 1295 | PHP_RSHUTDOWN_FUNCTION(memprof) 1296 | { 1297 | if (MEMPROF_G(profile_flags).enabled) { 1298 | memprof_disable(); 1299 | } 1300 | 1301 | zend_error_cb = rinit_zend_error_cb; 1302 | 1303 | return SUCCESS; 1304 | } 1305 | /* }}} */ 1306 | 1307 | /* {{{ PHP_MINFO_FUNCTION 1308 | */ 1309 | PHP_MINFO_FUNCTION(memprof) 1310 | { 1311 | php_info_print_table_start(); 1312 | php_info_print_table_header(2, "memprof support", "enabled"); 1313 | php_info_print_table_header(2, "memprof version", PHP_MEMPROF_VERSION); 1314 | php_info_print_table_header(2, "memprof native malloc support", HAVE_MALLOC_HOOKS ? "Yes" : "No"); 1315 | #if MEMPROF_DEBUG 1316 | php_info_print_table_header(2, "debug build", "Yes"); 1317 | #endif 1318 | php_info_print_table_end(); 1319 | 1320 | DISPLAY_INI_ENTRIES(); 1321 | } 1322 | /* }}} */ 1323 | 1324 | /* {{{ PHP_GINIT_FUNCTION 1325 | */ 1326 | PHP_GINIT_FUNCTION(memprof) 1327 | { 1328 | memprof_globals->output_dir = NULL; 1329 | memprof_globals->output_format = FORMAT_CALLGRIND; 1330 | } 1331 | /* }}} */ 1332 | 1333 | static void frame_inclusive_cost(frame * f, size_t * inclusive_size, size_t * inclusive_count) 1334 | { 1335 | size_t size = 0; 1336 | size_t count = 0; 1337 | alloc * alloc; 1338 | HashPosition pos; 1339 | zval * znext; 1340 | 1341 | LIST_FOREACH(alloc, &f->allocs, list) { 1342 | size += alloc->size; 1343 | count ++; 1344 | } 1345 | 1346 | zend_hash_internal_pointer_reset_ex(&f->next_cache, &pos); 1347 | while ((znext = zend_hash_get_current_data_ex(&f->next_cache, &pos)) != NULL) { 1348 | zend_string * str_key; 1349 | zend_ulong num_key; 1350 | size_t call_size; 1351 | size_t call_count; 1352 | frame * next = Z_PTR_P(znext); 1353 | 1354 | if (HASH_KEY_IS_STRING != zend_hash_get_current_key_ex(&f->next_cache, &str_key, &num_key, &pos)) { 1355 | continue; 1356 | } 1357 | 1358 | frame_inclusive_cost(next, &call_size, &call_count); 1359 | 1360 | size += call_size; 1361 | count += call_count; 1362 | 1363 | zend_hash_move_forward_ex(&f->next_cache, &pos); 1364 | } 1365 | 1366 | *inclusive_size = size; 1367 | *inclusive_count = count; 1368 | } 1369 | 1370 | static zend_bool dump_frame_array(zval * dest, frame * f) 1371 | { 1372 | HashPosition pos; 1373 | zval * znext; 1374 | zval * zframe = dest; 1375 | zval zcalled_functions; 1376 | alloc * alloc; 1377 | size_t alloc_size = 0; 1378 | size_t alloc_count = 0; 1379 | size_t inclusive_size; 1380 | size_t inclusive_count; 1381 | 1382 | array_init(zframe); 1383 | 1384 | LIST_FOREACH(alloc, &f->allocs, list) { 1385 | alloc_size += alloc->size; 1386 | alloc_count ++; 1387 | } 1388 | 1389 | add_assoc_long_ex(zframe, ZEND_STRL("memory_size"), alloc_size); 1390 | add_assoc_long_ex(zframe, ZEND_STRL("blocks_count"), alloc_count); 1391 | 1392 | frame_inclusive_cost(f, &inclusive_size, &inclusive_count); 1393 | add_assoc_long_ex(zframe, ZEND_STRL("memory_size_inclusive"), inclusive_size); 1394 | add_assoc_long_ex(zframe, ZEND_STRL("blocks_count_inclusive"), inclusive_count); 1395 | 1396 | add_assoc_long_ex(zframe, ZEND_STRL("calls"), f->calls); 1397 | 1398 | array_init(&zcalled_functions); 1399 | 1400 | zend_hash_internal_pointer_reset_ex(&f->next_cache, &pos); 1401 | while ((znext = zend_hash_get_current_data_ex(&f->next_cache, &pos)) != NULL) { 1402 | 1403 | zend_string * str_key; 1404 | zend_ulong num_key; 1405 | zval zcalled_function; 1406 | frame * next = Z_PTR_P(znext); 1407 | 1408 | if (HASH_KEY_IS_STRING != zend_hash_get_current_key_ex(&f->next_cache, &str_key, &num_key, &pos)) { 1409 | continue; 1410 | } 1411 | 1412 | dump_frame_array(&zcalled_function, next); 1413 | add_assoc_zval_ex(&zcalled_functions, ZSTR_VAL(str_key), ZSTR_LEN(str_key), &zcalled_function); 1414 | 1415 | zend_hash_move_forward_ex(&f->next_cache, &pos); 1416 | } 1417 | 1418 | add_assoc_zval_ex(zframe, ZEND_STRL("called_functions"), &zcalled_functions); 1419 | 1420 | return 1; 1421 | } 1422 | 1423 | static zend_bool dump_frame_callgrind(php_stream * stream, frame * f, char * fname, size_t * inclusive_size, size_t * inclusive_count) 1424 | { 1425 | size_t size = 0; 1426 | size_t count = 0; 1427 | size_t self_size = 0; 1428 | size_t self_count = 0; 1429 | alloc * alloc; 1430 | HashPosition pos; 1431 | zval * znext; 1432 | 1433 | zend_hash_internal_pointer_reset_ex(&f->next_cache, &pos); 1434 | while ((znext = zend_hash_get_current_data_ex(&f->next_cache, &pos)) != NULL) { 1435 | zend_string * str_key; 1436 | zend_ulong num_key; 1437 | size_t call_size; 1438 | size_t call_count; 1439 | frame * next = Z_PTR_P(znext); 1440 | 1441 | if (HASH_KEY_IS_STRING != zend_hash_get_current_key_ex(&f->next_cache, &str_key, &num_key, &pos)) { 1442 | continue; 1443 | } 1444 | 1445 | if (!dump_frame_callgrind(stream, next, ZSTR_VAL(str_key), &call_size, &call_count)) { 1446 | return 0; 1447 | } 1448 | 1449 | size += call_size; 1450 | count += call_count; 1451 | 1452 | zend_hash_move_forward_ex(&f->next_cache, &pos); 1453 | } 1454 | 1455 | if ( 1456 | !stream_printf(stream, "fl=%s\n", f->file_name) || 1457 | !stream_printf(stream, "fn=%s\n", fname) 1458 | ) { 1459 | return 0; 1460 | } 1461 | 1462 | LIST_FOREACH(alloc, &f->allocs, list) { 1463 | self_size += alloc->size; 1464 | self_count ++; 1465 | } 1466 | size += self_size; 1467 | count += self_count; 1468 | 1469 | if (!stream_printf(stream, "1 %zu %zu\n", self_size, self_count)) { 1470 | return 0; 1471 | } 1472 | 1473 | zend_hash_internal_pointer_reset_ex(&f->next_cache, &pos); 1474 | while ((znext = zend_hash_get_current_data_ex(&f->next_cache, &pos)) != NULL) { 1475 | zend_string * str_key; 1476 | zend_ulong num_key; 1477 | size_t call_size; 1478 | size_t call_count; 1479 | frame * next = Z_PTR_P(znext); 1480 | 1481 | if (HASH_KEY_IS_STRING != zend_hash_get_current_key_ex(&f->next_cache, &str_key, &num_key, &pos)) { 1482 | continue; 1483 | } 1484 | 1485 | frame_inclusive_cost(next, &call_size, &call_count); 1486 | 1487 | if ( 1488 | !stream_printf(stream, "cfl=%s\n", next->file_name) || 1489 | !stream_printf(stream, "cfn=%s\n", ZSTR_VAL(str_key)) || 1490 | !stream_printf(stream, "calls=%zu 1\n", next->calls) || 1491 | !stream_printf(stream, "1 %zu %zu\n", call_size, call_count) 1492 | ) { 1493 | return 0; 1494 | } 1495 | 1496 | zend_hash_move_forward_ex(&f->next_cache, &pos); 1497 | } 1498 | 1499 | if (!stream_printf(stream, "\n")) { 1500 | return 0; 1501 | } 1502 | 1503 | if (inclusive_size) { 1504 | *inclusive_size = size; 1505 | } 1506 | if (inclusive_count) { 1507 | *inclusive_count = count; 1508 | } 1509 | 1510 | return 1; 1511 | } 1512 | 1513 | static zend_bool dump_callgrind(php_stream * stream) { 1514 | size_t total_size; 1515 | size_t total_count; 1516 | 1517 | return ( 1518 | stream_printf(stream, "version: 1\n") && 1519 | stream_printf(stream, "cmd: unknown\n") && 1520 | stream_printf(stream, "positions: line\n") && 1521 | stream_printf(stream, "events: Memory_Size_(bytes) BlocksCount\n") && 1522 | stream_printf(stream, "\n") && 1523 | 1524 | dump_frame_callgrind(stream, &root_frame, "root", &total_size, &total_count) && 1525 | 1526 | stream_printf(stream, "total: %zu %zu\n", total_size, total_count) 1527 | ); 1528 | } 1529 | 1530 | static zend_bool dump_frames_pprof(php_stream * stream, HashTable * symbols, frame * f) 1531 | { 1532 | HashPosition pos; 1533 | frame * prev; 1534 | zval * znext; 1535 | size_t size = frame_alloc_size(f); 1536 | size_t stack_depth = frame_stack_depth(f); 1537 | 1538 | if (0 < size) { 1539 | stream_write_word(stream, size); 1540 | stream_write_word(stream, stack_depth); 1541 | 1542 | for (prev = f; prev != &root_frame; prev = prev->prev) { 1543 | zend_uintptr_t symaddr; 1544 | symaddr = (zend_uintptr_t) zend_hash_str_find_ptr(symbols, prev->fn_name, prev->fn_name_len); 1545 | if (symaddr == 0) { 1546 | /* shouldn't happen */ 1547 | zend_error(E_CORE_ERROR, "symbol address not found"); 1548 | return 0; 1549 | } 1550 | if (!stream_write_word(stream, symaddr)) { 1551 | return 0; 1552 | } 1553 | } 1554 | } 1555 | 1556 | zend_hash_internal_pointer_reset_ex(&f->next_cache, &pos); 1557 | while ((znext = zend_hash_get_current_data_ex(&f->next_cache, &pos)) != NULL) { 1558 | zend_string * str_key; 1559 | zend_ulong num_key; 1560 | frame * next = Z_PTR_P(znext); 1561 | 1562 | if (HASH_KEY_IS_STRING != zend_hash_get_current_key_ex(&f->next_cache, &str_key, &num_key, &pos)) { 1563 | continue; 1564 | } 1565 | 1566 | if (!dump_frames_pprof(stream, symbols, next)) { 1567 | return 0; 1568 | } 1569 | 1570 | zend_hash_move_forward_ex(&f->next_cache, &pos); 1571 | } 1572 | 1573 | return 1; 1574 | } 1575 | 1576 | static zend_bool dump_frames_pprof_symbols(php_stream * stream, HashTable * symbols, frame * f) 1577 | { 1578 | HashPosition pos; 1579 | zval * znext; 1580 | zend_uintptr_t symaddr; 1581 | 1582 | if (!zend_hash_str_exists(symbols, f->fn_name, f->fn_name_len)) { 1583 | /* addr only has to be unique */ 1584 | symaddr = (symbols->nNumOfElements+1)<<3; 1585 | zend_hash_str_add_ptr(symbols, f->fn_name, f->fn_name_len, (void*) symaddr); 1586 | if (!stream_printf(stream, "0x%0*x %s\n", sizeof(symaddr)*2, symaddr, f->fn_name)) { 1587 | return 0; 1588 | } 1589 | } 1590 | 1591 | zend_hash_internal_pointer_reset_ex(&f->next_cache, &pos); 1592 | while ((znext = zend_hash_get_current_data_ex(&f->next_cache, &pos)) != NULL) { 1593 | zend_string * str_key; 1594 | zend_ulong num_key; 1595 | frame * next = Z_PTR_P(znext); 1596 | 1597 | if (HASH_KEY_IS_STRING != zend_hash_get_current_key_ex(&f->next_cache, &str_key, &num_key, &pos)) { 1598 | continue; 1599 | } 1600 | 1601 | if (!dump_frames_pprof_symbols(stream, symbols, next)) { 1602 | return 0; 1603 | } 1604 | 1605 | zend_hash_move_forward_ex(&f->next_cache, &pos); 1606 | } 1607 | 1608 | return 1; 1609 | } 1610 | 1611 | static zend_bool dump_pprof_symbols_section(php_stream * stream, HashTable * symbols) { 1612 | return ( 1613 | stream_printf(stream, "--- symbol\n") && 1614 | stream_printf(stream, "binary=%s\n", stream->orig_path) && 1615 | 1616 | dump_frames_pprof_symbols(stream, symbols, &root_frame) && 1617 | 1618 | stream_printf(stream, "---\n") 1619 | ); 1620 | } 1621 | 1622 | static zend_bool dump_pprof_profile_section(php_stream * stream, HashTable * symbols) { 1623 | return ( 1624 | stream_printf(stream, "--- profile\n") && 1625 | 1626 | /* header count */ 1627 | stream_write_word(stream, 0) && 1628 | 1629 | /* header words after this one */ 1630 | stream_write_word(stream, 3) && 1631 | 1632 | /* format version */ 1633 | stream_write_word(stream, 0) && 1634 | 1635 | /* sampling period */ 1636 | stream_write_word(stream, 0) && 1637 | 1638 | /* unused padding */ 1639 | stream_write_word(stream, 0) && 1640 | 1641 | dump_frames_pprof(stream, symbols, &root_frame) 1642 | ); 1643 | } 1644 | 1645 | static zend_bool dump_pprof(php_stream * stream) { 1646 | HashTable symbols; 1647 | 1648 | zend_hash_init(&symbols, 8, NULL, NULL, 0); 1649 | 1650 | zend_bool success = ( 1651 | dump_pprof_symbols_section(stream, &symbols) && 1652 | dump_pprof_profile_section(stream, &symbols) 1653 | ); 1654 | 1655 | zend_hash_destroy(&symbols); 1656 | 1657 | return success; 1658 | } 1659 | 1660 | /* {{{ proto void memprof_dump_array(void) 1661 | Returns current memory usage as an array */ 1662 | PHP_FUNCTION(memprof_dump_array) 1663 | { 1664 | zend_bool success; 1665 | 1666 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE) { 1667 | return; 1668 | } 1669 | 1670 | if (!MEMPROF_G(profile_flags).enabled) { 1671 | zend_throw_exception(EG(exception_class), "memprof_dump_array(): memprof is not enabled", 0); 1672 | return; 1673 | } 1674 | 1675 | WITHOUT_MALLOC_TRACKING { 1676 | 1677 | success = dump_frame_array(return_value, &root_frame); 1678 | 1679 | } END_WITHOUT_MALLOC_TRACKING; 1680 | 1681 | memprof_dumped = 1; 1682 | 1683 | if (!success) { 1684 | zend_throw_exception(EG(exception_class), "memprof_dump_array(): dump failed, please check file permissions or disk capacity", 0); 1685 | return; 1686 | } 1687 | } 1688 | /* }}} */ 1689 | 1690 | /* {{{ proto void memprof_dump_callgrind(resource handle) 1691 | Dumps current memory usage in callgrind format to stream $handle */ 1692 | PHP_FUNCTION(memprof_dump_callgrind) 1693 | { 1694 | zval *arg1; 1695 | php_stream *stream; 1696 | zend_bool success; 1697 | 1698 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &arg1) == FAILURE) { 1699 | return; 1700 | } 1701 | 1702 | if (!MEMPROF_G(profile_flags).enabled) { 1703 | zend_throw_exception(EG(exception_class), "memprof_dump_callgrind(): memprof is not enabled", 0); 1704 | return; 1705 | } 1706 | 1707 | php_stream_from_zval(stream, arg1); 1708 | 1709 | WITHOUT_MALLOC_TRACKING { 1710 | success = dump_callgrind(stream); 1711 | } END_WITHOUT_MALLOC_TRACKING; 1712 | 1713 | memprof_dumped = 1; 1714 | 1715 | if (!success) { 1716 | zend_throw_exception(EG(exception_class), "memprof_dump_callgrind(): dump failed, please check file permissions or disk capacity", 0); 1717 | return; 1718 | } 1719 | } 1720 | /* }}} */ 1721 | 1722 | /* {{{ proto void memprof_dump_pprof(resource handle) 1723 | Dumps current memory usage in pprof heapprofile format to stream $handle */ 1724 | PHP_FUNCTION(memprof_dump_pprof) 1725 | { 1726 | zval *arg1; 1727 | php_stream *stream; 1728 | zend_bool success; 1729 | 1730 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &arg1) == FAILURE) { 1731 | return; 1732 | } 1733 | 1734 | if (!MEMPROF_G(profile_flags).enabled) { 1735 | zend_throw_exception(EG(exception_class), "memprof_dump_pprof(): memprof is not enabled", 0); 1736 | return; 1737 | } 1738 | 1739 | php_stream_from_zval(stream, arg1); 1740 | 1741 | WITHOUT_MALLOC_TRACKING { 1742 | success = dump_pprof(stream); 1743 | } END_WITHOUT_MALLOC_TRACKING; 1744 | 1745 | memprof_dumped = 1; 1746 | 1747 | if (!success) { 1748 | zend_throw_exception(EG(exception_class), "memprof_dump_pprof(): dump failed, please check file permissions or disk capacity", 0); 1749 | return; 1750 | } 1751 | } 1752 | /* }}} */ 1753 | 1754 | /* {{{ proto void memprof_memory_get_usage(bool real) 1755 | Returns the current memory usage */ 1756 | PHP_FUNCTION(memprof_memory_get_usage) 1757 | { 1758 | zend_bool real = 0; 1759 | 1760 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &real) == FAILURE) { 1761 | return; 1762 | } 1763 | 1764 | if (MEMPROF_G(profile_flags).enabled && orig_zheap) { 1765 | zend_mm_set_heap(orig_zheap); 1766 | RETVAL_LONG(zend_memory_usage(real)); 1767 | zend_mm_set_heap(zheap); 1768 | } else { 1769 | RETVAL_LONG(zend_memory_usage(real)); 1770 | } 1771 | } 1772 | /* }}} */ 1773 | 1774 | /* {{{ proto void memprof_memory_get_peak_usage(bool real) 1775 | Returns the peak memory usage */ 1776 | PHP_FUNCTION(memprof_memory_get_peak_usage) 1777 | { 1778 | zend_bool real = 0; 1779 | 1780 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &real) == FAILURE) { 1781 | return; 1782 | } 1783 | 1784 | if (MEMPROF_G(profile_flags).enabled && orig_zheap) { 1785 | zend_mm_set_heap(orig_zheap); 1786 | RETVAL_LONG(zend_memory_peak_usage(real)); 1787 | zend_mm_set_heap(zheap); 1788 | } else { 1789 | RETVAL_LONG(zend_memory_peak_usage(real)); 1790 | } 1791 | } 1792 | /* }}} */ 1793 | 1794 | /* {{{ proto bool memprof_enable() 1795 | Enables memprof */ 1796 | PHP_FUNCTION(memprof_enable) 1797 | { 1798 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE) { 1799 | return; 1800 | } 1801 | 1802 | if (MEMPROF_G(profile_flags).enabled) { 1803 | zend_throw_exception(EG(exception_class), "memprof_enable(): memprof is already enabled", 0); 1804 | return; 1805 | } 1806 | 1807 | zend_error(E_WARNING, "Calling memprof_enable() manually may not work as expected because of PHP optimizations. Prefer using MEMPROF_PROFILE=1 as environment variable, GET, or POST"); 1808 | 1809 | MEMPROF_G(profile_flags).enabled = 1; 1810 | memprof_enable(&MEMPROF_G(profile_flags)); 1811 | 1812 | RETURN_TRUE; 1813 | } 1814 | /* }}} */ 1815 | 1816 | /* {{{ proto bool memprof_disable() 1817 | Disables memprof */ 1818 | PHP_FUNCTION(memprof_disable) 1819 | { 1820 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE) { 1821 | return; 1822 | } 1823 | 1824 | if (!MEMPROF_G(profile_flags).enabled) { 1825 | zend_throw_exception(EG(exception_class), "memprof_disable(): memprof is not enabled", 0); 1826 | return; 1827 | } 1828 | 1829 | memprof_disable(); 1830 | 1831 | RETURN_TRUE; 1832 | } 1833 | /* }}} */ 1834 | 1835 | /* {{{ proto bool memprof_enabled() 1836 | Returns whether memprof is enabled */ 1837 | PHP_FUNCTION(memprof_enabled) 1838 | { 1839 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE) { 1840 | return; 1841 | } 1842 | 1843 | RETURN_BOOL(MEMPROF_G(profile_flags).enabled); 1844 | } 1845 | /* }}} */ 1846 | 1847 | /* {{{ proto array memprof_enabled_flags() 1848 | Returns whether memprof is enabled */ 1849 | PHP_FUNCTION(memprof_enabled_flags) 1850 | { 1851 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE) { 1852 | return; 1853 | } 1854 | 1855 | array_init(return_value); 1856 | add_assoc_bool(return_value, "enabled", MEMPROF_G(profile_flags).enabled); 1857 | add_assoc_bool(return_value, "native", MEMPROF_G(profile_flags).native); 1858 | add_assoc_bool(return_value, "dump_on_limit", MEMPROF_G(profile_flags).dump_on_limit); 1859 | } 1860 | /* }}} */ 1861 | 1862 | /* {{{ proto string memprof_version() 1863 | Returns memprof version as a string */ 1864 | PHP_FUNCTION(memprof_version) 1865 | { 1866 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "") == FAILURE) { 1867 | return; 1868 | } 1869 | 1870 | RETURN_STRING(PHP_MEMPROF_VERSION); 1871 | } 1872 | /* }}} */ 1873 | --------------------------------------------------------------------------------