Skip to content

Commit a0be7ff

Browse files
committed
fpm binding master and children processes to specific core(s).
rational is there is git snippets and similar floating around about how to achieve this via command line. So the idea is to do it "natively" especially those above mostly focus on linux whereas more platforms are cpu affinity "capable".
1 parent b9cd1cd commit a0be7ff

File tree

9 files changed

+259
-0
lines changed

9 files changed

+259
-0
lines changed

configure.ac

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,10 @@ pty.h \
403403
pwd.h \
404404
resolv.h \
405405
strings.h \
406+
sched.h \
406407
syslog.h \
407408
sysexits.h \
409+
sys/cpuset.h \
408410
sys/ioctl.h \
409411
sys/file.h \
410412
sys/mman.h \
@@ -568,6 +570,7 @@ AC_CHECK_FUNCS(
568570
alphasort \
569571
asctime_r \
570572
chroot \
573+
cpuset_setaffinity \
571574
ctime_r \
572575
explicit_memset \
573576
fdatasync \
@@ -605,6 +608,7 @@ poll \
605608
pthread_jit_write_protect_np \
606609
putenv \
607610
scandir \
611+
sched_setaffinity \
608612
setitimer \
609613
setenv \
610614
shutdown \

sapi/fpm/fpm/fpm_conf.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ static struct ini_value_parser_s ini_fpm_global_options[] = {
9898
{ "process_control_timeout", &fpm_conf_set_time, GO(process_control_timeout) },
9999
{ "process.max", &fpm_conf_set_integer, GO(process_max) },
100100
{ "process.priority", &fpm_conf_set_integer, GO(process_priority) },
101+
#if HAVE_FPM_CPUAFFINITY
102+
{ "process.cpumask", &fpm_conf_set_string, GO(process_cpumask) },
103+
#endif
101104
{ "daemonize", &fpm_conf_set_boolean, GO(daemonize) },
102105
{ "rlimit_files", &fpm_conf_set_integer, GO(rlimit_files) },
103106
{ "rlimit_core", &fpm_conf_set_rlimit_core, GO(rlimit_core) },
@@ -130,6 +133,9 @@ static struct ini_value_parser_s ini_fpm_pool_options[] = {
130133
#endif
131134
{ "process.priority", &fpm_conf_set_integer, WPO(process_priority) },
132135
{ "process.dumpable", &fpm_conf_set_boolean, WPO(process_dumpable) },
136+
#if HAVE_FPM_CPUAFFINITY
137+
{ "process.cpumask", &fpm_conf_set_string, WPO(process_cpumask) },
138+
#endif
133139
{ "pm", &fpm_conf_set_pm, WPO(pm) },
134140
{ "pm.max_children", &fpm_conf_set_integer, WPO(pm_max_children) },
135141
{ "pm.start_servers", &fpm_conf_set_integer, WPO(pm_start_servers) },
@@ -621,6 +627,9 @@ static void *fpm_worker_pool_config_alloc(void)
621627
wp->config->pm_process_idle_timeout = 10; /* 10s by default */
622628
wp->config->process_priority = 64; /* 64 means unset */
623629
wp->config->process_dumpable = 0;
630+
#if HAVE_FPM_CPUAFFINITY
631+
wp->config->process_cpumask = NULL;
632+
#endif
624633
wp->config->clear_env = 1;
625634
wp->config->decorate_workers_output = 1;
626635
#ifdef SO_SETFIB

sapi/fpm/fpm/fpm_conf.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ struct fpm_global_config_s {
4444
int systemd_watchdog;
4545
int systemd_interval;
4646
#endif
47+
#if HAVE_FPM_CPUAFFINITY
48+
char *process_cpumask;
49+
#endif
4750
};
4851

4952
extern struct fpm_global_config_s fpm_global_config;
@@ -107,6 +110,9 @@ struct fpm_worker_pool_config_s {
107110
#ifdef SO_SETFIB
108111
int listen_setfib;
109112
#endif
113+
#if HAVE_FPM_CPUAFFINITY
114+
char *process_cpumask;
115+
#endif
110116
};
111117

112118
struct ini_value_parser_s {

sapi/fpm/fpm/fpm_config.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,16 @@
7575
#else
7676
# define HAVE_FPM_LQ 0
7777
#endif
78+
79+
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY)
80+
/*
81+
* to expand to other platforms capable of this granularity (some BSD, solaris, ...).
82+
* macOS is a specific case with an api working fine on intel architecture
83+
* whereas on arm the api and semantic behind is different, since it is about
84+
* Quality Of Service, i.e. binding to a group of cores for high performance
85+
* vs cores for low energy consumption.
86+
*/
87+
# define HAVE_FPM_CPUAFFINITY 1
88+
#else
89+
# define HAVE_FPM_CPUAFFINITY 0
90+
#endif

sapi/fpm/fpm/fpm_unix.c

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@
3535
#include <selinux/selinux.h>
3636
#endif
3737

38+
#ifdef HAVE_SCHED_H
39+
#include <sched.h>
40+
#endif
41+
42+
#ifdef HAVE_SYS_CPUSET_H
43+
#include <sys/cpuset.h>
44+
typedef cpuset_t cpu_set_t;
45+
#endif
46+
3847
#include "fpm.h"
3948
#include "fpm_conf.h"
4049
#include "fpm_cleanup.h"
@@ -349,6 +358,103 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */
349358
}
350359
/* }}} */
351360

361+
#if HAVE_FPM_CPUAFFINITY
362+
struct fpm_cpuaffinity_conf {
363+
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY)
364+
cpu_set_t cset;
365+
#endif
366+
long min;
367+
long max;
368+
};
369+
370+
static long fpm_cpumax(void)
371+
{
372+
static long cpuid = LONG_MIN;
373+
if (cpuid == LONG_MIN) {
374+
cpuid = sysconf(_SC_NPROCESSORS_ONLN);
375+
}
376+
377+
return cpuid;
378+
}
379+
380+
static void fpm_cpuaffinity_init(struct fpm_cpuaffinity_conf *c)
381+
{
382+
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY)
383+
CPU_ZERO(&c->cset);
384+
#endif
385+
}
386+
387+
static void fpm_cpuaffinity_add(struct fpm_cpuaffinity_conf *c)
388+
{
389+
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY)
390+
int i;
391+
392+
for (i = c->min; i <= c->max; i ++) {
393+
if (!CPU_ISSET(i, &c->cset)) {
394+
CPU_SET(i, &c->cset);
395+
}
396+
}
397+
#endif
398+
}
399+
400+
static int fpm_cpuaffinity_set(struct fpm_cpuaffinity_conf *c)
401+
{
402+
#if defined(HAVE_SCHED_SETAFFINITY)
403+
return sched_setaffinity(0, sizeof(c->cset), &c->cset);
404+
#elif defined(HAVE_CPUSET_SETAFFINITY)
405+
return cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(c->cset), &c->cset);
406+
#endif
407+
}
408+
409+
static void fpm_cpuaffinity_destroy(struct fpm_cpuaffinity_conf *c)
410+
{
411+
// some platform/apis requires to allocates data on the heap
412+
}
413+
414+
static int fpm_setcpuaffinity(char *cpumask)
415+
{
416+
char *token, *buf;
417+
struct fpm_cpuaffinity_conf fconf;
418+
int r, cpumax;
419+
420+
r = -1;
421+
cpumax = fpm_cpumax();
422+
423+
fpm_cpuaffinity_init(&fconf);
424+
token = php_strtok_r(cpumask, ";", &buf);
425+
426+
while (token) {
427+
char *cpumasksep;
428+
429+
fconf.min = strtol(token, &cpumasksep, 0);
430+
if (errno || fconf.min > cpumax) {
431+
goto fail;
432+
}
433+
fconf.max = fconf.min;
434+
if (*cpumasksep == '-') {
435+
if (strlen(cpumasksep) > 1) {
436+
char *err;
437+
fconf.max = strtol(cpumasksep + 1, &err, 0);
438+
if (errno || *err != '\0' || fconf.max < fconf.min || fconf.max > cpumax) {
439+
return -1;
440+
}
441+
} else {
442+
return -1;
443+
}
444+
}
445+
446+
fpm_cpuaffinity_add(&fconf);
447+
448+
token = php_strtok_r(NULL, ";", &buf);
449+
}
450+
451+
fail:
452+
r = fpm_cpuaffinity_set(&fconf);
453+
fpm_cpuaffinity_destroy(&fconf);
454+
return r;
455+
}
456+
#endif
457+
352458
int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
353459
{
354460
int is_root = !geteuid();
@@ -373,6 +479,15 @@ int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
373479
zlog(ZLOG_SYSERROR, "[pool %s] failed to set rlimit_core for this pool. Please check your system limits or decrease rlimit_core. setrlimit(RLIMIT_CORE, %d)", wp->config->name, wp->config->rlimit_core);
374480
}
375481
}
482+
#if HAVE_FPM_CPUAFFINITY
483+
if (wp->config->process_cpumask) {
484+
485+
if (0 > fpm_setcpuaffinity(wp->config->process_cpumask)) {
486+
zlog(ZLOG_SYSERROR, "[pool %s] failed to fpm_setcpuaffinity(%s)", wp->config->name, wp->config->process_cpumask);
487+
return -1;
488+
}
489+
}
490+
#endif
376491

