-
Notifications
You must be signed in to change notification settings - Fork 7.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fpm binding master and children processes to specific core(s). #10075
base: master
Are you sure you want to change the base?
Conversation
557a88a
to
f55fb44
Compare
@devnexen Thank you! I'll have a look soon. 🙂 |
b097796
to
b049245
Compare
3ff190e
to
a0be7ff
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder generally whether the main use-case here would be to pin specific workers to specific CPUs, rather than pinning all workers to a range of CPUs. I've never actually used setaffinity
so I'm not sure if that makes a big difference or if the scheduler is smart enough to avoid these context switches.
sapi/fpm/tests/cpuaffinity-fail.phpt
Outdated
<?php include "skipif.inc"; | ||
|
||
if (!str_contains(PHP_OS, 'Linux')) { | ||
die('skipped supported only on Linux'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FreeBSD supports cpuset_setaffinity
, is there any reason not to test this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah why is this limited to Linux only if cpuset_setaffinity
is already implemented?
sapi/fpm/fpm/fpm_unix.c
Outdated
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY) | ||
cpu_set_t cset; | ||
#endif | ||
long min; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find these slightly misleading, as they are only used as temporary values for fpm_cpuaffinity_add
. I'd prefer if min
/max
were parameters to fpm_cpuaffinity_add
but it's not a big deal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah agree, probably not much point to define them in struct as it can be just param. Also agree that it's not a big deal though :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I will rephrase it. Please can you change this too.
a0be7ff
to
e61c205
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the idea makes sense in general. Added some comments. The ones that stand out for me are following 2 issue.
As I mentioned in one of the the comments I don't like the configuration as it seems very cryptic. I think more declarative approach (potentially introducing more options) would be less error prone and easier to understand IMO.
Support for containers (Docker) is necessary as this would likely be reported as a bug later if it doesn't work correctly. IIRC _SC_NPROCESSORS_ONLN
reports number of processors in the host rather than what the container is limited to so this might need to be addressed if that's the case.
sapi/fpm/fpm/fpm_unix.c
Outdated
@@ -349,6 +358,102 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */ | |||
} | |||
/* }}} */ | |||
|
|||
#if HAVE_FPM_CPUAFFINITY | |||
struct fpm_cpuaffinity_conf { | |||
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check seems useless as it is covered by HAVE_FPM_CPUAFFINITY
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the idea is to extend this with different checks that are platform specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well it should be then updated or if it's not ready for review yet, then it should be probably a draft PR. I'd rather not to merge anything that has got some part with "maybe in the future"... :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment still stands.
It sets affinity to all workers (children) in the pool. Pools are the way how the workers are differentiated in FPM. So if you need to select only specific ones, then you need multiple pools. |
master process and/or workers (if you do not set worker's, it will inherit master's setting). |
Ok finally got it - I initially thought that there's just pool config but now I see there's global config for master as well. |
sapi/fpm/www.conf.in
Outdated
; Default Value: inherits master's cpu affinity | ||
; process.cpumask = "8" | ||
; process.cpumask = "0-3" | ||
; process.cpumask = "4-7;10-12" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this format is chosen, it needs more description about the format.
@bukka ping :) |
@devnexen I just had a look and |
0abc26c
to
64b8306
Compare
Think I ve addressed your concerns now :-) cheers |
Cool I will take a proper look early in Feb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It still needs a bit of work
sapi/fpm/php-fpm.conf.in
Outdated
; the master process could be bound to a single core | ||
; process.cpu_list = "4" | ||
; a range from the minimum cpuid to the max required | ||
; process.cpu_list = "0-3" | ||
; or multiple ranges separated by a comma | ||
; process.cpu_list = "1-4,8-16,32-48" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to have the format docs before the value rather than actual example. See other entries to get some inspiration. For example
Lines 276 to 283 in 64b8306
; Valid syntaxes are: | |
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on | |
; a specific port; | |
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on | |
; a specific port; | |
; 'port' - to listen on a TCP socket to all addresses | |
; (IPv6 and IPv4-mapped) on a specific port; | |
; '/path/to/unix/socket' - to listen on a unix socket. |
sapi/fpm/fpm/fpm_unix.c
Outdated
|
||
static void fpm_cpuaffinity_init(struct fpm_cpuaffinity_conf *c) | ||
{ | ||
#if defined(HAVE_FPM_CPUAFFINITY) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the point of this actually. Isn't this already inside this block: https://github.com/php/php-src/pull/10075/files#diff-6b26b2bd2a798d905b4419198f73f61fac2ecc265b4d361d204f565fd91d407fR405
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yes it is last reminiscence of "further porting to other platforms" thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah so it needs to get removed...
sapi/fpm/fpm/fpm_unix.c
Outdated
|
||
static void fpm_cpuaffinity_add(struct fpm_cpuaffinity_conf *c) | ||
{ | ||
#if defined(HAVE_FPM_CPUAFFINITY) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the point of this actually. Isn't this already inside this block: https://github.com/php/php-src/pull/10075/files#diff-6b26b2bd2a798d905b4419198f73f61fac2ecc265b4d361d204f565fd91d407fR405
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still stands...
sapi/fpm/tests/cpuaffinity-fail.phpt
Outdated
<?php include "skipif.inc"; | ||
|
||
if (!str_contains(PHP_OS, 'Linux')) { | ||
die('skipped supported only on Linux'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah why is this limited to Linux only if cpuset_setaffinity
is already implemented?
sapi/fpm/www.conf.in
Outdated
; the pool process could be bound to a single core | ||
; process.cpu_list = "8" | ||
; a range from the minimum cpuid to the max required | ||
; process.cpu_list = "0-3" | ||
; or multiple ranges separated by a comma | ||
; process.cpu_list = "4-7,10-12" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above comment for global config - better description would be useful...
sapi/fpm/fpm/fpm_unix.c
Outdated
@@ -349,6 +358,102 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */ | |||
} | |||
/* }}} */ | |||
|
|||
#if HAVE_FPM_CPUAFFINITY | |||
struct fpm_cpuaffinity_conf { | |||
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment still stands.
sapi/fpm/fpm/fpm_unix.c
Outdated
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY) | ||
cpu_set_t cset; | ||
#endif | ||
long min; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah agree, probably not much point to define them in struct as it can be just param. Also agree that it's not a big deal though :)
@devnexen There are still bunch of comments. Please comment on or resolve them once you address them. It will be then clearer what needs to be done. I resolved all those that were done and added some other comments. |
0f4bb85
to
840c0d2
Compare
I think I have addressed those, unluckily, vm builders fail at the moment but at least you can try locally the multi range part :-) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added more comments
pm.start_servers = 1 | ||
pm.min_spare_servers = 1 | ||
pm.max_spare_servers = 1 | ||
process.cpu_list = 0-1,2-3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you maybe change it to 0,2-3
as it will make more sense...
if (!str_contains(PHP_OS, 'Linux') && !str_contains(PHP_OS, 'FreeBSD')) { | ||
die('skipped supported only on Linux and FreeBSD'); | ||
} | ||
|
||
if (str_contains(PHP_OS, 'Linux')) { | ||
$cmd = 'nproc'; | ||
} else { | ||
$cmd = 'sysctl hw.ncpus'; | ||
} | ||
|
||
$nproc = intval(exec($cmd)); | ||
if ($nproc < 4) { | ||
die('skipped supported only on 4 cores or more environments'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you integrate those independently to the Tester (static methods) as done elsewhere...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point.
sapi/fpm/php-fpm.conf.in
Outdated
; process.cpu_list = "cpu id" - to bind to a single core | ||
; process.cpu_list = "min cpu id-max cpu id" - to bind to a core range | ||
from minimum cpu id to max | ||
; process.cpu_list = "[min cpu id-max cpu id],[min cpu id-max cpu id],..." | ||
- to bind to multiple ranges | ||
separated by a comma |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually very confusing description and a bit incorrect. Could you maybe use BNF to describe and keep some examples. I didn't mean to replace the examples completely but to add some generic description.
sapi/fpm/fpm/fpm_unix.c
Outdated
@@ -416,6 +521,15 @@ int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */ | |||
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); | |||
} | |||
} | |||
#if HAVE_FPM_CPUAFFINITY | |||
if (wp->config->process_cpu_list) { | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why extra nesting here. Just use single condition.
sapi/fpm/fpm/fpm_unix.c
Outdated
|
||
static void fpm_cpuaffinity_add(struct fpm_cpuaffinity_conf *c) | ||
{ | ||
#if defined(HAVE_FPM_CPUAFFINITY) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still stands...
sapi/fpm/fpm/fpm_unix.c
Outdated
#if defined(HAVE_SCHED_SETAFFINITY) || defined(HAVE_CPUSET_SETAFFINITY) | ||
cpu_set_t cset; | ||
#endif | ||
long min; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I will rephrase it. Please can you change this too.
sapi/fpm/www.conf.in
Outdated
; Valid syntaxes are: | ||
; process.cpu_list = "cpu id" - to bind to a single core | ||
; process.cpu_list = "min cpu id-max cpu id" - to bind to a core range | ||
from minimum cpu id to max | ||
; process.cpu_list = "[min cpu id-max cpu id],[min cpu id-max cpu id],..." | ||
- to bind to multiple ranges | ||
separated by a comma |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ditto
5bd3e29
to
0b581e4
Compare
Thanks @bukka I did some changes let me know if I forgot something. |
b875208
to
4ae6d19
Compare
4ae6d19
to
d2c20d1
Compare
ping @bukka when you have a moment. 🙂 |
I will try to find some time for this in May. Already used almost all my FPM time for this month. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have done some testing and it contains bugs. The parser needs be fixed as the current implementation contain errors which are not that easily visible - needed some separated testing for that. It would be probably good if we had some unit testing framework (e.g. cmocka) that we could use for things like this.
sapi/fpm/fpm/fpm_unix.c
Outdated
#endif | ||
|
||
#ifdef HAVE_SYS_CPUSET_H |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT: this can be elif
I guess...
sapi/fpm/fpm/fpm_unix.c
Outdated
|
||
fpm_cpuaffinity_add(&c, min, max); | ||
|
||
token = php_strtok_r(NULL, ";", &buf); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be comma otherwise it considers only two elements. You should add some test with two commas to test this...
while (token) { | ||
char *cpu_listsep; | ||
|
||
min = strtol(token, &cpu_listsep, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is too forgiving and allows latters in the string. For example abbbbb123,1
results in min and max 0 for the first element and 1 for min max second element. Obviously this should be error. I converted this to a small program:
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
char *php_strtok_r(char *s, const char *delim, char **last)
{
char *spanp;
int c, sc;
char *tok;
if (s == NULL && (s = *last) == NULL) {
return NULL;
}
/*
* Skip (span) leading delimiters (s += strspn(s, delim), sort of).
*/
cont:
c = *s++;
for (spanp = (char *)delim; (sc = *spanp++) != 0; ) {
if (c == sc) {
goto cont;
}
}
if (c == 0) /* no non-delimiter characters */ {
*last = NULL;
return NULL;
}
tok = s - 1;
/*
* Scan token (scan for delimiters: s += strcspn(s, delim), sort of).
* Note that delim must have one NUL; we stop if we see that, too.
*/
for (;;)
{
c = *s++;
spanp = (char *)delim;
do {
if ((sc = *spanp++) == c) {
if (c == 0) {
s = NULL;
} else {
char *w = s - 1;
*w = '\0';
}
*last = s;
return tok;
}
}
while (sc != 0);
}
/* NOTREACHED */
}
int parse(const char *cpu_list_const, int cpumax)
{
char *token, *buf;
int min, max;
int rc = 0;
char *cpu_list = strdup(cpu_list_const);
printf("\nparsing %s\n", cpu_list);
token = php_strtok_r(cpu_list, ",", &buf);
while (token) {
char *cpu_listsep;
min = strtol(token, &cpu_listsep, 0);
if (errno || min < 0 || min > cpumax) {
rc = -1;
}
max = min;
if (*cpu_listsep == '-') {
if (strlen(cpu_listsep) > 1) {
char *err;
max = strtol(cpu_listsep + 1, &err, 0);
if (errno || *err != '\0' || max < min || max > cpumax) {
rc = -1;
goto end;
}
} else {
rc = -1;
goto end;
}
}
printf("min: %d, max: %d\n", min, max);
token = php_strtok_r(NULL, ",", &buf);
}
end:
printf("result: %d, cpu list: %s\n", rc, cpu_list);
free(cpu_list);
return rc;
}
int main()
{
parse("1,2,5", 10);
parse("1-2", 4);
parse("1-2,4-5,7-10", 12);
// error cases
parse("1-8", 4);
parse("abbbbb123,1", 10);
parse("123afafkalf,1ffff", 10);
}
which results to
parsing 1,2,5
min: 1, max: 1
min: 2, max: 2
min: 5, max: 5
result: 0, cpu list: 1
parsing 1-2
min: 1, max: 2
result: 0, cpu list: 1-2
parsing 1-2,4-5,7-10
min: 1, max: 2
min: 4, max: 5
min: 7, max: 10
result: 0, cpu list: 1-2
parsing 1-8
result: -1, cpu list: 1-8
parsing abbbbb123,1
min: 0, max: 0
min: 1, max: 1
result: 0, cpu list: abbbbb123
parsing 123afafkalf,1ffff
min: 123, max: 123
min: 1, max: 1
result: -1, cpu list: 123afafkalf
This needs to be fixed.
sapi/fpm/fpm/fpm_unix.c
Outdated
cpumax = fpm_cpumax(); | ||
|
||
fpm_cpuaffinity_init(&c); | ||
token = php_strtok_r(cpu_list, ",", &buf); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem here is that php_strtok_r
modifies the value so you are changing the configured value in the config which should not be done. You should copy the list to a new buffer (either on heap allocated or using alloca) before calling this.
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".
d2c20d1
to
c910c0e
Compare
rational is there is git snippets and similar floating around about how to achieve this via command line.
So 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.