Browse Source

Added gamemode-cpu.c

Added gamemode-cpu.c which contains the functions for cpu core parking and pinning
Henrik Holst 1 year ago
parent
commit
495a659895
1 changed files with 409 additions and 0 deletions
  1. 409 0
      daemon/gamemode-cpu.c

+ 409 - 0
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 <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;
+	}
+}
+