Skip to content

Instantly share code, notes, and snippets.

@ulcuber
Created June 20, 2026 06:05
Show Gist options
  • Select an option

  • Save ulcuber/ea20e10fa961721afb94e7552ec9992b to your computer and use it in GitHub Desktop.

Select an option

Save ulcuber/ea20e10fa961721afb94e7552ec9992b to your computer and use it in GitHub Desktop.
Xhprof+Laravel

Xhprof latest changes chronology

  • 2014 phacility/xhprof previous pecl xhprof. Includes xhprof_html. No composer.json
  • 2021 preinheimer/xhprof XHProf UI. MySQL focused while perftools/xhgui required mongo. No composer.json. Behind phacility/xhprof. No SVG support
  • 2023 tideways/php-xhprof-extension Archived in favor of longxinH/xhprof
  • 2025 longxinH/xhprof fork of phacility/xhprof. 2025 extension, 2021 same xhprof_html only with default image changed from png to svg for faster callgraph. Has composer.json. Current pecl xhprof. Upstream for https://packages.gentoo.org/packages/dev-php/xhprof. Replaced Tideways
  • 2026 perftools/xhgui alternative html, has composer.json, can work with mongo or PDO with different compatibility

Install

Artix

  • No xhprof for pacman Artix
  • No xhprof in packagist extensions for PIE
  • Arch docs suggest php-pear from AUR
  • Anyway xhprof_html+xhprof_lib and the extension are required so git clone git@github.com:longxinH/xhprof.git would be suitable
  • Don't forget sudo pacman -S graphviz for callgraphs

Extension

cd xhprof/extension/
phpize
./configure
make
sudo make install

configuration add to your php.ini / /etc/php/conf.d/xhprof.ini / /etc/php/cli-php8.5/ext/xhprof.ini

[xhprof]
extension = xhprof.so
xhprof.output_dir = /tmp/xhprof

Check if you need a symlink to /etc/php/cli-php8.5/ext-active

sudo ln -s ../ext/xhprof.ini xhprof.ini

Also mkdir /tmp/xhprof

Vhost

Configure server host with root at xhprof/xhprof_html

Example nginx vhost for preinheimer/xhprof while extension is from longxinH/xhprof

upstream xhprof-ui-fpm {
	# /etc/php/php-fpm.d/www.conf
	server unix:/run/php-fpm/php-fpm.sock;
}

server {
	listen 80;
	listen [::]:80;
	server_name xhprof-ui.localhost;
	server_tokens off;
	root /var/www/preinheimer_xhprof/xhprof_html;

	index index.php;

	access_log /var/log/nginx/xhprof-ui.access_log;
	error_log /var/log/nginx/xhprof-ui.error_log;

	location / {
		try_files $uri $uri/ /index.php?$args;
	}

	location ~ \.php$ {
		try_files $uri =404;
		include /etc/nginx/fastcgi_params;
		fastcgi_pass    xhprof-ui-fpm;
		fastcgi_index   index.php;
		fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
	}

	location = /favicon.ico {
		access_log off; log_not_found off;
	}
	location = /robots.txt {
		access_log off; log_not_found off;
	}

	# Deny access to hidden files
	location ~ /\. {
		deny all;
	}
}

Diff

phacility/xhprof

  • xhprof_lib uses ini_get("xhprof.output_dir") or sys_get_temp_dir() with error_log
  • Just stores runs in files in the dir

longxinH/xhprof

  • uses getenv('XHPROF_OUTPUT_DIR') or ini_get("xhprof.output_dir") or sys_get_temp_dir() with error_log
  • Same file storage

preinheimer/xhprof

  • Needs cp xhprof_lib/config.sample.php xhprof_lib/config.php
  • Adds utils/Db/<database of choice>.php and requires CREATE TABLE... from it
  • Requires extra includes
    <?php
        // preinheimer/xhprof
        require_once $libRoot . 'defaults.php';
        require_once $libRoot . 'config.php';
        // + classic phacility/xhprof
        require_once $libRoot . 'utils/xhprof_lib.php';
        require_once $libRoot . 'utils/xhprof_runs.php';
  • Can have fourth argument for external/footer.php: $run_id = $xhprof_runs->save_run(..., xhprof_details: ['type' => 0]);
  • Has external/header.php to hook into server to make one in one hundred profiled or by some request params
  • Made some windows compat fixes in extension
  • Has controlIPs to prevent unauthorized access to admin
  • Updated design
  • Has HighCharts non-commercial limited license

So check INSTALL file, do mysqladmin create xhprof, etc

perftools/xhgui

Completely different tool with vhost root at xhgui/webroot. Do not confuse with preinheimer/xhprof as the later has XH GUI logo

Integrate to Laravel

.env

XHPROF_LIB_ROOT=/var/www/preinheimer_xhprof/xhprof_lib
XHPROF_BASE_URL=http://xhprof-ui.localhost

config/xhprof.php

<?php

declare(strict_types=1);

/**
 * xhprof_start();
 * // CODE to profile
 * if ($url = xhprof_stop()) {
 *     info("xhprof url: $url");
 * }
 */

return [
    'lib_root' => env('XHPROF_LIB_ROOT'),
    'base_url' => env('XHPROF_BASE_URL'),
];

app/Support/helpers

<?php

declare(strict_types=1);

use Illuminate\Support\Str;

if (! function_exists('xhprof_start')) {
    function xhprof_start(?string $postfix = null)
    {
        // NOTE: not the classic lib var
        global $_xhprof;

        if ($postfix) {
            // reusing preinheimer/xhprof
            $_xhprof['namespace'] = $postfix;
        }

        if (! extension_loaded('xhprof')) {
            throw new RuntimeException('xhprof extension required');
        }

        if (! ini_get('xhprof.output_dir')) {
            $d = getenv('XHPROF_OUTPUT_DIR')
                ? 'XHPROF_OUTPUT_DIR is not recommended as must be the same for xhprof_html' : '';
            throw new RuntimeException('ini xhprof.output_dir required' . $d);
        }

        xhprof_enable(XHPROF_FLAGS_NO_BUILTINS | XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);

        register_shutdown_function(function (): void {
            xhprof_stop();
        });

        pcntl_signal(SIGINT, function (): void {
            // will trigger register_shutdown_function
            exit(0);
        });
    }
}

if (! function_exists('xhprof_stop')) {
    function xhprof_stop(): ?string
    {
        /**
         * Var declared in `xhprof_lib/config` of `preinheimer/xhprof` should be global
         * but we are in function scope
         */
        global $_xhprof;

        $xhprofData = xhprof_disable();
        if (! $xhprofData) {
            return null;
        }

        $canConfig = true;
        // Deferred init to have more ways than in xhprof_start
        $libRoot = $_xhprof['XHPROF_LIB_ROOT'] ?? null;
        if (! $libRoot) {
            if (defined('XHPROF_LIB_ROOT')) {
                $libRoot = XHPROF_LIB_ROOT;
            }
        }
        if (function_exists('config')) {
            try {
                // for cached config and post Laravel init stops
                $root = config('xhprof.lib_root');
                if (! $libRoot) {
                    $libRoot = $root;
                }
            } catch (Throwable $th) {
                $canConfig = false;
            }
        }
        if (! $libRoot) {
            if (function_exists('env')) {
                try {
                    $libRoot = env('XHPROF_LIB_ROOT');
                } catch (Throwable $th) {
                    //
                }
            }
        }
        if (! $libRoot) {
            // for cached config and pre Laravel init stops
            $libRoot = getenv('XHPROF_LIB_ROOT');
        }

        if (! $libRoot) {
            throw new RuntimeException('Provide XHPROF_LIB_ROOT env var');
        }

        $libRoot = Str::finish($libRoot, '/');

        // preinheimer/xhprof
        if (file_exists($d = $libRoot . 'defaults.php')) {
            require_once $d;
            require_once $libRoot . 'config.php';
        }
        // classic phacility/xhprof
        require_once $libRoot . 'utils/xhprof_lib.php';
        require_once $libRoot . 'utils/xhprof_runs.php';

        $appName = $canConfig ? config('app.name') : getenv('APP_NAME');
        $name = urlencode($appName . ($_xhprof['namespace'] ?? ''));

        // uses getenv('XHPROF_OUTPUT_DIR') or ini_get("xhprof.output_dir") or sys_get_temp_dir() with error_log
        // makes no sense to pass $dir argument as xhprof_html/index.php will fallback to the method above
        // `preinheimer/xhprof` ignores $dir completely
        $xhprof_runs = new XHProfRuns_Default();
        $run_id = $xhprof_runs->save_run($xhprofData, $name);

        if ($canConfig) {
            $baseUrl = Str::finish(config('xhprof.base_url'), '/');

            return "{$baseUrl}index.php?run={$run_id}&source={$name}";
        }

        return null;
    }
}

composer.json

    "autoload": {
        "files": [
            "app/Support/helpers.php"
        ]
    }

Octane

Note: $url = PHP_SAPI === 'cli' ? implode(' ', $_SERVER['argv']) : $_SERVER['REQUEST_URI']; so Octane will show swoole-server /var/www/project/storage/logs/octane-server-state.json

Don't forget to xhprof_stop as shutdown_function will help only for fpm and being called when whole Octane stops

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment