From 662c778fdee830da92633b72fe2fafb48222d699 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 1 Jul 2026 16:22:17 +0200 Subject: [PATCH 1/4] Implement perf stat --control for cgi perf stat --control allows the profiled process to enable and disable counters at runtime. This allows us to skip profiling startup and shutdown for more accurate results. The same already exists for valgrind. $ perf stat -D -1 --control fifo:/tmp/perfctl,/tmp/perfack -D -1 starts perf stat with counters disabled. --control makes perf stat connect to the /tmp/perfctl and /tmp/perfack fifo files. These need to exist when starting perf, so create them using mkfifo /tmp/perf{ctl,ack}. The ctl fifo is written to by cgi to enable/disable counters, whereas the ack fifo is written to by perf stat to acknolwedge counters have been installed. Additionally, you'll need to set the set the PERF_STAT_CTL_FIFO and PERF_STAT_ACK_FIFO env variables for cgi to find the fifo files. --- Zend/zend_perf_stat.h | 133 ++++++++++++++++++++++++++++++++++++++++++ sapi/cgi/cgi_main.c | 13 ++++- 2 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 Zend/zend_perf_stat.h diff --git a/Zend/zend_perf_stat.h b/Zend/zend_perf_stat.h new file mode 100644 index 000000000000..93d3ec9b787f --- /dev/null +++ b/Zend/zend_perf_stat.h @@ -0,0 +1,133 @@ +/* + +----------------------------------------------------------------------+ + | Zend Engine | + +----------------------------------------------------------------------+ + | Copyright © Zend Technologies Ltd., a subsidiary company of | + | Perforce Software, Inc., and Contributors. | + +----------------------------------------------------------------------+ + | This source file is subject to the Modified BSD License that is | + | bundled with this package in the file LICENSE, and is available | + | through the World Wide Web at . | + | | + | SPDX-License-Identifier: BSD-3-Clause | + +----------------------------------------------------------------------+ + | Authors: Ilija Tovilo | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_PERF_STAT_H +#define ZEND_PERF_STAT_H + +#include "zend_portability.h" + +# if !(HAVE_FCNTL_H && HAVE_SYS_SELECT_H && HAVE_SYS_STAT_H && HAVE_SYS_TIME_H && HAVE_UNISTD_H) +static void zend_perf_stat_enable(void) {} +static void zend_perf_stat_disable(void) {} +# else + +# include +# include +# include +# include + +# define ZPS_CTL_FIFO_ENV "PERF_STAT_CTL_FIFO" +# define ZPS_ACK_FIFO_ENV "PERF_STAT_ACK_FIFO" + +static int ctl_fd = -2; +static int ack_fd = -2; + +static int zps_open_fifo(const char *env_name, int flags) +{ + const char *path = getenv(env_name); + + if (path == NULL || path[0] == '\0') { + return -1; + } + +# ifdef O_CLOEXEC + flags |= O_CLOEXEC; +# endif + int fd = open(path, flags | O_NONBLOCK); + if (fd < 0) { + return -1; + } + + struct stat st; + if (fstat(fd, &st) != 0 || !S_ISFIFO(st.st_mode)) { + close(fd); + return -1; + } + return fd; +} + +static void zps_init(void) +{ + if (ctl_fd == -2) { + ctl_fd = zps_open_fifo(ZPS_CTL_FIFO_ENV, O_WRONLY); + } + if (ack_fd == -2) { + ack_fd = zps_open_fifo(ZPS_ACK_FIFO_ENV, O_RDONLY); + } +} + +static void zps_wait_ack(void) +{ + if (ack_fd < 0) { + return; + } + + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(ack_fd, &readfds); + if (select(ack_fd + 1, &readfds, NULL, NULL, &timeout) <= 0) { + return; + } + + char ack[sizeof("ack\n") - 1]; + ssize_t bytes_read; + while ((bytes_read = read(ack_fd, ack, sizeof(ack))) > 0) { + for (ssize_t i = 0; i < bytes_read; i++) { + if (ack[i] == '\n') { + return; + } + } + } +} + +static void zps_control(const char *command, size_t command_len) +{ + zps_init(); + + if (ctl_fd < 0) { + return; + } + + if (write(ctl_fd, command, command_len) != (ssize_t) command_len) { + close(ctl_fd); + ctl_fd = -1; + return; + } + + zps_wait_ack(); +} + +static void zend_perf_stat_enable(void) +{ + static const char command[] = "enable\n"; + + zps_control(command, sizeof(command) - 1); +} + +static void zend_perf_stat_disable(void) +{ + static const char command[] = "disable\n"; + + zps_control(command, sizeof(command) - 1); +} + +# endif +#endif diff --git a/sapi/cgi/cgi_main.c b/sapi/cgi/cgi_main.c index f49507827e2d..7efd8f159d59 100644 --- a/sapi/cgi/cgi_main.c +++ b/sapi/cgi/cgi_main.c @@ -95,6 +95,8 @@ int __riscosify_control = __RISCOSIFY_STRICT_UNIX_SPECS; # endif #endif +#include "zend_perf_stat.h" + #ifndef PHP_WIN32 /* XXX this will need to change later when threaded fastcgi is implemented. shane */ static struct sigaction act, old_term, old_quit, old_int; @@ -1733,6 +1735,7 @@ int main(int argc, char *argv[]) int warmup_repeats = 0; int repeats = 1; int benchmark = 0; + bool perf_enabled = false; #ifdef HAVE_GETTIMEOFDAY struct timeval start, end; #else @@ -2441,14 +2444,16 @@ consult the installation file that came with this distribution, or visit \n\ } } /* end !cgi && !fastcgi */ -#ifdef HAVE_VALGRIND if (warmup_repeats == 0) { + zend_perf_stat_enable(); + perf_enabled = true; +#ifdef HAVE_VALGRIND CALLGRIND_START_INSTRUMENTATION; # ifdef HAVE_VALGRIND_CACHEGRIND_H CACHEGRIND_START_INSTRUMENTATION; # endif - } #endif + } /* request startup only after we've done all we can to * get path_translated */ @@ -2568,6 +2573,10 @@ consult the installation file that came with this distribution, or visit \n\ SG(request_info).query_string = NULL; } + if (perf_enabled) { + zend_perf_stat_disable(); + perf_enabled = false; + } #ifdef HAVE_VALGRIND /* We're not interested in measuring shutdown */ CALLGRIND_STOP_INSTRUMENTATION; From 91f57a26451f46926cfd2c813574c93f6d063b6a Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 1 Jul 2026 19:52:00 +0200 Subject: [PATCH 2/4] Improve diagnostics --- Zend/zend_perf_stat.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Zend/zend_perf_stat.h b/Zend/zend_perf_stat.h index 93d3ec9b787f..11011366b420 100644 --- a/Zend/zend_perf_stat.h +++ b/Zend/zend_perf_stat.h @@ -30,6 +30,8 @@ static void zend_perf_stat_disable(void) {} # include # include +# include "Zend/zend.h" + # define ZPS_CTL_FIFO_ENV "PERF_STAT_CTL_FIFO" # define ZPS_ACK_FIFO_ENV "PERF_STAT_ACK_FIFO" @@ -49,13 +51,15 @@ static int zps_open_fifo(const char *env_name, int flags) # endif int fd = open(path, flags | O_NONBLOCK); if (fd < 0) { - return -1; + fprintf(stderr, "Failed to open fifo %s\n", path); + zend_bailout(); } struct stat st; if (fstat(fd, &st) != 0 || !S_ISFIFO(st.st_mode)) { close(fd); - return -1; + fprintf(stderr, "File %s is not a fifo\n", path); + zend_bailout(); } return fd; } From 7f6fb0ee67a587cc0b57a4f18b877d814c9e9801 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 1 Jul 2026 19:55:39 +0200 Subject: [PATCH 3/4] Avoid ack loop Just drain the pipe. --- Zend/zend_perf_stat.h | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Zend/zend_perf_stat.h b/Zend/zend_perf_stat.h index 11011366b420..59f2ddf01e2b 100644 --- a/Zend/zend_perf_stat.h +++ b/Zend/zend_perf_stat.h @@ -93,13 +93,9 @@ static void zps_wait_ack(void) char ack[sizeof("ack\n") - 1]; ssize_t bytes_read; - while ((bytes_read = read(ack_fd, ack, sizeof(ack))) > 0) { - for (ssize_t i = 0; i < bytes_read; i++) { - if (ack[i] == '\n') { - return; - } - } - } + do { + bytes_read = read(ack_fd, ack, sizeof(ack)); + } while (bytes_read > 0); } static void zps_control(const char *command, size_t command_len) From 8cd38b3c93d1d8361ba9e35da0a6e445b2b1b8bc Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 1 Jul 2026 20:01:57 +0200 Subject: [PATCH 4/4] Add fflush to be safe --- Zend/zend_perf_stat.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Zend/zend_perf_stat.h b/Zend/zend_perf_stat.h index 59f2ddf01e2b..447315de0133 100644 --- a/Zend/zend_perf_stat.h +++ b/Zend/zend_perf_stat.h @@ -52,6 +52,7 @@ static int zps_open_fifo(const char *env_name, int flags) int fd = open(path, flags | O_NONBLOCK); if (fd < 0) { fprintf(stderr, "Failed to open fifo %s\n", path); + fflush(stderr); zend_bailout(); } @@ -59,6 +60,7 @@ static int zps_open_fifo(const char *env_name, int flags) if (fstat(fd, &st) != 0 || !S_ISFIFO(st.st_mode)) { close(fd); fprintf(stderr, "File %s is not a fifo\n", path); + fflush(stderr); zend_bailout(); } return fd;