[PATCH 2/5] Add uclampset schedutil

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Utilization clamping is a new kernel feature that got merged in 5.3. It
allows controlling the performance of a process by manipulating the
utilization such that the task appears bigger or smaller than what it
really is.

There's a system-wide control to to restrict what maximum values the
process are allowed to use.

Man page added in a later patch attempts to explain the usage in more
detail.

Signed-off-by: Qais Yousef <qais.yousef@xxxxxxx>
---
 .gitignore             |   1 +
 include/sched_attr.h   |  30 ++++
 schedutils/uclampset.c | 378 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 409 insertions(+)
 create mode 100644 schedutils/uclampset.c

diff --git a/.gitignore b/.gitignore
index a6f379146..c01d2f644 100644
--- a/.gitignore
+++ b/.gitignore
@@ -180,3 +180,4 @@ ylwrap
 /wipefs
 /write
 /zramctl
+/uclampset
diff --git a/include/sched_attr.h b/include/sched_attr.h
index b39d37b5d..e8f8173ec 100644
--- a/include/sched_attr.h
+++ b/include/sched_attr.h
@@ -13,6 +13,8 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2004 Robert Love
+ * Copyright (C) 2020 Qais Yousef
+ * Copyright (C) 2020 Arm Ltd
  */
 #ifndef UTIL_LINUX_SCHED_ATTR_H
 #define UTIL_LINUX_SCHED_ATTR_H
@@ -42,6 +44,30 @@
 # define SCHED_FLAG_RESET_ON_FORK 0x01
 #endif
 
+#if defined(__linux__) && !defined(SCHED_FLAG_RECLAIM)
+# define SCHED_FLAG_RECLAIM 0x02
+#endif
+
+#if defined(__linux__) && !defined(SCHED_FLAG_DL_OVERRUN)
+# define SCHED_FLAG_DL_OVERRUN 0x04
+#endif
+
+#if defined(__linux__) && !defined(SCHED_FLAG_KEEP_POLICY)
+# define SCHED_FLAG_KEEP_POLICY 0x08
+#endif
+
+#if defined(__linux__) && !defined(SCHED_FLAG_KEEP_PARAMS)
+# define SCHED_FLAG_KEEP_PARAMS 0x10
+#endif
+
+#if defined(__linux__) && !defined(SCHED_FLAG_UTIL_CLAMP_MIN)
+# define SCHED_FLAG_UTIL_CLAMP_MIN 0x20
+#endif
+
+#if defined(__linux__) && !defined(SCHED_FLAG_UTIL_CLAMP_MAX)
+# define SCHED_FLAG_UTIL_CLAMP_MAX 0x40
+#endif
+
 #if defined (__linux__)
 # include <sys/syscall.h>
 #endif
@@ -73,6 +99,10 @@ struct sched_attr {
 	uint64_t sched_runtime;
 	uint64_t sched_deadline;
 	uint64_t sched_period;
+
+	/* UTILIZATION CLAMPING */
+	uint32_t sched_util_min;
+	uint32_t sched_util_max;
 };
 
 static int sched_setattr(pid_t pid, const struct sched_attr *attr, unsigned int flags)
