mirror of
https://github.com/FeralInteractive/gamemode.git
synced 2025-06-04 22:57:21 +02:00
Added gamemode-cpu.c
Added gamemode-cpu.c which contains the functions for cpu core parking and pinning
This commit is contained in:
parent
2dbd565340
commit
495a659895
409
daemon/gamemode-cpu.c
Normal file
409
daemon/gamemode-cpu.c
Normal file
@ -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 <sched.h>
|
||||
#include <linux/limits.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user