Skip to content

Commit caebe17

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". For now at least, focusing on simple cases, one core id or a range.
1 parent b9cd1cd commit caebe17

File tree

7 files changed

+146
-0
lines changed

7 files changed

+146
-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: 102 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,66 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */
349358
}
350359
/* }}} */
351360

361+
#if HAVE_FPM_CPUAFFINITY
362+
static long fpm_cpumax(void)
363+
{
364+
static long cpuid = LONG_MIN;
365+
if (cpuid == LONG_MIN) {
366+
cpuid = sysconf(_SC_NPROCESSORS_ONLN);
367+
}
368+
369+
return cpuid;
370+
}
371+
372+
static int fpm_cpusrange(char *cpumask, int *min, int *max) {
373+
char *cpumasksep;
374+
long cpumax;
375+
cpumax = fpm_cpumax();
376+
if (0 >= cpumax) {
377+
return -1;
378+
}
379+
*min = strtol(cpumask, &cpumasksep, 0);
380+
if (errno || *min < 0 || *min >= cpumax) {
381+
return -1;
382+
}
383+
384+
*max = *min;
385+
if (*cpumasksep == '-') {
386+
if (strlen(cpumasksep) > 1) {
387+
char *err;
388+
*max = strtol(cpumasksep + 1, &err, 0);
389+
if (errno || *err != '\0' || *max <= *min || *max >= cpumax) {
390+
return -1;
391+
}
392+
} else {
393+
return -1;
394+
}
395+
}
396+
return 0;
397+
}
398+
399+
static int fpm_setcpuaffinity(int min, int max)
400+
{
401+
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY)
402+
cpu_set_t cset;
403+
int i;
404+
405+
CPU_ZERO(&cset);
406+
for (i = min; i <= max; i ++) {
407+
if (!CPU_ISSET(i, &cset)) {
408+
CPU_SET(i, &cset);
409+
}
410+
}
411+
412+
#if defined(HAVE_SCHED_SETAFFINITY)
413+
return sched_setaffinity(0, sizeof(cset), &cset);
414+
#else
415+
return cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(cset), &cset);
416+
#endif
417+
#endif
418+
}
419+
#endif
420+
352421
int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
353422
{
354423
int is_root = !geteuid();
@@ -373,6 +442,20 @@ int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */
373442
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);
374443
}
375444
}
445+
#if HAVE_FPM_CPUAFFINITY
446+
if (is_root && wp->config->process_cpumask) {
447+
int fcpu, lcpu;
448+
449+
if (0 > fpm_cpusrange(wp->config->process_cpumask, &fcpu, &lcpu)) {
450+
zlog(ZLOG_SYSERROR, "[pool %s] failed to fpm_cpusrange(%s)", wp->config->name, wp->config->process_cpumask);
451+
return -1;
452+
}
453+
if (0 > fpm_setcpuaffinity(fcpu, lcpu)) {
454+
zlog(ZLOG_SYSERROR, "[pool %s] failed to fpm_setcpuaffinity(%s)", wp->config->name, wp->config->process_cpumask);
455+
return -1;
456+
}
457+
}
458+
#endif
376459

377460
if (is_root && wp->config->chroot && *wp->config->chroot) {
378461
if (0 > chroot(wp->config->chroot)) {
@@ -619,6 +702,25 @@ int fpm_unix_init_main(void)
619702
}
620703
}
621704

705+
#if HAVE_FPM_CPUAFFINITY
706+
if (fpm_global_config.process_cpumask) {
707+
if (is_root) {
708+
int fcpu, lcpu;
709+
710+
if (0 > fpm_cpusrange(fpm_global_config.process_cpumask, &fcpu, &lcpu)) {
711+
zlog(ZLOG_SYSERROR, "failed to fpm_cpusrange(%s)", fpm_global_config.process_cpumask);
712+
return -1;
713+
}
714+
if (0 > fpm_setcpuaffinity(fcpu, lcpu)) {
715+
zlog(ZLOG_SYSERROR, "failed to fpm_setcpuaffinity(%s)", fpm_global_config.process_cpumask);
716+
return -1;
717+
}
718+
} else {
719+
zlog(ZLOG_NOTICE, "'process.cpumask' directive is ignored when FPM is not running as root");
720+
}
721+
}
722+
#endif
723+
622724
fpm_globals.parent_pid = getpid();
623725
for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
624726
if (0 > fpm_unix_conf_wp(wp)) {

sapi/fpm/php-fpm.conf.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@
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 = 0-3
133+
128134
;;;;;;;;;;;;;;;;;;;;
129135
; Pool Definitions ;
130136
;;;;;;;;;;;;;;;;;;;;

sapi/fpm/www.conf.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ 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 = 4-7
82+
7783
; Set the process dumpable flag (PR_SET_DUMPABLE prctl for Linux or
7884
; PROC_TRACE_CTL procctl for FreeBSD) even if the process user
7985
; or group is different than the master process user. It allows to create process

0 commit comments

Comments
 (0)