Commit aae5edb8e36 for php.net

commit aae5edb8e360e5d08d331cce4556ba4cf1ed3238
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date:   Thu Jul 2 23:40:25 2026 +0200

    Implement perf stat --control for cgi (GH-22537)

    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 acknowledge 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.

diff --git a/Zend/zend_perf_stat.h b/Zend/zend_perf_stat.h
new file mode 100644
index 00000000000..447315de013
--- /dev/null
+++ b/Zend/zend_perf_stat.h
@@ -0,0 +1,135 @@
+/*
+   +----------------------------------------------------------------------+
+   | 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 <https://www.php.net/license/>.        |
+   |                                                                      |
+   | SPDX-License-Identifier: BSD-3-Clause                                |
+   +----------------------------------------------------------------------+
+   | Authors: Ilija Tovilo <ilutov@php.net>                               |
+   +----------------------------------------------------------------------+
+*/
+
+#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 <fcntl.h>
+# include <sys/select.h>
+# include <sys/stat.h>
+# include <unistd.h>
+
+# include "Zend/zend.h"
+
+# 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) {
+		fprintf(stderr, "Failed to open fifo %s\n", path);
+		fflush(stderr);
+		zend_bailout();
+	}
+
+	struct stat st;
+	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;
+}
+
+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;
+	do {
+		bytes_read = read(ack_fd, ack, sizeof(ack));
+	} while (bytes_read > 0);
+}
+
+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 f49507827e2..7efd8f159d5 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;