├── Makefile.frag ├── README.md ├── config.m4 ├── sample.json ├── src ├── zend_stat_arena.c ├── zend_stat_arena.h ├── zend_stat_buffer.c ├── zend_stat_buffer.h ├── zend_stat_control.c ├── zend_stat_control.h ├── zend_stat_ini.c ├── zend_stat_ini.h ├── zend_stat_io.c ├── zend_stat_io.h ├── zend_stat_request.c ├── zend_stat_request.h ├── zend_stat_sample.c ├── zend_stat_sample.h ├── zend_stat_sampler.c ├── zend_stat_sampler.h ├── zend_stat_stream.c ├── zend_stat_stream.h ├── zend_stat_strings.c └── zend_stat_strings.h ├── zend_stat.c └── zend_stat.h /Makefile.frag: -------------------------------------------------------------------------------- 1 | stat-test-coverage: 2 | CCACHE_DISABLE=1 EXTRA_CFLAGS="-fprofile-arcs -ftest-coverage" TEST_PHP_ARGS="-q" $(MAKE) clean test 3 | 4 | stat-test-coverage-lcov: stat-test-coverage 5 | lcov -c --directory $(top_srcdir)/src/.libs --output-file $(top_srcdir)/coverage.info 6 | 7 | stat-test-coverage-html: stat-test-coverage-lcov 8 | genhtml $(top_srcdir)/coverage.info --output-directory=$(top_srcdir)/html 9 | 10 | stat-test-coverage-travis: 11 | CCACHE_DISABLE=1 EXTRA_CFLAGS="-fprofile-arcs -ftest-coverage" $(MAKE) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stat 2 | 3 | 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. 4 | 5 | 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. 6 | 7 | # Requirements 8 | 9 | - PHP 7.1+ 10 | - Linux 11 | 12 | # How To 13 | 14 | Here is a quick run down of how to use Stat 15 | 16 | ## To build stat 17 | 18 | - `phpize` 19 | - `./configure` 20 | - `make` 21 | - `make install` 22 | 23 | ## To load stat 24 | 25 | Stat must be loaded as a Zend Extension: 26 | 27 | - add `zend_extension=stat.so` to the target configuration 28 | 29 | php -v should show something like: `... with Stat vX.X.X-X, Copyright (c) 2019, by krakjoe` 30 | 31 | ## To configure stat 32 | 33 | The following configuration directives are available: 34 | 35 | | Name | Default | Purpose | 36 | |:---------------|:--------------------------|:---------------------------------------------------------------| 37 | |stat.auto |`On` | Disable automatic creation of samplers for every request | 38 | |stat.samplers |`0` (unlimited) | Set to limit number of concurrent samplers | 39 | |stat.samples |`10000` | Set to the maximum number of samples in the buffer | 40 | |stat.interval |`100` | Set interval for sampling in microseconds, minimum 10ms | 41 | |stat.arginfo |`Off` | Enable collection of argument info | 42 | |stat.strings |`32M` | Set size of string buffer (supports suffixes, be generous) | 43 | |stat.stream |`zend.stat.stream` | Set stream socket, setting to 0 disables stream | 44 | |stat.control |`zend.stat.control` | Set control socket, setting to 0 disables control | 45 | |stat.dump |`0` (disabled) | Set to a file descriptor for dump on shutdown | 46 | 47 | ## To retrieve samples from Stat: 48 | 49 | Stat can stream over a unix or TCP socket, the following are valid examples: 50 | 51 | - `unix://zend.stat.socket` 52 | - `unix:///var/run/zend.stat.socket` 53 | - `tcp://127.0.0.1:8010` 54 | - `tcp://localhost:8010` 55 | 56 | *Note: If the scheme is omitted, the scheme is assumed to be unix* 57 | 58 | Upon connection, stat will stream the ring buffer with each sample on a new line, encoded as json with the following schema: 59 | 60 | 61 | { 62 | "type": "string", 63 | "request": { 64 | "pid": int, 65 | "elapsed": double, 66 | "path": "string", 67 | "method": "string", 68 | "uri": "string" 69 | }, 70 | "elapsed": double, 71 | "memory": { 72 | "used": int, 73 | "peak": int 74 | }, 75 | "symbol": { 76 | "scope": "string", 77 | "function": "string" 78 | }, 79 | "arginfo": ["type(meta)" ...] 80 | } 81 | 82 | 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. 83 | 84 | Notes: 85 | 86 | - `type` may be `memory`, `internal`, or `user` 87 | - the absence of `location` and `symbol` signifies that the executor is not currently executing 88 | - the presence of `location` and absence of `symbol` signifies that the executor is currently executing in a file 89 | - the absense of `line` in `location` signifies that a line number is not available for the current instruction 90 | - the `offset` in `location` refers to the offset of `opcode` from entry to `symbol` (always available) 91 | 92 | ## To control Stat: 93 | 94 | The stream of samples that Stat provides is uninterruptable; Stat is controlled by a separate unix or TCP socket. 95 | 96 | This control protocol is a work in progress, and this section is intended for the authors of integrating software. 97 | 98 | A control has the following structure: 99 | 100 | ``` 101 | struct { 102 | int64_t control; 103 | int64_t param; 104 | }; 105 | ``` 106 | 107 | The following controls are defined: 108 | 109 | | Name | Control | Information | 110 | |:---------------|:--------------------------|:---------------------------------------------------------------| 111 | | auto | `1<<1` | Enable/disable automatic creation of samplers | 112 | | samplers | `1<<2` | Controls the maximum number of samplers | 113 | | interval | `1<<3` | Sets the interval for sampling | 114 | | arginfo | `1<<4` | Enables/disables the collection of arginfo | 115 | 116 | *Note: the specifier 'q' should be used for pack (signed long long in machine byte order)* 117 | 118 | ### Control: auto 119 | 120 | Changing the auto option will effect the subsequent creation of samplers without effecting any currently active samplers. 121 | 122 | ### Control: samplers 123 | 124 | Changing the samplers option will effect the subsequent creation of samplers without effecting currently active samplers. 125 | 126 | ### Control: interval 127 | 128 | Changing the interval option will effect subsequent ticks of the clock in every active sampler, and subsequently created samplers. 129 | 130 | ### Control: arginfo 131 | 132 | Changing the arginfo option will effect all subsequently collected samples. 133 | 134 | ## Stat API: 135 | 136 | Stat is a first class citizen in PHP, so there are a few API functions to control and interface with Stat: 137 | 138 | ```php 139 | 174 | ``` 175 | 176 | ### Startup 177 | 178 | On startup (MINIT) Stat maps: 179 | 180 | - Strings - region of memory for copying persistent strings: file names, class names, and function names 181 | - Buffer - the sample ring buffer 182 | 183 | All memory is shared among forks and threads, and stat uses atomics, for maximum glory. 184 | 185 | 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. 186 | 187 | On request startup (RINIT) stat creates a sampler for the current request. 188 | 189 | ### Sampler 190 | 191 | 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. 192 | 193 | 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). 194 | 195 | 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. 196 | 197 | 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. 198 | 199 | 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. 200 | 201 | ### Shutdown 202 | 203 | On request shutdown (RSHUTDOWN) the sampler for the current request is deactivated, this doesn't effect any of the samples it collected. 204 | 205 | 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. 206 | 207 | ### Notes 208 | 209 | 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. 210 | 211 | #### TODO 212 | 213 | - Improve communication 214 | - CI 215 | - Tests 216 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 for extension zstat 2 | 3 | PHP_ARG_ENABLE([stat], 4 | [whether to enable stat support], 5 | [AS_HELP_STRING([--enable-stat], 6 | [Enable stat support])], 7 | [no]) 8 | 9 | PHP_ARG_ENABLE([stat-coverage], 10 | [whether to enable stat coverage support], 11 | [AS_HELP_STRING([--enable-stat-coverage], 12 | [Enable stat coverage support])], 13 | [no], [no]) 14 | 15 | PHP_ARG_ENABLE([stat-arena-debug], 16 | [whether to enable stat arena debug support], 17 | [AS_HELP_STRING([--enable-stat-arena-debug], 18 | [Enable stat arena debug support])], 19 | [no], [no]) 20 | 21 | if test "$PHP_STAT" != "no"; then 22 | PHP_ADD_LIBRARY(pthread,, STAT_SHARED_LIBADD) 23 | 24 | AC_DEFINE(HAVE_ZEND_STAT, 1, [ Have stat support ]) 25 | 26 | if test "$PHP_STAT_ARENA_DEBUG" != "no"; then 27 | AC_DEFINE(ZEND_STAT_ARENA_DEBUG, 1, [ Have stat arena debug support ]) 28 | else 29 | AC_DEFINE(ZEND_STAT_ARENA_DEBUG, 0, [ Do not have stat arena debug support ]) 30 | fi 31 | 32 | PHP_NEW_EXTENSION(stat, 33 | zend_stat.c \ 34 | src/zend_stat_arena.c \ 35 | src/zend_stat_buffer.c \ 36 | src/zend_stat_ini.c \ 37 | src/zend_stat_io.c \ 38 | src/zend_stat_request.c \ 39 | src/zend_stat_stream.c \ 40 | src/zend_stat_control.c \ 41 | src/zend_stat_sampler.c \ 42 | src/zend_stat_sample.c \ 43 | src/zend_stat_strings.c, 44 | $ext_shared,,-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1,,yes) 45 | 46 | PHP_ADD_BUILD_DIR($ext_builddir/src, 1) 47 | PHP_ADD_INCLUDE($ext_srcdir/src) 48 | PHP_ADD_INCLUDE($ext_srcdir) 49 | 50 | AC_MSG_CHECKING([stat coverage]) 51 | if test "$PHP_STAT_COVERAGE" != "no"; then 52 | AC_MSG_RESULT([enabled]) 53 | 54 | PHP_ADD_MAKEFILE_FRAGMENT 55 | else 56 | AC_MSG_RESULT([disabled]) 57 | fi 58 | 59 | 60 | PHP_SUBST(STAT_SHARED_LIBADD) 61 | fi 62 | -------------------------------------------------------------------------------- /sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": {}, 3 | "$schema": "http://json-schema.org/draft-07/schema#", 4 | "$id": "http://gitub.com/krakjoe/stat/sample.json", 5 | "type": "object", 6 | "title": "Sample Schema", 7 | "required": [ 8 | "type", 9 | "request", 10 | "elapsed", 11 | "memory" 12 | ], 13 | "properties": { 14 | "type": { 15 | "$id": "#/properties/type", 16 | "type": "string", 17 | "title": "Sample Type", 18 | "default": "", 19 | "examples": [ 20 | "memory", 21 | "internal", 22 | "user" 23 | ], 24 | "pattern": "^(memory|internal|user)$" 25 | }, 26 | "request": { 27 | "$id": "#/properties/request", 28 | "type": "object", 29 | "title": "Request", 30 | "required": [ 31 | "pid", 32 | "elapsed" 33 | ], 34 | "properties": { 35 | "pid": { 36 | "$id": "#/properties/request/properties/pid", 37 | "type": "integer", 38 | "title": "The Process ID" 39 | }, 40 | "elapsed": { 41 | "$id": "#/properties/request/properties/elapsed", 42 | "type": "number", 43 | "title": "Time" 44 | }, 45 | "path": { 46 | "$id": "#/properties/request/properties/path", 47 | "type": "string", 48 | "title": "The Translated Path", 49 | "examples": [ 50 | "/var/www/html/index.php" 51 | ], 52 | "pattern": "^(.*)$" 53 | }, 54 | "method": { 55 | "$id": "#/properties/request/properties/method", 56 | "type": "string", 57 | "title": "Request Method", 58 | "examples": [ 59 | "POST", 60 | "GET", 61 | "PUT", 62 | "PATCH" 63 | ], 64 | "pattern": "^(.*)$" 65 | }, 66 | "uri": { 67 | "$id": "#/properties/request/properties/uri", 68 | "type": "string", 69 | "title": "Request URI", 70 | "examples": [ 71 | "/index.php" 72 | ], 73 | "pattern": "^(.*)$" 74 | } 75 | } 76 | }, 77 | "elapsed": { 78 | "$id": "#/properties/elapsed", 79 | "type": "number", 80 | "title": "Time" 81 | }, 82 | "memory": { 83 | "$id": "#/properties/memory", 84 | "type": "object", 85 | "title": "Memory Usage", 86 | "required": [ 87 | "used", 88 | "peak" 89 | ], 90 | "properties": { 91 | "used": { 92 | "$id": "#/properties/memory/properties/used", 93 | "type": "integer", 94 | "title": "Used Memory" 95 | }, 96 | "peak": { 97 | "$id": "#/properties/memory/properties/peak", 98 | "type": "integer", 99 | "title": "Peak Used Memory" 100 | } 101 | } 102 | }, 103 | "symbol": { 104 | "$id": "#/properties/symbol", 105 | "type": "object", 106 | "title": "Symbol", 107 | "required": [ 108 | "file" 109 | ], 110 | "properties": { 111 | "file": { 112 | "$id": "#/properties/symbol/properties/file", 113 | "type": "string", 114 | "title": "File Name", 115 | "pattern": "^(.*)$" 116 | }, 117 | "scope": { 118 | "$id": "#/properties/symbol/properties/scope", 119 | "type": "string", 120 | "title": "Class Name", 121 | "pattern": "^(.*)$" 122 | }, 123 | "function": { 124 | "$id": "#/properties/symbol/properties/function", 125 | "type": "string", 126 | "title": "Function Name", 127 | "pattern": "^(.*)$" 128 | } 129 | } 130 | }, 131 | "opline": { 132 | "$id": "#/properties/opline", 133 | "type": "object", 134 | "title": "The Opline Schema", 135 | "required": [ 136 | "offset" 137 | ], 138 | "properties": { 139 | "offset": { 140 | "$id": "#/properties/opline/properties/offset", 141 | "type": "integer", 142 | "title": "Instruction Offset" 143 | }, 144 | "line": { 145 | "$id": "#/properties/opline/properties/line", 146 | "type": "integer", 147 | "title": "Line Number" 148 | }, 149 | "opcode": { 150 | "$id": "#/properties/opline/properties/opcode", 151 | "type": "string", 152 | "title": "Opcode Name", 153 | "pattern": "^(.*)$" 154 | } 155 | } 156 | }, 157 | "caller": { 158 | "$id": "#/properties/caller", 159 | "type": "object", 160 | "title": "Caller", 161 | "required": [ 162 | "file" 163 | ], 164 | "properties": { 165 | "file": { 166 | "$id": "#/properties/caller/properties/file", 167 | "type": "string", 168 | "title": "File Name", 169 | "pattern": "^(.*)$" 170 | }, 171 | "scope": { 172 | "$id": "#/properties/caller/properties/scope", 173 | "type": "string", 174 | "title": "Class Name", 175 | "pattern": "^(.*)$" 176 | }, 177 | "function": { 178 | "$id": "#/properties/caller/properties/function", 179 | "type": "string", 180 | "title": "Function Name", 181 | "pattern": "^(.*)$" 182 | } 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/zend_stat_arena.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_ARENA 20 | # define ZEND_STAT_ARENA 21 | 22 | #include "zend_stat.h" 23 | #include "zend_stat_arena.h" 24 | 25 | #ifndef ZEND_STAT_ARENA_DEBUG 26 | # define ZEND_STAT_ARENA_DEBUG 0 27 | #endif 28 | 29 | typedef struct _zend_stat_arena_block_t zend_stat_arena_block_t; 30 | 31 | typedef intptr_t zend_stat_arena_ptr_t; 32 | 33 | struct _zend_stat_arena_block_t { 34 | zend_long size; 35 | zend_long used; 36 | zend_stat_arena_block_t *next; 37 | zend_stat_arena_ptr_t mem[1]; 38 | }; 39 | 40 | #define ZEND_STAT_ARENA_BLOCK_SIZE \ 41 | zend_stat_arena_aligned(sizeof(zend_stat_arena_block_t)) 42 | #define ZEND_STAT_ARENA_BLOCK_MIN \ 43 | (ZEND_STAT_ARENA_BLOCK_SIZE * 2) 44 | 45 | struct _zend_stat_arena_t { 46 | pthread_mutex_t mutex; 47 | zend_long size; 48 | zend_ulong bytes; 49 | char *brk; 50 | char *end; 51 | struct { 52 | zend_stat_arena_block_t *start; 53 | zend_stat_arena_block_t *end; 54 | } list; 55 | }; 56 | 57 | #define ZEND_STAT_ARENA_SIZE \ 58 | zend_stat_arena_aligned(sizeof(zend_stat_arena_t)) 59 | #define ZEND_STAT_ARENA_OOM \ 60 | (void*) -1 61 | 62 | static zend_always_inline zend_long zend_stat_arena_aligned(zend_long size) { 63 | return (size + sizeof(zend_stat_arena_ptr_t) - 1) & ~(sizeof(zend_stat_arena_ptr_t) - 1); 64 | } 65 | 66 | static zend_always_inline zend_stat_arena_block_t* zend_stat_arena_block(void *mem) { 67 | return (zend_stat_arena_block_t*) (((char*) mem) - XtOffsetOf(zend_stat_arena_block_t, mem)); 68 | } 69 | 70 | static zend_always_inline zend_stat_arena_block_t* zend_stat_arena_find(zend_stat_arena_t *arena, zend_long size) { 71 | zend_stat_arena_block_t *block = arena->list.start; 72 | zend_long wasted; 73 | 74 | while (NULL != block) { 75 | if ((0 == block->used)) { 76 | if ((block->size >= size)) { 77 | goto _zend_stat_arena_found; 78 | } else { 79 | if (NULL != block->next) { 80 | if ((0 == block->next->used) && 81 | ((block->size + block->next->size) >= size)) { 82 | block->size += block->next->size; 83 | block->next = block->next->next; 84 | goto _zend_stat_arena_found; 85 | } 86 | } 87 | } 88 | } 89 | 90 | block = block->next; 91 | } 92 | 93 | return NULL; 94 | 95 | _zend_stat_arena_found: 96 | if (((wasted = (block->size - size)) > 0)) { 97 | if ((wasted > ZEND_STAT_ARENA_BLOCK_MIN)) { 98 | zend_stat_arena_block_t *reclaim = 99 | (zend_stat_arena_block_t*) 100 | (((char*) block->mem) + size); 101 | 102 | reclaim->used = 0; 103 | reclaim->size = 104 | (wasted - ZEND_STAT_ARENA_BLOCK_SIZE); 105 | reclaim->next = block->next; 106 | 107 | block->next = reclaim; 108 | } 109 | 110 | block->size = size; 111 | } 112 | block->used = size; 113 | 114 | return block; 115 | } 116 | 117 | zend_stat_arena_t* zend_stat_arena_create(zend_long size) { 118 | zend_long aligned = 119 | zend_stat_arena_aligned(ZEND_STAT_ARENA_SIZE + size); 120 | zend_stat_arena_t *arena = 121 | (zend_stat_arena_t*) 122 | zend_stat_map(aligned); 123 | 124 | if (!arena) { 125 | return NULL; 126 | } 127 | 128 | if (!zend_stat_mutex_init(&arena->mutex, 1)) { 129 | zend_stat_unmap(arena, aligned); 130 | return NULL; 131 | } 132 | 133 | arena->end = (char*) (((char*) arena) + aligned); 134 | arena->brk = (char*) (((char*) arena) + ZEND_STAT_ARENA_SIZE); 135 | arena->bytes = arena->end - arena->brk; 136 | arena->size = aligned; 137 | 138 | return arena; 139 | } 140 | 141 | static zend_stat_arena_block_t* zend_stat_arena_brk(zend_stat_arena_t *arena, zend_long size) { 142 | if (brk > 0) { 143 | size = 144 | zend_stat_arena_aligned( 145 | ZEND_STAT_ARENA_BLOCK_SIZE + size); 146 | 147 | if (UNEXPECTED((arena->brk + size) > arena->end)) { 148 | return ZEND_STAT_ARENA_OOM; 149 | } 150 | 151 | arena->brk += size; 152 | } 153 | 154 | return (zend_stat_arena_block_t*) arena->brk; 155 | } 156 | 157 | void* zend_stat_arena_alloc(zend_stat_arena_t *arena, zend_long size) { 158 | zend_long aligned = 159 | zend_stat_arena_aligned(size); 160 | zend_stat_arena_block_t *block; 161 | 162 | if (UNEXPECTED(SUCCESS != 163 | pthread_mutex_lock(&arena->mutex))) { 164 | return NULL; 165 | } 166 | 167 | block = zend_stat_arena_find(arena, aligned); 168 | 169 | if (EXPECTED(NULL != block)) { 170 | goto _zend_stat_arena_alloc_leave; 171 | } 172 | 173 | block = zend_stat_arena_brk(arena, 0); 174 | 175 | if (UNEXPECTED(zend_stat_arena_brk( 176 | arena, aligned) == ZEND_STAT_ARENA_OOM)) { 177 | goto _zend_stat_arena_alloc_oom; 178 | } 179 | 180 | block->used = 181 | block->size = aligned; 182 | 183 | if (UNEXPECTED(NULL == arena->list.start)) { 184 | arena->list.start = block; 185 | } 186 | 187 | if (EXPECTED(NULL != arena->list.end)) { 188 | arena->list.end->next = block; 189 | } 190 | 191 | arena->list.end = block; 192 | 193 | _zend_stat_arena_alloc_leave: 194 | pthread_mutex_unlock(&arena->mutex); 195 | 196 | return memset(block->mem, 0, block->size); 197 | 198 | _zend_stat_arena_alloc_oom: 199 | pthread_mutex_unlock(&arena->mutex); 200 | 201 | return NULL; 202 | } 203 | 204 | #if ZEND_STAT_ARENA_DEBUG 205 | static zend_always_inline zend_bool zend_stat_arena_owns(zend_stat_arena_t *arena, void *mem) { 206 | if (UNEXPECTED((((char*) mem) < ((char*) arena)) || (((char*) mem) > arena->end))) { 207 | return 0; 208 | } 209 | 210 | return 1; 211 | } 212 | #endif 213 | 214 | void zend_stat_arena_free(zend_stat_arena_t *arena, void *mem) { 215 | zend_stat_arena_block_t *block = zend_stat_arena_block(mem); 216 | 217 | #if ZEND_STAT_ARENA_DEBUG 218 | ZEND_ASSERT(zend_stat_arena_owns(arena, mem)); 219 | #endif 220 | 221 | /* Currently this is not a very high frequency function, it is only 222 | ever invoked by rshutdown. */ 223 | pthread_mutex_lock(&arena->mutex); 224 | 225 | while ((NULL != block->next)) { 226 | if ((0 == block->next->used)) { 227 | if ((NULL != arena->list.end) && 228 | (block->next == arena->list.end)) { 229 | arena->list.end = block->next->next; 230 | } 231 | 232 | block->size += block->next->size; 233 | block->next = block->next->next; 234 | } else { 235 | break; 236 | } 237 | } 238 | 239 | block->used = 0; 240 | 241 | pthread_mutex_unlock(&arena->mutex); 242 | } 243 | 244 | #if ZEND_STAT_ARENA_DEBUG 245 | static zend_always_inline void zend_stat_arena_debug(zend_stat_arena_t *arena) { 246 | zend_stat_arena_block_t *block = arena->list.start; 247 | zend_long wasted = 0; 248 | 249 | while (NULL != block) { 250 | if (block->used > 0) { 251 | wasted += block->size - block->used; 252 | } 253 | 254 | if (block->used > 0) { 255 | fprintf(stderr, 256 | "[STAT] %p leaked %p "ZEND_LONG_FMT" bytes\n", 257 | arena, block->mem, block->size); 258 | } 259 | block = block->next; 260 | } 261 | 262 | fprintf(stderr, "[STAT] %p wasted "ZEND_LONG_FMT" bytes\n", arena, wasted); 263 | } 264 | #endif 265 | 266 | void zend_stat_arena_destroy(zend_stat_arena_t *arena) { 267 | if (!arena) { 268 | return; 269 | } 270 | 271 | #if ZEND_STAT_ARENA_DEBUG 272 | zend_stat_arena_debug(arena); 273 | #endif 274 | 275 | zend_stat_mutex_destroy(&arena->mutex); 276 | 277 | zend_stat_unmap(arena, arena->size); 278 | } 279 | #endif 280 | -------------------------------------------------------------------------------- /src/zend_stat_arena.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_ARENA_H 20 | # define ZEND_STAT_ARENA_H 21 | 22 | typedef struct _zend_stat_arena_t zend_stat_arena_t; 23 | 24 | zend_stat_arena_t* zend_stat_arena_create(zend_long size); 25 | void* zend_stat_arena_alloc(zend_stat_arena_t *arena, zend_long size); 26 | void zend_stat_arena_free(zend_stat_arena_t *arena, void *mem); 27 | void zend_stat_arena_destroy(zend_stat_arena_t *arena); 28 | #endif 29 | -------------------------------------------------------------------------------- /src/zend_stat_buffer.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_BUFFER 20 | # define ZEND_STAT_BUFFER 21 | 22 | #include "zend_stat.h" 23 | #include "zend_stat_buffer.h" 24 | #include "zend_stat_io.h" 25 | 26 | struct _zend_stat_buffer_t { 27 | zend_stat_sample_t *samples; 28 | zend_stat_sample_t *position; 29 | zend_stat_sample_t *it; 30 | zend_stat_sample_t *end; 31 | zend_ulong max; 32 | zend_ulong used; 33 | }; 34 | 35 | static size_t zend_always_inline zend_stat_buffer_size(zend_long samples) { 36 | return sizeof(zend_stat_buffer_t) + 37 | (samples * sizeof(zend_stat_sample_t)); 38 | } 39 | 40 | zend_stat_buffer_t* zend_stat_buffer_startup(zend_long samples) { 41 | size_t size = zend_stat_buffer_size(samples); 42 | zend_stat_buffer_t *buffer = zend_stat_map(size); 43 | 44 | if (!buffer) { 45 | zend_error(E_WARNING, 46 | "[STAT] Failed to allocate shared memory for buffer"); 47 | return NULL; 48 | } 49 | 50 | memset(buffer, 0, size); 51 | 52 | buffer->samples = 53 | buffer->it = 54 | buffer->position = 55 | (zend_stat_sample_t*) (((char*) buffer) + sizeof(zend_stat_buffer_t)); 56 | buffer->max = samples; 57 | buffer->used = 0; 58 | buffer->end = buffer->position + buffer->max; 59 | 60 | memset(buffer->samples, 0, sizeof(zend_stat_sample_t) * buffer->max); 61 | 62 | return buffer; 63 | } 64 | 65 | zend_bool zend_stat_buffer_empty(zend_stat_buffer_t *buffer) { 66 | return 0 == __atomic_load_n(&buffer->used, __ATOMIC_SEQ_CST); 67 | } 68 | 69 | void zend_stat_buffer_insert(zend_stat_buffer_t *buffer, zend_stat_sample_t *input) { 70 | zend_stat_sample_t *sample; 71 | zend_bool _unused = 0, 72 | _used = 1; 73 | 74 | do { 75 | zend_bool _unbusy = 0, 76 | _busy = 1; 77 | 78 | sample = __atomic_fetch_add( 79 | &buffer->position, sizeof(zend_stat_sample_t), __ATOMIC_SEQ_CST); 80 | 81 | if (UNEXPECTED(sample >= buffer->end)) { 82 | __atomic_store_n( 83 | &buffer->position, 84 | buffer->samples, __ATOMIC_SEQ_CST); 85 | continue; 86 | } 87 | 88 | if (UNEXPECTED(!__atomic_compare_exchange( 89 | &sample->state.busy, 90 | &_unbusy, &_busy, 91 | 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))) { 92 | continue; 93 | } 94 | 95 | break; 96 | } while(1); 97 | 98 | zend_stat_request_release(&sample->request); 99 | 100 | memcpy( 101 | ZEND_STAT_SAMPLE_DATA(sample), 102 | ZEND_STAT_SAMPLE_DATA(input), 103 | ZEND_STAT_SAMPLE_DATA_SIZE); 104 | 105 | if (__atomic_compare_exchange(&sample->state.used, 106 | &_unused, &_used, 0, 107 | __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)) { 108 | __atomic_fetch_add(&buffer->used, 1, __ATOMIC_SEQ_CST); 109 | } 110 | 111 | __atomic_store_n(&sample->state.busy, 0, __ATOMIC_SEQ_CST); 112 | } 113 | 114 | zend_bool zend_stat_buffer_consume(zend_stat_buffer_t *buffer, zend_stat_buffer_consumer_t zend_stat_buffer_consumer, void *arg, zend_ulong max) { 115 | zend_stat_sample_t *sample; 116 | zend_ulong tried = 0; 117 | 118 | if (zend_stat_buffer_empty(buffer)) { 119 | return ZEND_STAT_BUFFER_CONSUMER_CONTINUE; 120 | } 121 | 122 | while (tried++ < max) { 123 | zend_stat_sample_t sampled = zend_stat_sample_empty; 124 | zend_bool _unbusy = 0, 125 | _busy = 1, 126 | _unused = 0, 127 | _used = 1; 128 | 129 | sample = __atomic_fetch_add( 130 | &buffer->it, sizeof(zend_stat_sample_t), __ATOMIC_SEQ_CST); 131 | 132 | if (UNEXPECTED(sample >= buffer->end)) { 133 | __atomic_store_n( 134 | &buffer->it, 135 | buffer->samples, __ATOMIC_SEQ_CST); 136 | continue; 137 | } 138 | 139 | if (UNEXPECTED(!__atomic_compare_exchange( 140 | &sample->state.busy, 141 | &_unbusy, &_busy, 142 | 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))) { 143 | continue; 144 | } 145 | 146 | if (EXPECTED(__atomic_compare_exchange( 147 | &sample->state.used, 148 | &_used, &_unused, 149 | 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST))) { 150 | 151 | __atomic_sub_fetch(&buffer->used, 1, __ATOMIC_SEQ_CST); 152 | 153 | memcpy(&sampled, sample, sizeof(zend_stat_sample_t)); 154 | } 155 | 156 | __atomic_store_n(&sample->state.busy, 0, __ATOMIC_SEQ_CST); 157 | 158 | if (UNEXPECTED(ZEND_STAT_SAMPLE_UNUSED == sampled.type)) { 159 | continue; 160 | } 161 | 162 | if (zend_stat_buffer_consumer(&sampled, arg) == ZEND_STAT_BUFFER_CONSUMER_STOP) { 163 | zend_stat_request_release(&sampled.request); 164 | return ZEND_STAT_BUFFER_CONSUMER_STOP; 165 | } 166 | 167 | zend_stat_request_release(&sampled.request); 168 | } 169 | 170 | return ZEND_STAT_BUFFER_CONSUMER_CONTINUE; 171 | } 172 | 173 | static zend_bool zend_stat_buffer_write(zend_stat_sample_t *sample, void *arg) { 174 | return zend_stat_sample_write(sample, *(int*) arg); 175 | } 176 | 177 | zend_bool zend_stat_buffer_dump(zend_stat_buffer_t *buffer, int fd) { 178 | return zend_stat_buffer_consume(buffer, zend_stat_buffer_write, (void*) &fd, buffer->max); 179 | } 180 | 181 | void zend_stat_buffer_shutdown(zend_stat_buffer_t *buffer) { 182 | #ifdef ZEND_DEBUG 183 | zend_stat_sample_t *sample = buffer->samples, 184 | *end = sample + buffer->max; 185 | 186 | while (sample < end) { 187 | if (sample->type != ZEND_STAT_SAMPLE_UNUSED) { 188 | zend_stat_request_release(&sample->request); 189 | } 190 | sample++; 191 | } 192 | #endif 193 | zend_stat_unmap(buffer, zend_stat_buffer_size(buffer->max)); 194 | } 195 | 196 | #endif /* ZEND_STAT_BUFFER */ 197 | -------------------------------------------------------------------------------- /src/zend_stat_buffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_BUFFER_H 20 | # define ZEND_STAT_BUFFER_H 21 | 22 | extern ZEND_FUNCTION(zend_stat_buffer_consume); 23 | 24 | typedef struct _zend_stat_buffer_t zend_stat_buffer_t; 25 | 26 | zend_stat_buffer_t* zend_stat_buffer_startup(zend_long samples); 27 | void zend_stat_buffer_shutdown(zend_stat_buffer_t *); 28 | 29 | void zend_stat_buffer_activate(zend_stat_buffer_t *buffer, pid_t pid); 30 | void zend_stat_buffer_deactivate(zend_stat_buffer_t *buffer, pid_t pid); 31 | 32 | #include "zend_stat_sampler.h" 33 | 34 | #define ZEND_STAT_BUFFER_CONSUMER_STOP 0 35 | #define ZEND_STAT_BUFFER_CONSUMER_CONTINUE 1 36 | 37 | typedef zend_bool (*zend_stat_buffer_consumer_t)(zend_stat_sample_t *, void *); 38 | 39 | double zend_stat_buffer_started(zend_stat_buffer_t *buffer); 40 | void zend_stat_buffer_insert(zend_stat_buffer_t *buffer, zend_stat_sample_t *sample); 41 | zend_bool zend_stat_buffer_empty(zend_stat_buffer_t *buffer); 42 | zend_bool zend_stat_buffer_dump(zend_stat_buffer_t *buffer, int fd); 43 | zend_bool zend_stat_buffer_consume(zend_stat_buffer_t *buffer, zend_stat_buffer_consumer_t zend_stat_buffer_consumer, void *arg, zend_ulong max); 44 | 45 | #endif /* ZEND_STAT_BUFFER_H */ 46 | -------------------------------------------------------------------------------- /src/zend_stat_control.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_CONTROL 20 | # define ZEND_STAT_CONTROL 21 | 22 | #include "zend_stat.h" 23 | #include "zend_stat_io.h" 24 | #include "zend_stat_control.h" 25 | 26 | typedef enum { 27 | ZEND_STAT_CONTROL_UNKNOWN = 0, 28 | ZEND_STAT_CONTROL_FAILED = (1<<0), 29 | ZEND_STAT_CONTROL_AUTO = (1<<1), 30 | ZEND_STAT_CONTROL_SAMPLERS = (1<<2), 31 | ZEND_STAT_CONTROL_INTERVAL = (1<<3), 32 | ZEND_STAT_CONTROL_ARGINFO = (1<<4) 33 | } zend_stat_control_type_t; 34 | 35 | typedef struct _zend_stat_control_t { 36 | int64_t type; 37 | int64_t param; 38 | } zend_stat_control_t; 39 | 40 | const static zend_stat_control_t 41 | zend_stat_control_empty = 42 | {ZEND_STAT_CONTROL_UNKNOWN, 0}; 43 | 44 | static zend_stat_control_type_t zend_stat_control_read(int client, int64_t *param) { 45 | zend_stat_control_t control = zend_stat_control_empty; 46 | ssize_t read = recv( 47 | client, 48 | &control, 49 | sizeof(zend_stat_control_t), MSG_WAITALL); 50 | 51 | if (read != sizeof(zend_stat_control_t)) { 52 | if (errno == EINTR) { 53 | return zend_stat_control_read(client, param); 54 | } 55 | 56 | return ZEND_STAT_CONTROL_FAILED; 57 | } 58 | 59 | *param = control.param; 60 | 61 | return control.type; 62 | } 63 | 64 | static void zend_stat_control(zend_stat_io_t *io, int client) { 65 | while (!zend_stat_io_closed(io)) { 66 | int64_t param = 0; 67 | 68 | switch (zend_stat_control_read(client, ¶m)) { 69 | case ZEND_STAT_CONTROL_AUTO: 70 | zend_stat_sampler_auto_set((zend_bool) param); 71 | break; 72 | 73 | case ZEND_STAT_CONTROL_SAMPLERS: 74 | zend_stat_sampler_limit_set((zend_long) param); 75 | break; 76 | 77 | case ZEND_STAT_CONTROL_INTERVAL: 78 | if (param >= ZEND_STAT_INTERVAL_MIN) { 79 | zend_stat_sampler_interval_set((zend_long) param); 80 | } 81 | break; 82 | 83 | case ZEND_STAT_CONTROL_ARGINFO: 84 | zend_stat_sampler_arginfo_set((zend_bool) param); 85 | break; 86 | 87 | case ZEND_STAT_CONTROL_FAILED: 88 | return; 89 | 90 | EMPTY_SWITCH_DEFAULT_CASE(); 91 | } 92 | } 93 | } 94 | 95 | zend_bool zend_stat_control_startup(zend_stat_io_t *io, zend_stat_buffer_t *buffer, char *control) { 96 | return zend_stat_io_startup(io, control, buffer, zend_stat_control); 97 | } 98 | 99 | void zend_stat_control_shutdown(zend_stat_io_t *io) { 100 | zend_stat_io_shutdown(io); 101 | } 102 | #endif 103 | -------------------------------------------------------------------------------- /src/zend_stat_control.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_CONTROL_H 20 | # define ZEND_STAT_CONTROL_H 21 | 22 | #include "zend_stat_io.h" 23 | 24 | zend_bool zend_stat_control_startup(zend_stat_io_t *io, zend_stat_buffer_t *buffer, char *control); 25 | void zend_stat_control_shutdown(zend_stat_io_t *io); 26 | #endif 27 | -------------------------------------------------------------------------------- /src/zend_stat_ini.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_INI 20 | # define ZEND_STAT_INI 21 | 22 | #include "zend_stat.h" 23 | #include "zend_stat_ini.h" 24 | 25 | zend_bool zend_stat_ini_auto = 0; 26 | zend_long zend_stat_ini_samplers = -1; 27 | zend_long zend_stat_ini_samples = -1; 28 | zend_long zend_stat_ini_interval = -1; 29 | zend_bool zend_stat_ini_arginfo = 0; 30 | zend_long zend_stat_ini_strings = -1; 31 | char* zend_stat_ini_stream = NULL; 32 | char* zend_stat_ini_control = NULL; 33 | int zend_stat_ini_dump = -1; 34 | 35 | #if PHP_VERSION_ID < 70300 36 | static zend_always_inline zend_bool zend_stat_ini_parse_bool(zend_string *new_value) { 37 | if (SUCCESS == strcasecmp("on", ZSTR_VAL(new_value)) || 38 | SUCCESS == strcasecmp("true", ZSTR_VAL(new_value)) || 39 | SUCCESS == strcasecmp("yes", ZSTR_VAL(new_value))) { 40 | return 1; 41 | } 42 | return (zend_bool) atoi(ZSTR_VAL(new_value)); 43 | } 44 | #else 45 | #define zend_stat_ini_parse_bool zend_ini_parse_bool 46 | #endif 47 | 48 | static ZEND_INI_MH(zend_stat_ini_update_auto) 49 | { 50 | zend_stat_ini_auto = 51 | zend_stat_ini_parse_bool(new_value); 52 | 53 | return SUCCESS; 54 | } 55 | 56 | static ZEND_INI_MH(zend_stat_ini_update_samplers) 57 | { 58 | if (UNEXPECTED(zend_stat_ini_samplers != -1)) { 59 | return FAILURE; 60 | } 61 | 62 | zend_stat_ini_samplers = 63 | zend_atol( 64 | ZSTR_VAL(new_value), 65 | ZSTR_LEN(new_value)); 66 | 67 | return SUCCESS; 68 | } 69 | 70 | static ZEND_INI_MH(zend_stat_ini_update_samples) 71 | { 72 | if (UNEXPECTED(zend_stat_ini_samples != -1)) { 73 | return FAILURE; 74 | } 75 | 76 | zend_stat_ini_samples = 77 | zend_atol( 78 | ZSTR_VAL(new_value), 79 | ZSTR_LEN(new_value)); 80 | 81 | return SUCCESS; 82 | } 83 | 84 | static ZEND_INI_MH(zend_stat_ini_update_interval) 85 | { 86 | if (UNEXPECTED(zend_stat_ini_interval != -1)) { 87 | return FAILURE; 88 | } 89 | 90 | zend_stat_ini_interval = 91 | zend_atol( 92 | ZSTR_VAL(new_value), 93 | ZSTR_LEN(new_value)); 94 | 95 | if (zend_stat_ini_interval < ZEND_STAT_INTERVAL_MIN) { 96 | zend_error( 97 | E_WARNING, 98 | "[STAT] minimum interval is %d, " 99 | "stat.interval set at " ZEND_LONG_FMT, 100 | ZEND_STAT_INTERVAL_MIN, 101 | zend_stat_ini_interval); 102 | zend_stat_ini_interval = ZEND_STAT_INTERVAL_MIN; 103 | } 104 | 105 | return SUCCESS; 106 | } 107 | 108 | static ZEND_INI_MH(zend_stat_ini_update_arginfo) 109 | { 110 | zend_stat_ini_arginfo = 111 | zend_stat_ini_parse_bool(new_value); 112 | 113 | return SUCCESS; 114 | } 115 | 116 | static ZEND_INI_MH(zend_stat_ini_update_strings) 117 | { 118 | if (UNEXPECTED(zend_stat_ini_strings != -1)) { 119 | return FAILURE; 120 | } 121 | 122 | zend_stat_ini_strings = 123 | zend_atol( 124 | ZSTR_VAL(new_value), 125 | ZSTR_LEN(new_value)); 126 | 127 | return SUCCESS; 128 | } 129 | 130 | static ZEND_INI_MH(zend_stat_ini_update_stream) 131 | { 132 | int skip = FAILURE; 133 | 134 | if (UNEXPECTED(NULL != zend_stat_ini_stream)) { 135 | return FAILURE; 136 | } 137 | 138 | if (sscanf(ZSTR_VAL(new_value), "%d", &skip) == 1) { 139 | if (SUCCESS == skip) { 140 | return SUCCESS; 141 | } 142 | } 143 | 144 | zend_stat_ini_stream = pestrndup(ZSTR_VAL(new_value), ZSTR_LEN(new_value), 1); 145 | 146 | return SUCCESS; 147 | } 148 | 149 | static ZEND_INI_MH(zend_stat_ini_update_control) 150 | { 151 | int skip = FAILURE; 152 | 153 | if (UNEXPECTED(NULL != zend_stat_ini_control)) { 154 | return FAILURE; 155 | } 156 | 157 | if (sscanf(ZSTR_VAL(new_value), "%d", &skip) == 1) { 158 | if (SUCCESS == skip) { 159 | return SUCCESS; 160 | } 161 | } 162 | 163 | zend_stat_ini_control = pestrndup(ZSTR_VAL(new_value), ZSTR_LEN(new_value), 1); 164 | 165 | return SUCCESS; 166 | } 167 | 168 | static ZEND_INI_MH(zend_stat_ini_update_dump) 169 | { 170 | if (UNEXPECTED(-1 != zend_stat_ini_dump)) { 171 | return FAILURE; 172 | } 173 | 174 | zend_stat_ini_dump = zend_atoi(ZSTR_VAL(new_value), ZSTR_LEN(new_value)); 175 | 176 | return SUCCESS; 177 | } 178 | 179 | ZEND_INI_BEGIN() 180 | ZEND_INI_ENTRY("stat.auto", "On", ZEND_INI_SYSTEM, zend_stat_ini_update_auto) 181 | ZEND_INI_ENTRY("stat.samplers", "0", ZEND_INI_SYSTEM, zend_stat_ini_update_samplers) 182 | ZEND_INI_ENTRY("stat.samples", "10000", ZEND_INI_SYSTEM, zend_stat_ini_update_samples) 183 | ZEND_INI_ENTRY("stat.interval", "100", ZEND_INI_SYSTEM, zend_stat_ini_update_interval) 184 | ZEND_INI_ENTRY("stat.arginfo", "Off", ZEND_INI_SYSTEM, zend_stat_ini_update_arginfo) 185 | ZEND_INI_ENTRY("stat.strings", "32M", ZEND_INI_SYSTEM, zend_stat_ini_update_strings) 186 | ZEND_INI_ENTRY("stat.stream", "zend.stat.stream", ZEND_INI_SYSTEM, zend_stat_ini_update_stream) 187 | ZEND_INI_ENTRY("stat.control", "zend.stat.control", ZEND_INI_SYSTEM, zend_stat_ini_update_control) 188 | ZEND_INI_ENTRY("stat.dump", "0", ZEND_INI_SYSTEM, zend_stat_ini_update_dump) 189 | ZEND_INI_END() 190 | 191 | void zend_stat_ini_startup() { 192 | zend_register_ini_entries(ini_entries, -1); 193 | } 194 | 195 | void zend_stat_ini_shutdown() { 196 | zend_unregister_ini_entries(-1); 197 | 198 | pefree(zend_stat_ini_stream, 1); 199 | pefree(zend_stat_ini_control, 1); 200 | } 201 | #endif /* ZEND_STAT_INI */ 202 | -------------------------------------------------------------------------------- /src/zend_stat_ini.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_INI_H 20 | # define ZEND_STAT_INI_H 21 | 22 | #include "zend_ini.h" 23 | 24 | extern zend_bool zend_stat_ini_auto; 25 | extern zend_long zend_stat_ini_samples; 26 | extern zend_long zend_stat_ini_samplers; 27 | extern zend_long zend_stat_ini_interval; 28 | extern zend_bool zend_stat_ini_arginfo; 29 | extern zend_long zend_stat_ini_strings; 30 | extern char* zend_stat_ini_stream; 31 | extern char* zend_stat_ini_control; 32 | extern int zend_stat_ini_dump; 33 | 34 | void zend_stat_ini_startup(); 35 | void zend_stat_ini_shutdown(); 36 | 37 | #endif /* ZEND_STAT_INI_H */ 38 | -------------------------------------------------------------------------------- /src/zend_stat_io.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_IO 20 | # define ZEND_STAT_IO 21 | 22 | #include "zend_stat.h" 23 | #include "zend_stat_buffer.h" 24 | #include "zend_stat_io.h" 25 | 26 | #define ZEND_STAT_IO_SIZE(t) \ 27 | ((t == ZEND_STAT_IO_UNIX) ? \ 28 | sizeof(struct sockaddr_un) : \ 29 | sizeof(struct sockaddr_in)) 30 | 31 | static zend_stat_io_type_t zend_stat_io_socket(char *uri, struct sockaddr **sa, int *so) { 32 | zend_stat_io_type_t type = ZEND_STAT_IO_UNKNOWN; 33 | char *buffer, 34 | *address = 35 | buffer = strdup(uri); 36 | size_t length = strlen(address); 37 | char *port = NULL; 38 | 39 | if ((length >= sizeof("unix://")-1) && (SUCCESS == memcmp(address, "unix://", sizeof("unix://")-1))) { 40 | type = ZEND_STAT_IO_UNIX; 41 | address += sizeof("unix://")-1; 42 | length -= sizeof("unix://")-1; 43 | } else if ((length >= sizeof("tcp://")-1) && (SUCCESS == memcmp(address, "tcp://", sizeof("tcp://")-1))) { 44 | type = ZEND_STAT_IO_TCP; 45 | address += sizeof("tcp://")-1; 46 | length -= sizeof("tcp://")-1; 47 | 48 | port = strrchr(address, ':'); 49 | 50 | if (NULL == port) { 51 | type = ZEND_STAT_IO_UNKNOWN; 52 | } else { 53 | address[port - address] = 0; 54 | 55 | port++; 56 | } 57 | } else { 58 | type = ZEND_STAT_IO_UNIX; 59 | } 60 | 61 | switch (type) { 62 | case ZEND_STAT_IO_UNIX: { 63 | int try; 64 | struct sockaddr_un *un = 65 | (struct sockaddr_un*) 66 | pecalloc(1, sizeof(struct sockaddr_un), 1); 67 | 68 | un->sun_family = AF_UNIX; 69 | 70 | try = socket(un->sun_family, SOCK_STREAM, 0); 71 | 72 | if (try == -1) { 73 | zend_error(E_WARNING, 74 | "[STAT] %s - cannot create socket for %s", 75 | strerror(errno), 76 | uri); 77 | type = ZEND_STAT_IO_FAILED; 78 | pefree(un, 1); 79 | 80 | break; 81 | } 82 | 83 | strcpy(un->sun_path, address); 84 | 85 | unlink(un->sun_path); 86 | 87 | if (bind(try, (struct sockaddr*) un, sizeof(struct sockaddr_un)) == SUCCESS) { 88 | *so = try; 89 | *sa = (struct sockaddr*) un; 90 | 91 | goto _zend_stat_io_socket_listen; 92 | } 93 | 94 | zend_error(E_WARNING, 95 | "[STAT] %s - cannot create socket for %s", 96 | strerror(errno), 97 | uri); 98 | type = ZEND_STAT_IO_FAILED; 99 | close(try); 100 | free(un); 101 | } break; 102 | 103 | case ZEND_STAT_IO_TCP: { 104 | struct addrinfo *ai, *rp, hi; 105 | int gai_errno; 106 | 107 | memset(&hi, 0, sizeof(struct addrinfo)); 108 | 109 | hi.ai_family = AF_UNSPEC; 110 | hi.ai_socktype = SOCK_STREAM; 111 | hi.ai_flags = AI_PASSIVE; 112 | hi.ai_protocol = IPPROTO_TCP; 113 | 114 | gai_errno = getaddrinfo(address, port, &hi, &ai); 115 | 116 | if (gai_errno != SUCCESS) { 117 | zend_error(E_WARNING, 118 | "[STAT] %s - cannot get address for %s", 119 | gai_strerror(gai_errno), 120 | uri); 121 | type = ZEND_STAT_IO_FAILED; 122 | 123 | break; 124 | } 125 | 126 | for (rp = ai; rp != NULL; rp = rp->ai_next) { 127 | int try = socket( 128 | rp->ai_family, rp->ai_socktype, rp->ai_protocol); 129 | 130 | if (try == -1) { 131 | continue; 132 | } 133 | 134 | #ifdef SO_REUSEADDR 135 | { 136 | int option = 1; 137 | 138 | setsockopt( 139 | try, 140 | SOL_SOCKET, SO_REUSEADDR, 141 | (void*) &option, sizeof(int)); 142 | } 143 | #endif 144 | 145 | if (bind(try, rp->ai_addr, rp->ai_addrlen) == SUCCESS) { 146 | *so = try; 147 | 148 | freeaddrinfo(ai); 149 | 150 | goto _zend_stat_io_socket_listen; 151 | } 152 | 153 | close(try); 154 | } 155 | 156 | zend_error(E_WARNING, 157 | "[STAT] %s - cannot create socket for %s", 158 | strerror(errno), 159 | uri); 160 | type = ZEND_STAT_IO_FAILED; 161 | freeaddrinfo(ai); 162 | } break; 163 | 164 | case ZEND_STAT_IO_UNKNOWN: 165 | zend_error(E_WARNING, 166 | "[STAT] Cannot setup socket, %s is a malformed uri", 167 | uri); 168 | break; 169 | 170 | EMPTY_SWITCH_DEFAULT_CASE(); 171 | } 172 | 173 | free(buffer); 174 | return type; 175 | 176 | _zend_stat_io_socket_listen: 177 | if (listen(*so, 256) != SUCCESS) { 178 | zend_error(E_WARNING, 179 | "[STAT] %s - cannot listen on %s, ", 180 | strerror(errno), uri); 181 | type = ZEND_STAT_IO_FAILED; 182 | } 183 | 184 | free(buffer); 185 | return type; 186 | } 187 | 188 | zend_bool zend_stat_io_write(int fd, char *message, size_t length) { 189 | ssize_t total = 0, 190 | bytes = 0; 191 | 192 | do { 193 | bytes = write(fd, message + total, length - total); 194 | 195 | if (bytes <= 0) { 196 | if (errno == EINTR) { 197 | continue; 198 | } 199 | 200 | return 0; 201 | } 202 | 203 | total += bytes; 204 | } while (total < length); 205 | 206 | return 1; 207 | } 208 | 209 | zend_bool zend_stat_io_buffer_alloc(zend_stat_io_buffer_t *buffer, zend_long size) { 210 | memset(buffer, 0, sizeof(zend_stat_io_buffer_t)); 211 | 212 | buffer->buf = calloc(sizeof(char), size); 213 | 214 | if (!buffer->buf) { 215 | return 0; 216 | } 217 | 218 | buffer->size = size; 219 | buffer->used = 0; 220 | 221 | return 1; 222 | } 223 | 224 | zend_bool zend_stat_io_buffer_append(zend_stat_io_buffer_t *buffer, const char *bytes, zend_long size) { 225 | zend_long used = buffer->used; 226 | 227 | if (UNEXPECTED((used + size) > buffer->size)) { 228 | buffer->size *= 2; 229 | buffer->buf = 230 | realloc( 231 | buffer->buf, buffer->size); 232 | 233 | if (UNEXPECTED(NULL == buffer->buf)) { 234 | return 0; 235 | } 236 | } 237 | 238 | memcpy( 239 | &buffer->buf[used], bytes, size); 240 | buffer->used += size; 241 | 242 | return 1; 243 | } 244 | 245 | zend_bool zend_stat_io_buffer_appends(zend_stat_io_buffer_t *buffer, zend_stat_string_t *string) { 246 | return zend_stat_io_buffer_append(buffer, string->value, string->length); 247 | } 248 | 249 | zend_bool zend_stat_io_buffer_appendf(zend_stat_io_buffer_t *buffer, char *format, ...) { 250 | char *formatted = NULL; 251 | int bytes; 252 | va_list args; 253 | 254 | va_start(args, format); 255 | bytes = vasprintf(&formatted, format, args); 256 | va_end(args); 257 | 258 | if (EXPECTED((bytes != FAILURE) && (NULL != formatted))) { 259 | zend_bool result = 260 | zend_stat_io_buffer_append( 261 | buffer, formatted, bytes); 262 | free(formatted); 263 | return result; 264 | } 265 | 266 | return FAILURE; 267 | } 268 | 269 | zend_bool zend_stat_io_buffer_flush(zend_stat_io_buffer_t *buffer, int fd) { 270 | if (UNEXPECTED(!zend_stat_io_write(fd, buffer->buf, buffer->used))) { 271 | zend_stat_io_buffer_free(buffer); 272 | return 0; 273 | } 274 | 275 | zend_stat_io_buffer_free(buffer); 276 | return 1; 277 | } 278 | 279 | void zend_stat_io_buffer_free(zend_stat_io_buffer_t *buffer) { 280 | if (buffer->buf) { 281 | free(buffer->buf); 282 | } 283 | 284 | memset(buffer, 0, sizeof(zend_stat_io_buffer_t)); 285 | } 286 | 287 | static void* zend_stat_io_thread(zend_stat_io_t *io) { 288 | struct sockaddr* address = 289 | (struct sockaddr*) 290 | pemalloc(ZEND_STAT_IO_SIZE(io->type), 1); 291 | socklen_t length = ZEND_STAT_IO_SIZE(io->type); 292 | 293 | do { 294 | int client; 295 | 296 | memset( 297 | address, 0, 298 | ZEND_STAT_IO_SIZE(io->type)); 299 | 300 | client = accept(io->descriptor, address, &length); 301 | 302 | if (UNEXPECTED(FAILURE == client)) { 303 | if (ECONNABORTED == errno || 304 | EINTR == errno) { 305 | continue; 306 | } 307 | 308 | break; 309 | } 310 | 311 | io->routine(io, client); 312 | 313 | close(client); 314 | } while (!zend_stat_io_closed(io)); 315 | 316 | pefree(address, 1); 317 | 318 | pthread_exit(NULL); 319 | } 320 | 321 | zend_bool zend_stat_io_startup(zend_stat_io_t *io, char *uri, zend_stat_buffer_t *buffer, zend_stat_io_routine_t *routine) { 322 | memset(io, 0, sizeof(zend_stat_io_t)); 323 | 324 | if (!uri) { 325 | return 1; 326 | } 327 | 328 | switch (io->type = zend_stat_io_socket(uri, &io->address, &io->descriptor)) { 329 | case ZEND_STAT_IO_UNKNOWN: 330 | case ZEND_STAT_IO_FAILED: 331 | return 0; 332 | 333 | case ZEND_STAT_IO_UNIX: 334 | case ZEND_STAT_IO_TCP: 335 | /* all good */ 336 | break; 337 | } 338 | 339 | io->buffer = buffer; 340 | io->routine = routine; 341 | 342 | if (pthread_create(&io->thread, 343 | NULL, 344 | (void*)(void*) 345 | zend_stat_io_thread, 346 | (void*) io) != SUCCESS) { 347 | zend_error(E_WARNING, 348 | "[STAT] %s - cannot create thread for io on %s", 349 | strerror(errno), uri); 350 | zend_stat_io_shutdown(io); 351 | return 0; 352 | } 353 | 354 | return 1; 355 | } 356 | 357 | zend_bool zend_stat_io_closed(zend_stat_io_t *io) { 358 | return __atomic_load_n(&io->closed, __ATOMIC_SEQ_CST); 359 | } 360 | 361 | void zend_stat_io_shutdown(zend_stat_io_t *io) { 362 | if (!io->descriptor) { 363 | return; 364 | } 365 | 366 | if (io->type == ZEND_STAT_IO_UNIX) { 367 | struct sockaddr_un *un = 368 | (struct sockaddr_un*) io->address; 369 | 370 | unlink(un->sun_path); 371 | pefree(un, 1); 372 | } 373 | 374 | shutdown(io->descriptor, SHUT_RD); 375 | close(io->descriptor); 376 | 377 | __atomic_store_n( 378 | &io->closed, 1, __ATOMIC_SEQ_CST); 379 | 380 | pthread_join(io->thread, NULL); 381 | } 382 | #endif /* ZEND_STAT_IO */ 383 | -------------------------------------------------------------------------------- /src/zend_stat_io.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_IO_H 20 | # define ZEND_STAT_IO_H 21 | 22 | #include "zend_stat_buffer.h" 23 | #include "zend_stat_strings.h" 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | typedef enum { 33 | ZEND_STAT_IO_UNKNOWN, 34 | ZEND_STAT_IO_UNIX, 35 | ZEND_STAT_IO_TCP, 36 | ZEND_STAT_IO_FAILED 37 | } zend_stat_io_type_t; 38 | 39 | typedef struct _zend_stat_io_t zend_stat_io_t; 40 | 41 | typedef void (zend_stat_io_routine_t) (zend_stat_io_t *io, int client); 42 | 43 | struct _zend_stat_io_t { 44 | zend_stat_io_type_t type; 45 | int descriptor; 46 | struct sockaddr *address; 47 | zend_bool closed; 48 | pthread_t thread; 49 | zend_stat_buffer_t *buffer; 50 | zend_stat_io_routine_t *routine; 51 | }; 52 | 53 | typedef struct _zend_stat_io_buffer_t { 54 | char *buf; 55 | zend_long size; 56 | zend_long used; 57 | } zend_stat_io_buffer_t; 58 | 59 | zend_bool zend_stat_io_buffer_alloc(zend_stat_io_buffer_t *buffer, zend_long size); 60 | zend_bool zend_stat_io_buffer_append(zend_stat_io_buffer_t *buffer, const char *bytes, zend_long size); 61 | zend_bool zend_stat_io_buffer_appends(zend_stat_io_buffer_t *buffer, zend_stat_string_t *string); 62 | zend_bool zend_stat_io_buffer_appendf(zend_stat_io_buffer_t *buffer, char *format, ...); 63 | zend_bool zend_stat_io_buffer_flush(zend_stat_io_buffer_t *buffer, int fd); 64 | void zend_stat_io_buffer_free(zend_stat_io_buffer_t *buffer); 65 | 66 | zend_bool zend_stat_io_startup(zend_stat_io_t *io, char *uri, zend_stat_buffer_t *buffer, zend_stat_io_routine_t *routine); 67 | zend_bool zend_stat_io_closed(zend_stat_io_t *io); 68 | void zend_stat_io_shutdown(zend_stat_io_t *io); 69 | 70 | zend_bool zend_stat_io_write(int fd, char *message, size_t length); 71 | #endif /* ZEND_STAT_IO_H */ 72 | -------------------------------------------------------------------------------- /src/zend_stat_request.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_REQUEST 20 | # define ZEND_STAT_REQUEST 21 | 22 | #include "zend_stat.h" 23 | #include "zend_stat_request.h" 24 | 25 | #include "SAPI.h" 26 | 27 | zend_bool zend_stat_request_create(zend_stat_request_t *request) { 28 | sapi_request_info *ri = &SG(request_info); 29 | 30 | memset(request, 0, sizeof(zend_stat_request_t)); 31 | 32 | request->pid = zend_stat_pid(); 33 | request->elapsed = zend_stat_time(); 34 | 35 | if (EXPECTED(ri->path_translated)) { 36 | request->path = 37 | zend_stat_string_temporary( 38 | ri->path_translated, strlen(ri->path_translated)); 39 | 40 | if (UNEXPECTED(NULL == request->path)) { 41 | zend_stat_request_release(request); 42 | 43 | return 0; 44 | } 45 | } 46 | 47 | if (EXPECTED(ri->request_method)) { 48 | request->method = 49 | zend_stat_string_temporary( 50 | ri->request_method, strlen(ri->request_method)); 51 | 52 | if (UNEXPECTED(NULL == request->method)) { 53 | zend_stat_request_release(request); 54 | 55 | return 0; 56 | } 57 | } 58 | 59 | if (EXPECTED(ri->request_uri)) { 60 | request->uri = 61 | zend_stat_string_temporary( 62 | ri->request_uri, strlen(ri->request_uri)); 63 | 64 | if (UNEXPECTED(NULL == request->uri)) { 65 | zend_stat_request_release(request); 66 | 67 | return 0; 68 | } 69 | } 70 | 71 | /* anything else ? */ 72 | 73 | return 1; 74 | } 75 | #endif /* ZEND_STAT_REQUEST */ 76 | -------------------------------------------------------------------------------- /src/zend_stat_request.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_REQUEST_H 20 | # define ZEND_STAT_REQUEST_H 21 | 22 | #include "zend_stat_strings.h" 23 | 24 | typedef struct _zend_stat_request_t { 25 | pid_t pid; 26 | double elapsed; 27 | zend_stat_string_t *path; 28 | zend_stat_string_t *method; 29 | zend_stat_string_t *uri; 30 | } zend_stat_request_t; 31 | 32 | zend_bool zend_stat_request_create(zend_stat_request_t *request); 33 | 34 | static zend_always_inline void zend_stat_request_copy(zend_stat_request_t *dest, zend_stat_request_t *src) { 35 | memcpy(dest, src, sizeof(zend_stat_request_t)); 36 | 37 | if (dest->path) { 38 | dest->path = zend_stat_string_copy(dest->path); 39 | } 40 | 41 | if (dest->method) { 42 | dest->method = zend_stat_string_copy(dest->method); 43 | } 44 | 45 | if (dest->uri) { 46 | dest->uri = zend_stat_string_copy(dest->uri); 47 | } 48 | } 49 | 50 | static zend_always_inline void zend_stat_request_release(zend_stat_request_t *request) { 51 | if (request->path) { 52 | zend_stat_string_release(request->path); 53 | } 54 | 55 | if (request->method) { 56 | zend_stat_string_release(request->method); 57 | } 58 | 59 | if (request->uri) { 60 | zend_stat_string_release(request->uri); 61 | } 62 | 63 | memset(request, 0, sizeof(zend_stat_request_t)); 64 | } 65 | #endif /* ZEND_STAT_REQUEST_H */ 66 | -------------------------------------------------------------------------------- /src/zend_stat_sample.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_SAMPLE 20 | # define ZEND_STAT_SAMPLE 21 | 22 | #include "zend_stat.h" 23 | #include "zend_stat_io.h" 24 | #include "zend_stat_sample.h" 25 | 26 | static zend_always_inline const char* zend_stat_sample_type_name(zend_uchar type) { 27 | switch (type) { 28 | case ZEND_STAT_SAMPLE_MEMORY: 29 | return "memory"; 30 | 31 | case ZEND_STAT_SAMPLE_INTERNAL: 32 | return "internal"; 33 | 34 | case ZEND_STAT_SAMPLE_USER: 35 | return "user"; 36 | } 37 | 38 | return "unknown"; 39 | } 40 | 41 | static zend_bool zend_stat_sample_write_type(zend_stat_io_buffer_t *iob, zend_uchar type) { 42 | const char *name = zend_stat_sample_type_name(type); 43 | 44 | if (!zend_stat_io_buffer_append(iob, "\"type\": \"", sizeof("\"type\": \"")-1) || 45 | !zend_stat_io_buffer_append(iob, name, strlen(name)) || 46 | !zend_stat_io_buffer_append(iob, "\"", sizeof("\"")-1)) { 47 | return 0; 48 | } 49 | 50 | return 1; 51 | } 52 | 53 | static zend_bool zend_stat_sample_write_request(zend_stat_io_buffer_t *iob, zend_stat_request_t *request) { 54 | if (!zend_stat_io_buffer_appendf(iob, 55 | ", \"request\": {\"pid\": %d, \"elapsed\": %.10f", 56 | request->pid, 57 | request->elapsed)) { 58 | return 0; 59 | } 60 | 61 | if (request->path) { 62 | if (!zend_stat_io_buffer_append(iob, ", \"path\": \"", sizeof(", \"path\": \"")-1) || 63 | !zend_stat_io_buffer_appends(iob, request->path) || 64 | !zend_stat_io_buffer_append(iob, "\"", sizeof("\"")-1)) { 65 | return 0; 66 | } 67 | } 68 | 69 | if (request->method) { 70 | if (!zend_stat_io_buffer_append(iob, ", \"method\": \"", sizeof("\"method\": \"")-1) || 71 | !zend_stat_io_buffer_appends(iob, request->method) || 72 | !zend_stat_io_buffer_append(iob, "\"", sizeof("\"")-1)) { 73 | return 0; 74 | } 75 | } 76 | 77 | if (request->uri) { 78 | if (!zend_stat_io_buffer_append(iob, ", \"uri\": \"", sizeof("\"uri\": \"")-1) || 79 | !zend_stat_io_buffer_appends(iob, request->uri) || 80 | !zend_stat_io_buffer_append(iob, "\"", sizeof("\"")-1)) { 81 | return 0; 82 | } 83 | } 84 | 85 | if (!zend_stat_io_buffer_append(iob, "}", sizeof("}")-1)) { 86 | return 0; 87 | } 88 | 89 | return 1; 90 | } 91 | 92 | static zend_bool zend_stat_sample_write_memory(zend_stat_io_buffer_t *iob, zend_stat_sample_memory_t *memory) { 93 | if (!zend_stat_io_buffer_appendf(iob, ", \"memory\": {\"used\": %d, \"peak\": %d}", memory->used, memory->peak)) { 94 | return 0; 95 | } 96 | 97 | return 1; 98 | } 99 | 100 | static zend_bool zend_stat_sample_write_symbol(zend_stat_io_buffer_t *iob, char *label, zend_stat_sample_symbol_t *symbol) { 101 | if (!symbol->file && 102 | !symbol->scope && 103 | !symbol->function) { 104 | return 1; 105 | } 106 | 107 | if (!zend_stat_io_buffer_append(iob, ", \"", sizeof(", \"")-1) || 108 | !zend_stat_io_buffer_append(iob, label, strlen(label)) || 109 | !zend_stat_io_buffer_append(iob, "\": {", sizeof("\": {")-1)) { 110 | return 0; 111 | } 112 | 113 | if (symbol->file) { 114 | if (!zend_stat_io_buffer_append(iob, "\"file\": \"", sizeof("\"file\": \"")-1) || 115 | !zend_stat_io_buffer_appends(iob, symbol->file) || 116 | !zend_stat_io_buffer_append(iob, "\"", sizeof("\"")-1)) { 117 | return 0; 118 | } 119 | } 120 | 121 | if (symbol->scope) { 122 | if (symbol->file) { 123 | if (!zend_stat_io_buffer_append(iob, ", ", sizeof(", ")-1)) { 124 | return 0; 125 | } 126 | } 127 | if (!zend_stat_io_buffer_append(iob, "\"scope\": \"", sizeof("\"scope\": \"")-1) || 128 | !zend_stat_io_buffer_appends(iob, symbol->scope) || 129 | !zend_stat_io_buffer_append(iob, "\"", sizeof("\"")-1)) { 130 | return 0; 131 | } 132 | } 133 | 134 | if (symbol->function) { 135 | if (symbol->file || symbol->scope) { 136 | if (!zend_stat_io_buffer_append(iob, ", ", sizeof(", ")-1)) { 137 | return 0; 138 | } 139 | } 140 | 141 | if (!zend_stat_io_buffer_append(iob, "\"function\": \"", sizeof("\"function\": \"")-1) || 142 | !zend_stat_io_buffer_appends(iob, symbol->function) || 143 | !zend_stat_io_buffer_append(iob, "\"", sizeof("\"")-1)) { 144 | return 0; 145 | } 146 | } 147 | 148 | if (!zend_stat_io_buffer_append(iob, "}", sizeof("}")-1)) { 149 | return 0; 150 | } 151 | 152 | return 1; 153 | } 154 | 155 | static zend_bool zend_stat_sample_write_opline(zend_stat_io_buffer_t *iob, zend_stat_sample_opline_t *opline) { 156 | if (!opline->line && 157 | !opline->offset && 158 | !opline->opcode) { 159 | return 1; 160 | } 161 | 162 | if (!zend_stat_io_buffer_append(iob, ", \"opline\": {", sizeof(", \"opline\": {")-1)) { 163 | return 0; 164 | } 165 | 166 | if (opline->line) { 167 | if (!zend_stat_io_buffer_appendf(iob, "\"line\": %d", opline->line)) { 168 | return 0; 169 | } 170 | } 171 | 172 | if (opline->offset) { 173 | if (opline->line) { 174 | if (!zend_stat_io_buffer_append(iob, ", ", sizeof(", ")-1)) { 175 | return 0; 176 | } 177 | } 178 | 179 | if (!zend_stat_io_buffer_appendf(iob, "\"offset\": %d", opline->offset)) { 180 | return 0; 181 | } 182 | } 183 | 184 | if ((opline->opcode > 0) && 185 | (opline->opcode <= ZEND_VM_LAST_OPCODE)) { 186 | if (opline->line || opline->offset) { 187 | if (!zend_stat_io_buffer_append(iob, ", ", sizeof(", ")-1)) { 188 | return 0; 189 | } 190 | } 191 | 192 | if (!zend_stat_io_buffer_append(iob, "\"opcode\": \"", sizeof("\"opcode\": \"")-1) || 193 | !zend_stat_io_buffer_appends(iob, zend_stat_string_opcode(opline->opcode)) || 194 | !zend_stat_io_buffer_append(iob, "\"", sizeof("\"")-1)) { 195 | return 0; 196 | } 197 | } 198 | 199 | if (!zend_stat_io_buffer_append(iob, "}", sizeof("}")-1)) { 200 | return 0; 201 | } 202 | 203 | return 1; 204 | } 205 | 206 | zend_bool zend_stat_sample_write_arginfo(zend_stat_io_buffer_t *iob, zend_stat_sample_arginfo_t *arginfo) { 207 | zval *it = arginfo->info, 208 | *end = it + arginfo->length; 209 | if (0 == arginfo->length) { 210 | return 1; 211 | } 212 | 213 | if (!zend_stat_io_buffer_append(iob, ", \"arginfo\": [", sizeof(", \"arginfo\": [")-1)) { 214 | return 0; 215 | } 216 | 217 | while (it < end) { 218 | if (!zend_stat_io_buffer_append(iob, "\"", sizeof("\"")-1)) { 219 | return 0; 220 | } 221 | 222 | switch (Z_TYPE_P(it)) { 223 | case IS_UNDEF: 224 | case IS_NULL: 225 | if (!zend_stat_io_buffer_append(iob, "null", sizeof("null")-1)) { 226 | return 0; 227 | } 228 | break; 229 | 230 | case IS_REFERENCE: 231 | if (!zend_stat_io_buffer_append(iob, "reference", sizeof("reference")-1)) { 232 | return 0; 233 | } 234 | break; 235 | 236 | case IS_DOUBLE: 237 | if (!zend_stat_io_buffer_appendf(iob, "float(%.10f)", Z_DVAL_P(it))) { 238 | return 0; 239 | } 240 | break; 241 | 242 | case IS_LONG: 243 | if (!zend_stat_io_buffer_appendf(iob, "int(%.10f)", Z_LVAL_P(it))) { 244 | return 0; 245 | } 246 | break; 247 | 248 | case IS_TRUE: 249 | case IS_FALSE: 250 | if (!zend_stat_io_buffer_appendf(iob, "bool(%s)", zend_is_true(it) ? "true" : "false")) { 251 | return 0; 252 | } 253 | break; 254 | 255 | case IS_STRING: 256 | if (!zend_stat_io_buffer_append(iob, "string", sizeof("string")-1)) { 257 | return 0; 258 | } 259 | break; 260 | 261 | case IS_OBJECT: 262 | if (!zend_stat_io_buffer_append(iob, "object", sizeof("object")-1)) { 263 | return 0; 264 | } 265 | break; 266 | 267 | case IS_RESOURCE: 268 | if (!zend_stat_io_buffer_append(iob, "resource", sizeof("resource")-1)) { 269 | return 0; 270 | } 271 | break; 272 | 273 | default: { 274 | const char *type = zend_get_type_by_const(Z_TYPE_P(it)); 275 | 276 | if (EXPECTED(type)) { 277 | if (!zend_stat_io_buffer_append(iob, type, strlen(type))) { 278 | return 0; 279 | } 280 | } 281 | } 282 | } 283 | if (!zend_stat_io_buffer_append(iob, "\"", sizeof("\"")-1)) { 284 | return 0; 285 | } 286 | it++; 287 | 288 | if (it < end) { 289 | if (!zend_stat_io_buffer_append(iob, ",", sizeof(",")-1)) { 290 | return 0; 291 | } 292 | } 293 | } 294 | 295 | if (!zend_stat_io_buffer_append(iob, "]", sizeof("]")-1)) { 296 | return 0; 297 | } 298 | 299 | return 1; 300 | } 301 | 302 | zend_bool zend_stat_sample_write(zend_stat_sample_t *sample, int fd) { 303 | zend_stat_io_buffer_t iob; 304 | 305 | if (!zend_stat_io_buffer_alloc(&iob, 8192)) { 306 | goto _zend_stat_sample_write_abort; 307 | } 308 | 309 | if (!zend_stat_io_buffer_append(&iob, "{", sizeof("{")-1)) { 310 | goto _zend_stat_sample_write_abort; 311 | } 312 | 313 | if (!zend_stat_sample_write_type(&iob, sample->type)) { 314 | goto _zend_stat_sample_write_abort; 315 | } 316 | 317 | if (!zend_stat_sample_write_request(&iob, &sample->request)) { 318 | goto _zend_stat_sample_write_abort; 319 | } 320 | 321 | if (!zend_stat_io_buffer_appendf(&iob, ", \"elapsed\": %.10f", sample->elapsed)) { 322 | goto _zend_stat_sample_write_abort; 323 | } 324 | 325 | if (!zend_stat_sample_write_memory(&iob, &sample->memory)) { 326 | goto _zend_stat_sample_write_abort; 327 | } 328 | 329 | if (sample->type == ZEND_STAT_SAMPLE_MEMORY) { 330 | if (!zend_stat_io_buffer_append(&iob, "}\n", sizeof("}\n")-1)) { 331 | goto _zend_stat_sample_write_abort; 332 | } 333 | goto _zend_stat_sample_write_flush; 334 | } 335 | 336 | if (!zend_stat_sample_write_symbol(&iob, "symbol", &sample->symbol)) { 337 | goto _zend_stat_sample_write_abort; 338 | } 339 | 340 | if (!zend_stat_sample_write_arginfo(&iob, &sample->arginfo)) { 341 | goto _zend_stat_sample_write_abort; 342 | } 343 | 344 | if (sample->type == ZEND_STAT_SAMPLE_USER) { 345 | if (!zend_stat_sample_write_opline(&iob, &sample->location.opline)) { 346 | goto _zend_stat_sample_write_abort; 347 | } 348 | } else { 349 | if (!zend_stat_sample_write_symbol(&iob, "caller", &sample->location.caller)) { 350 | goto _zend_stat_sample_write_abort; 351 | } 352 | } 353 | 354 | if (!zend_stat_io_buffer_append(&iob, "}\n", sizeof("}\n")-1)) { 355 | goto _zend_stat_sample_write_abort; 356 | } 357 | 358 | _zend_stat_sample_write_flush: 359 | return zend_stat_io_buffer_flush(&iob, fd); 360 | 361 | _zend_stat_sample_write_abort: 362 | zend_stat_io_buffer_free(&iob); 363 | return 0; 364 | } 365 | 366 | #endif 367 | -------------------------------------------------------------------------------- /src/zend_stat_sample.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_SAMPLE_H 20 | # define ZEND_STAT_SAMPLE_H 21 | 22 | #include "zend_stat_strings.h" 23 | #include "zend_stat_request.h" 24 | 25 | #ifndef ZEND_STAT_SAMPLE_MAX_ARGINFO 26 | # define ZEND_STAT_SAMPLE_MAX_ARGINFO 12 27 | #endif 28 | 29 | typedef struct _zend_stat_sample_state_t { 30 | zend_bool busy; 31 | zend_bool used; 32 | } zend_stat_sample_state_t; 33 | 34 | typedef struct _zend_stat_sample_memory_t { 35 | size_t used; 36 | size_t peak; 37 | } zend_stat_sample_memory_t; 38 | 39 | typedef struct _zend_stat_sample_symbol_t { 40 | zend_stat_string_t *file; 41 | zend_stat_string_t *scope; 42 | zend_stat_string_t *function; 43 | } zend_stat_sample_symbol_t; 44 | 45 | typedef struct _zend_stat_sample_opline_t { 46 | uint32_t line; 47 | uint32_t offset; 48 | zend_uchar opcode; 49 | } zend_stat_sample_opline_t; 50 | 51 | typedef struct _zend_stat_sample_arginfo_t { 52 | uint32_t length; 53 | zval info[ZEND_STAT_SAMPLE_MAX_ARGINFO]; 54 | } zend_stat_sample_arginfo_t; 55 | 56 | typedef struct _zend_stat_sample_t { 57 | zend_stat_sample_state_t state; 58 | zend_uchar type; 59 | zend_stat_request_t request; 60 | double elapsed; 61 | zend_stat_sample_memory_t memory; 62 | union { 63 | zend_stat_sample_opline_t opline; 64 | zend_stat_sample_symbol_t caller; 65 | } location; 66 | zend_stat_sample_symbol_t symbol; 67 | zend_stat_sample_arginfo_t arginfo; 68 | } zend_stat_sample_t; 69 | 70 | #define ZEND_STAT_SAMPLE_UNUSED 0 71 | #define ZEND_STAT_SAMPLE_MEMORY 1 72 | #define ZEND_STAT_SAMPLE_INTERNAL 2 73 | #define ZEND_STAT_SAMPLE_USER 4 74 | 75 | #define ZEND_STAT_SAMPLE_DATA(s) \ 76 | (((char*) s) + XtOffsetOf(zend_stat_sample_t, type)) 77 | #define ZEND_STAT_SAMPLE_DATA_SIZE \ 78 | (sizeof(zend_stat_sample_t) - XtOffsetOf(zend_stat_sample_t, type)) 79 | 80 | const static zend_stat_sample_t zend_stat_sample_empty = { 81 | .type = ZEND_STAT_SAMPLE_UNUSED, 82 | .state = {0, 0}, 83 | .request = {0}, 84 | .elapsed = 0.0, 85 | .memory = {0, 0}, 86 | .location = {{0}}, 87 | .symbol = {NULL, NULL, NULL}, 88 | .arginfo.length = 0 89 | }; 90 | 91 | zend_bool zend_stat_sample_write(zend_stat_sample_t *sample, int fd); 92 | #endif 93 | -------------------------------------------------------------------------------- /src/zend_stat_sampler.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_SAMPLER 20 | 21 | #include "zend_stat.h" 22 | #include "zend_stat_buffer.h" 23 | #include "zend_stat_sampler.h" 24 | 25 | static zend_bool zend_stat_sampler_auto = 1; 26 | static zend_long zend_stat_sampler_interval = 0; 27 | static zend_long zend_stat_sampler_count = 0; 28 | static zend_long zend_stat_sampler_limit = 0; 29 | static zend_bool zend_stat_sampler_arginfo = 0; 30 | 31 | static zend_stat_buffer_t* zend_stat_sampler_buffer; 32 | ZEND_TLS zend_stat_request_t zend_stat_sampler_request; 33 | 34 | typedef struct _zend_heap_header_t { 35 | int custom; 36 | void *storage; 37 | size_t size; 38 | size_t peak; 39 | } zend_heap_header_t; 40 | 41 | typedef struct _zend_stat_sampler_t { 42 | zend_stat_request_t *request; 43 | zend_stat_buffer_t *buffer; 44 | struct zend_stat_sampler_timer_t { 45 | pthread_mutex_t mutex; 46 | pthread_cond_t cond; 47 | zend_bool closed; 48 | zend_bool active; 49 | pthread_t thread; 50 | } timer; 51 | struct { 52 | HashTable strings; 53 | HashTable symbols; 54 | } cache; 55 | zend_heap_header_t *heap; 56 | zend_execute_data *fp; 57 | } zend_stat_sampler_t; 58 | 59 | ZEND_TLS zend_stat_sampler_t __sampler; 60 | 61 | #define ZEND_STAT_SAMPLER_RESET() \ 62 | memset(&__sampler, 0, sizeof(zend_stat_sampler_t)) 63 | #define ZEND_STAT_SAMPLER() &__sampler 64 | #define ZSS(v) __sampler.v 65 | 66 | /* {{{ */ 67 | void zend_stat_sampler_auto_set(zend_bool automatic) { 68 | __atomic_store_n(&zend_stat_sampler_auto, automatic, __ATOMIC_SEQ_CST); 69 | } 70 | 71 | static zend_always_inline zend_bool zend_stat_sampler_auto_get() { 72 | return __atomic_load_n(&zend_stat_sampler_auto, __ATOMIC_SEQ_CST); 73 | } 74 | 75 | void zend_stat_sampler_buffer_set(zend_stat_buffer_t *buffer) { 76 | zend_stat_sampler_buffer = buffer; 77 | } 78 | 79 | void zend_stat_sampler_arginfo_set(zend_bool arginfo) { 80 | __atomic_store_n(&zend_stat_sampler_arginfo, arginfo, __ATOMIC_SEQ_CST); 81 | } 82 | 83 | static zend_always_inline zend_bool zend_stat_sampler_arginfo_get() { 84 | return __atomic_load_n(&zend_stat_sampler_arginfo, __ATOMIC_SEQ_CST); 85 | } 86 | 87 | void zend_stat_sampler_interval_set(zend_long interval) { 88 | __atomic_store_n(&zend_stat_sampler_interval, interval * 1000, __ATOMIC_SEQ_CST); 89 | } 90 | 91 | zend_long zend_stat_sampler_interval_get() { 92 | return __atomic_load_n(&zend_stat_sampler_interval, __ATOMIC_SEQ_CST); 93 | } 94 | 95 | void zend_stat_sampler_limit_set(zend_long limit) { 96 | __atomic_store_n(&zend_stat_sampler_limit, limit, __ATOMIC_SEQ_CST); 97 | } 98 | 99 | zend_bool zend_stat_sampler_add() { 100 | zend_long samplers = __atomic_add_fetch(&zend_stat_sampler_limit, 1, __ATOMIC_SEQ_CST), 101 | limit = __atomic_load_n(&zend_stat_sampler_count, __ATOMIC_SEQ_CST); 102 | 103 | if ((limit <= 0) || (samplers <= limit)) { 104 | return 1; 105 | } 106 | 107 | return 0; 108 | } 109 | 110 | void zend_stat_sampler_remove() { 111 | __atomic_sub_fetch(&zend_stat_sampler_count, 1, __ATOMIC_SEQ_CST); 112 | } 113 | 114 | void zend_stat_buffer_samplers_set(zend_long samplers) { 115 | __atomic_store_n(&zend_stat_sampler_limit, samplers, __ATOMIC_SEQ_CST); 116 | } 117 | /* }}} */ 118 | 119 | #define ZEND_STAT_ADDRESS_OFFSET(address, offset) \ 120 | (((char*) address) + offset) 121 | #define ZEND_STAT_ADDRESSOF(type, address, member) \ 122 | ZEND_STAT_ADDRESS_OFFSET(address, XtOffsetOf(type, member)) 123 | 124 | #if defined(ZTS) 125 | # if defined(TSRMG_FAST_BULK) 126 | # define ZEND_EXECUTOR_ADDRESS \ 127 | ((char*) TSRMG_FAST_BULK(executor_globals_offset, zend_executor_globals*)) 128 | # else 129 | # define ZEND_EXECUTOR_ADDRESS \ 130 | ((char*) TSRMG_BULK(executor_globals_id, zend_executor_globals*)) 131 | # endif 132 | #else 133 | # define ZEND_EXECUTOR_ADDRESS \ 134 | ((char*) &executor_globals) 135 | #endif 136 | 137 | static zend_always_inline int zend_stat_sampler_read(zend_stat_sampler_t *sampler, const void *remote, void *symbol, size_t size) { /* {{{ */ 138 | struct iovec local; 139 | struct iovec target; 140 | 141 | local.iov_base = symbol; 142 | local.iov_len = size; 143 | target.iov_base = (void*) remote; 144 | target.iov_len = size; 145 | 146 | if (process_vm_readv(sampler->request->pid, &local, 1, &target, 1, 0) != size) { 147 | return FAILURE; 148 | } 149 | 150 | return SUCCESS; 151 | } /* }}} */ 152 | 153 | static zend_always_inline int zend_stat_sampler_read_symbol(zend_stat_sampler_t *sampler, const zend_function *remote, zend_function *local) { /* {{{ */ 154 | zend_function *cache; 155 | 156 | if (EXPECTED(cache = zend_hash_index_find_ptr(&sampler->cache.symbols, (zend_ulong) remote))) { 157 | return memcpy(local, cache, sizeof(zend_function)) == local ? SUCCESS : FAILURE; 158 | } 159 | 160 | if (UNEXPECTED(zend_stat_sampler_read(sampler, remote, local, sizeof(zend_function)) != SUCCESS)) { 161 | return FAILURE; 162 | } 163 | 164 | if ( 165 | (local->type == ZEND_INTERNAL_FUNCTION) 166 | #ifdef ZEND_ACC_IMMUTABLE 167 | || (local->common.fn_flags & ZEND_ACC_IMMUTABLE) 168 | #endif 169 | ) { 170 | zend_hash_index_add_mem( 171 | &sampler->cache.symbols, 172 | (zend_ulong) remote, 173 | local, sizeof(zend_function)); 174 | } 175 | 176 | return SUCCESS; 177 | } /* }}} */ 178 | 179 | static zend_always_inline zend_stat_string_t* zend_stat_sampler_read_string(zend_stat_sampler_t *sampler, zend_string *string) { /* {{{ */ 180 | zend_string *result; 181 | size_t length; 182 | 183 | if (EXPECTED((result = zend_hash_index_find_ptr(&sampler->cache.strings, (zend_ulong) string)))) { 184 | return (zend_stat_string_t*) result; 185 | } 186 | 187 | if (UNEXPECTED(zend_stat_sampler_read(sampler, 188 | ZEND_STAT_ADDRESSOF(zend_string, string, len), 189 | &length, sizeof(size_t)) != SUCCESS)) { 190 | return NULL; 191 | } 192 | 193 | result = zend_string_alloc(length, 1); 194 | 195 | if (UNEXPECTED(zend_stat_sampler_read(sampler, 196 | string, 197 | result, ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(length))) != SUCCESS)) { 198 | pefree(result, 1); 199 | return NULL; 200 | } 201 | 202 | if (EXPECTED(GC_FLAGS(result) & IS_STR_PERMANENT)) { 203 | return zend_hash_index_add_ptr( 204 | &sampler->cache.strings, 205 | (zend_ulong) string, zend_stat_string(result)); 206 | } 207 | 208 | return zend_stat_string(result); 209 | } /* }}} */ 210 | 211 | static zend_always_inline zend_stat_string_t* zend_stat_sampler_read_string_at(zend_stat_sampler_t *sampler, const void *symbol, size_t offset) { /* {{{ */ 212 | zend_string *string; 213 | 214 | /* Failure to read indicates the string, or symbol which contains the string, was freed: 215 | all of file, class, and function names should not fail since they 216 | cannot normally be freed during a request. 217 | Currently stat doesn't read any other strings ... 218 | */ 219 | 220 | if (UNEXPECTED(zend_stat_sampler_read(sampler, 221 | ZEND_STAT_ADDRESS_OFFSET(symbol, offset), 222 | &string, sizeof(zend_string*)) != SUCCESS)) { 223 | return NULL; 224 | } 225 | 226 | return zend_stat_sampler_read_string(sampler, string); 227 | } /* }}} */ 228 | 229 | static zend_always_inline zend_bool zend_stat_sample_unlined(zend_uchar opcode) { /* {{{ */ 230 | /* Certain opcodes don't have useful line information because they are internal 231 | implementation details where a line isn't relevant normally */ 232 | return 233 | opcode == ZEND_FE_FREE || 234 | opcode == ZEND_FREE || 235 | opcode == ZEND_ASSERT_CHECK || 236 | opcode == ZEND_VERIFY_RETURN_TYPE || 237 | opcode == ZEND_RECV || 238 | opcode == ZEND_RECV_INIT || 239 | opcode == ZEND_RECV_VARIADIC || 240 | opcode == ZEND_SEND_VAL || 241 | opcode == ZEND_SEND_VAR_EX || 242 | opcode == ZEND_SEND_VAR_NO_REF_EX || 243 | opcode == ZEND_SEND_REF || 244 | opcode == ZEND_SEND_UNPACK || 245 | opcode == ZEND_ROPE_INIT || 246 | opcode == ZEND_ROPE_ADD || 247 | opcode == ZEND_ROPE_END || 248 | opcode == ZEND_FAST_CONCAT || 249 | opcode == ZEND_CAST || 250 | opcode == ZEND_BOOL || 251 | opcode == ZEND_CASE 252 | ; 253 | } /* }}} */ 254 | 255 | /* {{{ */ 256 | static zend_always_inline void zend_stat_sample(zend_stat_sampler_t *sampler) { 257 | zend_execute_data *fp, frame; 258 | zend_function function; 259 | zend_op opline; 260 | zend_stat_sample_t sample = zend_stat_sample_empty; 261 | 262 | sample.elapsed = zend_stat_time(); 263 | 264 | /* This can never fail while the sampler is active */ 265 | zend_stat_sampler_read(sampler, 266 | ZEND_STAT_ADDRESSOF( 267 | zend_heap_header_t, sampler->heap, size), 268 | &sample.memory, sizeof(sample.memory)); 269 | 270 | if (UNEXPECTED((zend_stat_sampler_read(sampler, 271 | sampler->fp, &fp, sizeof(zend_execute_data*)) != SUCCESS) || (NULL == fp))) { 272 | /* There is no current execute data set */ 273 | sample.type = ZEND_STAT_SAMPLE_MEMORY; 274 | 275 | goto _zend_stat_sample_finish; 276 | } 277 | 278 | if (UNEXPECTED((zend_stat_sampler_read(sampler, 279 | fp, 280 | &frame, sizeof(zend_execute_data)) != SUCCESS))) { 281 | /* The frame was freed before it could be sampled */ 282 | sample.type = ZEND_STAT_SAMPLE_MEMORY; 283 | 284 | goto _zend_stat_sample_finish; 285 | } 286 | 287 | if (UNEXPECTED((NULL != frame.opline) && 288 | (zend_stat_sampler_read(sampler, 289 | frame.opline, &opline, sizeof(zend_op)) != SUCCESS))) { 290 | /* The instruction pointer is in an op array that was free'd */ 291 | sample.type = ZEND_STAT_SAMPLE_MEMORY; 292 | 293 | goto _zend_stat_sample_finish; 294 | } 295 | 296 | if (UNEXPECTED(zend_stat_sampler_arginfo_get())) { 297 | sample.arginfo.length = MIN(frame.This.u2.num_args, ZEND_STAT_SAMPLE_MAX_ARGINFO); 298 | 299 | if (EXPECTED(sample.arginfo.length > 0)) { 300 | if (UNEXPECTED(zend_stat_sampler_read(sampler, 301 | ZEND_CALL_ARG(fp, 1), 302 | &sample.arginfo.info, 303 | sizeof(zval) * sample.arginfo.length) != SUCCESS)) { 304 | /* The stack was freed by the sampled process, we don't bail, because 305 | the rest of the sampled frame should be readable */ 306 | sample.arginfo.length = 0; 307 | } 308 | } 309 | } 310 | 311 | /* Failures to read from here onward indicate that the sampled function has been 312 | or is being destroyed */ 313 | 314 | if (UNEXPECTED(zend_stat_sampler_read_symbol( 315 | sampler, frame.func, &function) != SUCCESS)) { 316 | sample.type = ZEND_STAT_SAMPLE_MEMORY; 317 | 318 | memset(&sample.arginfo, 0, sizeof(sample.arginfo)); 319 | 320 | goto _zend_stat_sample_finish; 321 | } 322 | 323 | if (function.type == ZEND_USER_FUNCTION) { 324 | sample.symbol.file = 325 | zend_stat_sampler_read_string( 326 | sampler, function.op_array.filename); 327 | 328 | if (UNEXPECTED(NULL == sample.symbol.file)) { 329 | sample.type = ZEND_STAT_SAMPLE_MEMORY; 330 | 331 | memset(&sample.arginfo, 0, sizeof(sample.arginfo)); 332 | 333 | goto _zend_stat_sample_finish; 334 | } 335 | 336 | sample.type = ZEND_STAT_SAMPLE_USER; 337 | sample.location.opline.opcode = opline.opcode; 338 | if (EXPECTED(!zend_stat_sample_unlined(opline.opcode))) { 339 | sample.location.opline.line = opline.lineno; 340 | } 341 | sample.location.opline.offset = frame.opline - function.op_array.opcodes; 342 | } else { 343 | zend_execute_data pframe; 344 | zend_function pfunc; 345 | 346 | sample.type = ZEND_STAT_SAMPLE_INTERNAL; 347 | 348 | while (zend_stat_sampler_read(sampler, 349 | frame.prev_execute_data, 350 | &pframe, sizeof(zend_execute_data)) == SUCCESS) { 351 | if (EXPECTED(zend_stat_sampler_read_symbol(sampler, 352 | pframe.func, &pfunc) == SUCCESS)) { 353 | if (pfunc.type == ZEND_USER_FUNCTION) { 354 | sample.location.caller.file = 355 | zend_stat_sampler_read_string( 356 | sampler, pfunc.op_array.filename); 357 | 358 | if (pfunc.op_array.scope) { 359 | sample.location.caller.scope = 360 | zend_stat_sampler_read_string_at( 361 | sampler, 362 | pfunc.op_array.scope, 363 | XtOffsetOf(zend_class_entry, name)); 364 | if (UNEXPECTED(NULL == sample.location.caller.scope)) { 365 | break; 366 | } 367 | } 368 | 369 | sample.location.caller.function = 370 | zend_stat_sampler_read_string(sampler, pfunc.op_array.function_name); 371 | break; 372 | } 373 | } else { 374 | break; 375 | } 376 | frame = pframe; 377 | } 378 | } 379 | 380 | if (function.common.scope) { 381 | sample.symbol.scope = 382 | zend_stat_sampler_read_string_at( 383 | sampler, 384 | function.common.scope, 385 | XtOffsetOf(zend_class_entry, name)); 386 | 387 | if (UNEXPECTED(NULL == sample.symbol.scope)) { 388 | sample.type = ZEND_STAT_SAMPLE_MEMORY; 389 | 390 | memset(&sample.location, 0, sizeof(sample.location)); 391 | memset(&sample.arginfo, 0, sizeof(sample.arginfo)); 392 | 393 | goto _zend_stat_sample_finish; 394 | } 395 | } 396 | 397 | sample.symbol.function = 398 | zend_stat_sampler_read_string( 399 | sampler, function.common.function_name); 400 | 401 | _zend_stat_sample_finish: 402 | /* This is just a memcpy and some adds, 403 | request data is refcounted. */ 404 | zend_stat_request_copy( 405 | &sample.request, sampler->request); 406 | 407 | zend_stat_buffer_insert(sampler->buffer, &sample); 408 | } /* }}} */ 409 | 410 | static zend_always_inline time_t zend_stat_sampler_clock(long cumulative, long *ns) { /* {{{ */ 411 | time_t result = 0; 412 | 413 | while (cumulative >= 1000000000L) { 414 | asm("" : "+rm"(cumulative)); 415 | 416 | cumulative -= 1000000000L; 417 | result++; 418 | } 419 | 420 | *ns = cumulative; 421 | 422 | return result; 423 | } /* }}} */ 424 | 425 | #ifdef ZEND_ACC_IMMUTABLE 426 | static void zend_stat_sampler_cache_symbol_free(zval *zv) { /* {{{ */ 427 | free(Z_PTR_P(zv)); 428 | } /* }}} */ 429 | #endif 430 | 431 | static zend_never_inline void* zend_stat_sampler(zend_stat_sampler_t *sampler) { /* {{{ */ 432 | struct zend_stat_sampler_timer_t 433 | *timer = &sampler->timer; 434 | struct timespec clk; 435 | 436 | if (clock_gettime(CLOCK_REALTIME, &clk) != SUCCESS) { 437 | goto _zend_stat_sampler_exit; 438 | } 439 | 440 | zend_hash_init(&sampler->cache.strings, 32, NULL, NULL, 1); 441 | #ifdef ZEND_ACC_IMMUTABLE 442 | zend_hash_init(&sampler->cache.symbols, 32, NULL, zend_stat_sampler_cache_symbol_free, 1); 443 | #endif 444 | 445 | pthread_mutex_lock(&timer->mutex); 446 | 447 | while (!timer->closed) { 448 | clk.tv_sec += 449 | zend_stat_sampler_clock( 450 | clk.tv_nsec + 451 | zend_stat_sampler_interval_get(), 452 | &clk.tv_nsec); 453 | 454 | switch (pthread_cond_timedwait(&timer->cond, &timer->mutex, &clk)) { 455 | case ETIMEDOUT: 456 | zend_stat_sample(sampler); 457 | break; 458 | 459 | case EINVAL: 460 | /* clock is in the past, loop to catch up */ 461 | 462 | case SUCCESS: 463 | /* do nothing */ 464 | break; 465 | 466 | default: 467 | goto _zend_stat_sampler_leave; 468 | } 469 | } 470 | 471 | _zend_stat_sampler_leave: 472 | pthread_mutex_unlock(&timer->mutex); 473 | 474 | zend_hash_destroy(&sampler->cache.strings); 475 | #ifdef ZEND_ACC_IMMUTABLE 476 | zend_hash_destroy(&sampler->cache.symbols); 477 | #endif 478 | 479 | _zend_stat_sampler_exit: 480 | pthread_exit(NULL); 481 | } /* }}} */ 482 | 483 | void zend_stat_sampler_startup( /* {{{ */ 484 | zend_bool automatic, 485 | zend_long interval, 486 | zend_bool arginfo, 487 | zend_long samplers, 488 | zend_stat_buffer_t *buffer) { 489 | 490 | zend_stat_sampler_auto_set(automatic); 491 | zend_stat_sampler_interval_set(interval); 492 | zend_stat_sampler_arginfo_set(arginfo); 493 | zend_stat_sampler_limit_set(samplers); 494 | 495 | zend_stat_sampler_buffer_set(buffer); 496 | } /* }}} */ 497 | 498 | ZEND_FUNCTION(zend_stat_sampler_activate) /* {{{ */ 499 | { 500 | ZEND_PARSE_PARAMETERS_START(0, 0) 501 | ZEND_PARSE_PARAMETERS_END(); 502 | 503 | if (UNEXPECTED(1 == zend_stat_sampler_active())) { 504 | RETURN_FALSE; 505 | } 506 | 507 | zend_stat_sampler_activate(1); 508 | 509 | RETURN_BOOL(zend_stat_sampler_active()); 510 | } /* }}} */ 511 | 512 | void zend_stat_sampler_activate(zend_bool start) { /* {{{ */ 513 | if ((0 == zend_stat_sampler_auto_get()) && (0 == start)) { 514 | return; 515 | } 516 | 517 | if (!zend_stat_sampler_add()) { 518 | return; 519 | } 520 | 521 | if (!zend_stat_request_create(&zend_stat_sampler_request)) { 522 | zend_error(E_WARNING, 523 | "[STAT] Could not allocate request, " 524 | "not activating sampler, may be low on memory"); 525 | return; 526 | } 527 | 528 | ZEND_STAT_SAMPLER_RESET(); 529 | 530 | ZSS(request) = &zend_stat_sampler_request; 531 | ZSS(buffer) = zend_stat_sampler_buffer; 532 | ZSS(heap) = 533 | (zend_heap_header_t*) zend_mm_get_heap(); 534 | ZSS(fp) = 535 | (zend_execute_data*) 536 | ZEND_STAT_ADDRESSOF( 537 | zend_executor_globals, 538 | ZEND_EXECUTOR_ADDRESS, 539 | current_execute_data); 540 | 541 | if (!zend_stat_mutex_init(&ZSS(timer).mutex, 0) || 542 | !zend_stat_condition_init(&ZSS(timer).cond, 0)) { 543 | return; 544 | } 545 | 546 | if (pthread_create( 547 | &ZSS(timer).thread, NULL, 548 | (void*)(void*) 549 | zend_stat_sampler, 550 | (void*) ZEND_STAT_SAMPLER()) != SUCCESS) { 551 | pthread_cond_destroy(&ZSS(timer).cond); 552 | pthread_mutex_destroy(&ZSS(timer).mutex); 553 | return; 554 | } 555 | 556 | ZSS(timer).active = 1; 557 | } /* }}} */ 558 | 559 | ZEND_FUNCTION(zend_stat_sampler_active) /* {{{ */ 560 | { 561 | ZEND_PARSE_PARAMETERS_START(0, 0) 562 | ZEND_PARSE_PARAMETERS_END(); 563 | 564 | RETURN_BOOL(zend_stat_sampler_active()); 565 | } /* }}} */ 566 | 567 | zend_bool zend_stat_sampler_active() { /* {{{ */ 568 | return ZSS(timer).active; 569 | } /* }}} */ 570 | 571 | ZEND_FUNCTION(zend_stat_sampler_deactivate) /* {{{ */ 572 | { 573 | ZEND_PARSE_PARAMETERS_START(0, 0) 574 | ZEND_PARSE_PARAMETERS_END(); 575 | 576 | if (UNEXPECTED(0 == zend_stat_sampler_active())) { 577 | RETURN_FALSE; 578 | } 579 | 580 | zend_stat_sampler_deactivate(); 581 | 582 | RETURN_TRUE; 583 | } /* }}} */ 584 | 585 | void zend_stat_sampler_deactivate() { /* {{{ */ 586 | if (0 == zend_stat_sampler_active()) { 587 | return; 588 | } 589 | 590 | pthread_mutex_lock(&ZSS(timer).mutex); 591 | 592 | ZSS(timer).closed = 1; 593 | 594 | pthread_cond_signal(&ZSS(timer).cond); 595 | pthread_mutex_unlock(&ZSS(timer).mutex); 596 | 597 | pthread_join(ZSS(timer).thread, NULL); 598 | 599 | zend_stat_condition_destroy(&ZSS(timer).cond); 600 | zend_stat_mutex_destroy(&ZSS(timer).mutex); 601 | 602 | zend_stat_request_release(&zend_stat_sampler_request); 603 | 604 | zend_stat_sampler_remove(); 605 | 606 | ZEND_STAT_SAMPLER_RESET(); 607 | } /* }}} */ 608 | 609 | #endif /* ZEND_STAT_SAMPLER */ 610 | -------------------------------------------------------------------------------- /src/zend_stat_sampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_SAMPLER_H 20 | # define ZEND_STAT_SAMPLER_H 21 | 22 | #include "zend_stat_sample.h" 23 | #include "zend_stat_request.h" 24 | 25 | extern ZEND_FUNCTION(zend_stat_sampler_activate); 26 | extern ZEND_FUNCTION(zend_stat_sampler_active); 27 | extern ZEND_FUNCTION(zend_stat_sampler_deactivate); 28 | 29 | void zend_stat_sampler_auto_set(zend_bool automatic); 30 | void zend_stat_sampler_buffer_set(zend_stat_buffer_t *buffer); 31 | void zend_stat_sampler_interval_set(zend_long interval); 32 | void zend_stat_sampler_limit_set(zend_long interval); 33 | zend_long zend_stat_sampler_interval_get(); 34 | void zend_stat_sampler_arginfo_set(zend_bool arginfo); 35 | void zend_stat_sampler_request_set(zend_stat_request_t *request); 36 | 37 | zend_bool zend_stat_sampler_add(); 38 | void zend_stat_sampler_remove(); 39 | 40 | void zend_stat_sampler_startup(zend_bool automatic, zend_long interval, zend_bool arginfo, zend_long samplers, zend_stat_buffer_t *buffer); 41 | void zend_stat_sampler_activate(zend_bool start); 42 | zend_bool zend_stat_sampler_active(); 43 | void zend_stat_sampler_deactivate(); 44 | #endif /* ZEND_STAT_SAMPLER_H */ 45 | -------------------------------------------------------------------------------- /src/zend_stat_stream.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_STREAM 20 | # define ZEND_STAT_STREAM 21 | 22 | #include "zend_stat.h" 23 | #include "zend_stat_io.h" 24 | #include "zend_stat_stream.h" 25 | 26 | static zend_always_inline void zend_stat_stream_yield(zend_stat_io_t *io) { 27 | zend_long interval = 28 | zend_stat_sampler_interval_get() / 1000; 29 | 30 | usleep(ceil(interval / 2)); 31 | } 32 | 33 | static void zend_stat_stream(zend_stat_io_t *io, int client) { 34 | while (zend_stat_buffer_dump(io->buffer, client)) { 35 | if (zend_stat_buffer_empty(io->buffer)) { 36 | if (zend_stat_io_closed(io)) { 37 | return; 38 | } 39 | 40 | zend_stat_stream_yield(io); 41 | } 42 | } 43 | } 44 | 45 | zend_bool zend_stat_stream_startup(zend_stat_io_t *io, zend_stat_buffer_t *buffer, char *stream) { 46 | return zend_stat_io_startup(io, stream, buffer, zend_stat_stream); 47 | } 48 | 49 | void zend_stat_stream_shutdown(zend_stat_io_t *io) { 50 | zend_stat_io_shutdown(io); 51 | } 52 | #endif 53 | -------------------------------------------------------------------------------- /src/zend_stat_stream.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_STREAM_H 20 | # define ZEND_STAT_STREAM_H 21 | 22 | #include "zend_stat_io.h" 23 | 24 | zend_bool zend_stat_stream_startup(zend_stat_io_t *io, zend_stat_buffer_t *buffer, char *stream); 25 | void zend_stat_stream_shutdown(zend_stat_io_t *io); 26 | #endif 27 | -------------------------------------------------------------------------------- /src/zend_stat_strings.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_STRINGS 20 | # define ZEND_STAT_STRINGS 21 | 22 | #include "zend_stat.h" 23 | #include "zend_stat_arena.h" 24 | #include "zend_stat_strings.h" 25 | 26 | typedef struct { 27 | zend_long size; 28 | zend_long used; 29 | zend_long slots; 30 | struct { 31 | void *memory; 32 | zend_long size; 33 | zend_long used; 34 | } buffer; 35 | zend_stat_arena_t *arena; 36 | zend_stat_string_t *strings; 37 | } zend_stat_strings_t; 38 | 39 | static zend_stat_strings_t* zend_stat_strings; 40 | static zend_stat_string_t* zend_stat_strings_opcodes[256]; 41 | 42 | #define ZTSG(v) zend_stat_strings->v 43 | #define ZTSB(v) ZTSG(buffer).v 44 | 45 | static zend_always_inline zend_stat_string_t* zend_stat_string_init(const char *value, size_t length) { 46 | zend_stat_string_t *string; 47 | zend_ulong slot; 48 | zend_ulong offset; 49 | zend_ulong hash = zend_inline_hash_func(value, length); 50 | 51 | if (UNEXPECTED(++ZTSG(used) >= ZTSG(slots))) { 52 | --ZTSG(used); 53 | 54 | return NULL; 55 | } 56 | 57 | slot = hash % ZTSG(slots); 58 | 59 | _zend_stat_string_init_load: 60 | string = &ZTSG(strings)[slot]; 61 | 62 | if (UNEXPECTED(string->hash == hash)) { 63 | return string; 64 | } else { 65 | if (UNEXPECTED(string->length)) { 66 | slot++; 67 | slot %= ZTSG(slots); 68 | 69 | goto _zend_stat_string_init_load; 70 | } 71 | } 72 | 73 | offset = ZTSB(used); 74 | 75 | if (UNEXPECTED((offset + length) >= ZTSB(size))) { 76 | /* panic OOM */ 77 | 78 | return NULL; 79 | } 80 | 81 | string->value = (char*) (((char*) ZTSB(memory)) + offset); 82 | 83 | memcpy(string->value, value, length); 84 | 85 | string->value[length] = 0; 86 | string->hash = hash; 87 | string->length = length; 88 | 89 | ZTSB(used) += length; 90 | 91 | return string; 92 | } 93 | 94 | static zend_always_inline zend_stat_string_t* zend_stat_string_persistent(zend_string *string) { 95 | zend_stat_string_t *copy; 96 | zend_ulong slot; 97 | zend_ulong offset; 98 | 99 | if (UNEXPECTED(__atomic_add_fetch( 100 | &ZTSG(used), 1, __ATOMIC_ACQ_REL) >= ZTSG(slots))) { 101 | __atomic_sub_fetch(&ZTSG(used), 1, __ATOMIC_ACQ_REL); 102 | 103 | /* panic OOM */ 104 | 105 | return NULL; 106 | } 107 | 108 | slot = ZSTR_HASH(string) % ZTSG(slots); 109 | 110 | _zend_stat_string_copy_load: 111 | copy = &ZTSG(strings)[slot]; 112 | 113 | if (EXPECTED(__atomic_load_n(©->length, __ATOMIC_SEQ_CST))) { 114 | _zend_stat_string_copy_check: 115 | if (UNEXPECTED(copy->hash != ZSTR_HASH(string))) { 116 | ++slot; 117 | 118 | slot %= ZTSG(slots); 119 | 120 | goto _zend_stat_string_copy_load; 121 | } 122 | 123 | __atomic_sub_fetch( 124 | &ZTSG(used), 1, __ATOMIC_ACQ_REL); 125 | 126 | free(string); 127 | 128 | return copy; 129 | } 130 | 131 | while (__atomic_exchange_n(©->locked, 1, __ATOMIC_RELAXED)); 132 | 133 | __atomic_thread_fence(__ATOMIC_ACQUIRE); 134 | 135 | offset = __atomic_fetch_add(&ZTSB(used), ZSTR_LEN(string), __ATOMIC_ACQ_REL); 136 | 137 | if (UNEXPECTED((offset + ZSTR_LEN(string)) >= ZTSB(size))) { 138 | __atomic_sub_fetch(&ZTSB(used), ZSTR_LEN(string), __ATOMIC_ACQ_REL); 139 | __atomic_thread_fence(__ATOMIC_RELEASE); 140 | __atomic_exchange_n(©->locked, 0, __ATOMIC_RELAXED); 141 | 142 | /* panic OOM */ 143 | 144 | return NULL; 145 | } 146 | 147 | if (UNEXPECTED(__atomic_load_n(©->length, __ATOMIC_SEQ_CST))) { 148 | __atomic_thread_fence(__ATOMIC_RELEASE); 149 | __atomic_exchange_n(©->locked, 0, __ATOMIC_RELAXED); 150 | 151 | goto _zend_stat_string_copy_check; 152 | } 153 | 154 | copy->value = (char*) (((char*) ZTSB(memory)) + offset); 155 | 156 | memcpy(copy->value, 157 | ZSTR_VAL(string), 158 | ZSTR_LEN(string)); 159 | 160 | copy->value[ZSTR_LEN(string)] = 0; 161 | copy->hash = ZSTR_HASH(string); 162 | 163 | __atomic_store_n(©->length, ZSTR_LEN(string), __ATOMIC_SEQ_CST); 164 | 165 | __atomic_thread_fence(__ATOMIC_RELEASE); 166 | 167 | __atomic_exchange_n(©->locked, 0, __ATOMIC_RELAXED); 168 | 169 | free(string); 170 | 171 | return copy; 172 | } 173 | 174 | zend_bool zend_stat_strings_startup(zend_long strings) { 175 | size_t zend_stat_strings_size = floor((strings / 5) * 1), 176 | zend_stat_strings_buffer_size = floor((strings / 5) * 4); 177 | 178 | zend_stat_strings = zend_stat_map(strings + sizeof(zend_stat_strings_t)); 179 | 180 | if (!zend_stat_strings) { 181 | zend_error(E_WARNING, 182 | "[STAT] Failed to allocate shared memory for strings"); 183 | return 0; 184 | } 185 | 186 | memset(zend_stat_strings, 0, sizeof(zend_stat_strings_t)); 187 | 188 | ZTSG(strings) = (void*) 189 | (((char*) zend_stat_strings) + sizeof(zend_stat_strings_t)); 190 | ZTSG(size) = zend_stat_strings_size; 191 | ZTSG(slots) = ZTSG(size) / sizeof(zend_stat_string_t); 192 | ZTSG(used) = 0; 193 | 194 | memset(ZTSG(strings), 0, zend_stat_strings_size); 195 | 196 | ZTSB(memory) = (void*) 197 | (((char*) ZTSG(strings)) + zend_stat_strings_size); 198 | ZTSB(size) = zend_stat_strings_buffer_size; 199 | ZTSB(used) = 0; 200 | 201 | memset(ZTSB(memory), 0, zend_stat_strings_buffer_size); 202 | 203 | { 204 | int it = 0, 205 | end = ZEND_VM_LAST_OPCODE; 206 | 207 | memset(zend_stat_strings_opcodes, 0, sizeof(zend_stat_strings_opcodes)); 208 | 209 | while (it <= end) { 210 | const char *name = zend_get_opcode_name(it); 211 | 212 | if (name) { 213 | zend_stat_strings_opcodes[it] = 214 | zend_stat_string_init( 215 | (char*) name + (sizeof("ZEND_")-1), 216 | strlen(name) - (sizeof("ZEND_")-1)); 217 | } else { 218 | zend_stat_strings_opcodes[it] = 219 | zend_stat_string_init(ZEND_STRL("UNKNOWN")); 220 | } 221 | it++; 222 | } 223 | } 224 | 225 | ZTSG(arena) = zend_stat_arena_create(strings); 226 | 227 | return 1; 228 | } 229 | 230 | zend_stat_string_t* zend_stat_string_temporary(const char *value, size_t length) { 231 | size_t size = sizeof(zend_stat_string_t) + (length + 1); 232 | zend_stat_string_t *temporary = 233 | (zend_stat_string_t*) 234 | zend_stat_arena_alloc(ZTSG(arena), size); 235 | 236 | if (UNEXPECTED(NULL == temporary)) { 237 | return NULL; 238 | } 239 | 240 | memset(temporary, 0, size); 241 | 242 | temporary->u.type = ZEND_STAT_STRING_TEMPORARY; 243 | temporary->u.refcount = 1; 244 | 245 | temporary->length = length; 246 | temporary->value = 247 | (char*) (((char*) temporary) + sizeof(zend_stat_string_t)); 248 | memcpy(temporary->value, value, length); 249 | 250 | temporary->value[length] = 0; 251 | 252 | return temporary; 253 | } 254 | 255 | zend_stat_string_t* zend_stat_string_copy(zend_stat_string_t *string) { 256 | if (UNEXPECTED(ZEND_STAT_STRING_PERSISTENT == __atomic_load_n(&string->u.type, __ATOMIC_SEQ_CST))) { 257 | return string; 258 | } 259 | 260 | __atomic_add_fetch( 261 | &string->u.refcount, 262 | 1, __ATOMIC_SEQ_CST); 263 | 264 | return string; 265 | } 266 | 267 | void zend_stat_string_release(zend_stat_string_t *string) { 268 | if (UNEXPECTED(__atomic_load_n(&string->u.type, __ATOMIC_SEQ_CST) == ZEND_STAT_STRING_PERSISTENT)) { 269 | return; 270 | } 271 | 272 | if (__atomic_sub_fetch(&string->u.refcount, 1, __ATOMIC_SEQ_CST) == 0) { 273 | zend_stat_arena_free(ZTSG(arena), string); 274 | return; 275 | } 276 | } 277 | 278 | zend_stat_string_t *zend_stat_string_opcode(zend_uchar opcode) { 279 | return zend_stat_strings_opcodes[opcode]; 280 | } 281 | 282 | zend_stat_string_t *zend_stat_string(zend_string *string) { 283 | return zend_stat_string_persistent(string); 284 | } 285 | 286 | void zend_stat_strings_shutdown(void) { 287 | zend_stat_arena_destroy(ZTSG(arena)); 288 | 289 | zend_stat_unmap(zend_stat_strings, ZTSG(size)); 290 | } 291 | 292 | #endif /* ZEND_STAT_STRINGS */ 293 | -------------------------------------------------------------------------------- /src/zend_stat_strings.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_STRINGS_H 20 | # define ZEND_STAT_STRINGS_H 21 | 22 | typedef struct _zend_stat_string_t { 23 | zend_bool locked; 24 | zend_ulong hash; 25 | zend_long length; 26 | char *value; 27 | struct { 28 | zend_uchar type; 29 | uint32_t refcount; 30 | } u; 31 | } zend_stat_string_t; 32 | 33 | #define ZEND_STAT_STRING_PERSISTENT 0 34 | #define ZEND_STAT_STRING_TEMPORARY 1 35 | 36 | zend_bool zend_stat_strings_startup(zend_long strings); 37 | zend_stat_string_t* zend_stat_string(zend_string *string); 38 | zend_stat_string_t *zend_stat_string_opcode(zend_uchar opcode); 39 | 40 | zend_stat_string_t* zend_stat_string_temporary(const char *value, size_t length); 41 | zend_stat_string_t* zend_stat_string_copy(zend_stat_string_t *string); 42 | void zend_stat_string_release(zend_stat_string_t *string); 43 | 44 | void zend_stat_strings_shutdown(void); 45 | #endif /* ZEND_STAT_STRINGS_H */ 46 | -------------------------------------------------------------------------------- /zend_stat.c: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT 20 | # define ZEND_STAT 21 | 22 | #define ZEND_STAT_EXTNAME "Stat" 23 | #define ZEND_STAT_VERSION "0.0.1-dev" 24 | #define ZEND_STAT_AUTHOR "krakjoe" 25 | #define ZEND_STAT_URL "https://github.com/krakjoe/stat" 26 | #define ZEND_STAT_COPYRIGHT "Copyright (c) 2019" 27 | 28 | #if defined(__GNUC__) && __GNUC__ >= 4 29 | # define ZEND_STAT_EXTENSION_API __attribute__ ((visibility("default"))) 30 | #else 31 | # define ZEND_STAT_EXTENSION_API 32 | #endif 33 | 34 | #include "zend_stat.h" 35 | #include "zend_stat_arena.h" 36 | #include "zend_stat_buffer.h" 37 | #include "zend_stat_control.h" 38 | #include "zend_stat_ini.h" 39 | #include "zend_stat_io.h" 40 | #include "zend_stat_request.h" 41 | #include "zend_stat_sampler.h" 42 | #include "zend_stat_stream.h" 43 | #include "zend_stat_strings.h" 44 | 45 | static pid_t zend_stat_main = 0; 46 | static zend_stat_buffer_t* zend_stat_buffer = NULL; 47 | static zend_stat_io_t zend_stat_stream; 48 | static zend_stat_io_t zend_stat_control; 49 | static double zend_stat_started = 0; 50 | 51 | static int zend_stat_startup(zend_extension*); 52 | static void zend_stat_shutdown(zend_extension *); 53 | static void zend_stat_activate(void); 54 | static void zend_stat_deactivate(void); 55 | 56 | ZEND_STAT_EXTENSION_API zend_extension_version_info extension_version_info = { 57 | ZEND_EXTENSION_API_NO, 58 | ZEND_EXTENSION_BUILD_ID 59 | }; 60 | 61 | ZEND_STAT_EXTENSION_API zend_extension zend_extension_entry = { 62 | ZEND_STAT_EXTNAME, 63 | ZEND_STAT_VERSION, 64 | ZEND_STAT_AUTHOR, 65 | ZEND_STAT_URL, 66 | ZEND_STAT_COPYRIGHT, 67 | zend_stat_startup, 68 | zend_stat_shutdown, 69 | zend_stat_activate, 70 | zend_stat_deactivate, 71 | NULL, 72 | NULL, 73 | NULL, 74 | NULL, 75 | NULL, 76 | NULL, 77 | NULL, 78 | STANDARD_ZEND_EXTENSION_PROPERTIES 79 | }; 80 | 81 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(zend_stat_api_returns_bool_arginfo, 0, 0, _IS_BOOL, 0) 82 | ZEND_END_ARG_INFO() 83 | 84 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(zend_stat_api_returns_long_arginfo, 0, 0, IS_LONG, 0) 85 | ZEND_END_ARG_INFO() 86 | 87 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(zend_stat_api_returns_double_arginfo, 0, 0, IS_DOUBLE, 0) 88 | ZEND_END_ARG_INFO() 89 | 90 | ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(zend_stat_api_returns_array_arginfo, 0, 0, IS_ARRAY, 0) 91 | ZEND_END_ARG_INFO() 92 | 93 | ZEND_FUNCTION(zend_stat_pid) 94 | { 95 | ZEND_PARSE_PARAMETERS_START(0, 0) 96 | ZEND_PARSE_PARAMETERS_END(); 97 | 98 | RETURN_LONG(zend_stat_pid()); 99 | } 100 | 101 | ZEND_FUNCTION(zend_stat_elapsed) 102 | { 103 | ZEND_PARSE_PARAMETERS_START(0, 0) 104 | ZEND_PARSE_PARAMETERS_END(); 105 | 106 | RETURN_DOUBLE(zend_stat_time()); 107 | } 108 | 109 | static zend_bool zend_stat_buffer_consume_u(zend_stat_sample_t *sample, zval *return_value) { 110 | array_init(return_value); 111 | 112 | if (sample->type == ZEND_STAT_SAMPLE_MEMORY) { 113 | 114 | } 115 | 116 | return ZEND_STAT_BUFFER_CONSUMER_STOP; 117 | } 118 | 119 | ZEND_FUNCTION(zend_stat_buffer_consume) 120 | { 121 | ZEND_PARSE_PARAMETERS_START(0, 0) 122 | ZEND_PARSE_PARAMETERS_END(); 123 | 124 | zend_stat_buffer_consume( 125 | zend_stat_buffer, 126 | (zend_stat_buffer_consumer_t) zend_stat_buffer_consume_u, 127 | return_value, 1); 128 | } 129 | 130 | static zend_function_entry zend_stat_api[] = { 131 | ZEND_NS_FENTRY("stat", pid, ZEND_FN(zend_stat_pid), zend_stat_api_returns_long_arginfo, 0) 132 | ZEND_NS_FENTRY("stat", elapsed, ZEND_FN(zend_stat_elapsed), zend_stat_api_returns_double_arginfo, 0) 133 | ZEND_NS_FENTRY("stat\\sampler", activate, ZEND_FN(zend_stat_sampler_activate), zend_stat_api_returns_bool_arginfo, 0) 134 | ZEND_NS_FENTRY("stat\\sampler", active, ZEND_FN(zend_stat_sampler_active), zend_stat_api_returns_bool_arginfo, 0) 135 | ZEND_NS_FENTRY("stat\\sampler", deactivate, ZEND_FN(zend_stat_sampler_deactivate), zend_stat_api_returns_bool_arginfo, 0) 136 | ZEND_NS_FENTRY("stat\\buffer", consume, ZEND_FN(zend_stat_buffer_consume), zend_stat_api_returns_array_arginfo, 0) 137 | ZEND_FE_END 138 | }; 139 | 140 | static int zend_stat_startup(zend_extension *ze) { 141 | zend_stat_ini_startup(); 142 | 143 | if (!zend_stat_ini_stream && !zend_stat_ini_dump) { 144 | zend_error(E_WARNING, 145 | "[STAT] stream and dump are both disabled by configuration, " 146 | "may be misconfigured"); 147 | zend_stat_ini_shutdown(); 148 | 149 | return SUCCESS; 150 | } 151 | 152 | if (!zend_stat_strings_startup(zend_stat_ini_strings)) { 153 | zend_stat_ini_shutdown(); 154 | 155 | return SUCCESS; 156 | } 157 | 158 | if (!(zend_stat_buffer = zend_stat_buffer_startup(zend_stat_ini_samples))) { 159 | zend_stat_strings_shutdown(); 160 | zend_stat_ini_shutdown(); 161 | 162 | return SUCCESS; 163 | } 164 | 165 | if (!zend_stat_control_startup( 166 | &zend_stat_control, 167 | zend_stat_buffer, 168 | zend_stat_ini_control)) { 169 | zend_stat_buffer_shutdown(zend_stat_buffer); 170 | zend_stat_strings_shutdown(); 171 | zend_stat_ini_shutdown(); 172 | 173 | return SUCCESS; 174 | } 175 | 176 | if (!zend_stat_stream_startup( 177 | &zend_stat_stream, 178 | zend_stat_buffer, 179 | zend_stat_ini_stream)) { 180 | zend_stat_control_shutdown(&zend_stat_control); 181 | zend_stat_buffer_shutdown(zend_stat_buffer); 182 | zend_stat_strings_shutdown(); 183 | zend_stat_ini_shutdown(); 184 | 185 | return SUCCESS; 186 | } 187 | 188 | zend_stat_sampler_startup( 189 | zend_stat_ini_auto, 190 | zend_stat_ini_interval, 191 | zend_stat_ini_arginfo, 192 | zend_stat_ini_samplers, 193 | zend_stat_buffer); 194 | 195 | zend_stat_started = zend_stat_time(); 196 | zend_stat_main = zend_stat_pid(); 197 | 198 | ze->handle = 0; 199 | 200 | zend_register_functions(NULL, zend_stat_api, NULL, MODULE_PERSISTENT); 201 | 202 | return SUCCESS; 203 | } 204 | 205 | static void zend_stat_shutdown(zend_extension *ze) { 206 | if (0 == zend_stat_started) { 207 | return; 208 | } 209 | 210 | if (zend_stat_pid() != zend_stat_main) { 211 | return; 212 | } 213 | 214 | if (zend_stat_ini_dump > 0) { 215 | zend_stat_buffer_dump( 216 | zend_stat_buffer, zend_stat_ini_dump); 217 | } 218 | 219 | zend_stat_control_shutdown(&zend_stat_control); 220 | zend_stat_stream_shutdown(&zend_stat_stream); 221 | zend_stat_buffer_shutdown(zend_stat_buffer); 222 | zend_stat_strings_shutdown(); 223 | zend_stat_ini_shutdown(); 224 | 225 | zend_stat_started = 0; 226 | } 227 | 228 | static void zend_stat_activate(void) { 229 | #if defined(ZTS) && defined(COMPILE_DL_STAT) 230 | ZEND_TSRMLS_CACHE_UPDATE(); 231 | #endif 232 | 233 | if (0 == zend_stat_started) { 234 | return; 235 | } 236 | 237 | zend_stat_sampler_activate(0); 238 | } 239 | 240 | static void zend_stat_deactivate(void) { 241 | if (0 == zend_stat_started) { 242 | return; 243 | } 244 | 245 | zend_stat_sampler_deactivate(); 246 | } 247 | 248 | double zend_stat_time(void) { 249 | struct timespec ts; 250 | 251 | if (clock_gettime(CLOCK_MONOTONIC, &ts) != SUCCESS) { 252 | return (double) -1; 253 | } 254 | 255 | return ((double) ts.tv_sec + ts.tv_nsec / 1000000000.00) - zend_stat_started; 256 | } 257 | 258 | #if defined(ZTS) && defined(COMPILE_DL_STAT) 259 | ZEND_TSRMLS_CACHE_DEFINE(); 260 | #endif 261 | 262 | #endif /* ZEND_STAT */ 263 | -------------------------------------------------------------------------------- /zend_stat.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | stat | 4 | +----------------------------------------------------------------------+ 5 | | Copyright (c) Joe Watkins 2019 | 6 | +----------------------------------------------------------------------+ 7 | | This source file is subject to version 3.01 of the PHP license, | 8 | | that is bundled with this package in the file LICENSE, and is | 9 | | available through the world-wide-web at the following url: | 10 | | http://www.php.net/license/3_01.txt | 11 | | If you did not receive a copy of the PHP license and are unable to | 12 | | obtain it through the world-wide-web, please send a note to | 13 | | license@php.net so we can mail you a copy immediately. | 14 | +----------------------------------------------------------------------+ 15 | | Author: krakjoe | 16 | +----------------------------------------------------------------------+ 17 | */ 18 | 19 | #ifndef ZEND_STAT_H 20 | # define ZEND_STAT_H 21 | 22 | #ifdef HAVE_CONFIG_H 23 | # include "config.h" 24 | #endif 25 | 26 | #include "zend.h" 27 | #include "zend_API.h" 28 | #include "zend_extensions.h" 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | 39 | double zend_stat_time(void); 40 | 41 | static zend_always_inline pid_t zend_stat_pid(void) { 42 | #ifdef ZTS 43 | return syscall(SYS_gettid); 44 | #else 45 | return getpid(); 46 | #endif 47 | } 48 | 49 | static zend_always_inline void* zend_stat_map(zend_long size) { 50 | void *mapped = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); 51 | 52 | if (EXPECTED(mapped != MAP_FAILED)) { 53 | return mapped; 54 | } 55 | 56 | return NULL; 57 | } 58 | 59 | static zend_always_inline void zend_stat_unmap(void *address, zend_long size) { 60 | if (UNEXPECTED(NULL == address)) { 61 | return; 62 | } 63 | 64 | munmap(address, size); 65 | } 66 | 67 | static zend_always_inline zend_bool zend_stat_mutex_init(pthread_mutex_t *mutex, zend_bool shared) { 68 | pthread_mutexattr_t attributes; 69 | 70 | pthread_mutexattr_init(&attributes); 71 | 72 | if (shared) { 73 | if (pthread_mutexattr_setpshared( 74 | &attributes, PTHREAD_PROCESS_SHARED) != SUCCESS) { 75 | pthread_mutexattr_destroy(&attributes); 76 | return 0; 77 | } 78 | } 79 | 80 | if (pthread_mutex_init(mutex, &attributes) != SUCCESS) { 81 | pthread_mutexattr_destroy(&attributes); 82 | return 0; 83 | } 84 | 85 | pthread_mutexattr_destroy(&attributes); 86 | return 1; 87 | } 88 | 89 | static zend_always_inline void zend_stat_mutex_destroy(pthread_mutex_t *mutex) { 90 | pthread_mutex_destroy(mutex); 91 | } 92 | 93 | static zend_always_inline zend_bool zend_stat_condition_init(pthread_cond_t *condition, zend_bool shared) { 94 | pthread_condattr_t attributes; 95 | 96 | pthread_condattr_init(&attributes); 97 | 98 | if (shared) { 99 | if (pthread_condattr_setpshared( 100 | &attributes, PTHREAD_PROCESS_SHARED) != SUCCESS) { 101 | pthread_condattr_destroy(&attributes); 102 | return 0; 103 | } 104 | } 105 | 106 | if (pthread_cond_init(condition, &attributes) != SUCCESS) { 107 | pthread_condattr_destroy(&attributes); 108 | return 0; 109 | } 110 | 111 | pthread_condattr_destroy(&attributes); 112 | return 1; 113 | } 114 | 115 | static zend_always_inline void zend_stat_condition_destroy(pthread_cond_t *condition) { 116 | pthread_cond_destroy(condition); 117 | } 118 | 119 | # if defined(ZTS) && defined(COMPILE_DL_STAT) 120 | ZEND_TSRMLS_CACHE_EXTERN() 121 | # endif 122 | 123 | #define ZEND_STAT_INTERVAL_MIN 10 124 | 125 | #endif /* ZEND_STAT_H */ 126 | --------------------------------------------------------------------------------