├── .github ├── test-curl-compilation │ ├── Dockerfile │ ├── analyse-curl.php │ ├── expected-curlext-no-libcurl-no.txt │ ├── expected-curlext-no-libcurl-yes.txt │ ├── expected-curlext-yes-libcurl-no.txt │ └── expected-curlext-yes-libcurl-yes.txt └── workflows │ ├── ci-build.yaml │ ├── get-build-dir.bat │ ├── get-previous-semver-version.sh │ ├── release-on-milestone-closed-triggering-release-event.yml │ └── windows-php.ini ├── .gitignore ├── .keep-a-changelog.ini ├── CMakeLists.txt ├── CREDITS ├── EXPERIMENTAL ├── LICENSE ├── README.md ├── benchmark.sh ├── config.m4 ├── config.w32 ├── example.php ├── full-clean.sh ├── package.xml ├── scout_curl_wrapper.c ├── scout_execute_ex.c ├── scout_execute_ex.h ├── scout_extern.h ├── scout_file_wrapper.c ├── scout_functions.c ├── scout_internal_handlers.c ├── scout_internal_handlers.h ├── scout_observer.c ├── scout_pdo_wrapper.c ├── scout_recording.c ├── scout_recording.h ├── scout_utils.c ├── tests ├── 001-check-ext-loaded.phpt ├── 002-file_get_contents.phpt ├── 003-scoutapm_get_calls-clears-calls-list.phpt ├── 004-namespaced-fgc-is-not-logged.phpt ├── 005-requiring-external-files-handled.phpt ├── 006-anonymous-classes-handled.phpt ├── 007-evaled-code-handled.phpt ├── 008-class-with-no-constructor-call-handled.phpt ├── 009-curl_exec.phpt ├── 010-fwrite-fread-fopen.phpt ├── 010-fwrite-fread-tmpfile.phpt ├── 011-pdo-exec.phpt ├── 011-pdo-query.phpt ├── 011-pdostatement-execute-pdo-prepare.phpt ├── 012-file_put_contents.phpt ├── 013-fix-memory-leak-when-scoutapm_get_calls-not-called.phpt ├── 014-predis-support.phpt ├── 015-phpredis-support.phpt ├── 016-memcached-support.phpt ├── 017-elastic-7-support.phpt ├── 018-do-not-instrument-by-default.phpt ├── 019-url-method-capture-fgc.phpt ├── 020-url-method-capture-curl-post.phpt ├── 021-url-method-capture-curl-customreq.phpt ├── 022-elastic-8-support.phpt ├── bug-47.phpt ├── bug-49.phpt ├── bug-55.phpt ├── bug-71.phpt ├── bug-88.phpt ├── bug-93.phpt └── external.inc ├── winbuild.bat ├── zend_scoutapm.c └── zend_scoutapm.h /.github/test-curl-compilation/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | RUN apt-get update && apt-get -y upgrade \ 3 | && apt-get -y install software-properties-common \ 4 | && add-apt-repository ppa:ondrej/php \ 5 | && apt-get update \ 6 | && DEBIAN_FRONTEND=noninteractive apt-get -y install php8.0 php8.0-xml php8.0-dev 7 | 8 | ARG WITH_CURL_EXT=no 9 | RUN if [ "$WITH_CURL_EXT" = "yes" ]; then apt-get -y install php8.0-curl; fi 10 | 11 | ARG WITH_LIBCURL=no 12 | RUN if [ "$WITH_LIBCURL" = "yes" ]; then apt-get -y install libcurl4-openssl-dev; fi 13 | 14 | ADD *.c *.h config.m4 /scoutapm/ 15 | 16 | RUN cd /scoutapm/ \ 17 | && ls -l \ 18 | && phpize \ 19 | && ./configure --enable-scoutapm \ 20 | && cat config.h \ 21 | && make \ 22 | && make install \ 23 | && echo "zend_extension=scoutapm" > /etc/php/8.0/mods-available/scoutapm.ini \ 24 | && phpenmod scoutapm 25 | 26 | ADD .github/test-curl-compilation/analyse-curl.php /scoutapm/analyse-curl.php 27 | 28 | ENTRYPOINT ["/usr/bin/php"] 29 | CMD ["/scoutapm/analyse-curl.php"] 30 | -------------------------------------------------------------------------------- /.github/test-curl-compilation/analyse-curl.php: -------------------------------------------------------------------------------- 1 | ', str_replace('scoutapm curl ', '', $s)); 8 | }, 9 | array_values(array_filter( 10 | explode("\n", ob_get_contents()), 11 | static function ($s) { 12 | return str_contains($s, 'scoutapm curl'); 13 | } 14 | )) 15 | ), 1, 0); 16 | ob_end_clean(); 17 | 18 | echo sprintf("HAVE_CURL: %s\n", $phpinfo['HAVE_CURL']); 19 | echo sprintf("HAVE_SCOUT_CURL: %s\n", $phpinfo['HAVE_SCOUT_CURL']); 20 | echo sprintf("curl instrumented: %s\n", $phpinfo['enabled']); 21 | echo sprintf("curl_exec function exists: %s\n", function_exists('curl_exec') ? 'Yes' : 'No'); 22 | echo sprintf("curl_exec is in instrumented list: %s\n", in_array('curl_exec', scoutapm_list_instrumented_functions()) ? 'Yes' : 'No'); 23 | 24 | scoutapm_enable_instrumentation(true); 25 | 26 | if (function_exists('curl_exec')) { 27 | $ch = curl_init(); 28 | curl_setopt($ch, CURLOPT_URL, "file://" . __FILE__); 29 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 30 | curl_exec($ch); 31 | } 32 | 33 | $calls = scoutapm_get_calls(); 34 | 35 | if (! array_key_exists(0, $calls)) { 36 | echo "curl_exec call recorded: No\n"; 37 | exit; 38 | } 39 | 40 | echo sprintf("%s call recorded: Yes\n", $calls[0]['function']); 41 | -------------------------------------------------------------------------------- /.github/test-curl-compilation/expected-curlext-no-libcurl-no.txt: -------------------------------------------------------------------------------- 1 | HAVE_CURL: No 2 | HAVE_SCOUT_CURL: No 3 | curl instrumented: No 4 | curl_exec function exists: No 5 | curl_exec is in instrumented list: No 6 | curl_exec call recorded: No 7 | -------------------------------------------------------------------------------- /.github/test-curl-compilation/expected-curlext-no-libcurl-yes.txt: -------------------------------------------------------------------------------- 1 | HAVE_CURL: No 2 | HAVE_SCOUT_CURL: Yes 3 | curl instrumented: No 4 | curl_exec function exists: No 5 | curl_exec is in instrumented list: No 6 | curl_exec call recorded: No 7 | -------------------------------------------------------------------------------- /.github/test-curl-compilation/expected-curlext-yes-libcurl-no.txt: -------------------------------------------------------------------------------- 1 | HAVE_CURL: No 2 | HAVE_SCOUT_CURL: No 3 | curl instrumented: No 4 | curl_exec function exists: Yes 5 | curl_exec is in instrumented list: No 6 | curl_exec call recorded: No 7 | -------------------------------------------------------------------------------- /.github/test-curl-compilation/expected-curlext-yes-libcurl-yes.txt: -------------------------------------------------------------------------------- 1 | HAVE_CURL: No 2 | HAVE_SCOUT_CURL: Yes 3 | curl instrumented: Yes 4 | curl_exec function exists: Yes 5 | curl_exec is in instrumented list: Yes 6 | curl_exec call recorded: Yes 7 | -------------------------------------------------------------------------------- /.github/workflows/ci-build.yaml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | curl-compilation-test: 9 | name: "Curl Compile" 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | with_curl_ext: ["yes", "no"] 14 | with_libcurl: ["yes", "no"] 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v3 18 | - name: Build the tester tool 19 | run: | 20 | docker build \ 21 | --build-arg WITH_CURL_EXT=${{ matrix.with_curl_ext }} \ 22 | --build-arg WITH_LIBCURL=${{ matrix.with_libcurl }} \ 23 | --file .github/test-curl-compilation/Dockerfile \ 24 | --tag scoutapm_ext_test \ 25 | . 26 | - name: Run the tool 27 | run: | 28 | docker run scoutapm_ext_test > results.txt 29 | cat results.txt 30 | - name: Check the output 31 | run: | 32 | diff .github/test-curl-compilation/expected-curlext-${{ matrix.with_curl_ext }}-libcurl-${{ matrix.with_libcurl }}.txt results.txt 33 | windows-test: 34 | name: "Win PHPT" 35 | defaults: 36 | run: 37 | shell: cmd 38 | strategy: 39 | fail-fast: false 40 | matrix: 41 | os: [ windows-2019, windows-2022 ] 42 | php: [ "8.3", "8.2", "8.1", "8.0", "7.4", "7.3", "7.2", "7.1" ] 43 | arch: [ x64, x86 ] 44 | ts: [ ts, nts ] 45 | exclude: 46 | - { os: windows-2019, php: "8.3" } 47 | - { os: windows-2019, php: "8.2" } 48 | - { os: windows-2019, php: "8.1" } 49 | - { os: windows-2019, php: "8.0" } 50 | - { os: windows-2019, php: "7.4" } 51 | - { os: windows-2019, php: "7.3" } 52 | - { os: windows-2022, php: "7.2" } 53 | - { os: windows-2022, php: "7.1" } 54 | runs-on: ${{matrix.os}} 55 | steps: 56 | - uses: actions/checkout@v3 57 | - name: Setup PHP SDK 58 | id: setup-php 59 | uses: php/setup-php-sdk@v0.8 60 | with: 61 | version: ${{matrix.php}} 62 | arch: ${{matrix.arch}} 63 | ts: ${{matrix.ts}} 64 | deps: libcurl, sqlite3 65 | - name: Enable Developer Command Prompt 66 | uses: ilammy/msvc-dev-cmd@v1 67 | with: 68 | arch: ${{matrix.arch}} 69 | toolset: ${{steps.setup-php.outputs.toolset}} 70 | - name: Enable required extensions 71 | run: | 72 | cp .github/workflows/windows-php.ini ${{steps.setup-php.outputs.prefix}}/php.ini 73 | echo extension_dir="${{steps.setup-php.outputs.prefix}}\ext" >> ${{steps.setup-php.outputs.prefix}}/php.ini 74 | dir ${{steps.setup-php.outputs.prefix}}\ext 75 | - name: phpize 76 | run: phpize 77 | - name: configure 78 | run: configure --enable-scoutapm --with-prefix=${{steps.setup-php.outputs.prefix}} 79 | - name: Zend extension 80 | run: sed -i 's/-d extension=/-d zend_extension=/g' Makefile 81 | - name: make 82 | run: nmake 83 | - name: test 84 | run: nmake test 85 | - name: Copy DLL to a known location 86 | if: ${{ github.event_name == 'push' }} 87 | run: | 88 | cp .github/workflows/get-build-dir.bat . 89 | for /F "usebackq tokens=*" %%i in (`get-build-dir.bat`) do set DLL_FILE=%%i\php_scoutapm.dll 90 | echo %DLL_FILE% 91 | cp %DLL_FILE% . 92 | - name: Upload DLL as artifact 93 | if: ${{ github.event_name == 'push' }} 94 | uses: actions/upload-artifact@v3 95 | with: 96 | name: DLL only ${{github.sha}}-${{matrix.php}}-${{matrix.ts}}-${{matrix.arch}} 97 | path: php_scoutapm.dll 98 | retention-days: 2 99 | 100 | phpt-test: 101 | name: "PHPT Tests" 102 | services: 103 | elasticsearch: 104 | image: elasticsearch:8.1.2 105 | env: 106 | discovery.type: single-node 107 | # Disable TLS which is on by default now: https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html 108 | xpack.security.enabled: false 109 | xpack.security.enrollment.enabled: false 110 | xpack.security.http.ssl.enabled: false 111 | xpack.security.transport.ssl.enabled: false 112 | ports: 113 | - 9200:9200 114 | memcached: 115 | image: memcached 116 | ports: 117 | - 11211:11211 118 | redis: 119 | image: redis 120 | options: >- 121 | --health-cmd "redis-cli ping" 122 | --health-interval 10s 123 | --health-timeout 5s 124 | --health-retries 5 125 | ports: 126 | - 6379:6379 127 | runs-on: ${{ matrix.os }} 128 | strategy: 129 | fail-fast: false 130 | matrix: 131 | os: 132 | - ubuntu-20.04 133 | - ubuntu-22.04 134 | php-version: 135 | - 7.1.9 136 | - 7.1.33 137 | - 7.2.0 138 | - 7.2.34 139 | - 7.3.0 140 | - 7.3.33 141 | - 7.4.0 142 | - 7.4.33 143 | - 8.0.0 144 | - 8.0.30 145 | - 8.1.0 146 | - 8.1.24 147 | - 8.2.0 148 | - 8.2.11 149 | - 8.3.0 150 | zts-mode: 151 | - zts 152 | - nts 153 | php-options: 154 | - "--with-curl" 155 | - "" 156 | exclude: 157 | - { os: ubuntu-22.04, php-version: 7.1.9 } 158 | - { os: ubuntu-22.04, php-version: 7.1.33 } 159 | - { os: ubuntu-22.04, php-version: 7.2.0 } 160 | - { os: ubuntu-22.04, php-version: 7.2.34 } 161 | - { os: ubuntu-22.04, php-version: 7.3.0 } 162 | - { os: ubuntu-22.04, php-version: 7.3.33 } 163 | - { os: ubuntu-22.04, php-version: 7.4.0 } 164 | - { os: ubuntu-22.04, php-version: 7.4.33 } 165 | - { os: ubuntu-22.04, php-version: 8.0.0 } 166 | - { os: ubuntu-22.04, php-version: 8.0.30 } 167 | steps: 168 | - name: "Purge built-in PHP version" 169 | run: | 170 | echo "libmemcached11 php* hhvm libhashkit2" | xargs -n 1 sudo apt-get purge --assume-yes || true 171 | sudo apt-add-repository --remove ppa:ondrej/php -y 172 | sudo apt-get update 173 | - uses: actions/checkout@v3 174 | 175 | - name: "Set ZTS mode, PHP 8+" 176 | if: ${{ matrix.zts-mode == 'zts' && startsWith(matrix.php-version, '8.') }} 177 | run: echo "zts_flag=--enable-zts" >> $GITHUB_ENV 178 | - name: "Set ZTS mode, PHP 7" 179 | if: ${{ matrix.zts-mode == 'zts' && startsWith(matrix.php-version, '7.') }} 180 | run: echo "zts_flag=--enable-maintainer-zts" >> $GITHUB_ENV 181 | - name: "Set NTS mode" 182 | if: ${{ matrix.zts-mode == 'nts' }} 183 | run: echo "zts_flag=" >> $GITHUB_ENV 184 | 185 | - name: "Set php-src download URL" 186 | run: echo "php_src_download_url=https://www.php.net/distributions/php-${{ matrix.php-version }}.tar.gz" >> $GITHUB_ENV 187 | 188 | - name: "Install PHP ${{ matrix.php-version }} (${{ env.zts_flag}} ${{ matrix.php-options }})" 189 | run: | 190 | sudo DEBIAN_FRONTEND=noninteractive apt-get update 191 | sudo DEBIAN_FRONTEND=noninteractive apt-get -yq install software-properties-common build-essential autoconf libxml2-dev libsqlite3-dev libcurl4-openssl-dev re2c 192 | mkdir -p /tmp/php 193 | cd /tmp/php 194 | echo "Downloading release from ${{ env.php_src_download_url }} ..." 195 | wget -O php.tgz ${{ env.php_src_download_url }} 196 | tar zxf php.tgz 197 | rm php.tgz 198 | ls -l 199 | cd * 200 | ls -l 201 | ./buildconf --force 202 | ./configure --with-pear --enable-debug --with-openssl ${{ env.zts_flag }} ${{ matrix.php-options }} 203 | make -j$(nproc) 204 | sudo make install 205 | cd $GITHUB_WORKSPACE 206 | - name: "Update PEAR/PECL channels" 207 | run: | 208 | sudo pear update-channels 209 | sudo pecl update-channels 210 | - name: "Install Redis extension (PHP 7.1)" 211 | if: ${{ startsWith(matrix.php-version, '7.1.') }} 212 | run: | 213 | echo '' | sudo pecl install redis-5.3.7 214 | - name: "Install Redis extension (PHP 7.2+)" 215 | if: ${{ startsWith(matrix.php-version, '7.1.') != true }} 216 | run: | 217 | echo '' | sudo pecl install redis 218 | - name: "Install Memcached extension" 219 | run: | 220 | sudo apt-get install libmemcached-dev 221 | echo '' | sudo pecl install memcached 222 | - name: "Base PHP configuration dump (information)" 223 | run: php -i 224 | - name: "Build scoutapm extension" 225 | run: | 226 | phpize 227 | ./configure --enable-scoutapm --enable-scoutapm-dev 228 | make 229 | - name: "Run the tests" 230 | run: | 231 | php run-tests.php -p `which php` -d zend_extension=`pwd`/modules/scoutapm.so -d extension=memcached.so -d extension=redis.so -g "FAIL,XFAIL,BORK,WARN,LEAK,SKIP" --offline --show-diff --set-timeout 120 -q 232 | - name: "Run the benchmark" 233 | run: | 234 | sudo apt update && sudo apt install time 235 | ./benchmark.sh 236 | ./benchmark.sh -w 237 | 238 | check-pecl-valid: 239 | name: "Check PECL Package validity" 240 | runs-on: ubuntu-latest 241 | strategy: 242 | matrix: 243 | php-version: 244 | - "8.2" 245 | steps: 246 | - uses: actions/checkout@v3 247 | - name: Setup PHP with PECL extension 248 | uses: shivammathur/setup-php@v2 249 | with: 250 | coverage: "none" 251 | php-version: "${{ matrix.php-version }}" 252 | tools: pecl 253 | - name: "Validate PECL Package" 254 | run: pecl package-validate 255 | - name: "Verify PECL Package Build" 256 | run: pecl package 257 | -------------------------------------------------------------------------------- /.github/workflows/get-build-dir.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | grep "BUILD_DIR=" Makefile | sed 's/BUILD_DIR=//g' | awk '{$1=$1};1' 3 | -------------------------------------------------------------------------------- /.github/workflows/get-previous-semver-version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Note, this doesn't work if the tags skip ANY versions, it expects all versions to be "sequential" and semver 4 | # Also we only work with M.N.P (e.g. 1.2.3) 5 | 6 | set -xeuo pipefail 7 | 8 | VERSION="$1" 9 | 10 | REGEX="([0-9]+)\.([0-9]+)\.([0-9]+)" 11 | 12 | MAJOR=`echo $VERSION | sed -r -e "s/$REGEX/\1/"` 13 | MINOR=`echo $VERSION | sed -r -e "s/$REGEX/\2/"` 14 | PATCH=`echo $VERSION | sed -r -e "s/$REGEX/\3/"` 15 | 16 | if [ "$PATCH" == "0" ]; then 17 | if [ "$MINOR" == "0" ]; then 18 | if [ "$MAJOR" == "0" ]; then 19 | echo "All versions for this release were zero" >&2 20 | exit 1 21 | else 22 | SERIES_GREP="$((MAJOR-1))." 23 | fi 24 | else 25 | SERIES_GREP="$MAJOR.$((MINOR-1))." 26 | fi 27 | else 28 | SERIES_GREP="$MAJOR.$MINOR.$((PATCH-1))" 29 | fi 30 | 31 | set +e # allow failure here, as we check for empty string 32 | NEWEST_TAG_IN_SERIES=`git tag -l --sort=-v:refname | grep "$SERIES_GREP" | head -n 1` 33 | set -e 34 | 35 | if [ "$NEWEST_TAG_IN_SERIES" == "" ]; then 36 | echo "Could not find a previous release before $VERSION" >&2 37 | exit 1 38 | fi 39 | 40 | echo $NEWEST_TAG_IN_SERIES 41 | -------------------------------------------------------------------------------- /.github/workflows/release-on-milestone-closed-triggering-release-event.yml: -------------------------------------------------------------------------------- 1 | # Alternate workflow example. 2 | # This one is identical to the one in release-on-milestone.yml, with one change: 3 | # the Release step uses the ORGANIZATION_ADMIN_TOKEN instead, to allow it to 4 | # trigger a release workflow event. This is useful if you have other actions 5 | # that intercept that event. 6 | 7 | name: "Automatic Releases" 8 | 9 | on: 10 | milestone: 11 | types: 12 | - "closed" 13 | 14 | jobs: 15 | release_parameters: 16 | runs-on: ubuntu-latest 17 | outputs: 18 | version_to_tag: ${{ steps.version_to_tag_step.outputs.version_to_tag }} 19 | previous_semver_version: ${{ steps.previous_semver_version_step.outputs.previous_semver_version }} 20 | ref_for_version_to_tag: ${{ steps.ref_for_version_to_tag_step.outputs.ref_for_version_to_tag }} 21 | steps: 22 | - uses: actions/checkout@v3 23 | with: 24 | fetch-depth: 0 25 | - name: Version we are making 26 | id: version_to_tag_step 27 | run: | 28 | echo "milestone title = ${{ github.event.milestone.title }}" 29 | echo "version_to_tag=${{ github.event.milestone.title }}" >> $GITHUB_OUTPUT 30 | - name: Previous semver version 31 | id: previous_semver_version_step 32 | run: | 33 | PREVIOUS_SEMVER_VERSION=$(./.github/workflows/get-previous-semver-version.sh ${{ steps.version_to_tag_step.outputs.version_to_tag }}) 34 | echo "previous semver version = $PREVIOUS_SEMVER_VERSION" 35 | echo "previous_semver_version=$PREVIOUS_SEMVER_VERSION" >> $GITHUB_OUTPUT 36 | - name: Get the git ref 37 | id: ref_for_version_to_tag_step 38 | run: | 39 | BRANCH_EXTRACT_REGEX="([0-9]+\.[0-9]+)\.[0-9]+" 40 | EXPECTED_BRANCH_NAME="$(echo "${{ steps.version_to_tag_step.outputs.version_to_tag }}" | sed -r -e "s/$BRANCH_EXTRACT_REGEX/\1/").x" 41 | echo "expected branch name = $EXPECTED_BRANCH_NAME" 42 | RELEASE_GIT_SHA=$(git rev-parse origin/$EXPECTED_BRANCH_NAME) 43 | echo "ref for version to be tagged = $RELEASE_GIT_SHA" 44 | echo "ref_for_version_to_tag=$RELEASE_GIT_SHA" >> $GITHUB_OUTPUT 45 | 46 | windows-release-build: 47 | needs: [ "release_parameters" ] 48 | strategy: 49 | fail-fast: false 50 | matrix: 51 | php: [ "8.3", "8.2", "8.1", "8.0", "7.4", "7.3", "7.2", "7.1" ] 52 | arch: [ x64, x86 ] 53 | ts: [ ts, nts ] 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v3 57 | with: 58 | ref: ${{ needs.release_parameters.outputs.ref_for_version_to_tag }} 59 | - uses: dawidd6/action-download-artifact@v2 60 | with: 61 | workflow: ci-build.yaml 62 | workflow_conclusion: success 63 | commit: ${{ needs.release_parameters.outputs.ref_for_version_to_tag }} 64 | name: DLL only ${{ needs.release_parameters.outputs.ref_for_version_to_tag }}-${{matrix.php}}-${{matrix.ts}}-${{matrix.arch}} 65 | - name: Get the release version 66 | id: win_get_release_version 67 | run: | 68 | HEADER_RELEASE="$(cat zend_scoutapm.h | grep "PHP_SCOUTAPM_VERSION" | awk '{print $3}' | tr -d '"')" 69 | echo "version=$HEADER_RELEASE" >> $GITHUB_OUTPUT 70 | - name: Prepare zip 71 | run: zip php_scoutapm-${{steps.win_get_release_version.outputs.version}}-${{matrix.php}}-${{matrix.ts}}-${{matrix.arch}}.zip php_scoutapm.dll LICENSE README.md CREDITS 72 | - name: Add zipped DLL as artifact for ${{ needs.release_parameters.outputs.version_to_tag }} 73 | uses: actions/upload-artifact@v3 74 | with: 75 | name: DLL ${{ needs.release_parameters.outputs.version_to_tag }} 76 | path: php_scoutapm-${{steps.win_get_release_version.outputs.version}}-${{matrix.php}}-${{matrix.ts}}-${{matrix.arch}}.zip 77 | 78 | pre-verify-release: 79 | name: "Pre-verify release" 80 | needs: [ "release_parameters", "windows-release-build" ] 81 | runs-on: ubuntu-latest 82 | steps: 83 | - uses: actions/checkout@v3 84 | with: 85 | ref: ${{ needs.release_parameters.outputs.ref_for_version_to_tag }} 86 | - name: "Fetch previous release tag from Github API" 87 | uses: octokit/request-action@v2.x 88 | id: get_previous_release_step 89 | with: 90 | route: GET /repos/scoutapp/scout-apm-php-ext/releases/tags/${{ needs.release_parameters.outputs.previous_semver_version }} 91 | env: 92 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 93 | - name: "Verify that PHP_SCOUTAPM_VERSION has been changed" 94 | run: | 95 | PREVIOUS_RELEASE="${{ fromJson(steps.get_previous_release_step.outputs.data).tag_name }}" 96 | HEADER_RELEASE="$(cat zend_scoutapm.h | grep "PHP_SCOUTAPM_VERSION" | awk '{print $3}' | tr -d '"')" 97 | echo "Previous release: $PREVIOUS_RELEASE" 98 | echo "PHP_SCOUTAPM_VERSION in header: $HEADER_RELEASE" 99 | if [[ "$PREVIOUS_RELEASE" = "$HEADER_RELEASE" ]] 100 | then 101 | echo "PHP_SCOUTAPM_VERSION in zend_scoutapm.h has NOT been changed, cannot release" 102 | exit 1 103 | else 104 | echo "Version in zend_scoutapm.h HAS been changed." 105 | exit 0 106 | fi 107 | - name: "Extract latest version from package.xml" 108 | uses: QwerMike/xpath-action@v1 109 | id: get_package_xml_version 110 | with: 111 | filename: 'package.xml' 112 | expression: "//*[local-name()='package']/*[local-name()='version']/*[local-name()='release']/text()" 113 | - name: "Check package.xml version release has been changed" 114 | run: | 115 | PREVIOUS_RELEASE="${{ fromJson(steps.get_previous_release_step.outputs.data).tag_name }}" 116 | XML_RELEASE="${{ steps.get_package_xml_version.outputs.result }}" 117 | echo "Previous release: $PREVIOUS_RELEASE" 118 | echo "package.xml version: $XML_RELEASE" 119 | if [[ "$PREVIOUS_RELEASE" = "$XML_RELEASE" ]] 120 | then 121 | echo "package.xml version has NOT been changed, cannot release" 122 | exit 1 123 | else 124 | echo "Version in package.xml HAS been changed." 125 | exit 0 126 | fi 127 | 128 | release: 129 | name: "Tag, release & create merge-up PR" 130 | needs: [ "pre-verify-release" ] 131 | runs-on: ubuntu-latest 132 | 133 | steps: 134 | - name: "Checkout" 135 | uses: actions/checkout@v3 136 | 137 | - name: "Release" 138 | uses: "docker://ghcr.io/laminas/automatic-releases:1.13" 139 | with: 140 | args: "laminas:automatic-releases:release" 141 | env: 142 | "GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }} 143 | "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} 144 | "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} 145 | "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} 146 | 147 | - name: "Fetch rate limit stats" 148 | uses: octokit/request-action@v2.x 149 | id: get_latest_release 150 | with: 151 | route: GET /rate_limit 152 | env: 153 | GITHUB_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }} 154 | - name: "Display rate limit stats" 155 | run: echo "${{ steps.get_latest_release.outputs.data }}" 156 | 157 | - name: "Create Merge-Up Pull Request" 158 | uses: "docker://ghcr.io/laminas/automatic-releases:1.13" 159 | with: 160 | args: "laminas:automatic-releases:create-merge-up-pull-request" 161 | env: 162 | "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} 163 | "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} 164 | "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} 165 | "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} 166 | 167 | - name: "Create and/or Switch to new Release Branch" 168 | uses: "docker://ghcr.io/laminas/automatic-releases:1.13" 169 | with: 170 | args: "laminas:automatic-releases:switch-default-branch-to-next-minor" 171 | env: 172 | "GITHUB_TOKEN": ${{ secrets.ORGANIZATION_ADMIN_TOKEN }} 173 | "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} 174 | "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} 175 | "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} 176 | 177 | - name: "Create new milestones" 178 | uses: "docker://ghcr.io/laminas/automatic-releases:1.13" 179 | with: 180 | args: "laminas:automatic-releases:create-milestones" 181 | env: 182 | "GITHUB_TOKEN": ${{ secrets.GITHUB_TOKEN }} 183 | "SIGNING_SECRET_KEY": ${{ secrets.SIGNING_SECRET_KEY }} 184 | "GIT_AUTHOR_NAME": ${{ secrets.GIT_AUTHOR_NAME }} 185 | "GIT_AUTHOR_EMAIL": ${{ secrets.GIT_AUTHOR_EMAIL }} 186 | 187 | post-release-assets: 188 | name: "Create assets post-release" 189 | needs: [ "release_parameters", "release" ] 190 | runs-on: ubuntu-latest 191 | permissions: 192 | contents: write 193 | packages: write 194 | steps: 195 | - uses: actions/checkout@v3 196 | with: 197 | ref: ${{ needs.release_parameters.outputs.ref_for_version_to_tag }} 198 | - name: Setup PHP with PECL extension 199 | uses: shivammathur/setup-php@v2 200 | with: 201 | coverage: "none" 202 | php-version: "8.0" 203 | tools: pecl 204 | # `hub` was removed... https://github.com/actions/runner-images/issues/8362 205 | - name: "Install hub" 206 | run: sudo apt-get update && sudo apt-get install -y hub 207 | - name: "Build PECL Package" 208 | run: pecl package 209 | - name: "Fetch new release from Github API" 210 | uses: octokit/request-action@v2.x 211 | id: get_new_release_step 212 | with: 213 | route: GET /repos/scoutapp/scout-apm-php-ext/releases/tags/${{ needs.release_parameters.outputs.version_to_tag }} 214 | env: 215 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 216 | - name: "Upload PECL package to latest release" 217 | uses: actions/upload-release-asset@v1 218 | env: 219 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 220 | with: 221 | upload_url: ${{ fromJson(steps.get_new_release_step.outputs.data).upload_url }} 222 | asset_path: ./scoutapm-${{ fromJson(steps.get_new_release_step.outputs.data).tag_name }}.tgz 223 | asset_name: scoutapm-${{ fromJson(steps.get_new_release_step.outputs.data).tag_name }}.tgz 224 | asset_content_type: application/gzip 225 | - uses: actions/download-artifact@v3 226 | with: 227 | name: DLL ${{ needs.release_parameters.outputs.version_to_tag }} 228 | - name: "Upload Windows DLLs to latest release" 229 | # sauce: https://github.com/actions/upload-release-asset/issues/28#issuecomment-675297045 230 | env: 231 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 232 | run: | 233 | ls -l php_scoutapm-*.zip 234 | RELEASE="${{ needs.release_parameters.outputs.version_to_tag }}" 235 | ASSETS=$(find . -type f -name "php_scoutapm-*.zip" -printf "-a %p ") 236 | echo "release = $RELEASE" 237 | echo "assets = $ASSETS" 238 | hub release edit $ASSETS -m "" "$RELEASE" 239 | -------------------------------------------------------------------------------- /.github/workflows/windows-php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | extension=php_curl.dll 3 | extension=php_pdo_sqlite.dll 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C Specific 2 | *.dep 3 | 4 | # Object files 5 | *.o 6 | # Libraries 7 | *.lib 8 | # Shared objects (inc. Windows DLLs) 9 | *.dll 10 | *.so 11 | # Executables 12 | *.exe 13 | *.out 14 | *.la 15 | *.lo 16 | 17 | # http://www.gnu.org/software/automake 18 | Makefile.in 19 | Makefile.Global 20 | 21 | # http://www.gnu.org/software/autoconf 22 | /acinclude.m4 23 | /autom4te.cache 24 | /aclocal.m4 25 | /compile 26 | /configure 27 | /depcomp 28 | /install-sh 29 | /missing 30 | 31 | # libtool 32 | /.deps 33 | libtool.m4 34 | mkdep.awk 35 | scan_makefile_in.awk 36 | shtool 37 | /ltmain.sh 38 | /ltmain.sh.backup 39 | /mkinstalldirs 40 | 41 | # configure 42 | /config.guess 43 | /config.h 44 | /config.h.in 45 | /config.h.in~ 46 | /config.sub 47 | /config.nice 48 | /configure.in 49 | /config.log 50 | /configure.ac 51 | /config.status 52 | /config.nice.bat 53 | /configure.bat 54 | /configure.js 55 | /configure~ 56 | 57 | # phpize 58 | /run-tests.php 59 | /run-tests.php:Zone.Identifier:$DATA 60 | .libs 61 | Makefile* 62 | build 63 | x64 64 | tests/*.diff 65 | tests/*.exp 66 | tests/*.log 67 | tests/*.php 68 | tests/*.sh 69 | modules 70 | libtool 71 | 72 | # OSX stupidity 73 | .DS_Store 74 | # Thumbnails 75 | ._* 76 | # Files that might appear on external disk 77 | .Spotlight-V100 78 | .Trashes 79 | 80 | # Ignored because CMake isn't really used apart from to tell CLion where to find libraries 81 | cmake-build-* 82 | -------------------------------------------------------------------------------- /.keep-a-changelog.ini: -------------------------------------------------------------------------------- 1 | [defaults] 2 | changelog_file = CHANGELOG.md 3 | provider = github 4 | remote = origin 5 | package = scoutapp/scout-apm-php-ext 6 | 7 | [providers] 8 | github[class] = Phly\KeepAChangelog\Provider\GitHub 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.9) 2 | project(scoutapm C) 3 | 4 | set(SOURCE_FILES 5 | zend_scoutapm.c 6 | scout_curl_wrapper.c 7 | scout_file_wrapper.c 8 | scout_pdo_wrapper.c 9 | scout_extern.h 10 | zend_scoutapm.h 11 | scout_utils.c 12 | scout_functions.c 13 | scout_execute_ex.c 14 | scout_observer.c 15 | scout_internal_handlers.c 16 | scout_internal_handlers.h 17 | scout_recording.c 18 | scout_recording.h 19 | scout_execute_ex.h 20 | ) 21 | 22 | execute_process( 23 | COMMAND php-config --include-dir 24 | OUTPUT_VARIABLE PHP_SOURCE 25 | ) 26 | string(REGEX REPLACE "\n$" "" PHP_SOURCE "${PHP_SOURCE}") 27 | message("Using source directory: ${PHP_SOURCE}") 28 | 29 | include_directories(${PHP_SOURCE}) 30 | include_directories(${PHP_SOURCE}/main) 31 | include_directories(${PHP_SOURCE}/Zend) 32 | include_directories(${PHP_SOURCE}/TSRM) 33 | include_directories(${PROJECT_SOURCE_DIR}) 34 | 35 | add_custom_target(configure 36 | COMMAND phpize && ./configure --enable-scoutapm-dev 37 | DEPENDS ${SOURCE_FILES} 38 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) 39 | 40 | add_library(___ EXCLUDE_FROM_ALL ${SOURCE_FILES}) 41 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | scoutapm 2 | Chris Schneider (lead), James Titcumb [asgrim] (lead) 3 | -------------------------------------------------------------------------------- /EXPERIMENTAL: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scoutapp/scout-apm-php-ext/01072fdd92b8adeb3a90598e115aeae444186d99/EXPERIMENTAL -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Scout APM 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scout APM PHP Extension 2 | 3 | [![CI Build](https://github.com/scoutapp/scout-apm-php-ext/actions/workflows/ci-build.yaml/badge.svg)](https://github.com/scoutapp/scout-apm-php-ext/actions/workflows/ci-build.yaml) 4 | 5 | The Scout APM PHP extension allows instrumentation of internal PHP 6 | functions that can't be done in regular PHP. The `scout-apm-php` 7 | package detects if the `scoutapm` extension is loaded and will 8 | automatically send this data if available. 9 | 10 | This extension allows instrumentation of: 11 | 12 | * Core functions: `file_get_contents`, `file_put_contents`, `fread`, `fwrite` 13 | * Curl functions: `curl_exec` 14 | * PDO methods: `PDO->exec`, `PDO->query`, `PDOStatement->execute` 15 | * Predis PHP library methods 16 | * phpredis PHP extension methods 17 | * Memcached PHP extension methods 18 | * Elasticsearch PHP library methods 19 | 20 | If you would like another function instrumented, please let us know on 21 | [our issues](https://github.com/scoutapp/scout-apm-php-ext/issues). 22 | 23 | The following functions are exposed when the extension is enabled: 24 | 25 | * `scoutapm_enable_instrumentation(bool $enabled): void` 26 | - Enable or disable instrumentation by ScoutAPM at runtime. Instrumentation is disabled by default, so this must 27 | be called with `$enabled` to `true`. 28 | * `scoutapm_get_calls(): array` 29 | - Returns a list of any instrumented function calls since 30 | `scoutapm_get_calls()` was last called. The list is cleared each time the 31 | function is called. 32 | * `scoutapm_list_instrumented_functions(): array` 33 | - Returns a list of the functions the extension will instrument if called. 34 | 35 | ## Prerequisites for cURL 36 | 37 | In order to have instrumentation for cURL functions enabled, you MUST have `libcurl` available when building or 38 | installing from `pecl`. 39 | 40 | For example, if you are using the `ppa:ondrej/php` PPA on Ubuntu, you must install `libcurl` before building `scoutapm` 41 | extension, e.g.: 42 | 43 | ```bash 44 | $ apt-get -y install libcurl4-openssl-dev 45 | $ pecl install scoutapm 46 | ``` 47 | 48 | To confirm if cURL instrumentation is working, check `php -i`: 49 | 50 | ``` 51 | scoutapm 52 | 53 | scoutapm support => enabled 54 | scoutapm Version => 1.9.1 55 | scoutapm curl HAVE_CURL => No 56 | scoutapm curl HAVE_SCOUT_CURL => Yes 57 | scoutapm curl enabled => Yes 58 | ``` 59 | 60 | * `scoutapm curl HAVE_CURL` was PHP itself compiled with cURL. This will not always be `Yes`, for example when using 61 | pre-packaged binaries where `curl` extension is separate. 62 | * `scoutapm curl HAVE_SCOUT_CURL` was the `scoutapm` extension compiled and `libcurl` available? If this is `No`, then 63 | cURL instrumentation will not be available at all. 64 | * `scoutapm curl enabled` tells you if `scoutapm` has enabled monitoring (i.e. `curl` extension was available at 65 | runtime, and `libcurl` was available when `scoutapm` was compiled). _If this value is `No` and you think it 66 | should be `Yes`, check that `libcurl` was available when `scoutapm` was compiled, and that you have the `curl` PHP 67 | extension enabled._ 68 | 69 | ## Installing from PECL 70 | 71 | The Scout APM extension is available to install using 72 | [PECL](https://pecl.php.net/package/scoutapm). 73 | 74 | ```bash 75 | $ sudo pecl install scoutapm 76 | ``` 77 | 78 | You may need to add `zend_extension=scoutapm.so` into your `php.ini` to 79 | enable the extension. 80 | 81 | ## Installing with Docker 82 | 83 | If you are using Docker, with the [official PHP images](https://hub.docker.com/_/php), you can install the `scoutapm` 84 | extension using PECL still, for example: 85 | 86 | ```dockerfile 87 | FROM php:8.2-cli 88 | 89 | RUN pecl install scoutapm-1.9.1 \ 90 | && docker-php-ext-enable scoutapm 91 | ``` 92 | 93 | For more information on this installation method, see [here](https://github.com/docker-library/docs/tree/master/php#pecl-extensions). 94 | 95 | ## Building 96 | 97 | ```bash 98 | $ phpize 99 | $ ./configure --enable-scoutapm 100 | $ make test 101 | ``` 102 | 103 | Run tests with installed PHP (avoids skipped tests): 104 | 105 | ```bash 106 | make && php run-tests.php -d zend_extension=$(pwd)/modules/scoutapm.so --show-diff -q 107 | ``` 108 | 109 | Note: whilst a CMakeLists.txt exists, this project does NOT use CMake. 110 | The CMakeLists.txt exists so this project can be worked on in CLion. 111 | See . 112 | 113 | ## Building with specific PHP build 114 | 115 | ```bash 116 | $ /path/to/bin/phpize 117 | $ ./configure --with-php-config=/path/to/bin/php-config --enable-scoutapm 118 | $ make test 119 | ``` 120 | 121 | ## Debugging 122 | 123 | Use `gdb` (e.g. in CLion) to debug. Once running, php-src has a GDB 124 | helper: 125 | 126 | ``` 127 | source /path/to/php-src/.gdbinit 128 | printzv 129 | print_ht 130 | zbacktrace 131 | print_cvs 132 | ``` 133 | 134 | ## Windows builds 135 | 136 | - Read this guide to put below into context: https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2 137 | - Read this guide to help get the environment set up: https://gist.github.com/cmb69/47b8c7fb392f5d79b245c74ac496632c 138 | - PHP Binary tools - use https://github.com/php/php-sdk-binary-tools (**not** the Microsoft one as it is not maintained) 139 | - Once the VS tools + php-sdk-binary-tools is installed, everything is done in this shell: 140 | - Start > `Developer Command Prompt for VS 2019` 141 | - Then `cd C:\php-sdk` 142 | - Then `phpsdk-vs16-x64.bat` - you should now have a prompt `$ ` 143 | - Install PHP from https://windows.php.net/download/ 144 | - Download, e.g. ZTS build, https://windows.php.net/downloads/releases/php-8.1.7-Win32-vs16-x64.zip 145 | - Extract into `C:\php` 146 | - Prepare to compile the ext 147 | - Download "Development package" from https://windows.php.net/download/ - make sure TS/NTS depending on above compilation 148 | - e.g. https://windows.php.net/downloads/releases/php-devel-pack-8.1.7-nts-Win32-vs16-x64.zip 149 | - Extract to `C:\php-sdk\php-8.1.7-devel-vs16-x64` 150 | - Add `C:\php-sdk\php-8.1.7-devel-vs16-x64` to your PATH (Start > `env` > Environment variables > "Path" > New) 151 | - restart the shell 152 | - Compile the ext - go to the ext directory (mine was a VM, mounted in `Z:\`) 153 | - Run `winbuild.bat` 154 | - Or alternatively: 155 | - `phpize` 156 | - `configure --enable-scoutapm --enable-debug --with-php-build="C:\php-sdk\phpdev\vs16\x64\deps" --with-prefix="C:\php\"` 157 | - `nmake` 158 | - Edit `Makefile` - find `CC="$(PHP_CL)"` and replace with `CC="cl.exe"` - for some reason that variable substitution didn't work 159 | - Also replace `-d extension=` with `-d zend_extension=` 160 | - Run `nmake run ARGS="-m"` and check scoutapm exists in both PHP Modules and Zend modules 161 | 162 | ## Release Procedure 163 | 164 | - Open `package.xml` 165 | - Copy the current release into a new `changelog.release` element 166 | - Update the current release section (date/time/version/stability/notes) 167 | - `pecl package-validate` to check everything looks good 168 | - Increase/verify `PHP_SCOUTAPM_VERSION` version listed in `zend_scoutapm.h` 169 | - Commit update to `package.xml` 170 | - Rebuild from scratch (`full-clean.sh`, then build as above) 171 | - `make test` to ensure everything passes locally 172 | - Push the branch (optionally, make a PR to GitHub) to trigger CI to build 173 | - Once merged, close the milestone to automatically release & generate the TGZ asset 174 | - Go to [the latest release](https://github.com/scoutapp/scout-apm-php-ext/releases) just created 175 | - Download the TGZ asset and upload it to `pecl.php.net` 176 | -------------------------------------------------------------------------------- /benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | PHP=`which php` 6 | PHP_DESCRIPTION="without scoutapm extension" 7 | PASSES=10 8 | ITERATIONS_PER_PASS=100000 9 | 10 | function usage { 11 | echo "$0 [-w] [-p N] [-i N]" 12 | echo "" 13 | echo " -w, --with-scoutapm enables ScoutAPM extension for benchmark" 14 | echo " -p 10, --passes 10 run 10 passes for average (default=$PASSES)" 15 | echo " -i 100000, --iterations-per-pass 100000 run 100000 iterations per pass (default=$ITERATIONS_PER_PASS)" 16 | } 17 | 18 | while [[ $# -gt 0 ]] 19 | do 20 | key="$1" 21 | case $key in 22 | -w|--with-scoutapm) 23 | PHP="`which php` -d zend_extension=`pwd`/modules/scoutapm.so" 24 | PHP_DESCRIPTION="with scoutapm extension ENABLED" 25 | shift 26 | ;; 27 | -p|--passes) 28 | PASSES="$2" 29 | shift # past argument 30 | shift # past value 31 | ;; 32 | -i|--iterations-per-pass) 33 | ITERATIONS_PER_PASS="$2" 34 | shift # past argument 35 | shift # past value 36 | ;; 37 | *) # unknown option 38 | echo "Unknown option $key." 39 | usage 40 | exit 41 | ;; 42 | esac 43 | done 44 | 45 | echo "Running $PASSES containing $ITERATIONS_PER_PASS iterations each, $PHP_DESCRIPTION..." 46 | 47 | PHP_SCRIPT=`mktemp` 48 | echo " $PHP_SCRIPT 49 | 50 | EXECUTION_TIMES_FILE=`mktemp` 51 | for (( i=0; i <= $PASSES; i++)) do 52 | printf "." 53 | command time --format="%e" -ao $EXECUTION_TIMES_FILE $PHP $PHP_SCRIPT 54 | done 55 | 56 | printf "\n\n" 57 | 58 | awk '{s+=$1}END{print "Average execution time:",s/NR}' RS=" " $EXECUTION_TIMES_FILE 59 | unlink $EXECUTION_TIMES_FILE 60 | unlink $PHP_SCRIPT 61 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 for extension scoutapm 2 | 3 | PHP_ARG_ENABLE([scoutapm], 4 | [whether to enable scoutapm support], 5 | [AS_HELP_STRING([--enable-scoutapm], 6 | [Enable scoutapm support])], 7 | [no]) 8 | 9 | PHP_ARG_ENABLE([scoutapm-dev], 10 | [whether to enable scoutapm developer build flags], 11 | [AS_HELP_STRING([--enable-scoutapm-dev], 12 | [Enable scoutapm developer flags])], 13 | ,[no]) 14 | 15 | if test "$PHP_SCOUTAPM" != "no"; then 16 | 17 | dnl modern version provides libcurl.pc 18 | AC_PATH_PROG(PKG_CONFIG, pkg-config, no) 19 | dnl old version only provides curl-config 20 | AC_PATH_PROG(CURL_CONFIG, curl-config, no) 21 | 22 | AC_MSG_CHECKING(for libcurl headers) 23 | if test -x "$PKG_CONFIG" && $PKG_CONFIG --exists libcurl; then 24 | AC_MSG_RESULT(found with pkg-config) 25 | CURL_INCL=`$PKG_CONFIG libcurl --cflags` 26 | elif test -x "$CURL_CONFIG"; then 27 | AC_MSG_RESULT(found with curl-config) 28 | CURL_INCL=`$CURL_CONFIG --cflags` 29 | else 30 | AC_MSG_WARN(neither pkg-config nor curl-config found) 31 | CURL_INCL=no 32 | fi 33 | 34 | if test "$CURL_INCL" = "no"; then 35 | AC_DEFINE(HAVE_SCOUT_CURL,0,[Curl is present on the system]) 36 | AC_MSG_WARN([curl library headers were not found on the system, scoutapm will not instrument curl functions]) 37 | else 38 | PHP_EVAL_INCLINE($CURL_INCL) 39 | AC_DEFINE(HAVE_SCOUT_CURL,1,[Curl is present on the system]) 40 | fi 41 | 42 | if test "$PHP_SCOUTAPM_DEV" = "yes"; then 43 | MAINTAINER_CFLAGS="-std=gnu99" 44 | STD_CFLAGS="-g -O0 -Wall" 45 | fi 46 | 47 | PHP_SCOUTAPM_CFLAGS="-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1 $STD_CFLAGS $MAINTAINER_CFLAGS" 48 | 49 | PHP_NEW_EXTENSION( 50 | scoutapm, 51 | zend_scoutapm.c \ 52 | scout_observer.c \ 53 | scout_execute_ex.c \ 54 | scout_internal_handlers.c \ 55 | scout_recording.c \ 56 | scout_functions.c \ 57 | scout_utils.c \ 58 | scout_curl_wrapper.c \ 59 | scout_file_wrapper.c \ 60 | scout_pdo_wrapper.c, 61 | $ext_shared,,$PHP_SCOUTAPM_CFLAGS,,yes) 62 | fi 63 | 64 | AC_CONFIG_COMMANDS_POST([ 65 | rm -f build/php 66 | ln -s "$PHP_EXECUTABLE" build/php 67 | ]) 68 | -------------------------------------------------------------------------------- /config.w32: -------------------------------------------------------------------------------- 1 | // vim:ft=javascript 2 | (function () { 3 | ARG_ENABLE('scoutapm', 'Whether to enable scoutapm support', 'no'); 4 | ARG_ENABLE('scoutapm-debug', 'Whether to enable scoutapm debugging', 'no'); 5 | 6 | if (PHP_SCOUTAPM != 'no') { 7 | var scoutapm_sources = 'zend_scoutapm.c ' + 8 | 'scout_observer.c ' + 9 | 'scout_execute_ex.c ' + 10 | 'scout_internal_handlers.c ' + 11 | 'scout_recording.c ' + 12 | 'scout_functions.c ' + 13 | 'scout_utils.c ' + 14 | 'scout_curl_wrapper.c ' + 15 | 'scout_file_wrapper.c ' + 16 | 'scout_pdo_wrapper.c '; 17 | 18 | AC_DEFINE('HAVE_SCOUTAPM', 1); 19 | ADD_FLAG('CFLAGS_SCOUTAPM', '/D WIN32_ONLY_COMPILER=1 /wd4005'); 20 | PHP_INSTALL_HEADERS("ext/scoutapm", "zend_scoutapm.h scout_recording.h scout_internal_handlers.h scout_extern.h scout_execute_ex.h"); 21 | 22 | if (PHP_CURL) { 23 | AC_DEFINE('HAVE_SCOUT_CURL', 1); 24 | } 25 | 26 | // PHP 7.1/7.2 don't have ZEND_EXTENSION 27 | if (typeof (ZEND_EXTENSION) == 'undefined') { 28 | EXTENSION('scoutapm', scoutapm_sources, true); 29 | } else { 30 | ZEND_EXTENSION('scoutapm', scoutapm_sources, true); 31 | } 32 | } 33 | })(); 34 | -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | 2 | 8 | scoutapm 9 | pecl.php.net 10 | Native Extension Component for ScoutAPM's PHP Agent 11 | 12 | ScoutAPM's extension for PHP provides additional capabilities to application monitoring over just using the base PHP userland library. 13 | 14 | 15 | James Titcumb 16 | asgrim 17 | james@asgrim.com 18 | yes 19 | 20 | 21 | Chris Schneider 22 | cschneid 23 | chris@scoutapm.com 24 | no 25 | 26 | 27 | 28 | 2023-12-01 29 | 30 | 31 | 1.10.0 32 | 1.10.0 33 | 34 | 35 | stable 36 | stable 37 | 38 | MIT 39 | 40 | - Add support for PHP 8.3 (#135) 41 | - Fix CI build matrix (#137) 42 | - Add help for installing the ext in a Dockerfile (#136) 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 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 7.1.0 109 | 8.3.99 110 | 111 | 112 | 1.9.1 113 | 114 | 115 | 116 | scoutapm 117 | 118 | 119 | 120 | 121 | 2022-12-14 122 | 123 | 124 | 1.9.1 125 | 1.9.1 126 | 127 | 128 | stable 129 | stable 130 | 131 | MIT 132 | 133 | - Fix missing PHP 8.2 assets for Windows (#130) 134 | 135 | 136 | 137 | 2022-12-09 138 | 139 | 140 | 1.9.0 141 | 1.9.0 142 | 143 | 144 | stable 145 | stable 146 | 147 | MIT 148 | 149 | - Add PHP 8.2 support (#125) 150 | 151 | 152 | 153 | 2022-10-18 154 | 155 | 156 | 1.8.3 157 | 1.8.3 158 | 159 | 160 | stable 161 | stable 162 | 163 | MIT 164 | 165 | - Improved MINFO output for curl availability (#126) 166 | 167 | 168 | 169 | 2022-08-26 170 | 171 | 172 | 1.8.2 173 | 1.8.2 174 | 175 | 176 | stable 177 | stable 178 | 179 | MIT 180 | 181 | - Enable HAVE_SCOUT_CURL if it is available in the Windows builds (#121) 182 | 183 | 184 | 185 | 2022-07-11 186 | 187 | 188 | 1.8.1 189 | 1.8.1 190 | 191 | 192 | stable 193 | stable 194 | 195 | MIT 196 | 197 | - Added automation to upload DLL for Windows releases (#112) 198 | 199 | 200 | 201 | 2022-06-29 202 | 203 | 204 | 1.8.0 205 | 1.8.0 206 | 207 | 208 | stable 209 | stable 210 | 211 | MIT 212 | 213 | - Added support for compiling on Windows (#109) 214 | 215 | 216 | 217 | 2022-04-21 218 | 219 | 220 | 1.7.0 221 | 1.7.0 222 | 223 | 224 | stable 225 | stable 226 | 227 | MIT 228 | 229 | - Pinned Elasticsearch to ^7.0 as ^8.0 has major BC breaks (#105) 230 | - Elasticsearch 8 support (#106) 231 | - Added release automation (#103) 232 | 233 | 234 | 235 | 2022-01-10 236 | 237 | 238 | 1.6.0 239 | 1.6.0 240 | 241 | 242 | stable 243 | stable 244 | 245 | MIT 246 | 247 | - Added support for PHP 8.1 (#101) 248 | 249 | 250 | 251 | 2021-12-24 252 | 253 | 254 | 1.5.1 255 | 1.5.1 256 | 257 | 258 | stable 259 | stable 260 | 261 | MIT 262 | 263 | - Fix json_encode usage where ext-json is shared or does not exist - thanks @remicollet (#100) 264 | 265 | 266 | 267 | 2021-12-23 268 | 269 | 270 | 1.5.0 271 | 1.5.0 272 | 273 | 274 | stable 275 | stable 276 | 277 | MIT 278 | 279 | - file_get_contents and curl_exec now record HTTP methods (#96) 280 | 281 | 282 | 283 | 2021-10-29 284 | 285 | 286 | 1.4.3 287 | 1.4.3 288 | 289 | 290 | stable 291 | stable 292 | 293 | MIT 294 | 295 | - Fixed segfault when static anonymous functions are called (#94) 296 | 297 | 298 | 299 | 2021-06-29 300 | 301 | 302 | 1.4.2 303 | 1.4.2 304 | 305 | 306 | stable 307 | stable 308 | 309 | MIT 310 | 311 | - Fixed some missed free calls after DYNAMIC_MALLOC_SPRINTF usage in PHP 8 only (#92) 312 | 313 | 314 | 315 | 2021-06-29 316 | 317 | 318 | 1.4.1 319 | 1.4.1 320 | 321 | 322 | stable 323 | stable 324 | 325 | MIT 326 | 327 | - Fixed memory leaks from DYNAMIC_MALLOC_SPRINTF un-freed usages (#91) 328 | 329 | 330 | 331 | 2021-06-17 332 | 333 | 334 | 1.4.0 335 | 1.4.0 336 | 337 | 338 | stable 339 | stable 340 | 341 | MIT 342 | 343 | - Only instrument if specifically enabled with scoutapm_enable_instrumentation() (#89) 344 | 345 | 346 | 347 | 2021-06-17 348 | 349 | 350 | 1.3.0 351 | 1.3.0 352 | 353 | 354 | stable 355 | stable 356 | 357 | MIT 358 | 359 | - Userland function recording for PHP 7 with zend_execute_ex (#77) 360 | - Userland function recording for PHP 8 with improved Zend Observer API (#79) 361 | - Added Predis library function instrumentation (#80) 362 | - Add support for phpredis Redis extension instrumentation (#82) 363 | - Add instrumentation for Memcached (#84) 364 | - Add instrumentation for Elasticsearch PHP library (#85) 365 | - Change CI from Circle to GitHub Actions (#78) 366 | - Fix SKIP block for phpredis test (#83) 367 | 368 | 369 | 370 | 2021-03-19 371 | 372 | 373 | 1.2.2 374 | 1.2.2 375 | 376 | 377 | stable 378 | stable 379 | 380 | MIT 381 | 382 | - Do not try to record arguments if PDO::prepare returns a non-object (#72) 383 | 384 | 385 | 386 | 2021-02-05 387 | 388 | 389 | 1.2.1 390 | 1.2.1 391 | 392 | 393 | stable 394 | stable 395 | 396 | MIT 397 | 398 | - Fixing builds on ZTS mode (thanks @remicollet, #69) 399 | 400 | 401 | 402 | 2021-02-04 403 | 404 | 405 | 1.2.0 406 | 1.2.0 407 | 408 | 409 | stable 410 | stable 411 | 412 | MIT 413 | 414 | - Added support for PHP 8.0 (#66) 415 | 416 | 417 | 418 | 2020-02-19 419 | 420 | 421 | 1.1.1 422 | 1.1.1 423 | 424 | 425 | stable 426 | stable 427 | 428 | MIT 429 | 430 | - Fixed typo in config.m4 for libcurl detection 431 | 432 | 433 | 434 | 2020-02-19 435 | 436 | 437 | 1.1.0 438 | 1.1.0 439 | 440 | 441 | stable 442 | stable 443 | 444 | MIT 445 | 446 | - Added support for PHP 7.4 (#60) 447 | - Improved cURL detection (thanks @remicollet, #58) 448 | 449 | 450 | 451 | 2019-11-06 452 | 453 | 454 | 1.0.1 455 | 1.0.1 456 | 457 | 458 | stable 459 | stable 460 | 461 | MIT 462 | 463 | - Fix segfault when trying to access args out of bounds (#48) 464 | - Fix exception raised when trying to fopen a file that does not exist (#50) 465 | - Removed notice emitted calling some functions (#51) 466 | 467 | 468 | 469 | 2019-11-04 470 | 471 | 472 | 1.0.0 473 | 1.0.0 474 | 475 | 476 | stable 477 | stable 478 | 479 | MIT 480 | 481 | - More documentation into README.md (#38) 482 | - Better text matrix introduced, including PHP 7.4 tests (#40) 483 | - Improved argument handling for functions like `curl_exec`, `fwrite`, `fread`, `PDOStatement->execute` (#42, #43) 484 | - Bug fixes for #41 and #29 to help prevent bad configuration of overwritten functions (#44) 485 | 486 | 487 | 488 | 2019-09-27 489 | 490 | 491 | 0.0.4 492 | 0.0.4 493 | 494 | 495 | alpha 496 | alpha 497 | 498 | MIT 499 | 500 | - Fixed test failing because differing behaviour of sqlite in some versions 501 | - Define i/j etc to follow c89 rules (thanks @remicollet) 502 | 503 | 504 | 505 | 2019-09-17 506 | 507 | 508 | 0.0.3 509 | 0.0.3 510 | 511 | 512 | alpha 513 | alpha 514 | 515 | MIT 516 | 517 | - Fixed version number and naming convension so PECL uploader picks up on mismatches (last release was wrong) 518 | 519 | 520 | 521 | 2019-09-17 522 | 523 | 524 | 0.0.2 525 | 0.0.2 526 | 527 | 528 | alpha 529 | alpha 530 | 531 | MIT 532 | 533 | - Added extra compiler flags in development mode with `--enable-scoutapm-dev` 534 | - Fixed compilation errors surfaced by `--enable-scoutapm-dev` option 535 | - Added missing file `external.inc` in tests 536 | 537 | 538 | 539 | 2019-09-17 540 | 541 | 542 | 0.0.1 543 | 0.0.1 544 | 545 | 546 | alpha 547 | alpha 548 | 549 | MIT 550 | 551 | - Basic monitoring of file_get_contents, file_put_contents, fwrite, fread, curl_exec, PDO->exec, PDO->query, PDOStatement->execute 552 | - Provides function scoutapm_get_calls() to return a list of recorded function calls 553 | 554 | 555 | 556 | 557 | -------------------------------------------------------------------------------- /scout_curl_wrapper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2019- 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #include "zend_scoutapm.h" 9 | 10 | #if HAVE_SCOUT_CURL 11 | #include 12 | #include "scout_extern.h" 13 | 14 | #define ASSIGN_CURL_HANDLE_CLASS_ENTRY \ 15 | zend_class_entry *curl_ce; \ 16 | curl_ce = zend_hash_str_find_ptr(CG(class_table), "curlhandle", sizeof("curlhandle") - 1); 17 | 18 | #define SCOUTAPM_CURLOPT_URL "CURLOPT_URL" 19 | #define SCOUTAPM_CURLOPT_POST "CURLOPT_POST" 20 | #define SCOUTAPM_CURLOPT_CUSTOMREQUEST "CURLOPT_CUSTOMREQUEST" 21 | 22 | #define SCOUTAPM_CURL_GET_CURLOPT_ADD_TO_ARGV(opt) \ 23 | recorded_arguments_index = scout_curl_get_curlopt(zid, opt); \ 24 | argv = realloc(argv, sizeof(zval) * ((unsigned long)argc + 1)); \ 25 | if (recorded_arguments_index >= 0) { \ 26 | argv[argc] = *SCOUTAPM_G(disconnected_call_argument_store)[recorded_arguments_index].argv; \ 27 | } else { \ 28 | zval null_value; \ 29 | ZVAL_NULL(&null_value); \ 30 | argv[argc] = null_value; \ 31 | } \ 32 | argc++; 33 | 34 | static void scout_curl_store_curlopt(zval *curlHandle, const char *optionName, zval* optionValue) 35 | { 36 | #if PHP_MAJOR_VERSION >= 8 37 | char *class_instance_id = (char*)unique_class_instance_id(curlHandle); 38 | class_instance_id = realloc(class_instance_id, strlen(class_instance_id) + strlen(optionName) + 1); 39 | strcat(class_instance_id, optionName); 40 | 41 | record_arguments_for_call(class_instance_id, 1, optionValue); 42 | free((void*) class_instance_id); 43 | #else 44 | char *resource_id = (char*)unique_resource_id(SCOUT_WRAPPER_TYPE_CURL, curlHandle); 45 | resource_id = realloc(resource_id, strlen(resource_id) + strlen(optionName) + 1); 46 | strcat(resource_id, optionName); 47 | 48 | record_arguments_for_call(resource_id, 1, optionValue); 49 | free((void*) resource_id); 50 | #endif 51 | } 52 | 53 | static zend_long scout_curl_get_curlopt(zval *curlHandle, const char *optionName) 54 | { 55 | zend_long recorded_arguments_index; 56 | 57 | #if PHP_MAJOR_VERSION >= 8 58 | char *class_instance_id = (char*)unique_class_instance_id(curlHandle); 59 | class_instance_id = realloc(class_instance_id, strlen(class_instance_id) + strlen(optionName) + 1); 60 | strcat(class_instance_id, optionName); 61 | 62 | recorded_arguments_index = find_index_for_recorded_arguments(class_instance_id); 63 | free((void*) class_instance_id); 64 | #else 65 | char *resource_id = (char*)unique_resource_id(SCOUT_WRAPPER_TYPE_CURL, curlHandle); 66 | resource_id = realloc(resource_id, strlen(resource_id) + strlen(optionName) + 1); 67 | strcat(resource_id, optionName); 68 | 69 | recorded_arguments_index = find_index_for_recorded_arguments(resource_id); 70 | free((void*) resource_id); 71 | #endif 72 | 73 | return recorded_arguments_index; 74 | } 75 | 76 | ZEND_NAMED_FUNCTION(scoutapm_curl_setopt_handler) 77 | { 78 | zval *zid, *zvalue; 79 | zend_long options; 80 | const char *passthru_function_name; 81 | 82 | #if PHP_MAJOR_VERSION >= 8 83 | ASSIGN_CURL_HANDLE_CLASS_ENTRY 84 | #endif 85 | 86 | SCOUT_PASSTHRU_IF_ALREADY_INSTRUMENTING(passthru_function_name) 87 | 88 | ZEND_PARSE_PARAMETERS_START(3, 3) 89 | #if PHP_MAJOR_VERSION >= 8 90 | Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) 91 | #else 92 | Z_PARAM_RESOURCE(zid) 93 | #endif 94 | Z_PARAM_LONG(options) 95 | Z_PARAM_ZVAL(zvalue) 96 | ZEND_PARSE_PARAMETERS_END(); 97 | 98 | if (options == CURLOPT_URL) { 99 | scout_curl_store_curlopt(zid, SCOUTAPM_CURLOPT_URL, zvalue); 100 | } 101 | if (options == CURLOPT_POST) { 102 | scout_curl_store_curlopt(zid, SCOUTAPM_CURLOPT_POST, zvalue); 103 | } 104 | if (options == CURLOPT_CUSTOMREQUEST) { 105 | scout_curl_store_curlopt(zid, SCOUTAPM_CURLOPT_CUSTOMREQUEST, zvalue); 106 | } 107 | 108 | SCOUT_INTERNAL_FUNCTION_PASSTHRU(passthru_function_name); 109 | } 110 | 111 | ZEND_NAMED_FUNCTION(scoutapm_curl_exec_handler) 112 | { 113 | int handler_index, argc = 0; 114 | double entered = scoutapm_microtime(); 115 | zval *zid; 116 | const char *called_function; 117 | zend_long recorded_arguments_index; 118 | zval *argv = NULL; 119 | 120 | #if PHP_MAJOR_VERSION >= 8 121 | ASSIGN_CURL_HANDLE_CLASS_ENTRY 122 | #endif 123 | 124 | SCOUT_PASSTHRU_IF_ALREADY_INSTRUMENTING(called_function) 125 | 126 | called_function = determine_function_name(execute_data); 127 | 128 | ZEND_PARSE_PARAMETERS_START(1, 1) 129 | #if PHP_MAJOR_VERSION >= 8 130 | Z_PARAM_OBJECT_OF_CLASS(zid, curl_ce) 131 | #else 132 | Z_PARAM_RESOURCE(zid) 133 | #endif 134 | ZEND_PARSE_PARAMETERS_END(); 135 | 136 | handler_index = handler_index_for_function(called_function); 137 | 138 | SCOUTAPM_CURL_GET_CURLOPT_ADD_TO_ARGV(SCOUTAPM_CURLOPT_URL); 139 | SCOUTAPM_CURL_GET_CURLOPT_ADD_TO_ARGV(SCOUTAPM_CURLOPT_POST); 140 | SCOUTAPM_CURL_GET_CURLOPT_ADD_TO_ARGV(SCOUTAPM_CURLOPT_CUSTOMREQUEST); 141 | 142 | original_handlers[handler_index](INTERNAL_FUNCTION_PARAM_PASSTHRU); 143 | 144 | record_observed_stack_frame( 145 | called_function, 146 | entered, 147 | scoutapm_microtime(), 148 | argc, 149 | argv 150 | ); 151 | free((void*) called_function); 152 | } 153 | #endif 154 | -------------------------------------------------------------------------------- /scout_execute_ex.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2021 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #include "zend_scoutapm.h" 9 | #include "scout_extern.h" 10 | 11 | #if SCOUTAPM_INSTRUMENT_USING_OBSERVER_API == 0 12 | void (*original_zend_execute_ex) (zend_execute_data *execute_data); 13 | void (*original_zend_execute_internal) (zend_execute_data *execute_data, zval *return_value); 14 | void scoutapm_execute_internal(zend_execute_data *execute_data, zval *return_value); 15 | void scoutapm_execute_ex(zend_execute_data *execute_data); 16 | #endif 17 | 18 | int setup_functions_for_zend_execute_ex() 19 | { 20 | #if SCOUTAPM_INSTRUMENT_USING_OBSERVER_API == 0 21 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->append"); 22 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->decr"); 23 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->decrBy"); 24 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->get"); 25 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->getBit"); 26 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->getRange"); 27 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->getSet"); 28 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->incr"); 29 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->incrBy"); 30 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->mGet"); 31 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->mSet"); 32 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->mSetNx"); 33 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->set"); 34 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->setBit"); 35 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->setEx"); 36 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->pSetEx"); 37 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->setNx"); 38 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->setRange"); 39 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->strlen"); 40 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->del"); 41 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elasticsearch\\Client->index"); 42 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elasticsearch\\Client->get"); 43 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elasticsearch\\Client->search"); 44 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elasticsearch\\Client->delete"); 45 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elastic\\Elasticsearch\\Client->index"); 46 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elastic\\Elasticsearch\\Client->get"); 47 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elastic\\Elasticsearch\\Client->search"); 48 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elastic\\Elasticsearch\\Client->delete"); 49 | #endif 50 | 51 | return SUCCESS; 52 | } 53 | 54 | void register_scout_execute_ex() 55 | { 56 | #if SCOUTAPM_INSTRUMENT_USING_OBSERVER_API == 0 57 | original_zend_execute_internal = zend_execute_internal; 58 | zend_execute_internal = scoutapm_execute_internal; 59 | 60 | original_zend_execute_ex = zend_execute_ex; 61 | zend_execute_ex = scoutapm_execute_ex; 62 | #endif 63 | } 64 | 65 | void deregister_scout_execute_ex() 66 | { 67 | #if SCOUTAPM_INSTRUMENT_USING_OBSERVER_API == 0 68 | zend_execute_internal = original_zend_execute_internal; 69 | zend_execute_ex = original_zend_execute_ex; 70 | #endif 71 | } 72 | 73 | #if SCOUTAPM_INSTRUMENT_USING_OBSERVER_API == 0 74 | 75 | void scoutapm_execute_internal(zend_execute_data *execute_data, zval *return_value) 76 | { 77 | const char *function_name; 78 | double entered = scoutapm_microtime(); 79 | int argc; 80 | zval *argv = NULL; 81 | 82 | if (SCOUTAPM_G(all_instrumentation_enabled) != 1 83 | || SCOUTAPM_G(currently_instrumenting) == 1 84 | || execute_data->func->common.function_name == NULL 85 | ) { 86 | if (original_zend_execute_internal) { 87 | original_zend_execute_internal(execute_data, return_value); 88 | } else { 89 | execute_internal(execute_data, return_value); 90 | } 91 | return; 92 | } 93 | 94 | function_name = determine_function_name(execute_data); 95 | 96 | if (should_be_instrumented(function_name, NULL) == 0) { 97 | free((void*) function_name); 98 | if (original_zend_execute_internal) { 99 | original_zend_execute_internal(execute_data, return_value); 100 | } else { 101 | execute_internal(execute_data, return_value); 102 | } 103 | return; 104 | } 105 | 106 | SCOUTAPM_G(currently_instrumenting) = 1; 107 | 108 | ZEND_PARSE_PARAMETERS_START(0, -1) 109 | Z_PARAM_VARIADIC(' ', argv, argc) 110 | ZEND_PARSE_PARAMETERS_END(); 111 | 112 | if (original_zend_execute_internal) { 113 | original_zend_execute_internal(execute_data, return_value); 114 | } else { 115 | execute_internal(execute_data, return_value); 116 | } 117 | 118 | record_observed_stack_frame(function_name, entered, scoutapm_microtime(), argc, argv); 119 | SCOUTAPM_G(currently_instrumenting) = 0; 120 | free((void*) function_name); 121 | } 122 | 123 | void scoutapm_execute_ex(zend_execute_data *execute_data) 124 | { 125 | const char *function_name; 126 | double entered = scoutapm_microtime(); 127 | int argc; 128 | zval *argv = NULL; 129 | 130 | if (SCOUTAPM_G(all_instrumentation_enabled) != 1 131 | || SCOUTAPM_G(currently_instrumenting) == 1 132 | || execute_data->func->common.function_name == NULL 133 | ) { 134 | original_zend_execute_ex(execute_data); 135 | return; 136 | } 137 | 138 | function_name = determine_function_name(execute_data); 139 | 140 | if (should_be_instrumented(function_name, NULL) == 0) { 141 | free((void*) function_name); 142 | original_zend_execute_ex(execute_data); 143 | return; 144 | } 145 | 146 | SCOUTAPM_G(currently_instrumenting) = 1; 147 | 148 | ZEND_PARSE_PARAMETERS_START(0, -1) 149 | Z_PARAM_VARIADIC(' ', argv, argc) 150 | ZEND_PARSE_PARAMETERS_END(); 151 | 152 | original_zend_execute_ex(execute_data); 153 | 154 | record_observed_stack_frame(function_name, entered, scoutapm_microtime(), argc, argv); 155 | SCOUTAPM_G(currently_instrumenting) = 0; 156 | free((void*) function_name); 157 | } 158 | 159 | #endif /* SCOUTAPM_INSTRUMENT_USING_OBSERVER_API */ 160 | -------------------------------------------------------------------------------- /scout_execute_ex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2021- 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #ifndef SCOUTAPM_SCOUT_EXECUTE_EX_H 9 | #define SCOUTAPM_SCOUT_EXECUTE_EX_H 10 | 11 | #define MAX_INSTRUMENTED_FUNCTIONS 100 12 | 13 | #endif //SCOUTAPM_SCOUT_EXECUTE_EX_H 14 | -------------------------------------------------------------------------------- /scout_extern.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2019- 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #ifndef SCOUT_API_H 9 | #define SCOUT_API_H 10 | 11 | extern ZEND_NAMED_FUNCTION(scoutapm_default_handler); 12 | extern double scoutapm_microtime(); 13 | extern void record_arguments_for_call(const char *call_reference, int argc, zval *argv); 14 | extern zend_long find_index_for_recorded_arguments(const char *call_reference); 15 | extern void record_observed_stack_frame(const char *function_name, double microtime_entered, double microtime_exited, int argc, zval *argv); 16 | extern int handler_index_for_function(const char *function_to_lookup); 17 | extern const char* determine_function_name(zend_execute_data *execute_data); 18 | extern const char *unique_resource_id(const char *scout_wrapper_type, zval *resource_id); 19 | extern const char *unique_class_instance_id(zval *class_instance); 20 | extern void safely_copy_argument_zval_as_scalar(zval *original_to_copy, zval *destination); 21 | extern int unchecked_handler_index_for_function(const char *function_to_lookup); 22 | extern void add_function_to_instrumentation(const char *function_name, const char *magic_method_name); 23 | extern int should_be_instrumented(const char *function_name, const char *magic_method_name); 24 | extern const char *scout_str_replace(const char *search, const char *replace, const char *subject); 25 | 26 | ZEND_EXTERN_MODULE_GLOBALS(scoutapm); 27 | extern indexed_handler_lookup handler_lookup[]; 28 | extern const int handler_lookup_size; 29 | extern zif_handler original_handlers[]; 30 | 31 | #endif /* SCOUT_API_H */ 32 | -------------------------------------------------------------------------------- /scout_file_wrapper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2019- 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #include "zend_scoutapm.h" 9 | #include "scout_extern.h" 10 | 11 | ZEND_NAMED_FUNCTION(scoutapm_fopen_handler) 12 | { 13 | zend_string *filename, *mode; 14 | zval argv[2]; 15 | const char *passthru_function_name, *resource_id; 16 | 17 | SCOUT_PASSTHRU_IF_ALREADY_INSTRUMENTING(passthru_function_name) 18 | 19 | ZEND_PARSE_PARAMETERS_START(2, 4) 20 | Z_PARAM_STR(filename) 21 | Z_PARAM_STR(mode) 22 | SCOUT_ZEND_PARSE_PARAMETERS_END(); 23 | 24 | ZVAL_STR(&argv[0], filename); 25 | ZVAL_STR(&argv[1], mode); 26 | 27 | SCOUT_INTERNAL_FUNCTION_PASSTHRU(passthru_function_name); 28 | 29 | if (Z_TYPE_P(return_value) == IS_RESOURCE) { 30 | resource_id = unique_resource_id(SCOUT_WRAPPER_TYPE_FILE, return_value); 31 | record_arguments_for_call(resource_id, 2, argv); 32 | free((void*) resource_id); 33 | } 34 | } 35 | 36 | ZEND_NAMED_FUNCTION(scoutapm_fread_handler) 37 | { 38 | int handler_index; 39 | double entered = scoutapm_microtime(); 40 | zval *resource_id; 41 | const char *called_function, *str_resource_id; 42 | zend_long recorded_arguments_index; 43 | 44 | SCOUT_PASSTHRU_IF_ALREADY_INSTRUMENTING(called_function) 45 | 46 | called_function = determine_function_name(execute_data); 47 | 48 | ZEND_PARSE_PARAMETERS_START(1, 10) 49 | Z_PARAM_RESOURCE(resource_id) 50 | SCOUT_ZEND_PARSE_PARAMETERS_END(); 51 | 52 | handler_index = handler_index_for_function(called_function); 53 | 54 | str_resource_id = unique_resource_id(SCOUT_WRAPPER_TYPE_FILE, resource_id); 55 | recorded_arguments_index = find_index_for_recorded_arguments(str_resource_id); 56 | free((void*) str_resource_id); 57 | 58 | if (recorded_arguments_index < 0) { 59 | free((void*) called_function); 60 | scoutapm_default_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); 61 | return; 62 | } 63 | 64 | original_handlers[handler_index](INTERNAL_FUNCTION_PARAM_PASSTHRU); 65 | 66 | record_observed_stack_frame( 67 | called_function, 68 | entered, 69 | scoutapm_microtime(), 70 | SCOUTAPM_G(disconnected_call_argument_store)[recorded_arguments_index].argc, 71 | SCOUTAPM_G(disconnected_call_argument_store)[recorded_arguments_index].argv 72 | ); 73 | free((void*) called_function); 74 | } 75 | 76 | ZEND_NAMED_FUNCTION(scoutapm_fwrite_handler) 77 | { 78 | int handler_index; 79 | double entered = scoutapm_microtime(); 80 | zval *resource_id; 81 | const char *called_function, *str_resource_id; 82 | zend_long recorded_arguments_index; 83 | 84 | SCOUT_PASSTHRU_IF_ALREADY_INSTRUMENTING(called_function) 85 | 86 | called_function = determine_function_name(execute_data); 87 | 88 | ZEND_PARSE_PARAMETERS_START(1, 10) 89 | Z_PARAM_RESOURCE(resource_id) 90 | SCOUT_ZEND_PARSE_PARAMETERS_END(); 91 | 92 | handler_index = handler_index_for_function(called_function); 93 | 94 | str_resource_id = unique_resource_id(SCOUT_WRAPPER_TYPE_FILE, resource_id); 95 | recorded_arguments_index = find_index_for_recorded_arguments(str_resource_id); 96 | free((void*) str_resource_id); 97 | 98 | if (recorded_arguments_index < 0) { 99 | free((void*) called_function); 100 | scoutapm_default_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); 101 | return; 102 | } 103 | 104 | original_handlers[handler_index](INTERNAL_FUNCTION_PARAM_PASSTHRU); 105 | 106 | record_observed_stack_frame( 107 | called_function, 108 | entered, 109 | scoutapm_microtime(), 110 | SCOUTAPM_G(disconnected_call_argument_store)[recorded_arguments_index].argc, 111 | SCOUTAPM_G(disconnected_call_argument_store)[recorded_arguments_index].argv 112 | ); 113 | free((void*) called_function); 114 | } 115 | -------------------------------------------------------------------------------- /scout_functions.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2021 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #include "zend_scoutapm.h" 9 | #include "scout_extern.h" 10 | 11 | /* {{{ proto array scoutapm_get_calls() 12 | Fetch all the recorded function or method calls recorded by the ScoutAPM extension. */ 13 | PHP_FUNCTION(scoutapm_get_calls) 14 | { 15 | int i, j; 16 | zval item, arg_items, arg_item; 17 | ZEND_PARSE_PARAMETERS_NONE(); 18 | 19 | SCOUTAPM_DEBUG_MESSAGE("scoutapm_get_calls: preparing return value... "); 20 | 21 | array_init(return_value); 22 | 23 | for (i = 0; i < SCOUTAPM_G(observed_stack_frames_count); i++) { 24 | array_init(&item); 25 | 26 | add_assoc_str_ex( 27 | &item, 28 | SCOUT_GET_CALLS_KEY_FUNCTION, strlen(SCOUT_GET_CALLS_KEY_FUNCTION), 29 | zend_string_init(SCOUTAPM_G(observed_stack_frames)[i].function_name, strlen(SCOUTAPM_G(observed_stack_frames)[i].function_name), 0) 30 | ); 31 | 32 | add_assoc_double_ex( 33 | &item, 34 | SCOUT_GET_CALLS_KEY_ENTERED, strlen(SCOUT_GET_CALLS_KEY_ENTERED), 35 | SCOUTAPM_G(observed_stack_frames)[i].entered 36 | ); 37 | 38 | add_assoc_double_ex( 39 | &item, 40 | SCOUT_GET_CALLS_KEY_EXITED, strlen(SCOUT_GET_CALLS_KEY_EXITED), 41 | SCOUTAPM_G(observed_stack_frames)[i].exited 42 | ); 43 | 44 | /* Time taken is calculated here because float precision gets knocked off otherwise - so this is a useful metric */ 45 | add_assoc_double_ex( 46 | &item, 47 | SCOUT_GET_CALLS_KEY_TIME_TAKEN, strlen(SCOUT_GET_CALLS_KEY_TIME_TAKEN), 48 | SCOUTAPM_G(observed_stack_frames)[i].exited - SCOUTAPM_G(observed_stack_frames)[i].entered 49 | ); 50 | 51 | array_init(&arg_items); 52 | for (j = 0; j < SCOUTAPM_G(observed_stack_frames)[i].argc; j++) { 53 | /* Must copy the argument to a new zval, otherwise it gets freed and we get segfault. */ 54 | ZVAL_COPY(&arg_item, &(SCOUTAPM_G(observed_stack_frames)[i].argv[j])); 55 | add_next_index_zval(&arg_items, &arg_item); 56 | zval_ptr_dtor(&(SCOUTAPM_G(observed_stack_frames)[i].argv[j])); 57 | } 58 | free(SCOUTAPM_G(observed_stack_frames)[i].argv); 59 | 60 | add_assoc_zval_ex( 61 | &item, 62 | SCOUT_GET_CALLS_KEY_ARGV, strlen(SCOUT_GET_CALLS_KEY_ARGV), 63 | &arg_items 64 | ); 65 | 66 | add_next_index_zval(return_value, &item); 67 | 68 | free((void*)SCOUTAPM_G(observed_stack_frames)[i].function_name); 69 | } 70 | 71 | SCOUTAPM_G(observed_stack_frames) = realloc(SCOUTAPM_G(observed_stack_frames), 0); 72 | SCOUTAPM_G(observed_stack_frames_count) = 0; 73 | 74 | SCOUTAPM_DEBUG_MESSAGE("done.\n"); 75 | } 76 | /* }}} */ 77 | 78 | /* {{{ proto array scoutapm_list_instrumented_functions() 79 | Fetch a list of functions that will be instrumented or monitored by the ScoutAPM extension. */ 80 | PHP_FUNCTION(scoutapm_list_instrumented_functions) 81 | { 82 | int i, lookup_count = handler_lookup_size / sizeof(indexed_handler_lookup); 83 | 84 | ZEND_PARSE_PARAMETERS_NONE(); 85 | 86 | array_init(return_value); 87 | 88 | for(i = 0; i < lookup_count; i++) { 89 | if (original_handlers[handler_lookup[i].index] == NULL) { 90 | continue; 91 | } 92 | 93 | add_next_index_stringl( 94 | return_value, 95 | handler_lookup[i].function_name, 96 | strlen(handler_lookup[i].function_name) 97 | ); 98 | } 99 | 100 | for(i = 0; i < SCOUTAPM_G(num_instrumented_functions); i++) { 101 | if (SCOUTAPM_G(instrumented_function_names[i].magic_method_name) != NULL) { 102 | add_next_index_string( 103 | return_value, 104 | scout_str_replace( 105 | "__call", 106 | SCOUTAPM_G(instrumented_function_names[i].magic_method_name), 107 | SCOUTAPM_G(instrumented_function_names[i].function_name) 108 | ) 109 | ); 110 | continue; 111 | } 112 | 113 | add_next_index_stringl( 114 | return_value, 115 | SCOUTAPM_G(instrumented_function_names[i].function_name), 116 | strlen(SCOUTAPM_G(instrumented_function_names[i].function_name)) 117 | ); 118 | } 119 | } 120 | /* }}} */ 121 | 122 | /* {{{ proto array scoutapm_list_instrumented_functions() 123 | Fetch a list of functions that will be instrumented or monitored by the ScoutAPM extension. */ 124 | PHP_FUNCTION(scoutapm_enable_instrumentation) 125 | { 126 | zend_bool should_enable; 127 | 128 | ZEND_PARSE_PARAMETERS_START(1, 1) 129 | Z_PARAM_BOOL(should_enable) 130 | ZEND_PARSE_PARAMETERS_END(); 131 | 132 | SCOUTAPM_G(all_instrumentation_enabled) = should_enable; 133 | } 134 | /* }}} */ 135 | -------------------------------------------------------------------------------- /scout_internal_handlers.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2021 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #include "zend_scoutapm.h" 9 | #include "scout_extern.h" 10 | 11 | #if HAVE_SCOUT_CURL 12 | extern ZEND_NAMED_FUNCTION(scoutapm_curl_setopt_handler); 13 | extern ZEND_NAMED_FUNCTION(scoutapm_curl_exec_handler); 14 | #endif 15 | extern ZEND_NAMED_FUNCTION(scoutapm_fopen_handler); 16 | extern ZEND_NAMED_FUNCTION(scoutapm_fread_handler); 17 | extern ZEND_NAMED_FUNCTION(scoutapm_fwrite_handler); 18 | extern ZEND_NAMED_FUNCTION(scoutapm_pdo_prepare_handler); 19 | extern ZEND_NAMED_FUNCTION(scoutapm_pdostatement_execute_handler); 20 | 21 | /* This is simply a map of function names to an index in original_handlers */ 22 | indexed_handler_lookup handler_lookup[] = { 23 | /* define each function we want to overload, which maps to an index in the `original_handlers` array */ 24 | { 0, "file_get_contents"}, 25 | { 1, "file_put_contents"}, 26 | { 2, "curl_setopt"}, 27 | { 3, "curl_exec"}, 28 | { 4, "fopen"}, 29 | { 5, "fread"}, 30 | { 6, "fwrite"}, 31 | { 7, "pdo->exec"}, 32 | { 8, "pdo->query"}, 33 | { 9, "pdo->prepare"}, 34 | {10, "pdostatement->execute"}, 35 | {11, "redis->append"}, 36 | {12, "redis->decr"}, 37 | {13, "redis->decrby"}, 38 | {14, "redis->get"}, 39 | {15, "redis->getbit"}, 40 | {16, "redis->getrange"}, 41 | {17, "redis->getset"}, 42 | {18, "redis->incr"}, 43 | {19, "redis->incrby"}, 44 | {20, "redis->mget"}, 45 | {21, "redis->mset"}, 46 | {22, "redis->msetnx"}, 47 | {23, "redis->set"}, 48 | {24, "redis->setbit"}, 49 | {25, "redis->setex"}, 50 | {26, "redis->psetex"}, 51 | {27, "redis->setnx"}, 52 | {28, "redis->setrange"}, 53 | {29, "redis->strlen"}, 54 | {30, "redis->del"}, 55 | {31, "memcached->add"}, 56 | {32, "memcached->addbykey"}, 57 | {33, "memcached->append"}, 58 | {34, "memcached->appendbykey"}, 59 | {35, "memcached->cas"}, 60 | {36, "memcached->casbykey"}, 61 | {37, "memcached->decrement"}, 62 | {38, "memcached->decrementbykey"}, 63 | {39, "memcached->delete"}, 64 | {40, "memcached->deletebykey"}, 65 | {41, "memcached->deletemulti"}, 66 | {42, "memcached->deletemultibykey"}, 67 | {43, "memcached->flush"}, 68 | {44, "memcached->get"}, 69 | {45, "memcached->getallkeys"}, 70 | {46, "memcached->getbykey"}, 71 | {47, "memcached->getmulti"}, 72 | {48, "memcached->getmultibykey"}, 73 | {49, "memcached->increment"}, 74 | {50, "memcached->incrementbykey"}, 75 | {51, "memcached->prepend"}, 76 | {52, "memcached->prependbykey"}, 77 | {53, "memcached->replace"}, 78 | {54, "memcached->replacebykey"}, 79 | {55, "memcached->set"}, 80 | {56, "memcached->setbykey"}, 81 | {57, "memcached->setmulti"}, 82 | {58, "memcached->setmultibykey"}, 83 | }; 84 | const int handler_lookup_size = sizeof(handler_lookup); 85 | 86 | /* handlers count needs to be bigger than the number of handler_lookup entries */ 87 | #define ORIGINAL_HANDLERS_TO_ALLOCATE 60 88 | zif_handler original_handlers[ORIGINAL_HANDLERS_TO_ALLOCATE] = {NULL}; 89 | 90 | int setup_recording_for_internal_handlers() 91 | { 92 | zend_function *original_function; 93 | int handler_index; 94 | zend_class_entry *ce; 95 | 96 | SCOUT_OVERLOAD_FUNCTION("file_get_contents", scoutapm_default_handler) 97 | SCOUT_OVERLOAD_FUNCTION("file_put_contents", scoutapm_default_handler) 98 | #if HAVE_SCOUT_CURL 99 | SCOUT_OVERLOAD_FUNCTION("curl_setopt", scoutapm_curl_setopt_handler) 100 | SCOUT_OVERLOAD_FUNCTION("curl_exec", scoutapm_curl_exec_handler) 101 | #endif 102 | SCOUT_OVERLOAD_FUNCTION("fopen", scoutapm_fopen_handler) 103 | SCOUT_OVERLOAD_FUNCTION("fwrite", scoutapm_fwrite_handler) 104 | SCOUT_OVERLOAD_FUNCTION("fread", scoutapm_fread_handler) 105 | SCOUT_OVERLOAD_METHOD("pdo", "exec", scoutapm_default_handler) 106 | SCOUT_OVERLOAD_METHOD("pdo", "query", scoutapm_default_handler) 107 | SCOUT_OVERLOAD_METHOD("pdo", "prepare", scoutapm_pdo_prepare_handler) 108 | SCOUT_OVERLOAD_METHOD("pdostatement", "execute", scoutapm_pdostatement_execute_handler) 109 | 110 | SCOUT_OVERLOAD_METHOD("redis", "append", scoutapm_default_handler) 111 | SCOUT_OVERLOAD_METHOD("redis", "decr", scoutapm_default_handler) 112 | SCOUT_OVERLOAD_METHOD("redis", "decrby", scoutapm_default_handler) 113 | SCOUT_OVERLOAD_METHOD("redis", "get", scoutapm_default_handler) 114 | SCOUT_OVERLOAD_METHOD("redis", "getbit", scoutapm_default_handler) 115 | SCOUT_OVERLOAD_METHOD("redis", "getrange", scoutapm_default_handler) 116 | SCOUT_OVERLOAD_METHOD("redis", "getset", scoutapm_default_handler) 117 | SCOUT_OVERLOAD_METHOD("redis", "incr", scoutapm_default_handler) 118 | SCOUT_OVERLOAD_METHOD("redis", "incrby", scoutapm_default_handler) 119 | SCOUT_OVERLOAD_METHOD("redis", "mget", scoutapm_default_handler) 120 | SCOUT_OVERLOAD_METHOD("redis", "mset", scoutapm_default_handler) 121 | SCOUT_OVERLOAD_METHOD("redis", "msetnx", scoutapm_default_handler) 122 | SCOUT_OVERLOAD_METHOD("redis", "set", scoutapm_default_handler) 123 | SCOUT_OVERLOAD_METHOD("redis", "setbit", scoutapm_default_handler) 124 | SCOUT_OVERLOAD_METHOD("redis", "setex", scoutapm_default_handler) 125 | SCOUT_OVERLOAD_METHOD("redis", "psetex", scoutapm_default_handler) 126 | SCOUT_OVERLOAD_METHOD("redis", "setnx", scoutapm_default_handler) 127 | SCOUT_OVERLOAD_METHOD("redis", "setrange", scoutapm_default_handler) 128 | SCOUT_OVERLOAD_METHOD("redis", "strlen", scoutapm_default_handler) 129 | SCOUT_OVERLOAD_METHOD("redis", "del", scoutapm_default_handler) 130 | 131 | SCOUT_OVERLOAD_METHOD("memcached", "add", scoutapm_default_handler) 132 | SCOUT_OVERLOAD_METHOD("memcached", "addbykey", scoutapm_default_handler) 133 | SCOUT_OVERLOAD_METHOD("memcached", "append", scoutapm_default_handler) 134 | SCOUT_OVERLOAD_METHOD("memcached", "appendbykey", scoutapm_default_handler) 135 | SCOUT_OVERLOAD_METHOD("memcached", "cas", scoutapm_default_handler) 136 | SCOUT_OVERLOAD_METHOD("memcached", "casbykey", scoutapm_default_handler) 137 | SCOUT_OVERLOAD_METHOD("memcached", "decrement", scoutapm_default_handler) 138 | SCOUT_OVERLOAD_METHOD("memcached", "decrementbykey", scoutapm_default_handler) 139 | SCOUT_OVERLOAD_METHOD("memcached", "delete", scoutapm_default_handler) 140 | SCOUT_OVERLOAD_METHOD("memcached", "deletebykey", scoutapm_default_handler) 141 | SCOUT_OVERLOAD_METHOD("memcached", "deletemulti", scoutapm_default_handler) 142 | SCOUT_OVERLOAD_METHOD("memcached", "deletemultibykey", scoutapm_default_handler) 143 | SCOUT_OVERLOAD_METHOD("memcached", "flush", scoutapm_default_handler) 144 | SCOUT_OVERLOAD_METHOD("memcached", "get", scoutapm_default_handler) 145 | SCOUT_OVERLOAD_METHOD("memcached", "getallkeys", scoutapm_default_handler) 146 | SCOUT_OVERLOAD_METHOD("memcached", "getbykey", scoutapm_default_handler) 147 | SCOUT_OVERLOAD_METHOD("memcached", "getmulti", scoutapm_default_handler) 148 | SCOUT_OVERLOAD_METHOD("memcached", "getmultibykey", scoutapm_default_handler) 149 | SCOUT_OVERLOAD_METHOD("memcached", "increment", scoutapm_default_handler) 150 | SCOUT_OVERLOAD_METHOD("memcached", "incrementbykey", scoutapm_default_handler) 151 | SCOUT_OVERLOAD_METHOD("memcached", "prepend", scoutapm_default_handler) 152 | SCOUT_OVERLOAD_METHOD("memcached", "prependbykey", scoutapm_default_handler) 153 | SCOUT_OVERLOAD_METHOD("memcached", "replace", scoutapm_default_handler) 154 | SCOUT_OVERLOAD_METHOD("memcached", "replacebykey", scoutapm_default_handler) 155 | SCOUT_OVERLOAD_METHOD("memcached", "set", scoutapm_default_handler) 156 | SCOUT_OVERLOAD_METHOD("memcached", "setbykey", scoutapm_default_handler) 157 | SCOUT_OVERLOAD_METHOD("memcached", "setmulti", scoutapm_default_handler) 158 | SCOUT_OVERLOAD_METHOD("memcached", "setmultibykey", scoutapm_default_handler) 159 | 160 | return SUCCESS; 161 | } 162 | 163 | /* 164 | * This handles most recorded calls by grabbing all arguments (we treat it as a variadic), finding the "original" handler 165 | * for the function and calling it. Once the function is called, record how long it took. Some "special" calls (e.g. 166 | * curl_exec) have special handling because the arguments to curl_exec don't indicate the URL, for example. 167 | */ 168 | ZEND_NAMED_FUNCTION(scoutapm_default_handler) 169 | { 170 | int handler_index; 171 | double entered = scoutapm_microtime(); 172 | int argc; 173 | zval *argv = NULL; 174 | const char *called_function; 175 | 176 | SCOUT_PASSTHRU_IF_ALREADY_INSTRUMENTING(called_function) 177 | 178 | /* note - no strdup needed as we copy it in record_observed_stack_frame */ 179 | called_function = determine_function_name(execute_data); 180 | 181 | ZEND_PARSE_PARAMETERS_START(0, -1) 182 | Z_PARAM_VARIADIC(' ', argv, argc) 183 | ZEND_PARSE_PARAMETERS_END(); 184 | 185 | handler_index = handler_index_for_function(called_function); 186 | 187 | original_handlers[handler_index](INTERNAL_FUNCTION_PARAM_PASSTHRU); 188 | 189 | record_observed_stack_frame(called_function, entered, scoutapm_microtime(), argc, argv); 190 | free((void*) called_function); 191 | } 192 | 193 | int unchecked_handler_index_for_function(const char *function_to_lookup) 194 | { 195 | int i = 0; 196 | const char *current = handler_lookup[i].function_name; 197 | 198 | while (current) { 199 | if (strcasecmp(current, function_to_lookup) == 0) { 200 | if (handler_lookup[i].index >= ORIGINAL_HANDLERS_TO_ALLOCATE) { 201 | /* 202 | * Note: as this is done in startup, and a critical failure, php_error_docref or zend_throw_exception_ex 203 | * aren't suitable here, as they are "exceptions"; therefore the most informative thing we can do is 204 | * write a message directly, and return -1, capture this in the PHP_RINIT_FUNCTION and return FAILURE. 205 | * The -1 should be checked in SCOUT_OVERLOAD_CLASS_ENTRY_FUNCTION and SCOUT_OVERLOAD_FUNCTION 206 | */ 207 | php_printf("ScoutAPM overwrote a handler for '%s' but but we did not allocate enough original_handlers", function_to_lookup); 208 | return -1; 209 | } 210 | 211 | return handler_lookup[i].index; 212 | } 213 | current = handler_lookup[++i].function_name; 214 | } 215 | 216 | /* Practically speaking, this shouldn't happen as long as we defined the handlers properly */ 217 | zend_throw_exception_ex(NULL, 0, "ScoutAPM overwrote a handler for '%s' but did not have a handler lookup for it", function_to_lookup); 218 | return -1; 219 | } 220 | 221 | /* 222 | * In our handler_lookup, find what the "index" in our overridden handlers is for a particular function name 223 | */ 224 | int handler_index_for_function(const char *function_to_lookup) 225 | { 226 | int handler_index = unchecked_handler_index_for_function(function_to_lookup); 227 | 228 | if (original_handlers[handler_index] == NULL) { 229 | zend_throw_exception_ex(NULL, 0, "ScoutAPM overwrote a handler for '%s' but the handler for index '%d' was not defined", function_to_lookup, handler_lookup[handler_index].index); 230 | return -1; 231 | } 232 | 233 | return handler_index; 234 | } 235 | -------------------------------------------------------------------------------- /scout_internal_handlers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2021- 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #ifndef SCOUT_INTERNAL_HANDLERS_H 9 | #define SCOUT_INTERNAL_HANDLERS 10 | 11 | typedef struct _handler_lookup { 12 | int index; 13 | const char *function_name; 14 | } indexed_handler_lookup; 15 | 16 | /* overload a regular function by wrapping its handler with our own handler */ 17 | #define SCOUT_OVERLOAD_FUNCTION(function_name, handler_to_use) \ 18 | original_function = zend_hash_str_find_ptr(EG(function_table), function_name, sizeof(function_name) - 1); \ 19 | if (original_function != NULL) { \ 20 | handler_index = unchecked_handler_index_for_function(function_name); \ 21 | if (handler_index < 0) return FAILURE; \ 22 | original_handlers[handler_index] = original_function->internal_function.handler; \ 23 | original_function->internal_function.handler = handler_to_use; \ 24 | } 25 | 26 | /* Don't use this macro directly, use SCOUT_OVERLOAD_STATIC_METHOD or SCOUT_OVERLOAD_METHOD for consistency */ 27 | #define SCOUT_OVERLOAD_CLASS_ENTRY_FUNCTION(lowercase_class_name, instance_or_static, method_name, handler_to_use) \ 28 | ce = zend_hash_str_find_ptr(CG(class_table), lowercase_class_name, sizeof(lowercase_class_name) - 1); \ 29 | if (ce != NULL) { \ 30 | original_function = zend_hash_str_find_ptr(&ce->function_table, method_name, sizeof(method_name)-1); \ 31 | if (original_function != NULL) { \ 32 | handler_index = unchecked_handler_index_for_function(lowercase_class_name instance_or_static method_name); \ 33 | if (handler_index < 0) return FAILURE; \ 34 | original_handlers[handler_index] = original_function->internal_function.handler; \ 35 | original_function->internal_function.handler = handler_to_use; \ 36 | } \ 37 | } 38 | 39 | /* overload a static class method by wrapping its handler with our own handler */ 40 | #define SCOUT_OVERLOAD_STATIC_METHOD(lowercase_class_name, method_name, handler_to_use) SCOUT_OVERLOAD_CLASS_ENTRY_FUNCTION(lowercase_class_name, "::", method_name, handler_to_use) 41 | 42 | /* overload an instance class method by wrapping its handler with our own handler */ 43 | #define SCOUT_OVERLOAD_METHOD(lowercase_class_name, method_name, handler_to_use) SCOUT_OVERLOAD_CLASS_ENTRY_FUNCTION(lowercase_class_name, "->", method_name, handler_to_use) 44 | 45 | #define SCOUT_INTERNAL_FUNCTION_PASSTHRU(macro_pt_func_name) \ 46 | macro_pt_func_name = determine_function_name(execute_data); \ 47 | original_handlers[handler_index_for_function(macro_pt_func_name)](INTERNAL_FUNCTION_PARAM_PASSTHRU); \ 48 | free((void *) (macro_pt_func_name)) 49 | 50 | #define SCOUT_PASSTHRU_IF_ALREADY_INSTRUMENTING(macro_pt_func_name) \ 51 | if (SCOUTAPM_G(all_instrumentation_enabled) != 1 \ 52 | || SCOUTAPM_G(currently_instrumenting) == 1) { \ 53 | SCOUT_INTERNAL_FUNCTION_PASSTHRU(macro_pt_func_name); \ 54 | return; \ 55 | } 56 | 57 | #endif /* SCOUT_INTERNAL_HANDLERS_H */ 58 | -------------------------------------------------------------------------------- /scout_observer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2021 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #include "zend_scoutapm.h" 9 | #include "scout_extern.h" 10 | 11 | #if SCOUTAPM_INSTRUMENT_USING_OBSERVER_API == 1 12 | #include "Zend/zend_observer.h" 13 | #endif 14 | 15 | int setup_functions_for_observer_api() 16 | { 17 | #if SCOUTAPM_INSTRUMENT_USING_OBSERVER_API == 1 18 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "append"); 19 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "decr"); 20 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "decrBy"); 21 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "get"); 22 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "getBit"); 23 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "getRange"); 24 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "getSet"); 25 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "incr"); 26 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "incrBy"); 27 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "mGet"); 28 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "mSet"); 29 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "mSetNx"); 30 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "set"); 31 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "setBit"); 32 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "setEx"); 33 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "pSetEx"); 34 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "setNx"); 35 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "setRange"); 36 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "strlen"); 37 | ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Predis\\Client->__call", "del"); 38 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elasticsearch\\Client->index"); 39 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elasticsearch\\Client->get"); 40 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elasticsearch\\Client->search"); 41 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elasticsearch\\Client->delete"); 42 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elastic\\Elasticsearch\\Client->index"); 43 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elastic\\Elasticsearch\\Client->get"); 44 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elastic\\Elasticsearch\\Client->search"); 45 | ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH("Elastic\\Elasticsearch\\Client->delete"); 46 | #endif 47 | 48 | return SUCCESS; 49 | } 50 | 51 | #if SCOUTAPM_INSTRUMENT_USING_OBSERVER_API == 1 52 | 53 | static void scout_observer_begin(zend_execute_data *execute_data) 54 | { 55 | if (SCOUTAPM_G(all_instrumentation_enabled) != 1 || SCOUTAPM_G(currently_instrumenting)) { 56 | return; 57 | } 58 | 59 | SCOUTAPM_G(observer_api_start_time) = scoutapm_microtime(); 60 | SCOUTAPM_G(currently_instrumenting) = 1; 61 | } 62 | 63 | static void scout_observer_end(zend_execute_data *execute_data, zval *return_value) 64 | { 65 | char *function_name, *magic_function_name; 66 | size_t magic_function_name_len; 67 | int argc; 68 | zval *argv = NULL; 69 | 70 | if (SCOUTAPM_G(all_instrumentation_enabled) != 1 71 | || SCOUTAPM_G(currently_instrumenting) != 1 72 | || SCOUTAPM_G(observer_api_start_time) <= 0 73 | ) { 74 | return; // Possibly a weird situation? Not sure how we could get into this state 75 | } 76 | 77 | if (strcasecmp("__call", ZSTR_VAL(execute_data->func->common.function_name)) == 0) { 78 | // @todo would be nice to find a way to "unpack" the array here into argv 79 | ZEND_PARSE_PARAMETERS_START(1, -1) 80 | Z_PARAM_STRING(magic_function_name, magic_function_name_len) 81 | Z_PARAM_VARIADIC(' ', argv, argc) 82 | ZEND_PARSE_PARAMETERS_END(); 83 | 84 | DYNAMIC_MALLOC_SPRINTF(function_name, magic_function_name_len, "%s->%s", 85 | ZSTR_VAL(execute_data->func->common.scope->name), 86 | magic_function_name 87 | ); 88 | } else { 89 | function_name = (char*) determine_function_name(execute_data); 90 | 91 | ZEND_PARSE_PARAMETERS_START(0, -1) 92 | Z_PARAM_VARIADIC(' ', argv, argc) 93 | ZEND_PARSE_PARAMETERS_END(); 94 | } 95 | 96 | record_observed_stack_frame(function_name, SCOUTAPM_G(observer_api_start_time), scoutapm_microtime(), argc, argv); 97 | SCOUTAPM_G(currently_instrumenting) = 0; 98 | SCOUTAPM_G(observer_api_start_time) = 0; 99 | 100 | free((void*) function_name); 101 | } 102 | 103 | // Note: this is only called FIRST time each function is invoked (better that way) 104 | zend_observer_fcall_handlers scout_observer_api_register(zend_execute_data *execute_data) 105 | { 106 | const char *function_name; 107 | 108 | zend_observer_fcall_handlers handlers = {NULL, NULL}; 109 | 110 | if (execute_data->func == NULL || execute_data->func->common.function_name == NULL) { 111 | return handlers; 112 | } 113 | 114 | function_name = determine_function_name(execute_data); 115 | 116 | // Can only resolve magic method name at call-time with Observer API, so pass NULL for the magic method name 117 | if (should_be_instrumented(function_name, NULL) == 0) { 118 | free((void*) function_name); 119 | return handlers; 120 | } 121 | 122 | free((void*) function_name); 123 | 124 | handlers.begin = scout_observer_begin; 125 | handlers.end = scout_observer_end; 126 | return handlers; 127 | } 128 | #endif 129 | 130 | void register_scout_observer() 131 | { 132 | #if SCOUTAPM_INSTRUMENT_USING_OBSERVER_API == 1 133 | zend_observer_fcall_register(scout_observer_api_register); 134 | #endif 135 | } 136 | -------------------------------------------------------------------------------- /scout_pdo_wrapper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2019- 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #include "zend_scoutapm.h" 9 | #include "scout_extern.h" 10 | 11 | ZEND_NAMED_FUNCTION(scoutapm_pdo_prepare_handler) 12 | { 13 | zval *statement; 14 | const char *passthru_function_name, *class_instance_id; 15 | 16 | SCOUT_PASSTHRU_IF_ALREADY_INSTRUMENTING(passthru_function_name) 17 | 18 | ZEND_PARSE_PARAMETERS_START(1, 10) 19 | Z_PARAM_ZVAL(statement) 20 | SCOUT_ZEND_PARSE_PARAMETERS_END(); 21 | 22 | SCOUT_INTERNAL_FUNCTION_PASSTHRU(passthru_function_name); 23 | 24 | if (Z_TYPE_P(return_value) != IS_OBJECT) { 25 | return; 26 | } 27 | 28 | class_instance_id = unique_class_instance_id(return_value); 29 | record_arguments_for_call(class_instance_id, 1, statement); 30 | free((void*) class_instance_id); 31 | } 32 | 33 | ZEND_NAMED_FUNCTION(scoutapm_pdostatement_execute_handler) 34 | { 35 | int handler_index; 36 | double entered = scoutapm_microtime(); 37 | const char *called_function, *class_instance_id; 38 | zend_long recorded_arguments_index; 39 | 40 | SCOUT_PASSTHRU_IF_ALREADY_INSTRUMENTING(called_function) 41 | 42 | called_function = determine_function_name(execute_data); 43 | 44 | handler_index = handler_index_for_function(called_function); 45 | 46 | class_instance_id = unique_class_instance_id(getThis()); 47 | recorded_arguments_index = find_index_for_recorded_arguments(class_instance_id); 48 | free((void*) class_instance_id); 49 | 50 | if (recorded_arguments_index < 0) { 51 | free((void*) called_function); 52 | scoutapm_default_handler(INTERNAL_FUNCTION_PARAM_PASSTHRU); 53 | return; 54 | } 55 | 56 | original_handlers[handler_index](INTERNAL_FUNCTION_PARAM_PASSTHRU); 57 | 58 | record_observed_stack_frame( 59 | called_function, 60 | entered, 61 | scoutapm_microtime(), 62 | SCOUTAPM_G(disconnected_call_argument_store)[recorded_arguments_index].argc, 63 | SCOUTAPM_G(disconnected_call_argument_store)[recorded_arguments_index].argv 64 | ); 65 | free((void*) called_function); 66 | } 67 | -------------------------------------------------------------------------------- /scout_recording.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2021 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #include "zend_scoutapm.h" 9 | #include "scout_extern.h" 10 | 11 | void add_function_to_instrumentation(const char *function_name, const char *magic_method_name) 12 | { 13 | if (SCOUTAPM_G(num_instrumented_functions) >= MAX_INSTRUMENTED_FUNCTIONS) { 14 | zend_throw_exception_ex(NULL, 0, "Unable to add instrumentation for function '%s' - MAX_INSTRUMENTED_FUNCTIONS of %d reached", function_name, MAX_INSTRUMENTED_FUNCTIONS); 15 | return; 16 | } 17 | 18 | SCOUTAPM_G(instrumented_function_names)[SCOUTAPM_G(num_instrumented_functions)].function_name = strdup(function_name); 19 | if (magic_method_name != NULL) { 20 | SCOUTAPM_G(instrumented_function_names)[SCOUTAPM_G(num_instrumented_functions)].magic_method_name = strdup(magic_method_name); 21 | } 22 | SCOUTAPM_G(num_instrumented_functions)++; 23 | } 24 | 25 | int should_be_instrumented(const char *function_name, const char *magic_method_name) 26 | { 27 | int i = 0; 28 | for (; i < SCOUTAPM_G(num_instrumented_functions); i++) { 29 | if (strcasecmp(function_name, SCOUTAPM_G(instrumented_function_names)[i].function_name) == 0) { 30 | if (magic_method_name == NULL 31 | || strcasecmp(magic_method_name, SCOUTAPM_G(instrumented_function_names)[i].magic_method_name) == 0 32 | ) { 33 | return 1; 34 | } 35 | } 36 | } 37 | return 0; 38 | } 39 | 40 | void allocate_stack_frames_for_request() 41 | { 42 | SCOUTAPM_DEBUG_MESSAGE("Initialising stacks..."); 43 | SCOUTAPM_G(observed_stack_frames_count) = 0; 44 | SCOUTAPM_G(observed_stack_frames) = calloc(0, sizeof(scoutapm_stack_frame)); 45 | SCOUTAPM_G(disconnected_call_argument_store_count) = 0; 46 | SCOUTAPM_G(disconnected_call_argument_store) = calloc(0, sizeof(scoutapm_disconnected_call_argument_store)); 47 | SCOUTAPM_DEBUG_MESSAGE("Stacks made\n"); 48 | 49 | SCOUTAPM_G(currently_instrumenting) = 0; 50 | SCOUTAPM_G(num_instrumented_functions) = 0; 51 | } 52 | 53 | void free_instrumented_function_names_list() 54 | { 55 | int i; 56 | 57 | for (i = 0; i < SCOUTAPM_G(num_instrumented_functions); i++) { 58 | if (SCOUTAPM_G(instrumented_function_names)[i].magic_method_name != NULL) { 59 | free((void *) SCOUTAPM_G(instrumented_function_names)[i].magic_method_name); 60 | } 61 | free((void*) SCOUTAPM_G(instrumented_function_names)[i].function_name); 62 | } 63 | SCOUTAPM_G(num_instrumented_functions) = 0; 64 | } 65 | 66 | void free_observed_stack_frames() 67 | { 68 | int i, j; 69 | 70 | SCOUTAPM_DEBUG_MESSAGE("Freeing stacks... "); 71 | 72 | for (i = 0; i < SCOUTAPM_G(observed_stack_frames_count); i++) { 73 | for (j = 0; j < SCOUTAPM_G(observed_stack_frames)[i].argc; j++) { 74 | zval_ptr_dtor(&(SCOUTAPM_G(observed_stack_frames)[i].argv[j])); 75 | } 76 | free(SCOUTAPM_G(observed_stack_frames)[i].argv); 77 | free((void*)SCOUTAPM_G(observed_stack_frames)[i].function_name); 78 | } 79 | 80 | if (SCOUTAPM_G(observed_stack_frames)) { 81 | free(SCOUTAPM_G(observed_stack_frames)); 82 | } 83 | SCOUTAPM_G(observed_stack_frames_count) = 0; 84 | 85 | SCOUTAPM_DEBUG_MESSAGE("Stacks freed\n"); 86 | } 87 | 88 | void free_recorded_call_arguments() 89 | { 90 | zend_long i, j; 91 | 92 | for (i = 0; i < SCOUTAPM_G(disconnected_call_argument_store_count); i++) { 93 | free((void*)SCOUTAPM_G(disconnected_call_argument_store)[i].reference); 94 | 95 | for (j = 0; j < SCOUTAPM_G(disconnected_call_argument_store)[i].argc; j++) { 96 | zval_ptr_dtor(&SCOUTAPM_G(disconnected_call_argument_store)[i].argv[j]); 97 | } 98 | free(SCOUTAPM_G(disconnected_call_argument_store)[i].argv); 99 | } 100 | 101 | free(SCOUTAPM_G(disconnected_call_argument_store)); 102 | SCOUTAPM_G(disconnected_call_argument_store_count) = 0; 103 | } 104 | 105 | void record_arguments_for_call(const char *call_reference, int argc, zval *argv) 106 | { 107 | zend_long i = 0; 108 | 109 | if (SCOUTAPM_G(all_instrumentation_enabled) != 1) { 110 | return; 111 | } 112 | 113 | SCOUTAPM_G(disconnected_call_argument_store) = realloc( 114 | SCOUTAPM_G(disconnected_call_argument_store), 115 | (SCOUTAPM_G(disconnected_call_argument_store_count)+1) * sizeof(scoutapm_disconnected_call_argument_store) 116 | ); 117 | 118 | SCOUTAPM_G(disconnected_call_argument_store)[SCOUTAPM_G(disconnected_call_argument_store_count)].reference = strdup(call_reference); 119 | SCOUTAPM_G(disconnected_call_argument_store)[SCOUTAPM_G(disconnected_call_argument_store_count)].argc = argc; 120 | SCOUTAPM_G(disconnected_call_argument_store)[SCOUTAPM_G(disconnected_call_argument_store_count)].argv = calloc(argc, sizeof(zval)); 121 | 122 | for(; i < argc; i++) { 123 | safely_copy_argument_zval_as_scalar( 124 | &argv[i], 125 | &SCOUTAPM_G(disconnected_call_argument_store)[SCOUTAPM_G(disconnected_call_argument_store_count)].argv[i] 126 | ); 127 | } 128 | 129 | SCOUTAPM_G(disconnected_call_argument_store_count)++; 130 | } 131 | 132 | zend_long find_index_for_recorded_arguments(const char *call_reference) 133 | { 134 | zend_long i = 0; 135 | for (; i < SCOUTAPM_G(disconnected_call_argument_store_count); i++) { 136 | if (SCOUTAPM_G(disconnected_call_argument_store)[i].reference 137 | && strcasecmp( 138 | SCOUTAPM_G(disconnected_call_argument_store)[i].reference, 139 | call_reference 140 | ) == 0 141 | ) { 142 | return i; 143 | } 144 | } 145 | 146 | #if SCOUT_APM_EXT_DEBUGGING == 1 147 | php_error_docref("", E_NOTICE, "ScoutAPM could not determine arguments for this call"); 148 | #endif 149 | 150 | return -1; 151 | } 152 | 153 | /* 154 | * Helper function to handle memory allocation for recorded stack frames. Called each time a function has completed 155 | * that we're interested in. 156 | */ 157 | void record_observed_stack_frame(const char *function_name, double microtime_entered, double microtime_exited, int argc, zval *argv) 158 | { 159 | int i; 160 | 161 | if (SCOUTAPM_G(all_instrumentation_enabled) != 1) { 162 | return; 163 | } 164 | 165 | if (argc > 0) { 166 | SCOUTAPM_DEBUG_MESSAGE("Adding observed stack frame for %s (%s) ... ", function_name, Z_STRVAL(argv[0])); 167 | } else { 168 | SCOUTAPM_DEBUG_MESSAGE("Adding observed stack frame for %s ... ", function_name); 169 | } 170 | SCOUTAPM_G(observed_stack_frames) = realloc( 171 | SCOUTAPM_G(observed_stack_frames), 172 | (SCOUTAPM_G(observed_stack_frames_count)+1) * sizeof(scoutapm_stack_frame) 173 | ); 174 | 175 | SCOUTAPM_G(observed_stack_frames)[SCOUTAPM_G(observed_stack_frames_count)].function_name = strdup(function_name); 176 | SCOUTAPM_G(observed_stack_frames)[SCOUTAPM_G(observed_stack_frames_count)].entered = microtime_entered; 177 | SCOUTAPM_G(observed_stack_frames)[SCOUTAPM_G(observed_stack_frames_count)].exited = microtime_exited; 178 | SCOUTAPM_G(observed_stack_frames)[SCOUTAPM_G(observed_stack_frames_count)].argc = argc; 179 | SCOUTAPM_G(observed_stack_frames)[SCOUTAPM_G(observed_stack_frames_count)].argv = calloc(argc, sizeof(zval)); 180 | 181 | for (i = 0; i < argc; i++) { 182 | safely_copy_argument_zval_as_scalar( 183 | &(argv[i]), 184 | &(SCOUTAPM_G(observed_stack_frames)[SCOUTAPM_G(observed_stack_frames_count)].argv[i]) 185 | ); 186 | } 187 | 188 | SCOUTAPM_G(observed_stack_frames_count)++; 189 | SCOUTAPM_DEBUG_MESSAGE("Done\n"); 190 | } 191 | -------------------------------------------------------------------------------- /scout_recording.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2021- 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #ifndef SCOUTAPM_SCOUT_RECORDING_H 9 | #define SCOUTAPM_SCOUT_RECORDING_H 10 | 11 | #include 12 | 13 | /* Describes information we store about a recorded stack frame */ 14 | typedef struct _scoutapm_stack_frame { 15 | const char *function_name; 16 | double entered; 17 | double exited; 18 | int argc; 19 | zval *argv; 20 | } scoutapm_stack_frame; 21 | 22 | typedef struct _scoutapm_disconnected_call_argument_store { 23 | const char *reference; 24 | int argc; 25 | zval *argv; 26 | } scoutapm_disconnected_call_argument_store; 27 | 28 | typedef struct _scoutapm_instrumented_function { 29 | const char *function_name; 30 | const char *magic_method_name; 31 | } scoutapm_instrumented_function; 32 | 33 | #define ADD_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH(function_name) \ 34 | zend_try { \ 35 | add_function_to_instrumentation(function_name, NULL); \ 36 | } zend_catch { \ 37 | php_printf("ScoutAPM tried instrumenting '%s' - increase MAX_INSTRUMENTED_FUNCTIONS", function_name); \ 38 | return FAILURE; \ 39 | } zend_end_try() 40 | 41 | /** 42 | * NOTE: magic method definitions are ONLY needed for Observer API instrumentation. The execute_data in zend_execte_ex 43 | * gives us the "called" method name (e.g. "MyObj->myMethod" instead of "MyObj->__call") so you don't need to use this 44 | * macro for defining methods in zend_execute_ex. 45 | */ 46 | #define ADD_MAGIC_FUNCTION_TO_INSTRUMENTATION_SAFE_CATCH(function_name, magic_method_function) \ 47 | zend_try { \ 48 | add_function_to_instrumentation(function_name, magic_method_function); \ 49 | } zend_catch { \ 50 | php_printf("ScoutAPM tried instrumenting '%s' - increase MAX_INSTRUMENTED_FUNCTIONS", function_name); \ 51 | return FAILURE; \ 52 | } zend_end_try() 53 | 54 | #endif //SCOUTAPM_SCOUT_RECORDING_H 55 | -------------------------------------------------------------------------------- /scout_utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2021 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #include "zend_scoutapm.h" 9 | #include "scout_extern.h" 10 | #include 11 | #include 12 | #include 13 | 14 | /* 15 | * Given some zend_execute_data, figure out what the function/method/static method is being called. The convention used 16 | * is `ClassName::methodName` for static methods, `ClassName->methodName` for instance methods, and `functionName` for 17 | * regular functions. 18 | * 19 | * Result must ALWAYS be free'd, or you'll leak memory. 20 | */ 21 | const char* determine_function_name(zend_execute_data *execute_data) 22 | { 23 | int len; 24 | char *ret; 25 | 26 | if (!execute_data->func) { 27 | return strdup(""); 28 | } 29 | 30 | if (execute_data->func->common.scope && execute_data->func->common.fn_flags & ZEND_ACC_STATIC) { 31 | DYNAMIC_MALLOC_SPRINTF(ret, len, "%s::%s", 32 | ZSTR_VAL(Z_CE(execute_data->This)->name), 33 | ZSTR_VAL(execute_data->func->common.function_name) 34 | ); 35 | return ret; 36 | } 37 | 38 | if (Z_TYPE(execute_data->This) == IS_OBJECT) { 39 | DYNAMIC_MALLOC_SPRINTF(ret, len, "%s->%s", 40 | ZSTR_VAL(execute_data->func->common.scope->name), 41 | ZSTR_VAL(execute_data->func->common.function_name) 42 | ); 43 | return ret; 44 | } 45 | 46 | return strdup(ZSTR_VAL(execute_data->func->common.function_name)); 47 | } 48 | 49 | /* 50 | * Using gettimeofday, determine the time using microsecond precision, and return as a double. 51 | * @todo consider using HR monotonic time? https://github.com/scoutapp/scout-apm-php-ext/issues/23 52 | */ 53 | double scoutapm_microtime() 54 | { 55 | struct timeval tp = {0}; 56 | if (gettimeofday(&tp, NULL)) { 57 | zend_throw_exception_ex(zend_ce_exception, 0, "Could not call gettimeofday"); 58 | return 0; 59 | } 60 | return (double)(tp.tv_sec + tp.tv_usec / 1000000.00); 61 | } 62 | 63 | void safely_copy_argument_zval_as_scalar(zval *original_to_copy, zval *destination) 64 | { 65 | int len, should_free = 0; 66 | char *ret; 67 | 68 | reference_retry_point: 69 | switch (Z_TYPE_P(original_to_copy)) { 70 | case IS_NULL: 71 | case IS_TRUE: 72 | case IS_FALSE: 73 | case IS_LONG: 74 | case IS_DOUBLE: 75 | case IS_STRING: 76 | ZVAL_COPY(destination, original_to_copy); 77 | return; 78 | case IS_REFERENCE: 79 | original_to_copy = Z_REFVAL_P(original_to_copy); 80 | goto reference_retry_point; 81 | case IS_ARRAY: 82 | /** 83 | * @todo improvement for future; copy array but only if it contains scalar 84 | * @link https://github.com/scoutapp/scout-apm-php-ext/issues/76 85 | */ 86 | ret = (char*)"(array)"; 87 | break; 88 | case IS_RESOURCE: 89 | if (strcasecmp("stream-context", zend_rsrc_list_get_rsrc_type(Z_RES_P(original_to_copy))) == 0) { 90 | php_stream_context *stream_context = zend_fetch_resource_ex(original_to_copy, NULL, php_le_stream_context()); 91 | if (stream_context != NULL) { 92 | #if PHP_VERSION_ID < 80000 93 | /* ext/json can be shared */ 94 | zval args[1], jsonenc; 95 | 96 | ZVAL_STRINGL(&jsonenc, "json_encode", sizeof("json_encode")-1); 97 | args[0] = stream_context->options; 98 | if (FAILURE == call_user_function(EG(function_table), NULL, &jsonenc, destination, 1, args)) { 99 | ZVAL_NULL(destination); 100 | } 101 | zval_ptr_dtor(&jsonenc); 102 | #else 103 | /* ext/json is always there */ 104 | smart_str json_encode_string_buffer = {0}; 105 | php_json_encode(&json_encode_string_buffer, &stream_context->options, 0); 106 | smart_str_0(&json_encode_string_buffer); 107 | ZVAL_STR_COPY(destination, json_encode_string_buffer.s); 108 | smart_str_free(&json_encode_string_buffer); 109 | #endif 110 | return; 111 | } 112 | } 113 | 114 | should_free = 1; 115 | #if PHP_VERSION_ID >= 80100 116 | DYNAMIC_MALLOC_SPRINTF(ret, len, 117 | "resource(%ld)", 118 | Z_RES_HANDLE_P(original_to_copy) 119 | ); 120 | #else 121 | DYNAMIC_MALLOC_SPRINTF(ret, len, 122 | "resource(%d)", 123 | Z_RES_HANDLE_P(original_to_copy) 124 | ); 125 | #endif 126 | break; 127 | case IS_OBJECT: 128 | #if PHP_MAJOR_VERSION == 7 && PHP_MINOR_VERSION == 1 129 | /** 130 | * Workaround for memory leak with the DYNAMIC_MALLOC_SPRINTF in PHP 7.1 131 | * @link https://github.com/scoutapp/scout-apm-php-ext/issues/81 132 | */ 133 | ret = (char*)"object"; 134 | #else 135 | should_free = 1; 136 | DYNAMIC_MALLOC_SPRINTF(ret, len, 137 | "object(%s)", 138 | ZSTR_VAL(Z_OBJ_HT_P(original_to_copy)->get_class_name(Z_OBJ_P(original_to_copy))) 139 | ); 140 | #endif // PHP_MAJOR_VERSION == 7 && PHP_MINOR_VERSION == 1 141 | break; 142 | default: 143 | ret = (char*)"(unknown)"; 144 | } 145 | 146 | ZVAL_STRING(destination, ret); 147 | 148 | if (should_free) { 149 | free((void*) ret); 150 | } 151 | } 152 | 153 | /** Always free the result from this */ 154 | const char *zval_type_and_value_if_possible(zval *val) 155 | { 156 | int len; 157 | char *ret; 158 | 159 | reference_retry_point: 160 | switch (Z_TYPE_P(val)) { 161 | case IS_NULL: 162 | return strdup("null"); 163 | case IS_TRUE: 164 | return strdup("bool(true)"); 165 | case IS_FALSE: 166 | return strdup("bool(false)"); 167 | case IS_LONG: 168 | DYNAMIC_MALLOC_SPRINTF(ret, len, 169 | "int(%ld)", 170 | Z_LVAL_P(val) 171 | ); 172 | return ret; 173 | case IS_DOUBLE: 174 | DYNAMIC_MALLOC_SPRINTF(ret, len, 175 | "float(%g)", 176 | Z_DVAL_P(val) 177 | ); 178 | return ret; 179 | case IS_STRING: 180 | DYNAMIC_MALLOC_SPRINTF(ret, len, 181 | "string(%zd, \"%s\")", 182 | Z_STRLEN_P(val), 183 | Z_STRVAL_P(val) 184 | ); 185 | return ret; 186 | case IS_RESOURCE: 187 | #if PHP_VERSION_ID >= 80100 188 | DYNAMIC_MALLOC_SPRINTF(ret, len, 189 | "resource(%ld)", 190 | Z_RES_HANDLE_P(val) 191 | ); 192 | #else 193 | DYNAMIC_MALLOC_SPRINTF(ret, len, 194 | "resource(%d)", 195 | Z_RES_HANDLE_P(val) 196 | ); 197 | #endif 198 | return ret; 199 | case IS_ARRAY: 200 | return strdup("array"); 201 | case IS_OBJECT: 202 | DYNAMIC_MALLOC_SPRINTF(ret, len, 203 | "object(%s)", 204 | ZSTR_VAL(Z_OBJ_HT_P(val)->get_class_name(Z_OBJ_P(val))) 205 | ); 206 | return ret; 207 | case IS_REFERENCE: 208 | val = Z_REFVAL_P(val); 209 | goto reference_retry_point; 210 | default: 211 | return strdup("(unknown)"); 212 | } 213 | } 214 | 215 | /** Always free the result from this */ 216 | const char *unique_resource_id(const char *scout_wrapper_type, zval *resource_id) 217 | { 218 | int len; 219 | char *ret; 220 | const char *zval_type_as_string; 221 | 222 | if (Z_TYPE_P(resource_id) != IS_RESOURCE) { 223 | zval_type_as_string = zval_type_and_value_if_possible(resource_id); 224 | zend_throw_exception_ex(NULL, 0, "ScoutAPM ext expected a resource, received: %s", zval_type_as_string); 225 | free((void*) zval_type_as_string); 226 | return ""; 227 | } 228 | 229 | #if PHP_VERSION_ID >= 80100 230 | DYNAMIC_MALLOC_SPRINTF(ret, len, 231 | "%s_handle(%ld)_type(%d)", 232 | scout_wrapper_type, 233 | Z_RES_HANDLE_P(resource_id), 234 | Z_RES_TYPE_P(resource_id) 235 | ); 236 | #else 237 | DYNAMIC_MALLOC_SPRINTF(ret, len, 238 | "%s_handle(%d)_type(%d)", 239 | scout_wrapper_type, 240 | Z_RES_HANDLE_P(resource_id), 241 | Z_RES_TYPE_P(resource_id) 242 | ); 243 | #endif 244 | return ret; 245 | } 246 | 247 | /** Always free the result from this */ 248 | const char *unique_class_instance_id(zval *class_instance) 249 | { 250 | int len; 251 | char *ret; 252 | const char *zval_type_as_string; 253 | 254 | if (Z_TYPE_P(class_instance) != IS_OBJECT) { 255 | zval_type_as_string = zval_type_and_value_if_possible(class_instance); 256 | zend_throw_exception_ex(NULL, 0, "ScoutAPM ext expected an object, received: %s", zval_type_as_string); 257 | free((void*) zval_type_as_string); 258 | return ""; 259 | } 260 | 261 | DYNAMIC_MALLOC_SPRINTF(ret, len, 262 | "class(%s)_instance(%d)", 263 | ZSTR_VAL(Z_OBJ_HT_P(class_instance)->get_class_name(Z_OBJ_P(class_instance))), 264 | Z_OBJ_HANDLE_P(class_instance) 265 | ); 266 | 267 | return ret; 268 | } 269 | 270 | /** 271 | * This function wraps PHP's implementation of str_replace so we don't have to re-implement the same mechanism :) 272 | */ 273 | const char *scout_str_replace(const char *search, const char *replace, const char *subject) 274 | { 275 | zval args[3]; 276 | zval retval, func; 277 | const char *replaced_string; 278 | 279 | ZVAL_STRING(&func, "str_replace"); 280 | 281 | ZVAL_STRING(&args[0], search); 282 | ZVAL_STRING(&args[1], replace); 283 | ZVAL_STRING(&args[2], subject); 284 | 285 | call_user_function(EG(function_table), NULL, &func, &retval, 3, args); 286 | 287 | // Only return strings - if something went wrong, return the original subject 288 | if (Z_TYPE(retval) != IS_STRING) { 289 | return subject; 290 | } 291 | 292 | replaced_string = strdup(Z_STRVAL(retval)); 293 | 294 | zval_ptr_dtor(&args[0]); 295 | zval_ptr_dtor(&args[1]); 296 | zval_ptr_dtor(&args[2]); 297 | zval_ptr_dtor(&func); 298 | zval_ptr_dtor(&retval); 299 | 300 | return replaced_string; 301 | } 302 | -------------------------------------------------------------------------------- /tests/001-check-ext-loaded.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Check Scout APM extension is loaded 3 | --FILE-- 4 | 16 | --EXPECTF-- 17 | string(%d) " with scoutapm v%s, Copyright %d, by Scout APM" 18 | bool(true) 19 | -------------------------------------------------------------------------------- /tests/002-file_get_contents.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Calls to file_get_contents are logged 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | $call['entered']); 19 | var_dump($call['argv']); 20 | ?> 21 | --EXPECTF-- 22 | bool(true) 23 | string(17) "file_get_contents" 24 | float(%f) 25 | float(%f) 26 | float(%f) 27 | bool(true) 28 | array(1) { 29 | [0]=> 30 | string(%d) "%s%etests%e002-file_get_contents.php" 31 | } 32 | -------------------------------------------------------------------------------- /tests/003-scoutapm_get_calls-clears-calls-list.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Ensures that calls to scoutapm_get_calls() clears the call list 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 15 | --EXPECT-- 16 | int(1) 17 | int(0) 18 | int(1) 19 | int(0) 20 | -------------------------------------------------------------------------------- /tests/004-namespaced-fgc-is-not-logged.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Calls to file_get_contents are logged 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 41 | --EXPECTF-- 42 | bool(true) 43 | should not be logged: function defined in namespace, call implicitly resolves to namespaced function 44 | array(0) { 45 | } 46 | should not be logged - explicit, unimported call to another namespace 47 | array(0) { 48 | } 49 | should not be logged - imported call resolves to namespaced function 50 | array(0) { 51 | } 52 | SHOULD be logged 53 | array(1) { 54 | [0]=> 55 | array(5) { 56 | ["function"]=> 57 | string(17) "file_get_contents" 58 | ["entered"]=> 59 | float(%f) 60 | ["exited"]=> 61 | float(%f) 62 | ["time_taken"]=> 63 | float(%f) 64 | ["argv"]=> 65 | array(1) { 66 | [0]=> 67 | string(%d) "%s%etests%e004-namespaced-fgc-is-not-logged.php" 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/005-requiring-external-files-handled.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Requires to external files do not crash 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 11 | --EXPECTF-- 12 | External file has been included. 13 | End. 14 | -------------------------------------------------------------------------------- /tests/006-anonymous-classes-handled.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Executing a closure/anonymous function does not crash 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 14 | --EXPECTF-- 15 | Anonymous function called. 16 | End. 17 | -------------------------------------------------------------------------------- /tests/007-evaled-code-handled.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Running evaled code does not crash 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 11 | --EXPECTF-- 12 | Evaled code called. 13 | End. 14 | -------------------------------------------------------------------------------- /tests/008-class-with-no-constructor-call-handled.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Class without a defined constructor does not crash 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 12 | --EXPECTF-- 13 | End. 14 | -------------------------------------------------------------------------------- /tests/009-curl_exec.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Calls to curl_exec are logged 3 | --SKIPIF-- 4 | 5 | 6 | --FILE-- 7 | $call['entered']); 24 | var_dump($call['argv']); 25 | ?> 26 | --EXPECTF-- 27 | bool(true) 28 | string(9) "curl_exec" 29 | float(%f) 30 | float(%f) 31 | float(%f) 32 | bool(true) 33 | array(%d) { 34 | [%d]=> 35 | string(%d) "file://%s%etests%e009-curl_exec.php" 36 | [%d]=> 37 | NULL 38 | [%d]=> 39 | NULL 40 | } 41 | -------------------------------------------------------------------------------- /tests/010-fwrite-fread-fopen.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Calls to fwrite and fread are logged with handle from fopen() 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 27 | --EXPECTF-- 28 | bool(true) 29 | bool(true) 30 | fread/fwrite test 31 | string(6) "fwrite" 32 | array(2) { 33 | [0]=> 34 | string(%d) "%s" 35 | [1]=> 36 | string(%d) "w+" 37 | } 38 | string(5) "fread" 39 | array(2) { 40 | [0]=> 41 | string(%d) "%s" 42 | [1]=> 43 | string(%d) "w+" 44 | } 45 | -------------------------------------------------------------------------------- /tests/010-fwrite-fread-tmpfile.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Calls to fwrite and fread are logged with handle from tmpfile() 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 27 | --EXPECTF-- 28 | bool(true) 29 | bool(true) 30 | fread/fwrite test 31 | string(6) "fwrite" 32 | array(2) { 33 | [0]=> 34 | string(%d) "resource(%d)" 35 | [1]=> 36 | string(18) "fread/fwrite test 37 | " 38 | } 39 | string(5) "fread" 40 | array(2) { 41 | [0]=> 42 | string(%d) "resource(%d)" 43 | [1]=> 44 | int(18) 45 | } 46 | -------------------------------------------------------------------------------- /tests/011-pdo-exec.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Calls to PDO::exec are logged 3 | --SKIPIF-- 4 | 5 | 6 | 7 | --FILE-- 8 | exec', scoutapm_list_instrumented_functions())); 11 | scoutapm_enable_instrumentation(true); 12 | 13 | $dbh = new PDO('sqlite::memory:'); 14 | $dbh->exec("CREATE TABLE foo (col INT PRIMARY KEY)"); 15 | $dbh->exec("INSERT INTO foo (col) VALUES (1), (2) "); 16 | 17 | $calls = scoutapm_get_calls(); 18 | var_dump($calls[0]['function']); 19 | var_dump($calls[0]['argv'][0]); 20 | var_dump($calls[1]['function']); 21 | var_dump($calls[1]['argv'][0]); 22 | ?> 23 | --EXPECTF-- 24 | bool(true) 25 | string(9) "PDO->exec" 26 | string(38) "CREATE TABLE foo (col INT PRIMARY KEY)" 27 | string(9) "PDO->exec" 28 | string(38) "INSERT INTO foo (col) VALUES (1), (2) " 29 | -------------------------------------------------------------------------------- /tests/011-pdo-query.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Calls to PDO::query are logged 3 | --SKIPIF-- 4 | 5 | 6 | 7 | --FILE-- 8 | query', scoutapm_list_instrumented_functions())); 11 | scoutapm_enable_instrumentation(true); 12 | 13 | $dbh = new PDO('sqlite::memory:'); 14 | $stmt = $dbh->query("SELECT cast(1 + 2 AS text) AS result"); 15 | var_dump($stmt->fetch(PDO::FETCH_ASSOC)); 16 | 17 | $calls = scoutapm_get_calls(); 18 | var_dump($calls[0]['function']); 19 | var_dump($calls[0]['argv'][0]); 20 | ?> 21 | --EXPECTF-- 22 | bool(true) 23 | array(%d) { 24 | ["result"]=> 25 | string(%d) "3" 26 | } 27 | string(%d) "PDO->query" 28 | string(%d) "SELECT cast(1 + 2 AS text) AS result" 29 | -------------------------------------------------------------------------------- /tests/011-pdostatement-execute-pdo-prepare.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Calls to PDOStatement::execute are logged when created from PDO->prepare() 3 | --SKIPIF-- 4 | 5 | 6 | 7 | --FILE-- 8 | prepare', scoutapm_list_instrumented_functions())); 11 | var_dump(in_array('pdostatement->execute', scoutapm_list_instrumented_functions())); 12 | scoutapm_enable_instrumentation(true); 13 | 14 | $dbh = new PDO('sqlite::memory:'); 15 | $stmt1 = $dbh->prepare("SELECT 1 + 2"); 16 | $stmt2 = $dbh->prepare("SELECT 3 + 4"); 17 | $stmt1->execute(); 18 | $stmt2->execute(); 19 | 20 | $calls = scoutapm_get_calls(); 21 | var_dump($calls[0]['function']); 22 | var_dump($calls[0]['argv']); 23 | var_dump($calls[1]['function']); 24 | var_dump($calls[1]['argv']); 25 | ?> 26 | --EXPECTF-- 27 | bool(true) 28 | bool(true) 29 | string(21) "PDOStatement->execute" 30 | array(1) { 31 | [0]=> 32 | string(%d) "SELECT 1 + 2" 33 | } 34 | string(21) "PDOStatement->execute" 35 | array(1) { 36 | [0]=> 37 | string(%d) "SELECT 3 + 4" 38 | } 39 | -------------------------------------------------------------------------------- /tests/012-file_put_contents.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Calls to file_put_contents are logged 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 19 | --EXPECTF-- 20 | bool(true) 21 | string(12) "test content" 22 | string(17) "file_put_contents" 23 | array(2) { 24 | [0]=> 25 | string(%d) "%s" 26 | [1]=> 27 | string(12) "test content" 28 | } 29 | -------------------------------------------------------------------------------- /tests/013-fix-memory-leak-when-scoutapm_get_calls-not-called.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | When calls to functions are recorded, and scoutapm_get_calls is not called, don't leak memory 3 | --SKIPIF-- 4 | 5 | 6 | --FILE-- 7 | 13 | --EXPECTREGEX-- 14 | end((?!memory leaks detected).)* 15 | -------------------------------------------------------------------------------- /tests/014-predis-support.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Predis userland functions are supported 3 | --SKIPIF-- 4 | connect(); 24 | } catch (\Predis\Connection\ConnectionException $e) { 25 | die("skip " . $e->getMessage()); 26 | } 27 | } 28 | ?> 29 | --FILE-- 30 | append', 35 | 'Predis\Client->decr', 36 | 'Predis\Client->decrBy', 37 | 'Predis\Client->get', 38 | 'Predis\Client->getBit', 39 | 'Predis\Client->getRange', 40 | 'Predis\Client->getSet', 41 | 'Predis\Client->incr', 42 | 'Predis\Client->incrBy', 43 | 'Predis\Client->mGet', 44 | 'Predis\Client->mSet', 45 | 'Predis\Client->mSetNx', 46 | 'Predis\Client->set', 47 | 'Predis\Client->setBit', 48 | 'Predis\Client->setEx', 49 | 'Predis\Client->pSetEx', 50 | 'Predis\Client->setNx', 51 | 'Predis\Client->setRange', 52 | 'Predis\Client->strlen', 53 | 'Predis\Client->del', 54 | ], 55 | scoutapm_list_instrumented_functions() 56 | )) . "\n"; 57 | scoutapm_enable_instrumentation(true); 58 | 59 | require "/tmp/scout_predis_test/vendor/autoload.php"; 60 | 61 | $client = new \Predis\Client(); 62 | 63 | // Simple operations 64 | $client->set('foo', 'bar'); 65 | var_dump($client->get('foo')); 66 | $client->append('foo', 'baz'); 67 | $client->del('foo'); 68 | $client->getSet('foo', 'bat'); 69 | $client->getRange('foo', 0, 2); 70 | $client->setRange('foo', 0, 'qux'); 71 | $client->setEx('expire1', 1, 'value1'); 72 | $client->pSetEx('expire2', 1, 'value2'); 73 | $client->setNx('fuu', 'new'); 74 | $client->strlen('fuu'); 75 | 76 | // Increment/Decrement 77 | $client->set('count', 0); 78 | $client->incr('count'); 79 | $client->decr('count'); 80 | $client->incrBy('count', 2); 81 | $client->decrBy('count', 2); 82 | 83 | // Multi-operations 84 | $client->mSet(['a' => 'a', 'b' => 'b']); 85 | $client->mSetNx(['c' => 'c', 'd' => 'd']); 86 | $client->mGet(['a', 'b', 'c', 'd']); 87 | 88 | // Bit operations 89 | $client->set('bit', 0); 90 | $client->setBit('bit', 8, 1); 91 | $client->getBit('bit', 8); 92 | 93 | $calls = scoutapm_get_calls(); 94 | 95 | var_dump(array_column($calls, 'function')); 96 | 97 | ?> 98 | --CLEAN-- 99 | 102 | --EXPECTF-- 103 | Predis\Client->append 104 | Predis\Client->decr 105 | Predis\Client->decrBy 106 | Predis\Client->get 107 | Predis\Client->getBit 108 | Predis\Client->getRange 109 | Predis\Client->getSet 110 | Predis\Client->incr 111 | Predis\Client->incrBy 112 | Predis\Client->mGet 113 | Predis\Client->mSet 114 | Predis\Client->mSetNx 115 | Predis\Client->set 116 | Predis\Client->setBit 117 | Predis\Client->setEx 118 | Predis\Client->pSetEx 119 | Predis\Client->setNx 120 | Predis\Client->setRange 121 | Predis\Client->strlen 122 | Predis\Client->del 123 | string(%s) "bar" 124 | array(%d) { 125 | [%d]=> 126 | string(%d) "Predis\Client->set" 127 | [%d]=> 128 | string(%d) "Predis\Client->get" 129 | [%d]=> 130 | string(%d) "Predis\Client->append" 131 | [%d]=> 132 | string(%d) "Predis\Client->del" 133 | [%d]=> 134 | string(%d) "Predis\Client->getSet" 135 | [%d]=> 136 | string(%d) "Predis\Client->getRange" 137 | [%d]=> 138 | string(%d) "Predis\Client->setRange" 139 | [%d]=> 140 | string(%d) "Predis\Client->setEx" 141 | [%d]=> 142 | string(%d) "Predis\Client->pSetEx" 143 | [%d]=> 144 | string(%d) "Predis\Client->setNx" 145 | [%d]=> 146 | string(%d) "Predis\Client->strlen" 147 | [%d]=> 148 | string(%d) "Predis\Client->set" 149 | [%d]=> 150 | string(%d) "Predis\Client->incr" 151 | [%d]=> 152 | string(%d) "Predis\Client->decr" 153 | [%d]=> 154 | string(%d) "Predis\Client->incrBy" 155 | [%d]=> 156 | string(%d) "Predis\Client->decrBy" 157 | [%d]=> 158 | string(%d) "Predis\Client->mSet" 159 | [%d]=> 160 | string(%d) "Predis\Client->mSetNx" 161 | [%d]=> 162 | string(%d) "Predis\Client->mGet" 163 | [%d]=> 164 | string(%d) "Predis\Client->set" 165 | [%d]=> 166 | string(%d) "Predis\Client->setBit" 167 | [%d]=> 168 | string(%d) "Predis\Client->getBit" 169 | } 170 | -------------------------------------------------------------------------------- /tests/015-phpredis-support.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | PHP Redis C extension functions are instrumented 3 | --SKIPIF-- 4 | connect('127.0.0.1', 6379); 14 | } catch (\RedisException $e) { 15 | die("skip " . $e->getMessage()); 16 | } 17 | } 18 | ?> 19 | --FILE-- 20 | append', 25 | 'redis->decr', 26 | 'redis->decrby', 27 | 'redis->get', 28 | 'redis->getbit', 29 | 'redis->getrange', 30 | 'redis->getset', 31 | 'redis->incr', 32 | 'redis->incrby', 33 | 'redis->mget', 34 | 'redis->mset', 35 | 'redis->msetnx', 36 | 'redis->set', 37 | 'redis->setbit', 38 | 'redis->setex', 39 | 'redis->psetex', 40 | 'redis->setnx', 41 | 'redis->setrange', 42 | 'redis->strlen', 43 | 'redis->del', 44 | ], 45 | scoutapm_list_instrumented_functions() 46 | )) . "\n"; 47 | scoutapm_enable_instrumentation(true); 48 | 49 | $client = new Redis(); 50 | $client->connect('127.0.0.1', 6379); 51 | 52 | // Simple operations 53 | $client->set('foo', 'bar'); 54 | var_dump($client->get('foo')); 55 | $client->append('foo', 'baz'); 56 | $client->del('foo'); 57 | $client->getSet('foo', 'bat'); 58 | $client->getRange('foo', 0, 2); 59 | $client->setRange('foo', 0, 'qux'); 60 | $client->setEx('expire1', 1, 'value1'); 61 | $client->pSetEx('expire2', 1, 'value2'); 62 | $client->setNx('fuu', 'new'); 63 | $client->strlen('fuu'); 64 | 65 | // Increment/Decrement 66 | $client->set('count', 0); 67 | $client->incr('count'); 68 | $client->decr('count'); 69 | $client->incrBy('count', 2); 70 | $client->decrBy('count', 2); 71 | 72 | // Multi-operations 73 | $client->mSet(['a' => 'a', 'b' => 'b']); 74 | $client->mSetNx(['c' => 'c', 'd' => 'd']); 75 | $client->mGet(['a', 'b', 'c', 'd']); 76 | 77 | // Bit operations 78 | $client->set('bit', 0); 79 | $client->setBit('bit', 8, 1); 80 | $client->getBit('bit', 8); 81 | 82 | $calls = scoutapm_get_calls(); 83 | 84 | var_dump(array_column($calls, 'function')); 85 | 86 | ?> 87 | --EXPECTF-- 88 | redis->append 89 | redis->decr 90 | redis->decrby 91 | redis->get 92 | redis->getbit 93 | redis->getrange 94 | redis->getset 95 | redis->incr 96 | redis->incrby 97 | redis->mget 98 | redis->mset 99 | redis->msetnx 100 | redis->set 101 | redis->setbit 102 | redis->setex 103 | redis->psetex 104 | redis->setnx 105 | redis->setrange 106 | redis->strlen 107 | redis->del 108 | string(%s) "bar" 109 | array(%d) { 110 | [%d]=> 111 | string(%d) "Redis->set" 112 | [%d]=> 113 | string(%d) "Redis->get" 114 | [%d]=> 115 | string(%d) "Redis->append" 116 | [%d]=> 117 | string(%d) "Redis->del" 118 | [%d]=> 119 | string(%d) "Redis->get%cet" 120 | [%d]=> 121 | string(%d) "Redis->getRange" 122 | [%d]=> 123 | string(%d) "Redis->setRange" 124 | [%d]=> 125 | string(%d) "Redis->setex" 126 | [%d]=> 127 | string(%d) "Redis->psetex" 128 | [%d]=> 129 | string(%d) "Redis->setnx" 130 | [%d]=> 131 | string(%d) "Redis->strlen" 132 | [%d]=> 133 | string(%d) "Redis->set" 134 | [%d]=> 135 | string(%d) "Redis->incr" 136 | [%d]=> 137 | string(%d) "Redis->decr" 138 | [%d]=> 139 | string(%d) "Redis->incrBy" 140 | [%d]=> 141 | string(%d) "Redis->decrBy" 142 | [%d]=> 143 | string(%d) "Redis->mset" 144 | [%d]=> 145 | string(%d) "Redis->msetnx" 146 | [%d]=> 147 | string(%d) "Redis->mget" 148 | [%d]=> 149 | string(%d) "Redis->set" 150 | [%d]=> 151 | string(%d) "Redis->setBit" 152 | [%d]=> 153 | string(%d) "Redis->getBit" 154 | } 155 | -------------------------------------------------------------------------------- /tests/016-memcached-support.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Memcached C extension functions are instrumented 3 | --SKIPIF-- 4 | addServer('localhost', 11211); 13 | if (!$m->flush()) { 14 | die("skip Could not connect to Memcached - is it running?"); 15 | } 16 | } 17 | ?> 18 | --FILE-- 19 | add', 24 | 'memcached->addbykey', 25 | 'memcached->append', 26 | 'memcached->appendbykey', 27 | 'memcached->cas', 28 | 'memcached->decrement', 29 | 'memcached->decrementbykey', 30 | 'memcached->delete', 31 | 'memcached->deletebykey', 32 | 'memcached->deletemulti', 33 | 'memcached->deletemultibykey', 34 | 'memcached->flush', 35 | 'memcached->get', 36 | 'memcached->getallkeys', 37 | 'memcached->getbykey', 38 | 'memcached->getmulti', 39 | 'memcached->getmultibykey', 40 | 'memcached->increment', 41 | 'memcached->incrementbykey', 42 | 'memcached->prepend', 43 | 'memcached->prependbykey', 44 | 'memcached->replace', 45 | 'memcached->replacebykey', 46 | 'memcached->set', 47 | 'memcached->setbykey', 48 | 'memcached->setmulti', 49 | 'memcached->setmultibykey', 50 | ], 51 | scoutapm_list_instrumented_functions() 52 | )) . "\n"; 53 | scoutapm_enable_instrumentation(true); 54 | 55 | $m = new Memcached(); 56 | $m->addServer('localhost', 11211); 57 | $m->setOption(Memcached::OPT_COMPRESSION, false); 58 | 59 | $m->set('foo', 'bar'); 60 | var_dump($m->get('foo')); 61 | $m->append('foo', 'baz'); 62 | $m->prepend('foo', 'gaz'); 63 | $m->replace('foo', 'bar'); 64 | $m->cas(0, 'foo', 'bar'); 65 | $m->add('num', 1); 66 | $m->decrement('num'); 67 | $m->increment('num'); 68 | $m->delete('num'); 69 | $m->setMulti(['a' => 'a', 'b' => 'b']); 70 | $m->getMulti(['a', 'b']); 71 | $m->deleteMulti(['a', 'b']); 72 | 73 | $m->setByKey('key', 'foo', 'bar'); 74 | $m->getByKey('key', 'foo'); 75 | $m->appendByKey('key', 'foo', 'baz'); 76 | $m->prependByKey('key', 'foo', 'gaz'); 77 | $m->replaceByKey('key', 'foo', 'bar'); 78 | $m->casByKey(0, 'key', 'foo', 'bar'); 79 | $m->addByKey('key', 'num', 1); 80 | $m->decrementByKey('key', 'num'); 81 | $m->incrementByKey('key', 'num'); 82 | $m->deleteByKey('key', 'num'); 83 | $m->setMultiByKey('key', ['a' => 'a', 'b' => 'b']); 84 | $m->getMultiByKey('key', ['a', 'b']); 85 | $m->deleteMultiByKey('key', ['a', 'b']); 86 | 87 | $m->getAllKeys(); 88 | $m->flush(); 89 | 90 | $calls = scoutapm_get_calls(); 91 | 92 | var_dump(array_column($calls, 'function')); 93 | 94 | ?> 95 | --EXPECTF-- 96 | memcached->add 97 | memcached->addbykey 98 | memcached->append 99 | memcached->appendbykey 100 | memcached->cas 101 | memcached->decrement 102 | memcached->decrementbykey 103 | memcached->delete 104 | memcached->deletebykey 105 | memcached->deletemulti 106 | memcached->deletemultibykey 107 | memcached->flush 108 | memcached->get 109 | memcached->getallkeys 110 | memcached->getbykey 111 | memcached->getmulti 112 | memcached->getmultibykey 113 | memcached->increment 114 | memcached->incrementbykey 115 | memcached->prepend 116 | memcached->prependbykey 117 | memcached->replace 118 | memcached->replacebykey 119 | memcached->set 120 | memcached->setbykey 121 | memcached->setmulti 122 | memcached->setmultibykey 123 | string(%s) "bar" 124 | array(%d) { 125 | [%d]=> 126 | string(%d) "Memcached->set" 127 | [%d]=> 128 | string(%d) "Memcached->get" 129 | [%d]=> 130 | string(%d) "Memcached->append" 131 | [%d]=> 132 | string(%d) "Memcached->prepend" 133 | [%d]=> 134 | string(%d) "Memcached->replace" 135 | [%d]=> 136 | string(%d) "Memcached->cas" 137 | [%d]=> 138 | string(%d) "Memcached->add" 139 | [%d]=> 140 | string(%d) "Memcached->decrement" 141 | [%d]=> 142 | string(%d) "Memcached->increment" 143 | [%d]=> 144 | string(%d) "Memcached->delete" 145 | [%d]=> 146 | string(%d) "Memcached->setMulti" 147 | [%d]=> 148 | string(%d) "Memcached->getMulti" 149 | [%d]=> 150 | string(%d) "Memcached->deleteMulti" 151 | [%d]=> 152 | string(%d) "Memcached->setByKey" 153 | [%d]=> 154 | string(%d) "Memcached->getByKey" 155 | [%d]=> 156 | string(%d) "Memcached->appendByKey" 157 | [%d]=> 158 | string(%d) "Memcached->prependByKey" 159 | [%d]=> 160 | string(%d) "Memcached->replaceByKey" 161 | [%d]=> 162 | string(%d) "Memcached->casByKey" 163 | [%d]=> 164 | string(%d) "Memcached->addByKey" 165 | [%d]=> 166 | string(%d) "Memcached->decrementByKey" 167 | [%d]=> 168 | string(%d) "Memcached->incrementByKey" 169 | [%d]=> 170 | string(%d) "Memcached->deleteByKey" 171 | [%d]=> 172 | string(%d) "Memcached->setMultiByKey" 173 | [%d]=> 174 | string(%d) "Memcached->getMultiByKey" 175 | [%d]=> 176 | string(%d) "Memcached->deleteMultiByKey" 177 | [%d]=> 178 | string(%d) "Memcached->getAllKeys" 179 | [%d]=> 180 | string(%d) "Memcached->flush" 181 | } 182 | -------------------------------------------------------------------------------- /tests/017-elastic-7-support.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Elasticsearch userland functions are supported 3 | --SKIPIF-- 4 | build(); 23 | try { 24 | $client->search([]); 25 | } catch (\Elasticsearch\Common\Exceptions\NoNodesAvailableException $e) { 26 | die("skip " . $e->getMessage()); 27 | } 28 | } 29 | ?> 30 | --FILE-- 31 | index', 36 | 'Elasticsearch\Client->get', 37 | 'Elasticsearch\Client->search', 38 | 'Elasticsearch\Client->delete', 39 | ], 40 | scoutapm_list_instrumented_functions() 41 | )) . "\n"; 42 | scoutapm_enable_instrumentation(true); 43 | 44 | require "/tmp/scout_elastic_test/vendor/autoload.php"; 45 | 46 | $client = \Elasticsearch\ClientBuilder::create()->build(); 47 | 48 | $client->index(['index' => 'my_index', 'id' => 'my_id', 'body' => ['testField' => 'abc']]); 49 | $client->get(['index' => 'my_index', 'id' => 'my_id']); 50 | $client->search(['index' => 'my_index', 'body' => ['query' => ['match' => ['testField' => 'abc']]]]); 51 | $client->delete(['index' => 'my_index', 'id' => 'my_id']); 52 | 53 | $calls = scoutapm_get_calls(); 54 | 55 | var_dump(array_column($calls, 'function')); 56 | 57 | ?> 58 | --CLEAN-- 59 | 62 | --EXPECTF-- 63 | Elasticsearch\Client->index 64 | Elasticsearch\Client->get 65 | Elasticsearch\Client->search 66 | Elasticsearch\Client->delete 67 | array(%d) { 68 | [%d]=> 69 | string(%d) "Elasticsearch\Client->index" 70 | [%d]=> 71 | string(%d) "Elasticsearch\Client->get" 72 | [%d]=> 73 | string(%d) "Elasticsearch\Client->search" 74 | [%d]=> 75 | string(%d) "Elasticsearch\Client->delete" 76 | } 77 | -------------------------------------------------------------------------------- /tests/018-do-not-instrument-by-default.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Do not instrument anything by default, unless scoutapm_enable_instrumentation(true) is called 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 21 | --EXPECTF-- 22 | bool(true) 23 | array(0) { 24 | } 25 | array(1) { 26 | [0]=> 27 | string(%s) "file_get_contents" 28 | } 29 | array(0) { 30 | } 31 | -------------------------------------------------------------------------------- /tests/019-url-method-capture-fgc.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Both URL and Method can be captured using file_get_contents 3 | --SKIPIF-- 4 | 5 | 6 | --FILE-- 7 | [ 17 | 'method' => 'POST', 18 | 'header' => [ 19 | 'User-Agent: scoutapp/scout-apm-php-ext Test Suite FGC', 20 | ], 21 | ], 22 | ]) 23 | ); 24 | $call = scoutapm_get_calls()[0]; 25 | 26 | var_dump($call['function']); 27 | var_dump($call['argv'][0]); 28 | var_dump(json_decode($call['argv'][2], true)['http']['method']); 29 | ?> 30 | --EXPECTF-- 31 | bool(true) 32 | string(%d) "file_get_contents" 33 | string(%d) "https://scoutapm.com/robots.txt" 34 | string(%d) "POST" 35 | -------------------------------------------------------------------------------- /tests/020-url-method-capture-curl-post.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Both URL and Method can be captured using curl + CURLOPT_POST 3 | --SKIPIF-- 4 | 5 | 6 | --FILE-- 7 | 24 | --EXPECTF-- 25 | bool(true) 26 | string(9) "curl_exec" 27 | array(%d) { 28 | [%d]=> 29 | string(%d) "https://scoutapm.com/robots.txt" 30 | [%d]=> 31 | bool(true) 32 | [%d]=> 33 | NULL 34 | } 35 | -------------------------------------------------------------------------------- /tests/021-url-method-capture-curl-customreq.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Both URL and Method can be captured using curl + CURLOPT_CUSTOMREQUEST 3 | --SKIPIF-- 4 | 5 | 6 | --FILE-- 7 | 24 | --EXPECTF-- 25 | bool(true) 26 | string(9) "curl_exec" 27 | array(%d) { 28 | [%d]=> 29 | string(%d) "https://scoutapm.com/robots.txt" 30 | [%d]=> 31 | NULL 32 | [%d]=> 33 | string(%d) "POST" 34 | } 35 | -------------------------------------------------------------------------------- /tests/022-elastic-8-support.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Elasticsearch userland functions are supported 3 | --SKIPIF-- 4 | setHosts(['localhost:9200']) 33 | ->build(); 34 | try { 35 | $client->search([]); 36 | } catch (\Elastic\Elasticsearch\Common\Exceptions\NoNodesAvailableException $e) { 37 | die("skip " . $e->getMessage()); 38 | } 39 | } 40 | ?> 41 | --FILE-- 42 | index', 47 | 'Elastic\Elasticsearch\Client->get', 48 | 'Elastic\Elasticsearch\Client->search', 49 | 'Elastic\Elasticsearch\Client->delete', 50 | ], 51 | scoutapm_list_instrumented_functions() 52 | )) . "\n"; 53 | scoutapm_enable_instrumentation(true); 54 | 55 | require "/tmp/scout_elastic_test/vendor/autoload.php"; 56 | 57 | $client = \Elastic\Elasticsearch\ClientBuilder::create() 58 | ->setHosts(['localhost:9200']) 59 | ->build(); 60 | 61 | $client->index(['index' => 'my_index', 'id' => 'my_id', 'body' => ['testField' => 'abc']]); 62 | $client->get(['index' => 'my_index', 'id' => 'my_id']); 63 | $client->search(['index' => 'my_index', 'body' => ['query' => ['match' => ['testField' => 'abc']]]]); 64 | $client->delete(['index' => 'my_index', 'id' => 'my_id']); 65 | 66 | $calls = scoutapm_get_calls(); 67 | 68 | var_dump(array_column($calls, 'function')); 69 | 70 | ?> 71 | --CLEAN-- 72 | 75 | --EXPECTF-- 76 | Elastic\Elasticsearch\Client->index 77 | Elastic\Elasticsearch\Client->get 78 | Elastic\Elasticsearch\Client->search 79 | Elastic\Elasticsearch\Client->delete 80 | array(%d) { 81 | [%d]=> 82 | string(%d) "Elastic\Elasticsearch\Client->index" 83 | [%d]=> 84 | string(%d) "Elastic\Elasticsearch\Client->get" 85 | [%d]=> 86 | string(%d) "Elastic\Elasticsearch\Client->search" 87 | [%d]=> 88 | string(%d) "Elastic\Elasticsearch\Client->delete" 89 | } 90 | -------------------------------------------------------------------------------- /tests/bug-47.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Bug https://github.com/scoutapp/scout-apm-php-ext/issues/47 - fix segfault when accessing argument store out of bounds 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 15 | --EXPECTF-- 16 | array(2) { 17 | [0]=> 18 | string(%d) "resource(%d)" 19 | [1]=> 20 | string(%d) "fread/fwrite test" 21 | } 22 | -------------------------------------------------------------------------------- /tests/bug-49.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Bug https://github.com/scoutapp/scout-apm-php-ext/issues/49 - only record arguments for fopen if it returns a resource 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 14 | --EXPECTF-- 15 | Warning: fopen(%s): %cailed to open stream: No such file or directory in %s 16 | bool(false) 17 | array(0) { 18 | } 19 | -------------------------------------------------------------------------------- /tests/bug-55.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Bug https://github.com/scoutapp/scout-apm-php-ext/issues/55 - don't crash when using an observed method in extended class 3 | --SKIPIF-- 4 | 5 | 6 | 7 | --FILE-- 8 | query("SELECT cast(1 + 2 AS text) AS result"); 15 | var_dump($stmt->fetch(PDO::FETCH_ASSOC)); 16 | 17 | $calls = scoutapm_get_calls(); 18 | var_dump($calls[0]['function']); 19 | var_dump($calls[0]['argv'][0]); 20 | ?> 21 | --EXPECTF-- 22 | array(%d) { 23 | ["result"]=> 24 | string(%d) "3" 25 | } 26 | string(%d) "PDO->query" 27 | string(%d) "SELECT cast(1 + 2 AS text) AS result" 28 | -------------------------------------------------------------------------------- /tests/bug-71.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Bug https://github.com/scoutapp/scout-apm-php-ext/issues/71 - only record arguments for prepare if it returns an object 3 | --SKIPIF-- 4 | 5 | 6 | 7 | --FILE-- 8 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); 13 | $stmt = $dbh->prepare("SELECT nonexist FROM nonexist"); 14 | 15 | ?> 16 | --EXPECTF-- 17 | Warning: PDO::prepare(): SQLSTATE[HY000]: General error: 1 no such table: nonexist in %s 18 | -------------------------------------------------------------------------------- /tests/bug-88.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Bug https://github.com/scoutapp/scout-apm-php-ext/issues/88 - memory usage should not increase when no instrumentation happens 3 | --SKIPIF-- 4 | 5 | 6 | --FILE-- 7 | b(); 20 | } 21 | $after = (int) (exec("ps --pid " . getmypid() . " --no-headers -o rss")); 22 | 23 | echo "Before & after:\n"; 24 | var_dump($before, $after); 25 | 26 | echo "Difference:\n"; 27 | var_dump($after - $before); 28 | 29 | $threshold = 500; // bytes 30 | echo "Within $threshold bytes limit:\n"; 31 | var_dump(($after - $before) < $threshold); 32 | 33 | ?> 34 | --EXPECTF-- 35 | Before & after: 36 | int(%d) 37 | int(%d) 38 | Difference: 39 | int(%d) 40 | Within %d bytes limit: 41 | bool(true) 42 | -------------------------------------------------------------------------------- /tests/bug-93.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Bug https://github.com/scoutapp/scout-apm-php-ext/issues/93 - Should not segfault on static function usage 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 27 | --EXPECTF-- 28 | Called 1. 29 | Called 2. 30 | Called 3. 31 | -------------------------------------------------------------------------------- /tests/external.inc: -------------------------------------------------------------------------------- 1 | 11 | 12 | static PHP_GINIT_FUNCTION(scoutapm); 13 | static PHP_RINIT_FUNCTION(scoutapm); 14 | static PHP_RSHUTDOWN_FUNCTION(scoutapm); 15 | static PHP_MINIT_FUNCTION(scoutapm); 16 | static PHP_MSHUTDOWN_FUNCTION(scoutapm); 17 | static int zend_scoutapm_startup(zend_extension*); 18 | 19 | extern void allocate_stack_frames_for_request(); 20 | extern void free_instrumented_function_names_list(); 21 | extern void free_observed_stack_frames(); 22 | extern void free_recorded_call_arguments(); 23 | extern int setup_recording_for_internal_handlers(); 24 | extern int setup_functions_for_zend_execute_ex(); 25 | extern int setup_functions_for_observer_api(); 26 | extern void register_scout_execute_ex(); 27 | extern void deregister_scout_execute_ex(); 28 | extern void register_scout_observer(); 29 | 30 | ZEND_DECLARE_MODULE_GLOBALS(scoutapm) 31 | 32 | ZEND_BEGIN_ARG_INFO_EX(no_arguments, 0, 0, 0) 33 | ZEND_END_ARG_INFO() 34 | 35 | ZEND_BEGIN_ARG_INFO_EX(scoutapm_enable_instrumentation, 0, 0, 1) 36 | ZEND_ARG_TYPE_INFO(0, enable, _IS_BOOL, 0) 37 | ZEND_END_ARG_INFO() 38 | 39 | /* a PHP module defines what functions it exports */ 40 | static const zend_function_entry scoutapm_functions[] = { 41 | PHP_FE(scoutapm_get_calls, no_arguments) 42 | PHP_FE(scoutapm_list_instrumented_functions, no_arguments) 43 | PHP_FE(scoutapm_enable_instrumentation, scoutapm_enable_instrumentation) 44 | PHP_FE_END 45 | }; 46 | 47 | PHP_MINFO_FUNCTION(scoutapm) 48 | { 49 | bool have_scout_curl = false, found_curl_exec = false; 50 | 51 | php_info_print_table_start(); 52 | php_info_print_table_header(2, "scoutapm support", "enabled"); 53 | php_info_print_table_row(2, "scoutapm Version", PHP_SCOUTAPM_VERSION); 54 | 55 | #if HAVE_CURL 56 | php_info_print_table_row(2, "scoutapm curl HAVE_CURL", "Yes"); 57 | #else 58 | php_info_print_table_row(2, "scoutapm curl HAVE_CURL", "No"); 59 | #endif 60 | 61 | #if HAVE_SCOUT_CURL 62 | have_scout_curl = true; 63 | php_info_print_table_row(2, "scoutapm curl HAVE_SCOUT_CURL", "Yes"); 64 | #else 65 | php_info_print_table_row(2, "scoutapm curl HAVE_SCOUT_CURL", "No"); 66 | #endif 67 | 68 | if (zend_hash_str_find_ptr(EG(function_table), "curl_exec", sizeof("curl_exec") - 1) != NULL) { 69 | found_curl_exec = true; 70 | } 71 | 72 | php_info_print_table_row(2, "scoutapm curl enabled", (have_scout_curl && found_curl_exec) ? "Yes" : "No"); 73 | 74 | php_info_print_table_end(); 75 | } 76 | 77 | /* scoutapm_module_entry provides the metadata/information for PHP about this PHP module */ 78 | static zend_module_entry scoutapm_module_entry = { 79 | STANDARD_MODULE_HEADER, 80 | PHP_SCOUTAPM_NAME, 81 | scoutapm_functions, /* function entries */ 82 | PHP_MINIT(scoutapm), /* module init */ 83 | PHP_MSHUTDOWN(scoutapm), /* module shutdown */ 84 | PHP_RINIT(scoutapm), /* request init */ 85 | PHP_RSHUTDOWN(scoutapm), /* request shutdown */ 86 | PHP_MINFO(scoutapm), /* module information */ 87 | PHP_SCOUTAPM_VERSION, /* module version */ 88 | PHP_MODULE_GLOBALS(scoutapm), /* module global variables */ 89 | PHP_GINIT(scoutapm), /* init global */ 90 | NULL, 91 | NULL, 92 | STANDARD_MODULE_PROPERTIES_EX 93 | }; 94 | 95 | /* 96 | * Do not export this module, so it cannot be registered with `extension=scoutapm.so` - must be `zend_extension=` 97 | * Instead, see `zend_scoutapm_startup` - we load the module there. 98 | ZEND_GET_MODULE(scoutapm); 99 | */ 100 | #if defined(COMPILE_DL_SCOUTAPM) && defined(ZTS) 101 | ZEND_TSRMLS_CACHE_DEFINE() 102 | #endif 103 | 104 | /* extension_version_info is used by PHP */ 105 | ZEND_DLEXPORT zend_extension_version_info extension_version_info = { 106 | ZEND_EXTENSION_API_NO, 107 | (char*) ZEND_EXTENSION_BUILD_ID 108 | }; 109 | 110 | /* zend_extension_entry provides the metadata/information for PHP about this zend extension */ 111 | ZEND_DLEXPORT zend_extension zend_extension_entry = { 112 | (char*) PHP_SCOUTAPM_NAME, 113 | (char*) PHP_SCOUTAPM_VERSION, 114 | (char*) "Scout APM", 115 | (char*) "https://scoutapm.com/", 116 | (char*) "Copyright 2019", 117 | zend_scoutapm_startup, /* extension startup */ 118 | NULL, /* extension shutdown */ 119 | NULL, /* request startup */ 120 | NULL, /* request shutdown */ 121 | NULL, /* message handler */ 122 | NULL, /* compiler op_array_ahndler */ 123 | NULL, /* VM statement_handler */ 124 | NULL, /* VM fcall_begin_handler */ 125 | NULL, /* VM_fcall_end_handler */ 126 | NULL, /* compiler op_array_ctor */ 127 | NULL, /* compiler op_array_dtor */ 128 | STANDARD_ZEND_EXTENSION_PROPERTIES 129 | }; 130 | 131 | /* 132 | * This is a hybrid "Zend Extension / PHP Module", and because the PHP module is not exported, we become responsible 133 | * for starting up the module. For more details on the PHP/Zend extension lifecycle, see: 134 | * http://www.phpinternalsbook.com/php7/extensions_design/zend_extensions.html#hybrid-extensions 135 | */ 136 | static int zend_scoutapm_startup(zend_extension *ze) { 137 | return zend_startup_module(&scoutapm_module_entry); 138 | } 139 | 140 | static PHP_GINIT_FUNCTION(scoutapm) 141 | { 142 | #if defined(COMPILE_DL_SCOUTAPM) && defined(ZTS) 143 | ZEND_TSRMLS_CACHE_UPDATE(); 144 | #endif 145 | memset(scoutapm_globals, 0, sizeof(zend_scoutapm_globals)); 146 | } 147 | 148 | /* 149 | * Set up the handlers and stack. This is called at the start of each request to PHP. 150 | */ 151 | static PHP_RINIT_FUNCTION(scoutapm) 152 | { 153 | #if defined(COMPILE_DL_SCOUTAPM) && defined(ZTS) 154 | ZEND_TSRMLS_CACHE_UPDATE(); 155 | #endif 156 | 157 | allocate_stack_frames_for_request(); 158 | 159 | if (SCOUTAPM_G(handlers_set) != 1) { 160 | SCOUTAPM_DEBUG_MESSAGE("Overriding function handlers.\n"); 161 | 162 | if (setup_functions_for_zend_execute_ex() == FAILURE) { 163 | return FAILURE; 164 | } 165 | 166 | if (setup_recording_for_internal_handlers() == FAILURE) { 167 | return FAILURE; 168 | } 169 | 170 | if (setup_functions_for_observer_api() == FAILURE) { 171 | return FAILURE; 172 | } 173 | 174 | SCOUTAPM_G(handlers_set) = 1; 175 | } else { 176 | SCOUTAPM_DEBUG_MESSAGE("Handlers have already been set, skipping.\n"); 177 | } 178 | 179 | return SUCCESS; 180 | } 181 | 182 | /* 183 | * At the end of the request, this function is responsible for freeing all memory we allocated. Note that if we forget 184 | * to free something, if PHP is compiled with --enable-debug, then we will see memory leaks. 185 | */ 186 | static PHP_RSHUTDOWN_FUNCTION(scoutapm) 187 | { 188 | free_instrumented_function_names_list(); 189 | free_observed_stack_frames(); 190 | free_recorded_call_arguments(); 191 | 192 | return SUCCESS; 193 | } 194 | 195 | static PHP_MINIT_FUNCTION(scoutapm) 196 | { 197 | register_scout_observer(); 198 | register_scout_execute_ex(); 199 | 200 | return SUCCESS; 201 | } 202 | 203 | static PHP_MSHUTDOWN_FUNCTION(scoutapm) 204 | { 205 | deregister_scout_execute_ex(); 206 | 207 | return SUCCESS; 208 | } 209 | -------------------------------------------------------------------------------- /zend_scoutapm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Scout APM extension for PHP 3 | * 4 | * Copyright (C) 2019- 5 | * For license information, please see the LICENSE file. 6 | */ 7 | 8 | #ifndef ZEND_SCOUTAPM_H 9 | #define ZEND_SCOUTAPM_H 10 | 11 | #ifdef HAVE_CONFIG_H 12 | #include "config.h" 13 | #endif 14 | 15 | #include "php.h" 16 | #include 17 | #include 18 | #include 19 | #include "ext/standard/php_var.h" 20 | 21 | #include "scout_recording.h" 22 | #include "scout_internal_handlers.h" 23 | #include "scout_execute_ex.h" 24 | 25 | #define PHP_SCOUTAPM_NAME "scoutapm" 26 | #define PHP_SCOUTAPM_VERSION "1.10.0" 27 | 28 | /* Extreme amounts of debugging, set to 1 to enable it and `make clean && make` (tests will fail...) */ 29 | #define SCOUT_APM_EXT_DEBUGGING 0 30 | 31 | #if PHP_VERSION_ID > 80000 32 | #define SCOUTAPM_INSTRUMENT_USING_OBSERVER_API 1 33 | #else 34 | #define SCOUTAPM_INSTRUMENT_USING_OBSERVER_API 0 35 | #endif 36 | 37 | PHP_FUNCTION(scoutapm_enable_instrumentation); 38 | PHP_FUNCTION(scoutapm_get_calls); 39 | PHP_FUNCTION(scoutapm_list_instrumented_functions); 40 | 41 | /* These are the "module globals". In non-ZTS mode, they're just regular variables, but means in ZTS mode they get handled properly */ 42 | ZEND_BEGIN_MODULE_GLOBALS(scoutapm) 43 | zend_bool all_instrumentation_enabled; 44 | zend_bool handlers_set; 45 | zend_long observed_stack_frames_count; 46 | scoutapm_stack_frame *observed_stack_frames; 47 | zend_long disconnected_call_argument_store_count; 48 | scoutapm_disconnected_call_argument_store *disconnected_call_argument_store; 49 | scoutapm_instrumented_function instrumented_function_names[MAX_INSTRUMENTED_FUNCTIONS]; 50 | int num_instrumented_functions; 51 | int currently_instrumenting; 52 | #if SCOUTAPM_INSTRUMENT_USING_OBSERVER_API == 1 53 | double observer_api_start_time; 54 | #endif //SCOUTAPM_INSTRUMENT_USING_OBSERVER_API 55 | ZEND_END_MODULE_GLOBALS(scoutapm) 56 | 57 | /* Accessor for "module globals" for non-ZTS and ZTS modes. */ 58 | #ifdef ZTS 59 | #define SCOUTAPM_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(scoutapm, v) 60 | #else 61 | #define SCOUTAPM_G(v) (scoutapm_globals.v) 62 | #endif 63 | 64 | /* zif_handler is not always defined, so define this roughly equivalent */ 65 | #if PHP_VERSION_ID < 70200 66 | typedef void (*zif_handler)(INTERNAL_FUNCTION_PARAMETERS); 67 | #endif 68 | 69 | /* Sometimes this isn't defined, so define it in that case */ 70 | #ifndef ZEND_PARSE_PARAMETERS_NONE 71 | #define ZEND_PARSE_PARAMETERS_NONE() if (zend_parse_parameters_none() != SUCCESS) { return; } 72 | #endif 73 | 74 | /* The debugging toggle allows us to output unnecessary amounts of information. Because it's a preprocessor flag, this stuff is compiled out */ 75 | #if SCOUT_APM_EXT_DEBUGGING == 1 76 | #define SCOUTAPM_DEBUG_MESSAGE(x, ...) php_printf(x, ##__VA_ARGS__) 77 | #else 78 | #define SCOUTAPM_DEBUG_MESSAGE(...) /**/ 79 | #endif 80 | 81 | /* 82 | * Shortcut defined to allow using a `char *` with snprintf - determine the size first, allocate, then snprintf 83 | * NOTE: When using this macro, the destString must ALWAYS be freed! 84 | */ 85 | #define DYNAMIC_MALLOC_SPRINTF(destString, sizeNeeded, fmt, ...) \ 86 | sizeNeeded = snprintf(NULL, 0, fmt, ##__VA_ARGS__) + 1; \ 87 | destString = (char*)malloc(sizeNeeded); \ 88 | snprintf(destString, sizeNeeded, fmt, ##__VA_ARGS__) 89 | 90 | /* these are the string keys used in scoutapm_get_calls associative array return value */ 91 | #define SCOUT_GET_CALLS_KEY_FUNCTION "function" 92 | #define SCOUT_GET_CALLS_KEY_ENTERED "entered" 93 | #define SCOUT_GET_CALLS_KEY_EXITED "exited" 94 | #define SCOUT_GET_CALLS_KEY_TIME_TAKEN "time_taken" 95 | #define SCOUT_GET_CALLS_KEY_ARGV "argv" 96 | 97 | /* stored argument wrapper constants */ 98 | #define SCOUT_WRAPPER_TYPE_CURL "curl_exec" 99 | #define SCOUT_WRAPPER_TYPE_FILE "file" 100 | 101 | /* 102 | * PHP 8.1 adds an assertion for max parameter count; we are greedy with parameters to avoid not enough args errors 103 | * See: https://github.com/php/php-src/commit/5070549577bbad13941d7c2bb9a9a8456797baf1 104 | */ 105 | #if PHP_VERSION_ID >= 80100 106 | #define SCOUT_ZEND_PARSE_PARAMETERS_END() \ 107 | } while (0); \ 108 | if (UNEXPECTED(_error_code != ZPP_ERROR_OK)) { \ 109 | if (!(_flags & ZEND_PARSE_PARAMS_QUIET)) { \ 110 | zend_wrong_parameter_error(_error_code, _i, _error, _expected_type, _arg); \ 111 | } \ 112 | return; \ 113 | } \ 114 | } while (0) 115 | #else 116 | #define SCOUT_ZEND_PARSE_PARAMETERS_END() ZEND_PARSE_PARAMETERS_END() 117 | #endif 118 | 119 | #endif /* ZEND_SCOUTAPM_H */ 120 | --------------------------------------------------------------------------------