Skip to content

Commit e5a8d14

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 c8ec2ed commit e5a8d14

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
@@ -405,8 +405,10 @@ pty.h \
405405
pwd.h \
406406
resolv.h \
407407
strings.h \
408+
sched.h \
408409
syslog.h \
409410
sysexits.h \
411+
sys/cpuset.h \
410412
sys/ioctl.h \
411413
sys/file.h \
412414
sys/mman.h \
@@ -574,6 +576,7 @@ AC_CHECK_FUNCS(
574576
alphasort \
575577
asctime_r \
576578
chroot \
579+
cpuset_setaffinity \
577580
ctime_r \
578581
explicit_memset \
579582
fdatasync \
@@ -611,6 +614,7 @@ poll \
611614
pthread_jit_write_protect_np \
612615
putenv \
613616
scandir \
617+
sched_setaffinity \
614618
setitimer \
615619
setenv \
616620
shutdown \

sapi/fpm/fpm/fpm_conf.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ static struct ini_value_parser_s ini_fpm_global_options[] = {
9999
{ "process_control_timeout", &fpm_conf_set_time, GO(process_control_timeout) },
100100
{ "process.max", &fpm_conf_set_integer, GO(process_max) },
101101
{ "process.priority", &fpm_conf_set_integer, GO(process_priority) },
102+
#if HAVE_FPM_CPUAFFINITY
103+
{ "process.cpumask", &fpm_conf_set_string, GO(process_cpumask) },
104+
#endif
102105
{ "daemonize", &fpm_conf_set_boolean, GO(daemonize) },
103106
{ "rlimit_files", &fpm_conf_set_integer, GO(rlimit_files) },
104107
{ "rlimit_core", &fpm_conf_set_rlimit_core, GO(rlimit_core) },
@@ -131,6 +134,9 @@ static struct ini_value_parser_s ini_fpm_pool_options[] = {
131134
#endif
132135
{ "process.priority", &fpm_conf_set_integer, WPO(process_priority) },
133136
{ "process.dumpable", &fpm_conf_set_boolean, WPO(process_dumpable) },
137+
#if HAVE_FPM_CPUAFFINITY
138+
{ "process.cpumask", &fpm_conf_set_string, WPO(process_cpumask) },
139+
#endif
134140
{ "pm", &fpm_conf_set_pm, WPO(pm) },
135141
{ "pm.max_children", &fpm_conf_set_integer, WPO(pm_max_children) },
136142
{ "pm.start_servers", &fpm_conf_set_integer, WPO(pm_start_servers) },
@@ -622,6 +628,9 @@ static void *fpm_worker_pool_config_alloc(void)
622628
wp->config->pm_process_idle_timeout = 10; /* 10s by default */
623629
wp->config->process_priority = 64; /* 64 means unset */
624630
wp->config->process_dumpable = 0;
631+
#if HAVE_FPM_CPUAFFINITY
632+
wp->config->process_cpumask = NULL;
633+
#endif
625634
wp->config->clear_env = 1;
626635
wp->config->decorate_workers_output = 1;
627636
#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"
@@ -392,6 +401,103 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */
392401
}
393402
/* }}} */
394403

404+
#if HAVE_FPM_CPUAFFINITY
405+
struct fpm_cpuaffinity_conf {
406+
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY)
407+
cpu_set_t cset;
408+
#endif
409+
long min;
410+
long max;
411+
};
412+
413+
static long fpm_cpumax(void)
414+
{
415+
static long cpuid = LONG_MIN;
416+
if (cpuid == LONG_MIN) {
417+
cpuid = sysconf(_SC_NPROCESSORS_ONLN);
418+
}
419+
420+
return cpuid;
421+
}
422+
423+
static void fpm_cpuaffinity_init(struct fpm_cpuaffinity_conf *c)
424+
{
425+
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY)
426+
CPU_ZERO(&c->cset);
427+
#endif
428+
}
429+
430+
static void fpm_cpuaffinity_add(struct fpm_cpuaffinity_conf *c)
431+
{
432+
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY)
433+
int i;
434+
435+
for (i = c->min; i <= c->max; i ++) {
436+
if (!CPU_ISSET(i, &c->cset)) {
437+
CPU_SET(i, &c->cset);
438+
}
439+
}
440+
#endif
441+
}
442+
443+
static int fpm_cpuaffinity_set(struct fpm_cpuaffinity_conf *c)
444+
{
445+
#if defined(HAVE_SCHED_SETAFFINITY)
446+
return sched_setaffinity(0, sizeof(c->cset), &c->cset);
447+
#elif defined(HAVE_CPUSET_SETAFFINITY)
448+
return cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(c->cset), &c->cset);
449+
#endif
450+
}
451+
452+
static void fpm_cpuaffinity_destroy(struct fpm_cpuaffinity_conf *c)
453+
{
454+
// some platform/apis requires to allocates data on the heap
455+
}
456+
457+
static int fpm_setcpuaffinity(char *cpumask)
458+
{
459+
char *token, *buf;
460+
struct fpm_cpuaffinity_conf fconf;
461+
int r, cpumax;
462+
463+
r = -1;
464+
cpumax = fpm_cpumax();
465+
466+
fpm_cpuaffinity_init(&fconf);
467+
token = php_strtok_r(cpumask, ";", &buf);
468+
469+
while (token) {
470+
char *cpumasksep;
471+
472+
fconf.min = strtol(token, &cpumasksep, 0);
473+
if (errno || fconf.min > cpumax) {
474+
goto fail;
475+
}
476+
fconf.max = fconf.min;
477+
if (*cpumasksep == '-') {
478+
if (strlen(cpumasksep) > 1) {
479+
char *err;
480+
fconf.max = strtol(cpumasksep + 1, &err, 0);
481+
if (errno || *err != '\0' || fconf.max < fconf.min || fconf.max > cpumax) {
482+
return -1;
483+
}
484+
} else {
485+
return -1;
486+
}
487+
}
488+
489+
fpm_cpuaffinity_add(&fconf);
490+
491+
token = php_strtok_r(NULL, ";", &buf);
492+
}
493+
494+
fail:
495+
r = fpm_cpuaffinity_set(&fconf);
496+
fpm_cpuaffinity_destroy(&fconf);
497+
return r;
498+
}
499+
#endif
500+
395501
int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
396502
{
397503
int is_root = !geteuid();
@@ -416,6 +522,15 @@ int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
416522
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);
417523
}
418524
}
525+
#if HAVE_FPM_CPUAFFINITY
526+
if (wp->config->process_cpumask) {
527+
528+
if (0 > fpm_setcpuaffinity(wp->config->process_cpumask)) {
529+
zlog(ZLOG_SYSERROR, "[pool %s] failed to fpm_setcpuaffinity(%s)", wp->config->name, wp->config->process_cpumask);
530+
return -1;
531+
}
532+
}
533+
#endif
419534

