From 495a659895d105f6de3652888633de58c05c5204 Mon Sep 17 00:00:00 2001 From: Henrik Holst Date: Wed, 3 May 2023 07:33:24 +0200 Subject: [PATCH] Added gamemode-cpu.c Added gamemode-cpu.c which contains the functions for cpu core parking and pinning --- daemon/gamemode-cpu.c | 409 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 409 insertions(+) create mode 100644 daemon/gamemode-cpu.c diff --git a/daemon/gamemode-cpu.c b/daemon/gamemode-cpu.c new file mode 100644 index 0000000..e77a14a --- /dev/null +++ b/daemon/gamemode-cpu.c @@ -0,0 +1,409 @@ + +/* + +Copyright (c) 2017-2019, Feral Interactive +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Feral Interactive nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + */ + +#define _GNU_SOURCE + +#include +#include + +#include "common-external.h" +#include "common-cpu.h" +#include "common-helpers.h" +#include "common-logging.h" + +#include "gamemode.h" +#include "gamemode-config.h" + +#include "build-config.h" + +static int read_small_file (char *path, char **buf, size_t *buflen) +{ + FILE *f = fopen(path, "r"); + + if (!f) { + LOG_ERROR("Couldn't open file at %s (%s), will not apply cpu core parking!\n", path, strerror(errno)); + return 0; + } + + ssize_t nread = getline(buf, buflen, f); + + if (nread == -1) { + LOG_ERROR("Couldn't read file at %s (%s), will not apply cpu core parking!\n", path, strerror(errno)); + fclose(f); + return 0; + } + + fclose (f); + + while (nread > 0 && ((*buf)[nread - 1] == '\n' || (*buf)[nread - 1] == '\r')) + nread--; + + (*buf)[nread] = '\0'; + + return 1; +} + +static int walk_sysfs (char *cpulist, char **buf, size_t *buflen, GameModeCPUInfo *info) +{ + char path[PATH_MAX]; + unsigned long long max_cache = 0; + long from, to; + + char *list = cpulist; + while ((list = parse_cpulist(list, &from, &to))) { + for (long cpu = from; cpu < to + 1; cpu++) { + int ret = snprintf(path, PATH_MAX, "/sys/devices/system/cpu/cpu%ld/cache/index3/size", cpu); + if (ret < 0 || ret >= PATH_MAX) { + LOG_ERROR("snprintf failed, will not apply cpu core parking!\n"); + return 0; + } + + if (!read_small_file(path, buf, buflen)) + return 0; + + char *endp; + unsigned long long cache_size = strtoull (*buf, &endp, 10); + + if (*endp == 'K') { + cache_size *= 1024; + } else if (*endp == 'M') { + cache_size *= 1024 * 1024; + } else if (*endp == 'G') { + cache_size *= 1024 * 1024 * 1024; + } else if (*endp != '\0') { + LOG_ERROR("cpu L3 cache size (%s) is silly, will not apply cpu core parking!\n", *buf); + return 0; + } + + if (cache_size > max_cache) { + max_cache = cache_size; + CPU_ZERO_S(CPU_ALLOC_SIZE(info->num_cpu), info->to_keep); + } + + if (cache_size == max_cache) + CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep); + + CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online); + } + } + + return 1; +} + +static int walk_string (char *cpulist, char *config_cpulist, GameModeCPUInfo *info) +{ + long from, to; + + char *list = cpulist; + while ((list = parse_cpulist(list, &from, &to))) { + for (long cpu = from; cpu < to + 1; cpu++) { + CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online); + + if (info->park_or_pin == 0) + CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep); + } + } + + list = config_cpulist; + while ((list = parse_cpulist(list, &from, &to))) { + for (long cpu = from; cpu < to + 1; cpu++) { + if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online)) { + if (info->park_or_pin == 0) + CPU_CLR_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep); + else + CPU_SET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep); + } + } + } + + return 1; +} + +/** + * Attempts to identify the current in use CPU information + */ +int game_mode_initialise_cpu(GameModeConfig *config, GameModeCPUInfo **info) +{ + /* Verify input, this is programmer error */ + if (!info || *info) + FATAL_ERROR("Invalid GameModeCPUInfo passed to %s", __func__); + + /* Early out if we have this feature turned off */ + char park_cores[CONFIG_VALUE_MAX]; + char pin_cores[CONFIG_VALUE_MAX]; + config_get_cpu_park_cores(config, park_cores); + config_get_cpu_pin_cores(config, pin_cores); + + int park_or_pin = -1; + + if (pin_cores[0] != '\0') { + if (strcasecmp (pin_cores, "no") == 0 || strcasecmp (pin_cores, "false") == 0 || strcmp (pin_cores, "0") == 0) { + park_or_pin = -2; + } else if (strcasecmp (pin_cores, "yes") == 0 || strcasecmp (pin_cores, "true") == 0 || strcmp (pin_cores, "1") == 0) { + pin_cores[0] = '\0'; + park_or_pin = 1; + } else { + park_or_pin = 1; + } + } + + if (park_or_pin < 1 && park_cores[0] != '\0') { + if (strcasecmp (park_cores, "no") == 0 || strcasecmp (park_cores, "false") == 0 || strcmp (park_cores, "0") == 0) { + if (park_or_pin == -2) + return 0; + + park_or_pin = -1; + } else if (strcasecmp (park_cores, "yes") == 0 || strcasecmp (park_cores, "true") == 0 || strcmp (park_cores, "1") == 0) { + park_cores[0] = '\0'; + park_or_pin = 0; + } else { + park_or_pin = 0; + } + } + + /* always default to pin */ + if (park_or_pin < 0) + park_or_pin = 1; + + char *buf = NULL, *buf2 = NULL; + size_t buflen = 0, buf2len = 0; + + /* first we find which cores are online, this also helps us to determine the max + * cpu core number that we need to allocate the cpulist later */ + if (!read_small_file("/sys/devices/system/cpu/online", &buf, &buflen)) + goto error_exit; + + long from, to, max = 0; + char *s = buf; + while ((s = parse_cpulist(s, &from, &to))) { + if (to > max) + max = to; + } + + /* either parsing failed or we have only a single core, in either case + * we cannot optimize anyway */ + if (max == 0) + goto early_exit; + + GameModeCPUInfo *new_info = malloc(sizeof(GameModeCPUInfo)); + memset(new_info, 0, sizeof(GameModeCPUInfo)); + + new_info->num_cpu = (size_t)(max + 1); + new_info->park_or_pin = park_or_pin; + new_info->online = CPU_ALLOC(new_info->num_cpu); + new_info->to_keep = CPU_ALLOC(new_info->num_cpu); + + CPU_ZERO_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->online); + CPU_ZERO_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->to_keep); + + if (park_or_pin == 0 && park_cores[0] != '\0') { + if (!walk_string (buf, park_cores, new_info)) + goto error_exit; + } else if (park_or_pin == 1 && pin_cores[0] != '\0') { + if (!walk_string (buf, pin_cores, new_info)) + goto error_exit; + } else if (!walk_sysfs (buf, &buf2, &buf2len, new_info)) { + goto error_exit; + } + + if (park_or_pin == 0 && CPU_EQUAL_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->online, new_info->to_keep)) { + game_mode_free_cpu(&new_info); + LOG_MSG("cpu L3 cache is uniform, will not apply cpu core parking!\n"); + goto error_exit; + } + + if (CPU_COUNT_S(CPU_ALLOC_SIZE(new_info->num_cpu), new_info->to_keep) == 0) { + game_mode_free_cpu(&new_info); + LOG_MSG("logic or config wanted to park/unpin every single cpu core, will not apply cpu core parking/pinning!\n"); + goto error_exit; + } + + /* Give back the new cpu info */ + *info = new_info; + +early_exit: + free (buf); + free (buf2); + return 0; + +error_exit: + free (buf); + free (buf2); + return -1; +} + +static int log_state (char *cpulist, int *pos, const long first, const long last) +{ + int ret; + if (*pos != 0) { + ret = snprintf(cpulist+*pos, ARG_MAX - (size_t)*pos, ","); + + if (ret < 0 || (size_t)ret >= (ARG_MAX - (size_t)*pos)) { + LOG_ERROR("snprintf failed, will not apply cpu core parking!\n"); + return 0; + } + + *pos += ret; + } + + if (first == last) + ret = snprintf(cpulist+*pos, ARG_MAX - (size_t)*pos, "%ld", first); + else + ret = snprintf(cpulist+*pos, ARG_MAX - (size_t)*pos, "%ld-%ld", first,last); + + if (ret < 0 || (size_t)ret >= (ARG_MAX - (size_t)*pos)) { + LOG_ERROR("snprintf failed, will not apply cpu core parking!\n"); + return 0; + } + + *pos += ret; + return 1; +} + +/** + * Park the unwanted cpu cores when gamemode is active + */ +int game_mode_park_cpu(const GameModeCPUInfo *info) +{ + if (!info || info->park_or_pin == 1) + return 0; + + long first = -1, last = -1; + + char cpulist[ARG_MAX]; + int pos = 0; + + for (long cpu = 0; cpu < (long)(info->num_cpu); cpu++) { + if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online) && !CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep)) { + if (first == -1) { + first = cpu; + last = cpu; + } else if (last + 1 == cpu) { + last = cpu; + } else { + if (!log_state (cpulist, &pos, first, last)) + return 0; + + first = cpu; + last = cpu; + } + } + } + + if (first != -1) + log_state (cpulist, &pos, first, last); + + const char *const exec_args[] = { + "pkexec", LIBEXECDIR "/cpucorectl", "offline", cpulist, NULL, + }; + + LOG_MSG("Requesting parking of cores %s\n", cpulist); + int ret = run_external_process(exec_args, NULL, -1); + if (ret != 0) { + LOG_ERROR("Failed to park cpu cores\n"); + return ret; + } + + return 0; +} + +/** + * Restore the parked cpu cores when gamemode is disabled + */ +int game_mode_unpark_cpu(const GameModeCPUInfo *info) +{ + if (!info || info->park_or_pin == 1) + return 0; + + long first = -1, last = -1; + + char cpulist[ARG_MAX]; + int pos = 0; + + for (long cpu = 0; cpu < (long)(info->num_cpu); cpu++) { + if (CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->online) && !CPU_ISSET_S((size_t)cpu, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep)) { + if (first == -1) { + first = cpu; + last = cpu; + } else if (last + 1 == cpu) { + last = cpu; + } else { + if (!log_state (cpulist, &pos, first, last)) + return 0; + + first = cpu; + last = cpu; + } + } + } + + if (first != -1) + log_state (cpulist, &pos, first, last); + + const char *const exec_args[] = { + "pkexec", LIBEXECDIR "/cpucorectl", "online", cpulist, NULL, + }; + + LOG_MSG("Requesting unparking of cores %s\n", cpulist); + int ret = run_external_process(exec_args, NULL, -1); + if (ret != 0) { + LOG_ERROR("Failed to unpark cpu cores\n"); + return ret; + } + + return 0; +} + +void game_mode_apply_core_pinning(const GameModeCPUInfo *info, const pid_t client) +{ + if (!info || info->park_or_pin == 0) + return; + + if (sched_setaffinity(client, CPU_ALLOC_SIZE(info->num_cpu), info->to_keep) != 0) + LOG_ERROR("Failed to pin process: %s\n", strerror(errno)); +} + +/* Simply used to free the CPU info object */ +void game_mode_free_cpu(GameModeCPUInfo **info) +{ + if (!(*info)) { + CPU_FREE((*info)->online); + (*info)->online = NULL; + + CPU_FREE((*info)->to_keep); + (*info)->to_keep = NULL; + + free(*info); + *info = NULL; + } +} +