377492
if (is_root && wp->config->chroot && *wp->config->chroot) {
378493
if (0 > chroot(wp->config->chroot)) {
@@ -619,6 +734,15 @@ int fpm_unix_init_main(void)
619734
}
620735
}
621736

737+
#if HAVE_FPM_CPUAFFINITY
738+
if (fpm_global_config.process_cpumask) {
739+
if (0 > fpm_setcpuaffinity(fpm_global_config.process_cpumask)) {
740+
zlog(ZLOG_SYSERROR, "failed to fpm_setcpuaffinity(%s)", fpm_global_config.process_cpumask);
741+
return -1;
742+
}
743+
}
744+
#endif
745+
622746
fpm_globals.parent_pid = getpid();
623747
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
624748
if (0 > fpm_unix_conf_wp(wp)) {

sapi/fpm/php-fpm.conf.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,14 @@
125125
; Default value: 10
126126
;systemd_interval = 10
127127

128+
; Bind the master process to a cpu set.
129+
; The value can be one cpu id or a range.
130+
;
131+
; Default Value: not set
132+
; process.cpumask = "4"
133+
; process.cpumask = "0-3"
134+
; process.cpumask = "1-4;8-16;32-48"
135+
128136
;;;;;;;;;;;;;;;;;;;;
129137
; Pool Definitions ;
130138
;;;;;;;;;;;;;;;;;;;;

sapi/fpm/tests/cpuaffinity-fail.phpt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
--TEST--
2+
FPM: cpu affinity test
3+
--SKIPIF--
4+
<?php include "skipif.inc";
5+
6+
if (!str_contains(PHP_OS, 'Linux')) {
7+
die('skipped supported only on Linux');
8+
}
9+
?>
10+
--FILE--
11+
<?php
12+
13+
require_once "tester.inc";
14+
15+
$cfg = <<<EOT
16+
[global]
17+
process.cpumask = 1000024
18+
error_log = {{FILE:LOG}}
19+
pid = {{FILE:PID}}
20+
[unconfined]
21+
listen = {{ADDR:IPv4}}
22+
pm = dynamic
23+
pm.max_children = 1
24+
pm.start_servers = 1
25+
pm.min_spare_servers = 1
26+
pm.max_spare_servers = 1
27+
EOT;
28+
29+
$tester = new FPM\Tester($cfg);
30+
$tester->start();
31+
$tester->expectLogError("failed to fpm_setcpuaffinity\(\d+\): Invalid argument \(\d+\)");
32+
$tester->expectLogError("FPM initialization failed");
33+
$tester->close();
34+
35+
?>
36+
Done
37+
--EXPECT--
38+
Done
39+
--CLEAN--
40+
<?php
41+
require_once "tester.inc";
42+
FPM\Tester::clean();
43+
?>

sapi/fpm/tests/cpuaffinity.phpt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
FPM: cpu affinity test
3+
--SKIPIF--
4+
<?php include "skipif.inc";
5+
6+
if (!str_contains(PHP_OS, 'Linux')) {
7+
die('skipped supported only on Linux');
8+
}
9+
?>
10+
--FILE--
11+
<?php
12+
13+
require_once "tester.inc";
14+
15+
$cfg = <<<EOT
16+
[global]
17+
error_log = {{FILE:LOG}}
18+
pid = {{FILE:PID}}
19+
[unconfined]
20+
listen = {{ADDR:IPv4}}
21+
pm = dynamic
22+
pm.max_children = 1
23+
pm.start_servers = 1
24+
pm.min_spare_servers = 1
25+
pm.max_spare_servers = 1
26+
process.cpumask = 1
27+
EOT;
28+
29+
$tester = new FPM\Tester($cfg);
30+
$tester->start();
31+
$tester->expectLogStartNotices();
32+
$tester->terminate();
33+
$tester->expectLogTerminatingNotices();
34+
$tester->close();
35+
36+
?>
37+
Done
38+
--EXPECT--
39+
Done
40+
--CLEAN--
41+
<?php
42+
require_once "tester.inc";
43+
FPM\Tester::clean();
44+
?>

sapi/fpm/www.conf.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ listen = 127.0.0.1:9000
7474
; Default Value: no set
7575
; process.priority = -19
7676

77+
; Bind the pool processes to a cpu set.
78+
; The value can be one cpu id or a range.
79+
;
80+
; Default Value: inherits master's cpu affinity
81+
; process.cpumask = "8"
82+
; process.cpumask = "0-3"
83+
; process.cpumask = "4-7;10-12"
84+
7785
; Set the process dumpable flag (PR_SET_DUMPABLE prctl for Linux or
7886
; PROC_TRACE_CTL procctl for FreeBSD) even if the process user
7987
; or group is different than the master process user. It allows to create process

0 commit comments

Comments
 (0)