420535
if (is_root && wp->config->chroot && *wp->config->chroot) {
421536
if (0 > chroot(wp->config->chroot)) {
@@ -662,6 +777,15 @@ int fpm_unix_init_main(void)
662777
}
663778
}
664779

780+
#if HAVE_FPM_CPUAFFINITY
781+
if (fpm_global_config.process_cpumask) {
782+
if (0 > fpm_setcpuaffinity(fpm_global_config.process_cpumask)) {
783+
zlog(ZLOG_SYSERROR, "failed to fpm_setcpuaffinity(%s)", fpm_global_config.process_cpumask);
784+
return -1;
785+
}
786+
}
787+
#endif
788+
665789
fpm_globals.parent_pid = getpid();
666790
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
667791
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
@@ -80,6 +80,14 @@ listen = 127.0.0.1:9000
8080
; Default Value: no set
8181
; process.priority = -19
8282

83+
; Bind the pool processes to a cpu set.
84+
; The value can be one cpu id or a range.
85+
;
86+
; Default Value: inherits master's cpu affinity
87+
; process.cpumask = "8"
88+
; process.cpumask = "0-3"
89+
; process.cpumask = "4-7;10-12"
90+
8391
; Set the process dumpable flag (PR_SET_DUMPABLE prctl for Linux or
8492
; PROC_TRACE_CTL procctl for FreeBSD) even if the process user
8593
; or group is different than the master process user. It allows to create process

0 commit comments

Comments
 (0)