From 76ece2fc26cba9b27e1cf22292c562d617718c0b Mon Sep 17 00:00:00 2001 From: Kai Krakow Date: Wed, 23 May 2018 21:56:25 +0200 Subject: [PATCH 1/8] Leverage soft real time capabilities Kernels that support SCHED_ISO scheduling policy can give processes soft real time support. This improves latency without compromising system stability. See https://lwn.net/Articles/720227/. This commit adds support for setting this policy with a safe fall back if kernel support is lacking by just ignoring the error condition. Additionally, it also tries to raise the nice priority of the game to -4 to give it a slight IO and CPU priority over other background processes. This needs PAM adjustments to allow users raising priority to certain levels. If it doesn't work, the fall back strategy is also ignoring the error condition. See /etc/security/limits.conf. Kernels that currently support SCHED_ISO include kernels with Con Kolivas MuQSS patchset (likely the CK patchset). This patchset is generally recommended for desktop machines but usually not found in standard distribution kernels due to lack of widespread stability tests. Signed-off-by: Kai Krakow --- README.md | 4 ++++ daemon/gamemode.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/README.md b/README.md index 41bf8ec..d19b702 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ The design has a clear-cut abstraction between the host daemon and library (`gam GameMode was designed primarily as a stop-gap solution to problems with the Intel and AMD CPU powersave or ondemand governors, but is now able to launch custom user defined plugins, and is intended to be expanded further, as there are a wealth of automation tasks one might want to apply. +GameMode can leverage support for soft real time mode if the running kernel supports `SCHED_ISO`. This adjusts the scheduling of the game to real time without sacrificing system stability by starving other processes. + +GameMode adjusts the nice priority of games to -4 to give it a slight IO and CPU priority over other background processes. This only works if your user is permitted to adjust priorities within the limits configured by PAM. + Issues with GameMode should be reported here in the issues section, and not reported to Feral directly. --- diff --git a/daemon/gamemode.c b/daemon/gamemode.c index 3903dc5..1775312 100644 --- a/daemon/gamemode.c +++ b/daemon/gamemode.c @@ -38,12 +38,26 @@ POSSIBILITY OF SUCH DAMAGE. #include "logging.h" #include +#include #include +#include #include #include #include +#include #include +/* SCHED_ISO may not be defined as it is a reserved value not yet + * implemented in official kernel sources, see linux/sched.h. + */ +#ifndef SCHED_ISO +#define SCHED_ISO 4 +#endif + +/* Priority to renice the process to. + */ +#define NICE_PRIORITY -4 + /** * The GameModeClient encapsulates the remote connection, providing a list * form to contain the pid and credentials. @@ -150,6 +164,33 @@ void game_mode_context_destroy(GameModeContext *self) pthread_rwlock_destroy(&self->rwlock); } +/** + * Apply scheduling policies + * + * This tries to change the scheduler of the client to soft realtime mode + * available in some kernels as SCHED_ISO. It also tries to adjust the nice + * level. If some of each fail, ignore this and log a warning. + * + * We don't need to store the current values because when the client exits, + * everything will be good: Scheduling is only applied to the client and + * its children. + */ +static void game_mode_apply_scheduler(pid_t client) +{ + LOG_MSG("Setting scheduling policies...\n"); + + if (setpriority(PRIO_PROCESS, (id_t)client, NICE_PRIORITY)) { + LOG_ERROR("Renicing client [%d] failed with error %d, ignoring.\n", client, errno); + } + + const struct sched_param p = { .sched_priority = 0 }; + if (sched_setscheduler(client, SCHED_ISO, &p)) { + LOG_ERROR("Setting client [%d] to SCHED_ISO failed with error %d, ignoring.\n", + client, + errno); + } +} + /** * Pivot into game mode. * @@ -343,6 +384,9 @@ bool game_mode_context_register(GameModeContext *self, pid_t client) game_mode_context_enter(self); } + /* Apply scheduler policies */ + game_mode_apply_scheduler(client); + return true; } From d8231a2972cf8ee556281c918da5695751d7346a Mon Sep 17 00:00:00 2001 From: Kai Krakow Date: Thu, 24 May 2018 22:28:18 +0200 Subject: [PATCH 2/8] scheduler: Don't renice if already reniced This is a heuristic to not apply a nice level if the user already did that (or the process itself). Signed-off-by: Kai Krakow --- daemon/gamemode.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/daemon/gamemode.c b/daemon/gamemode.c index 1775312..db1c048 100644 --- a/daemon/gamemode.c +++ b/daemon/gamemode.c @@ -179,7 +179,12 @@ static void game_mode_apply_scheduler(pid_t client) { LOG_MSG("Setting scheduling policies...\n"); - if (setpriority(PRIO_PROCESS, (id_t)client, NICE_PRIORITY)) { + /* + * don't adjust priority if it was already adjusted + */ + if (getpriority(PRIO_PROCESS, (id_t)client) != 0) { + LOG_ERROR("Client [%d] already reniced, ignoring.\n", client); + } else if (setpriority(PRIO_PROCESS, (id_t)client, NICE_PRIORITY)) { LOG_ERROR("Renicing client [%d] failed with error %d, ignoring.\n", client, errno); } From 9dfd71880789e38ae37c2c420723982e0bd1dd83 Mon Sep 17 00:00:00 2001 From: Kai Krakow Date: Thu, 24 May 2018 22:31:57 +0200 Subject: [PATCH 3/8] config: Introduce new setting "softrealtime" This adds support for a new configuration option "softrealtime" to be read from the general section. This commit alone does nothing, the following commit adds actually making use of the value. Signed-off-by: Kai Krakow --- daemon/daemon_config.c | 12 ++++++++++++ daemon/daemon_config.h | 5 +++++ data/gamemoded.1 | 4 ++++ example/gamemode.ini | 4 ++++ 4 files changed, 25 insertions(+) diff --git a/daemon/daemon_config.c b/daemon/daemon_config.c index 9fdd4ca..1afa2b7 100644 --- a/daemon/daemon_config.c +++ b/daemon/daemon_config.c @@ -67,6 +67,8 @@ struct GameModeConfig { char defaultgov[CONFIG_VALUE_MAX]; char desiredgov[CONFIG_VALUE_MAX]; + char softrealtime[CONFIG_VALUE_MAX]; + long reaper_frequency; }; @@ -157,6 +159,8 @@ static int inih_handler(void *user, const char *section, const char *name, const valid = get_string_value(value, self->defaultgov); } else if (strcmp(name, "desiredgov") == 0) { valid = get_string_value(value, self->desiredgov); + } else if (strcmp(name, "softrealtime") == 0) { + valid = get_string_value(value, self->softrealtime); } } else if (strcmp(section, "custom") == 0) { /* Custom subsection */ @@ -401,3 +405,11 @@ void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALU { memcpy_locked_config(self, governor, self->desiredgov, sizeof(self->desiredgov)); } + +/* + * Get the chosen soft realtime behavior + */ +void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]) +{ + memcpy_locked_config(self, softrealtime, self->softrealtime, sizeof(self->softrealtime)); +} diff --git a/daemon/daemon_config.h b/daemon/daemon_config.h index 1be1940..59e90c9 100644 --- a/daemon/daemon_config.h +++ b/daemon/daemon_config.h @@ -103,3 +103,8 @@ void config_get_default_governor(GameModeConfig *self, char governor[CONFIG_VALU * Get the chosen desired governor */ void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALUE_MAX]); + +/* + * Get the chosen soft realtime behavior + */ +void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]); diff --git a/data/gamemoded.1 b/data/gamemoded.1 index 8c50b38..518a24e 100644 --- a/data/gamemoded.1 +++ b/data/gamemoded.1 @@ -102,6 +102,10 @@ desiredgov=performance ; The default governer is used when leaving GameMode instead of restoring the original value defaultgov=powersave +; By default, GameMode changes the scheduler policy to SCHED_ISO with 4 or more CPU cores, +; force enable or disable with "on" or "off" +softrealtime=auto + [filter] ; If "whitelist" entry has a value(s) ; gamemode will reject anything not in the whitelist diff --git a/example/gamemode.ini b/example/gamemode.ini index 9c04fb2..e4a95e1 100644 --- a/example/gamemode.ini +++ b/example/gamemode.ini @@ -7,6 +7,10 @@ desiredgov=performance ; The default governer is used when leaving GameMode instead of restoring the original value defaultgov=powersave +; By default, GameMode changes the scheduler policy to SCHED_ISO with 4 or more CPU cores, +; force enable or disable with "on" or "off" +softrealtime=auto + [filter] ; If "whitelist" entry has a value(s) ; gamemode will reject anything not in the whitelist From addfe1fbc0f8583ad5b4cf68ba1ffc505ed2cba7 Mon Sep 17 00:00:00 2001 From: Kai Krakow Date: Thu, 24 May 2018 22:36:56 +0200 Subject: [PATCH 4/8] scheduler: Add heuristics and option to force on/off This commit adds a simple heuristic to auto detect whether to use SCHED_ISO or not. It does this by looking at the number of cores: According to some reports by users of SCHED_ISO and an explanation by Con Kolivas, games running busy loops and running on too few cores might start fighting with scheduling of the graphic driver, leading to priority inversion. This results in actually lower performance. So let's only enable SCHED_ISO on at least four cores. The user can still force SCHED_ISO on or off by setting the configuration value. All other values (or none) will apply heuristics. Signed-off-by: Kai Krakow --- daemon/gamemode.c | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/daemon/gamemode.c b/daemon/gamemode.c index db1c048..0012e44 100644 --- a/daemon/gamemode.c +++ b/daemon/gamemode.c @@ -45,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include #include +#include #include /* SCHED_ISO may not be defined as it is a reserved value not yet @@ -175,7 +176,7 @@ void game_mode_context_destroy(GameModeContext *self) * everything will be good: Scheduling is only applied to the client and * its children. */ -static void game_mode_apply_scheduler(pid_t client) +static void game_mode_apply_scheduler(GameModeContext *self, pid_t client) { LOG_MSG("Setting scheduling policies...\n"); @@ -188,11 +189,32 @@ static void game_mode_apply_scheduler(pid_t client) LOG_ERROR("Renicing client [%d] failed with error %d, ignoring.\n", client, errno); } - const struct sched_param p = { .sched_priority = 0 }; - if (sched_setscheduler(client, SCHED_ISO, &p)) { - LOG_ERROR("Setting client [%d] to SCHED_ISO failed with error %d, ignoring.\n", - client, - errno); + /* + * read configuration "softrealtime" (on, off, auto) + */ + char softrealtime[CONFIG_VALUE_MAX] = { 0 }; + config_get_soft_realtime(self->config, softrealtime); + + /* + * Enable unconditionally or auto-detect soft realtime usage, + * auto detection is based on observations where dual-core CPU suffered + * priority inversion problems with the graphics driver thus running + * slower as a result, so enable only with more than 3 cores. + */ + bool enable_softrealtime = (strcmp(softrealtime, "on") == 0) || (get_nprocs() > 3); + + /* + * Actually apply the scheduler policy if not explicitly turned off + */ + if (!(strcmp(softrealtime, "off") == 0) && (enable_softrealtime)) { + const struct sched_param p = { .sched_priority = 0 }; + if (sched_setscheduler(client, SCHED_ISO, &p)) { + LOG_ERROR("Setting client [%d] to SCHED_ISO failed with error %d, ignoring.\n", + client, + errno); + } + } else { + LOG_ERROR("Not using softrealtime, setting is '%s'.\n", softrealtime); } } @@ -390,7 +412,7 @@ bool game_mode_context_register(GameModeContext *self, pid_t client) } /* Apply scheduler policies */ - game_mode_apply_scheduler(client); + game_mode_apply_scheduler(self, client); return true; } From 57c6bbb4443a50c6ed0a94218bf593a8c9a2af2b Mon Sep 17 00:00:00 2001 From: Kai Krakow Date: Sun, 10 Jun 2018 09:33:02 +0200 Subject: [PATCH 5/8] config: Allow renice configuration This commit adds configuration support for the renice value and amends documentation and examples. This commit by itself does nothing, the following commit is needed to actually apply the new settings. Signed-off-by: Kai Krakow --- README.md | 2 +- daemon/daemon_config.c | 11 +++++++++++ daemon/daemon_config.h | 5 +++++ data/gamemoded.1 | 4 ++++ example/gamemode.ini | 4 ++++ 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d19b702..be97adb 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ GameMode was designed primarily as a stop-gap solution to problems with the Inte GameMode can leverage support for soft real time mode if the running kernel supports `SCHED_ISO`. This adjusts the scheduling of the game to real time without sacrificing system stability by starving other processes. -GameMode adjusts the nice priority of games to -4 to give it a slight IO and CPU priority over other background processes. This only works if your user is permitted to adjust priorities within the limits configured by PAM. +GameMode adjusts the nice priority of games to -4 by default to give it a slight IO and CPU priority over other background processes. This only works if your user is permitted to adjust priorities within the limits configured by PAM. See `/etc/security/limits.conf`. Issues with GameMode should be reported here in the issues section, and not reported to Feral directly. diff --git a/daemon/daemon_config.c b/daemon/daemon_config.c index 1afa2b7..53b4434 100644 --- a/daemon/daemon_config.c +++ b/daemon/daemon_config.c @@ -68,6 +68,7 @@ struct GameModeConfig { char desiredgov[CONFIG_VALUE_MAX]; char softrealtime[CONFIG_VALUE_MAX]; + long renice; long reaper_frequency; }; @@ -161,6 +162,8 @@ static int inih_handler(void *user, const char *section, const char *name, const valid = get_string_value(value, self->desiredgov); } else if (strcmp(name, "softrealtime") == 0) { valid = get_string_value(value, self->softrealtime); + } else if (strcmp(name, "renice") == 0) { + valid = get_long_value(name, value, &self->renice); } } else if (strcmp(section, "custom") == 0) { /* Custom subsection */ @@ -413,3 +416,11 @@ void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VAL { memcpy_locked_config(self, softrealtime, self->softrealtime, sizeof(self->softrealtime)); } + +/* + * Get the renice value + */ +void config_get_renice_value(GameModeConfig *self, long *value) +{ + memcpy_locked_config(self, value, &self->renice, sizeof(long)); +} diff --git a/daemon/daemon_config.h b/daemon/daemon_config.h index 59e90c9..f6f9975 100644 --- a/daemon/daemon_config.h +++ b/daemon/daemon_config.h @@ -108,3 +108,8 @@ void config_get_desired_governor(GameModeConfig *self, char governor[CONFIG_VALU * Get the chosen soft realtime behavior */ void config_get_soft_realtime(GameModeConfig *self, char softrealtime[CONFIG_VALUE_MAX]); + +/* + * Get the renice value + */ +void config_get_renice_value(GameModeConfig *self, long *value); diff --git a/data/gamemoded.1 b/data/gamemoded.1 index 518a24e..6bc91e3 100644 --- a/data/gamemoded.1 +++ b/data/gamemoded.1 @@ -106,6 +106,10 @@ defaultgov=powersave ; force enable or disable with "on" or "off" softrealtime=auto +; By default, GameMode renices the client to -4, you can put any value between 1 and 20 here, +; the value will be negated and applied as a nice value +renice = 4 + [filter] ; If "whitelist" entry has a value(s) ; gamemode will reject anything not in the whitelist diff --git a/example/gamemode.ini b/example/gamemode.ini index e4a95e1..36697f6 100644 --- a/example/gamemode.ini +++ b/example/gamemode.ini @@ -11,6 +11,10 @@ defaultgov=powersave ; force enable or disable with "on" or "off" softrealtime=auto +; By default, GameMode renices the client to -4, you can put any value between 1 and 20 here, +; the value will be negated and applied as a nice value +renice = 4 + [filter] ; If "whitelist" entry has a value(s) ; gamemode will reject anything not in the whitelist From 6f93020d0be493bdee934b496c4a4469f3d73672 Mon Sep 17 00:00:00 2001 From: Kai Krakow Date: Sun, 10 Jun 2018 09:36:21 +0200 Subject: [PATCH 6/8] scheduler: Apply renice configuration to clients This commit applies the configured nice value to the client. It accepts values from 1 to 20, the negated value is applied as a nice value. Negation was chosen due to limits of the configuration parser. Since low priority values (0 to 19) make no sense in the scope of GameMode, this is a safe approach. Signed-off-by: Kai Krakow --- daemon/gamemode.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/daemon/gamemode.c b/daemon/gamemode.c index 0012e44..9a8380d 100644 --- a/daemon/gamemode.c +++ b/daemon/gamemode.c @@ -57,7 +57,7 @@ POSSIBILITY OF SUCH DAMAGE. /* Priority to renice the process to. */ -#define NICE_PRIORITY -4 +#define NICE_DEFAULT_PRIORITY -4 /** * The GameModeClient encapsulates the remote connection, providing a list @@ -180,12 +180,24 @@ static void game_mode_apply_scheduler(GameModeContext *self, pid_t client) { LOG_MSG("Setting scheduling policies...\n"); + /* + * read configuration "renice" (1..20) + */ + long int renice = 0; + config_get_renice_value(self->config, &renice); + if ((renice < 1) || (renice > 20)) { + LOG_ERROR("Renice value [%ld] defaulted to [%d].\n", renice, -NICE_DEFAULT_PRIORITY); + renice = NICE_DEFAULT_PRIORITY; + } else { + renice = -renice; + } + /* * don't adjust priority if it was already adjusted */ if (getpriority(PRIO_PROCESS, (id_t)client) != 0) { LOG_ERROR("Client [%d] already reniced, ignoring.\n", client); - } else if (setpriority(PRIO_PROCESS, (id_t)client, NICE_PRIORITY)) { + } else if (setpriority(PRIO_PROCESS, (id_t)client, (int)renice)) { LOG_ERROR("Renicing client [%d] failed with error %d, ignoring.\n", client, errno); } From 048b7b1c1788d16aceb2ed8f57811ca68773df6f Mon Sep 17 00:00:00 2001 From: Kai Krakow Date: Sun, 10 Jun 2018 09:38:57 +0200 Subject: [PATCH 7/8] docs: Document priority inversion caveats Depending on the local system architecture and the game architecture, changes to the scheduling may result in priority inversion which has counter-intuitive effects on performance. Let's document these to give the user a chance to detect and fix this problem. Signed-off-by: Kai Krakow --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index be97adb..a89ac7e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ GameMode can leverage support for soft real time mode if the running kernel supp GameMode adjusts the nice priority of games to -4 by default to give it a slight IO and CPU priority over other background processes. This only works if your user is permitted to adjust priorities within the limits configured by PAM. See `/etc/security/limits.conf`. +Please take note that some games may actually run seemingly slower with `SCHED_ISO` if the game makes use of busy looping while interacting with the graphic driver. The same may happen if you apply too strong nice values. This effect is called priority inversion: Due to the high priority given to busy loops, there may be too few resources left for the graphics driver. Thus, sane defaults were chosen to not expose this effect on most systems. Part of this default is a heuristic which automatically turns off `SCHED_ISO` if GameMode detects three or less CPU cores. Your experience may change based on using GL threaded optimizations, CPU core binding (taskset), the graphic driver, or different CPU architectures. If you experience bad input latency or inconsistent FPS, try switching these configurations on or off first and report back. `SCHED_ISO` comes with a protection against this effect by falling back to normal scheduling as soon as the `SCHED_ISO` process uses more than 70% avarage of all CPU cores. This default value can be adjusted outside of the scope of GameMode (it's in `/proc/sys/kernel/iso_cpu`). + Issues with GameMode should be reported here in the issues section, and not reported to Feral directly. --- From ead417be3696c468a509aaf5ea4ccdbe9e937bf7 Mon Sep 17 00:00:00 2001 From: Kai Krakow Date: Sun, 10 Jun 2018 10:07:23 +0200 Subject: [PATCH 8/8] docs: Add a warning about potential resource starvation Warn the user about adjusting `SCHED_ISO` to too high CPU usage values as it may render the OS unresponsive. Signed-off-by: Kai Krakow --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a89ac7e..4a2755b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ GameMode can leverage support for soft real time mode if the running kernel supp GameMode adjusts the nice priority of games to -4 by default to give it a slight IO and CPU priority over other background processes. This only works if your user is permitted to adjust priorities within the limits configured by PAM. See `/etc/security/limits.conf`. -Please take note that some games may actually run seemingly slower with `SCHED_ISO` if the game makes use of busy looping while interacting with the graphic driver. The same may happen if you apply too strong nice values. This effect is called priority inversion: Due to the high priority given to busy loops, there may be too few resources left for the graphics driver. Thus, sane defaults were chosen to not expose this effect on most systems. Part of this default is a heuristic which automatically turns off `SCHED_ISO` if GameMode detects three or less CPU cores. Your experience may change based on using GL threaded optimizations, CPU core binding (taskset), the graphic driver, or different CPU architectures. If you experience bad input latency or inconsistent FPS, try switching these configurations on or off first and report back. `SCHED_ISO` comes with a protection against this effect by falling back to normal scheduling as soon as the `SCHED_ISO` process uses more than 70% avarage of all CPU cores. This default value can be adjusted outside of the scope of GameMode (it's in `/proc/sys/kernel/iso_cpu`). +Please take note that some games may actually run seemingly slower with `SCHED_ISO` if the game makes use of busy looping while interacting with the graphic driver. The same may happen if you apply too strong nice values. This effect is called priority inversion: Due to the high priority given to busy loops, there may be too few resources left for the graphics driver. Thus, sane defaults were chosen to not expose this effect on most systems. Part of this default is a heuristic which automatically turns off `SCHED_ISO` if GameMode detects three or less CPU cores. Your experience may change based on using GL threaded optimizations, CPU core binding (taskset), the graphic driver, or different CPU architectures. If you experience bad input latency or inconsistent FPS, try switching these configurations on or off first and report back. `SCHED_ISO` comes with a protection against this effect by falling back to normal scheduling as soon as the `SCHED_ISO` process uses more than 70% avarage across all CPU cores. This default value can be adjusted outside of the scope of GameMode (it's in `/proc/sys/kernel/iso_cpu`). This value also protects against compromising system stability, do not set it to 100% as this would turn the game into a full real time process, thus potentially starving all other OS components from CPU resources. Issues with GameMode should be reported here in the issues section, and not reported to Feral directly.