diff --git a/schedutils/uclampset.c b/schedutils/uclampset.c
new file mode 100644
index 000000000..d7d5619ad
--- /dev/null
+++ b/schedutils/uclampset.c
@@ -0,0 +1,378 @@
+/*
+ * uclampset.c - change utilization clamping attributes of a task or the system
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2, as
+ * published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2020 Qais Yousef
+ * Copyright (C) 2020 Arm Ltd
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "closestream.h"
+#include "procutils.h"
+#include "sched_attr.h"
+#include "strutils.h"
+
+#define PROCFS(file)	"/proc/sys/kernel/" #file
+
+#define PROCFS_UCLAMP_MIN	PROCFS(sched_util_clamp_min)
+#define PROCFS_UCLAMP_MAX	PROCFS(sched_util_clamp_max)
+
+#define MAX_OPT		1000
+#define COMM_LEN	64
+#define NOT_SET		-1U
+
+struct uclampset {
+	unsigned int util_min;
+	unsigned int util_max;
+
+	pid_t pid;
+	unsigned int all_tasks;			/* all threads of the PID */
+
+	unsigned int system;
+
+	unsigned int verbose;
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+	FILE *out = stdout;
+
+	fputs(_("Show or change the utilization clamping attributes of a process or the system.\n"), out);
+	fputs(USAGE_SEPARATOR, out);
+	fputs(_("Set util clamp for a process:\n"
+	" uclampset [options] [-m <util_min>] [-M <util_max>] [cmd <arg>...]\n"
+	" uclampset [options] [-m <util_min>] [-M <util_max>] --pid <pid>\n"), out);
+	fputs(USAGE_SEPARATOR, out);
+	fputs(_("Get util clamp for a process:\n"
+	" uclampset [options] -p <pid>\n"), out);
+
+	fputs(USAGE_SEPARATOR, out);
+	fputs(_("Set util clamp for the sytem:\n"
+	" uclampset [options] --system [-m <util_min>] [-M <util_max>]\n"), out);
+	fputs(USAGE_SEPARATOR, out);
+	fputs(_("Get util clamp for the system:\n"
+	" uclampset [options] -s\n"), out);
+
+	fputs(USAGE_SEPARATOR, out);
+	fputs(_("Other options:\n"), out);
+	fputs(_(" -m                   util_min value to set\n"), out);
+	fputs(_(" -M                   util_max value to set\n"), out);
+	fputs(_(" -a, --all-tasks      operate on all the tasks (threads) for a given pid\n"), out);
+	fputs(_(" -p, --pid            operate on existing given pid\n"), out);
+	fputs(_(" -s, --system         operate on system\n"), out);
+	fputs(_(" --max                show min and max valid uclamp values\n"), out);
+	fputs(_(" -v, --verbose        display status information\n"), out);
+
+	fputs(USAGE_SEPARATOR, out);
+	printf(USAGE_HELP_OPTIONS(22));
+
+	printf(USAGE_MAN_TAIL("uclampset(1)"));
+	exit(EXIT_SUCCESS);
+}
+
+static void proc_pid_name(pid_t pid, char *name, int len)
+{
+	char *proc_comm_fmt = "/proc/%d/comm";
+	char proc_comm[COMM_LEN];
+	FILE *fp;
+	int size;
+
+	size = snprintf(proc_comm, COMM_LEN, proc_comm_fmt, pid);
+	if (size >= COMM_LEN || size < 0)
+		goto error;
+
+	fp = fopen(proc_comm, "r");
+	if (!fp)
+		goto error;
+
+	size = fread(name, 1, len, fp);
+	name[size-1] = '\0';
+
+	fclose(fp);
+
+	if (ferror(fp))
+		goto error;
+
+	return;
+error:
+	strncpy(name, "unknown", len);
+}
+
+static void show_uclamp_pid_info(pid_t pid)
+{
+#ifdef HAVE_SCHED_SETATTR
+	struct sched_attr sa;
+	char comm[COMM_LEN];
+
+	/* don't display "pid 0" as that is confusing */
+	if (!pid)
+		pid = getpid();
+
+
+	proc_pid_name(pid, comm, COMM_LEN);
+
+	if (sched_getattr(pid, &sa, sizeof(sa), 0) != 0)
+		err(EXIT_FAILURE, _("failed to get pid %d's uclamp values"), pid);
+
+	printf(_("%s-%d\n\tutil_min: %d\n\tutil_max: %d\n"),
+		  comm, pid, sa.sched_util_min, sa.sched_util_max);
+#else
+	err(EXIT_FAILURE, _("uclamp is not supported on this system"));
+#endif
+}
+
+static unsigned int read_uclamp_sysfs(char *dir)
+{
+	unsigned int size;
+	char buf[16];
+	FILE *fp;
+
+	fp = fopen(dir, "r");
+	if (!fp)
+		err(EXIT_FAILURE, _("cannot open %s"), dir);
+
+	size = fread(buf, 1, sizeof(buf), fp);
+	buf[size-1] = '\0';
+
+	if (ferror(fp)) {
+		fclose(fp);
+		err(EXIT_FAILURE, _("error writing %s"), dir);
+	}
+
+	fclose(fp);
+
+	return strtou32_or_err(buf, _("invalid util clamp value"));
+}
+
+static void write_uclamp_sysfs(char *dir, unsigned int val)
+{
+	unsigned int size;
+	char buf[16];
+	FILE *fp;
+
+	fp = fopen(dir, "w");
+	if (!fp)
+		err(EXIT_FAILURE, _("cannot open %s"), dir);
+
+	size = snprintf(buf, sizeof(buf), "%d", val);
+	buf[size] = '\n';
+	buf[size+1] = '\0';
+	fwrite(buf, 1, sizeof(buf), fp);
+
+	if (ferror(fp)) {
+		fclose(fp);
+		err(EXIT_FAILURE, _("error writing %s"), dir);
+	}
+
+	fclose(fp);
+}
+
+static void show_uclamp_system_info(void)
+{
+	unsigned int min, max;
+
+	min = read_uclamp_sysfs(PROCFS_UCLAMP_MIN);
+	max = read_uclamp_sysfs(PROCFS_UCLAMP_MAX);
+
+	printf(_("System\n\tutil_min: %u\n\tutil_max: %u\n"), min, max);
+}
+
+static void show_uclamp_info(struct uclampset *ctl)
+{
+	if (ctl->system) {
+		show_uclamp_system_info();
+	} else if (ctl->all_tasks) {
+		pid_t tid;
+		struct proc_tasks *ts = proc_open_tasks(ctl->pid);
+
+		if (!ts)
+			err(EXIT_FAILURE, _("cannot obtain the list of tasks"));
+
+		while (!proc_next_tid(ts, &tid))
+			show_uclamp_pid_info(tid);
+
+		proc_close_tasks(ts);
+	} else {
+		show_uclamp_pid_info(ctl->pid);
+	}
+}
+
+static void show_min_max(void)
+{
+	printf(_("util_min and util_max must be in the range of [0:1024] inclusive\n"));
+}
+
+#ifndef HAVE_SCHED_SETATTR
+static int set_uclamp_one(struct uclampset *ctl, pid_t pid)
+{
+	err(EXIT_FAILURE, _("uclamp is not supported on this system"));
+}
+
+#else /* !HAVE_SCHED_SETATTR */
+static int set_uclamp_one(struct uclampset *ctl, pid_t pid)
+{
+	struct sched_attr sa;
+
+	if (sched_getattr(pid, &sa, sizeof(sa), 0) != 0)
+		err(EXIT_FAILURE, _("failed to get pid %d's uclamp values"), pid);
+
+	if (ctl->util_min != NOT_SET)
+		sa.sched_util_min = ctl->util_min;
+	if (ctl->util_max != NOT_SET)
+		sa.sched_util_max = ctl->util_max;
+
+	sa.sched_flags = SCHED_FLAG_KEEP_POLICY |
+			 SCHED_FLAG_KEEP_PARAMS |
+			 SCHED_FLAG_UTIL_CLAMP_MIN |
+			 SCHED_FLAG_UTIL_CLAMP_MAX;
+
+	return sched_setattr(pid, &sa, 0);
+}
+#endif /* HAVE_SCHED_SETATTR */
+
+static void set_uclamp_pid(struct uclampset *ctl)
+{
+	if (ctl->all_tasks) {
+		pid_t tid;
+		struct proc_tasks *ts = proc_open_tasks(ctl->pid);
+
+		if (!ts)
+			err(EXIT_FAILURE, _("cannot obtain the list of tasks"));
+
+		while (!proc_next_tid(ts, &tid))
+			if (set_uclamp_one(ctl, tid) == -1)
+				err(EXIT_FAILURE, _("failed to set tid %d's uclamp values"), tid);
+
+		proc_close_tasks(ts);
+
+	} else if (set_uclamp_one(ctl, ctl->pid) == -1) {
+		err(EXIT_FAILURE, _("failed to set pid %d's uclamp values"), ctl->pid);
+	}
+}
+
+static void set_uclamp_system(struct uclampset *ctl)
+{
+	if (ctl->util_min == NOT_SET)
+		ctl->util_min = read_uclamp_sysfs(PROCFS_UCLAMP_MIN);
+
+	if (ctl->util_max == NOT_SET)
+		ctl->util_max = read_uclamp_sysfs(PROCFS_UCLAMP_MAX);
+
+	if (ctl->util_min > ctl->util_max) {
+		errno = EINVAL;
+		err(EXIT_FAILURE, _("util_min must be <= util_max"));
+	}
+
+	write_uclamp_sysfs(PROCFS_UCLAMP_MIN, ctl->util_min);
+	write_uclamp_sysfs(PROCFS_UCLAMP_MAX, ctl->util_max);
+}
+
+int main(int argc, char **argv)
+{
+	struct uclampset _ctl = {
+		.pid = -1,
+		.util_min = NOT_SET,
+		.util_max = NOT_SET
+	};
+	struct uclampset *ctl = &_ctl;
+	bool no_input;
+	int c;
+
+	static const struct option longopts[] = {
+		{ "all-tasks",  no_argument, NULL, 'a' },
+		{ "pid",	no_argument, NULL, 'p' },
+		{ "system",	no_argument, NULL, 's' },
+		{ "help",	no_argument, NULL, 'h' },
+		{ "max",        no_argument, NULL, MAX_OPT },
+		{ "verbose",	no_argument, NULL, 'v' },
+		{ "version",	no_argument, NULL, 'V' },
+		{ NULL,		no_argument, NULL, 0 }
+	};
+
+	setlocale(LC_ALL, "");
+	bindtextdomain(PACKAGE, LOCALEDIR);
+	textdomain(PACKAGE);
+	close_stdout_atexit();
+
+	while((c = getopt_long(argc, argv, "+asphmMvV", longopts, NULL)) != -1)
+	{
+		switch (c) {
+		case 'a':
+			ctl->all_tasks = 1;
+			break;
+		case MAX_OPT:
+			show_min_max();
+			return EXIT_SUCCESS;
+		case 'p':
+			errno = 0;
+			ctl->pid = strtos32_or_err(argv[optind], _("invalid PID argument"));
+			optind++;
+			break;
+		case 's':
+			ctl->system = 1;
+			break;
+		case 'v':
+			ctl->verbose = 1;
+			break;
+		case 'm':
+			ctl->util_min = strtos32_or_err(argv[optind], _("invalid util_min argument"));
+			optind++;
+			break;
+		case 'M':
+			ctl->util_max = strtos32_or_err(argv[optind], _("invalid util_max argument"));
+			optind++;
+			break;
+		case 'V':
+			print_version(EXIT_SUCCESS);
+			/* fallthrough */
+		case 'h':
+			usage();
+		default:
+			errtryhelp(EXIT_FAILURE);
+		}
+	}
+
+	no_input = ctl->util_min == NOT_SET && ctl->util_max == NOT_SET;
+
+	if (no_input) {
+		show_uclamp_info(ctl);
+		return EXIT_SUCCESS;
+	}
+
+	if (ctl->pid == -1)
+		ctl->pid = 0;
+
+	if (ctl->system)
+		set_uclamp_system(ctl);
+	else
+		set_uclamp_pid(ctl);
+
+	if (ctl->verbose)
+		show_uclamp_info(ctl);
+
+	if (!ctl->pid && !ctl->system) {
+		argv += optind;
+		execvp(argv[0], argv);
+		errexec(argv[0]);
+	}
+
+	return EXIT_SUCCESS;
+}
-- 
2.25.1




[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux