Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Natively profile multiple scripts in PHP7

Since the release of PHP 7 it is now not possible to profile an entire selection of scripts using declare(ticks=1) in your base file and then using register_tick_function() to monitor each tick as it no longer follows include paths. According to the PHP bug filed at https://bugs.php.net/bug.php?id=71448 this will never be available again in PHP 7.

Due to an implementation bug, the declare(ticks=1) directive leaked into different compilation units prior to PHP 7.0. This is not how declare() directives, which are per-file or per-scope, are supposed to work.

Are there any alternatives to this approach using native PHP (not C or pear extensions etc.) that are available to me in PHP 7 that will allow me to profile each function or file called in a page load, getting details of the actual file path at least.

My original question that led to finding the bug can be found at How to avoid redeclaring ticks on every file in PHP 7, this question is now about alternative methods.

like image 206
Peter Featherstone Avatar asked Jun 10 '17 13:06

Peter Featherstone


1 Answers

One common way to do this without declare(ticks=1) is to use a profiler. A profiler will take notice of any method/function called, file loaded etc. and even take timing information so you not only can say which function was called when and by what code and which files were opened but also which part of the program took how long.

A well known profiler in PHP comes with the famous Xdebug extension. It also ships with a debugger:

  • https://xdebug.org/

One benefit is that you don't need to change the code to do the profiling, it is just the PHP configuration you need to adopt so you can switch it on and off as you need it (e.g. debug / profiling session).

PHP Userland (tick function)

As a work-around not having declare(ticks=1); at the beginning of each file (after #71448), it is possible to add this on-the-fly via a stream-wrapper on the file protocol (for files in the local file-system which is common) that injects it.

This is technically feasible by creating a stream-wrapper that is registered on the file protocol to proxy standard file i/o operations. In this PoC (Gist on Github) the bare-minimum implementation is shown to demonstrate that it works for includes. When test.php is executed and despite that other.php has not declare(ticks=1); in it on disk, the registered tick function is called on the include as the print of the backtraces show:

...
tick_handler() called
#0  tick_handler(1) called at [/home/hakre/stream-wrapper-default-files/test.php:18]
#1  tick_handler() called at [/home/hakre/stream-wrapper-default-files/other.php:2]
#2  include(/home/hakre/stream-wrapper-default-files/other.php) called at [/home/hakre/stream-wrapper-default-files/test.php:24]
...

The output is generated from the registered tick function (here: test.php):

<?php
/**
 * Inject declare ticks on include
 */
declare(ticks=1);

require __DIR__ . '/streamwrapper.php';

FileStreamWrapper::init();

// using a function as the callback
register_tick_function('tick_handler', true);


// Function which is called on each tick-event
function tick_handler()
{
    echo "tick_handler() called\n";
    debug_print_backtrace();
}

register_tick_function('tick_handler');

include "other.php";
include "another.php"; # file does not exists

The stream wrapper in the gist example has only implemented as little as needed to work for the two include statements, as PHP scripts normally do more file i/o it needs to be extended as needed. When it goes about seeking etc., the dynamic insertion needs to be taken into account etc. but there is state per file operation (handle) as there is one instance per each one so this should be well encapsulated. The global state is used for registering/unregistering the stream wrapper for each operation to proxy into the real file-system functions as otherwise it creates endless recursion (wrapper uses the wrapper uses the wrapper ...). The PoC so far shows how it works on principle.

This can be (mis-)used for other things as well, but this PoC is for your specific declare ticks and include use-case.

like image 162
hakre Avatar answered Oct 22 '22 11:10

hakre