• Stars
    star
    209
  • Rank 188,325 (Top 4 %)
  • Language
    C
  • Created over 5 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A super modern high performance profiler for production

stat

Conventional PHP profilers use various Zend API's to overload the engine in order to build and usually dump (or serve) a profile for one single process. This gives us a problem when we want to look at a whole application in profile - we can't enable the profiler for every process in a pool in a production (or even staging) environment. Conventional profilers undertake their work in the same thread as is supposed to be executing your script, severely interfering with the performance characteristics of the code they are meant to be providing a profile for.

Stat is an unconventional provider of profile information: Stat uses an atomic ring buffer to provide realtime profile information for a set of PHP processes over a TCP or unix socket.

Requirements

  • PHP 7.1+
  • Linux

How To

Here is a quick run down of how to use Stat

To build stat

  • phpize
  • ./configure
  • make
  • make install

To load stat

Stat must be loaded as a Zend Extension:

  • add zend_extension=stat.so to the target configuration

php -v should show something like: ... with Stat vX.X.X-X, Copyright (c) 2019, by krakjoe

To configure stat

The following configuration directives are available:

Name Default Purpose
stat.auto On Disable automatic creation of samplers for every request
stat.samplers 0 (unlimited) Set to limit number of concurrent samplers
stat.samples 10000 Set to the maximum number of samples in the buffer
stat.interval 100 Set interval for sampling in microseconds, minimum 10ms
stat.arginfo Off Enable collection of argument info
stat.strings 32M Set size of string buffer (supports suffixes, be generous)
stat.stream zend.stat.stream Set stream socket, setting to 0 disables stream
stat.control zend.stat.control Set control socket, setting to 0 disables control
stat.dump 0 (disabled) Set to a file descriptor for dump on shutdown

To retrieve samples from Stat:

Stat can stream over a unix or TCP socket, the following are valid examples:

  • unix://zend.stat.socket
  • unix:///var/run/zend.stat.socket
  • tcp://127.0.0.1:8010
  • tcp://localhost:8010

Note: If the scheme is omitted, the scheme is assumed to be unix

Upon connection, stat will stream the ring buffer with each sample on a new line, encoded as json with the following schema:

{
    "type": "string",
    "request": {
        "pid": int,
        "elapsed": double,
        "path": "string",
        "method": "string",
        "uri": "string"
    },
    "elapsed": double,
    "memory": {
        "used": int,
        "peak": int
    },
    "symbol": {
        "scope": "string",
        "function": "string"
    },
    "arginfo": ["type(meta)" ...]
}

The nature of a ring buffer means that the samples may not be in the correct temporal sequence (as contained in elapsed), the receiving software must be prepared to deal with that.

Notes:

  • type may be memory, internal, or user
  • the absence of location and symbol signifies that the executor is not currently executing
  • the presence of location and absence of symbol signifies that the executor is currently executing in a file
  • the absense of line in location signifies that a line number is not available for the current instruction
  • the offset in location refers to the offset of opcode from entry to symbol (always available)

To control Stat:

The stream of samples that Stat provides is uninterruptable; Stat is controlled by a separate unix or TCP socket.

This control protocol is a work in progress, and this section is intended for the authors of integrating software.

A control has the following structure:

struct {
    int64_t control;
    int64_t param;
};

The following controls are defined:

Name Control Information
auto 1<<1 Enable/disable automatic creation of samplers
samplers 1<<2 Controls the maximum number of samplers
interval 1<<3 Sets the interval for sampling
arginfo 1<<4 Enables/disables the collection of arginfo

Note: the specifier 'q' should be used for pack (signed long long in machine byte order)

Control: auto

Changing the auto option will effect the subsequent creation of samplers without effecting any currently active samplers.

Control: samplers

Changing the samplers option will effect the subsequent creation of samplers without effecting currently active samplers.

Control: interval

Changing the interval option will effect subsequent ticks of the clock in every active sampler, and subsequently created samplers.

Control: arginfo

Changing the arginfo option will effect all subsequently collected samples.

Stat API:

Stat is a first class citizen in PHP, so there are a few API functions to control and interface with Stat:

<?php
namespace stat {
    /**
    * Shall return the identifier of the current process as identified by Stat
    **/
    function pid() : int;

    /**
    * Shall return seconds elapsed since startup
    **/
    function elapsed() : double;
}

namespace stat\sampler {
    /**
    * Shall activate the sampler for the current request
    **/
    function activate() : bool;

    /**
    * Shall detect if the sampler for the current request is active
    **/
    function active() : bool;

    /**
    * Shall deactivate the sampler for the current request
    **/
    function deactivate() : bool;
}

namespace stat\buffer {
    /* NOT IMPLEMENTED YET */
    
}
?>

Startup

On startup (MINIT) Stat maps:

  • Strings - region of memory for copying persistent strings: file names, class names, and function names
  • Buffer - the sample ring buffer

All memory is shared among forks and threads, and stat uses atomics, for maximum glory.

Should mapping fail, because there isn't enough memory for example, Stat will not stop the process from starting up but will only output a warning. Should mapping succeed, the configured socket will be opened. Should opening the socket fail, Stat will be shutdown immediately but allow the process to continue.

On request startup (RINIT) stat creates a sampler for the current request.

Sampler

Rather than using Zend hooks and interfering with the VM or runtime (function tables etc), Stats sampler is based on parallel uio. When the sampler is created on RINIT, it creates a timer thread which keeps time without repeated syscalls and periodically invokes the sampling routine at the configured interval.

Because sampling occurs in parallel, it's possible to run PHP code at full speed while profiling: In (bench) testing, the overhead of stat running micro bench is statistically insignificant (1-2%, the same margin as without stat loaded) even with an interval of 10us (100k samples per second).

Using uio in parallel, rather than trying to load from the memory of the target process directly protects stat from segfaults - the module globals which the executor uses at runtime are not manipulated atomically by zend, so that if the sampling thread tries to read a location in memory from the PHP process that changes while the read occurs, a segfault would result even if the sampler performs the read atomically - UIO will simply fail under conditions that would cause faults.

This does mean that it's possible (in theory) for sampling to fail. However, in practice, this is not really an issue: When there is a frame pointer in executor globals, it will be copied at once to the stack of the sampler, so that even if the frame pointer changes in the target process while the sampler is working, it doesn't matter because the sampler is still working on the frame it sampled. Another posibility is that the frame is freed between the read of the frame pointer and the frame, in which case failing is the only sensible thing to do as there would be no useful symbol information available to include in a sample.

Fetching argument information for a frame is disabled by default because this is in theory less reliable. The stack space is allocated with the frame by zend, so when the sampler copies the frame to its stack from the heap of the target process, it doesn't have the arguments (they come after the frame). In the time between the sampler copying the frame (without arguments) to its stack, and the sampler copying the arguments from the end of the frame on the heap of the target process, the arguments and their values may have changed. In practice, this is behaviour we are used too - when Zend gathers a backtrace, the values shown are the values at the time of the trace, not at the time of the call.

Shutdown

On request shutdown (RSHUTDOWN) the sampler for the current request is deactivated, this doesn't effect any of the samples it collected.

On shutdown (MSHUTDOWN) the socket is shutdown, any clients connected will recieve the rest of the buffer (beware this may cause a delay in shutting down the process) before the buffer and strings are unmapped.

Notes

Stat is forward compatible with the JIT, and allows the JIT to run at as near as makes no difference full speed. However, the JIT is not obliged to maintain the instruction pointer, and I'm not sure what that would look like anyway. Hopefully, before the JIT becomes a production feature, there will be a way to detect easily if a function (or maybe an instruction) is in the JIT'd area so that we can treat those samples slightly differently.

TODO

  • Improve communication
  • CI
  • Tests

More Repositories

1

pthreads

Threading for PHP - Share Nothing, Do Everything :)
C
3,471
star
2

parallel

A succinct parallel concurrency API for PHP8
C
1,448
star
3

apcu

APCu - APC User Cache
C
964
star
4

phpdbg

The Interactive PHP Debugger
C
837
star
5

pcov

PCOV - CodeCoverage compatible driver for PHP
C
695
star
6

ui

Cross platform UI development in PHP
C
516
star
7

tombs

Detect unused code in production
C
428
star
8

uopz

User Operations for Zend
C
354
star
9

jitfu

Creating native instructions in PHP since 2014
C
191
star
10

explain

Explain
JavaScript
110
star
11

pthreads-polyfill

A polyfill for pthreads
PHP
80
star
12

ilimit

Limit time and memory consumption of individual calls
C
70
star
13

idbg

Inspector Debugger
PHP
70
star
14

wkhtmltox

Converting HTML to X since 2017
C
66
star
15

promises

Promises in PHP
PHP
64
star
16

ustring

UnicodeString for PHP7
C++
64
star
17

autostrict

Automatic strict types in PHP7
C
64
star
18

inspector

Disassembler and Debug Kit for PHP 7
C
55
star
19

sandbox

A sandbox environment for PHP7.1+
C
54
star
20

cmark

CommonMark for PHP
C
46
star
21

mimus

mocking framework as light as a bird ...
PHP
46
star
22

pcov-clobber

Run PCOV in versions of PHPUnit before 8, if you must ...
PHP
40
star
23

componere

Complex Type Composition and Manipulation
C
39
star
24

apcu-bc

APCu Backwards Compatiblity Module
C
33
star
25

SIMD

Single Instruction, Multiple Data
C
30
star
26

pthreads-autoloading-composer

An example of how to use composers autoloader in conjunction with Pools
PHP
29
star
27

memoize

Caching the result of your expensive function calls since 2016
C
23
star
28

profiler

An extension to profile PHP
C
19
star
29

php-jansson

jannson based json encoder and decoder for PHP
C
18
star
30

trace

Tracing for PHP7 Processes
C
18
star
31

snappy

Snappy Compression for PHP
PHP
15
star
32

indexed

Educational Extension for PHP7
C
13
star
33

router

This extension serves to provide a sane, easy router for modern PHP applications:
C
13
star
34

uref

weak refs, for the brave ...
C++
13
star
35

phpdbg-ui

phpdbg remote console client
Java
13
star
36

ponion

onion server for php
C
10
star
37

operators

Override operators in userland
C
9
star
38

HandlerSocket

HandlerSocket PHP 7 Extension
C
9
star
39

apcup

APCu Pooling
C
9
star
40

utypes

User verified types
C
9
star
41

overload

Overloading Zend (RESEARCH)
C
9
star
42

bundle

An experimental method of bundling PHP code with extensions
C
7
star
43

nocheq

Sick of pesky type checking making your code too slow (and correct) ?
C
7
star
44

kore

kore php functions
C
6
star
45

perf

Sampling Profiler for PHP 7 (Unfinished)
C
6
star
46

Object

Multiple Inheritance at Runtime
C
5
star
47

cmark-visitors

Visitors for CommonMark implementing some useful AST transformations
PHP
4
star
48

instrumental

Componere Instrumental
PHP
4
star
49

u2fh

PHP7 u2fh
C
4
star
50

u2fs

PHP7 u2fs
C
3
star
51

sysconf

Get configuration information at run time
C
3
star
52

jitfi

PHP FFI header for libjit
C
3
star
53

cloname

Demonstrate how to give closures long names
C
2
star
54

dec64

dec64
Assembly
2
star
55

transformer

C
2
star
56

zi

Zend Instruments
C
2
star
57

ohash

spl_object_hash alternative playground
C
1
star
58

ui-doc

Documentation Resources for UI
PHP
1
star
59

apcu-ps

APCu PS Module
C
1
star