]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
Add uclampset schedutil
authorQais Yousef <qais.yousef@arm.com>
Tue, 13 Oct 2020 15:03:44 +0000 (16:03 +0100)
committerQais Yousef <qais.yousef@arm.com>
Tue, 2 Feb 2021 10:56:26 +0000 (10:56 +0000)
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@arm.com>
.gitignore
include/pathnames.h
schedutils/sched_attr.h
schedutils/uclampset.c [new file with mode: 0644]

index a6f3791465ffa75a9f2e8ad16acfa32d7d592a6b..c01d2f644a29454a074f86db45768513a992304e 100644 (file)
@@ -180,3 +180,4 @@ ylwrap
 /wipefs
 /write
 /zramctl
+/uclampset
index 8f62337493105cce87b7ee022e881567d4aed422..69a0b55249773ca20f2207dfcab98f0acc2b4076 100644 (file)
 #define _PATH_DEV_RFKILL       "/dev/rfkill"
 #define _PATH_SYS_RFKILL       "/sys/class/rfkill"
 
+#define _PATH_PROC_KERNEL(file)        "/proc/sys/kernel" #file
+#define _PATH_PROC_UCLAMP_MIN  _PATH_PROC_KERNEL(/sched_util_clamp_min)
+#define _PATH_PROC_UCLAMP_MAX  _PATH_PROC_KERNEL(/sched_util_clamp_max)
+
 #endif /* PATHNAMES_H */
index b39d37b5d56d32dc09a97268d55490f5612cf57d..39d8532a358576a81c016b3c4ae3078035c2eadc 100644 (file)
@@ -13,6 +13,8 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
  * Copyright (C) 2004 Robert Love
+ * Copyright (C) 2020-2021 Qais Yousef
+ * Copyright (C) 2020-2021 Arm Ltd
  */
 #ifndef UTIL_LINUX_SCHED_ATTR_H
 #define UTIL_LINUX_SCHED_ATTR_H
 # 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 (file)
index 0000000..846a4c5
--- /dev/null
@@ -0,0 +1,337 @@
+/*
+ * 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-2021 Qais Yousef
+ * Copyright (C) 2020-2021 Arm Ltd
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "closestream.h"
+#include "path.h"
+#include "pathnames.h"
+#include "procutils.h"
+#include "sched_attr.h"
+#include "strutils.h"
+
+#define NOT_SET                -2U
+
+struct uclampset {
+       unsigned int util_min;
+       unsigned int util_max;
+
+       pid_t pid;
+       unsigned int    all_tasks:1,            /* all threads of the PID */
+                       system:1,
+                       util_min_set:1,         /* indicates -m option was passed */
+                       util_max_set:1,         /* indicates -M option was passed */
+                       reset_on_fork:1,
+                       verbose:1;
+       char *cmd;
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+       FILE *out = stdout;
+
+       fprintf(out,
+               _(" %1$s [options]\n"
+                 " %1$s [options] --pid <pid> | --system | <command> <arg>...\n"),
+               program_invocation_short_name);
+
+       fputs(USAGE_SEPARATOR, out);
+       fputs(_("Show or change the utilization clamping attributes of a process or the system.\n"), out);
+       fputs(_("Utilization range: [0:1024]\n"), out);
+       fputs(_("Use special -1 value to reset to system's default\n"), out);
+
+       fputs(USAGE_OPTIONS, 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(_(" -R, --reset-on-fork  set reset-on-fork flag\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 show_uclamp_pid_info(pid_t pid, char *cmd)
+{
+       struct sched_attr sa;
+       char *comm;
+
+       /* don't display "pid 0" as that is confusing */
+       if (!pid)
+               pid = getpid();
+
+       if (sched_getattr(pid, &sa, sizeof(sa), 0) != 0)
+               err(EXIT_FAILURE, _("failed to get pid %d's uclamp values"), pid);
+
+       if (cmd)
+               comm = cmd;
+       else
+               comm = proc_get_command_name(pid);
+
+       printf(_("%s (%d) util_clamp: min: %d max: %d\n"),
+              comm ? : "uknown", pid, sa.sched_util_min, sa.sched_util_max);
+
+       if (!cmd)
+               free(comm);
+}
+
+static unsigned int read_uclamp_sysfs(char *filename)
+{
+       unsigned int val;
+
+       if (ul_path_read_u32(NULL, &val, filename) != 0)
+               err(EXIT_FAILURE, _("cannot read %s"), filename);
+
+       return val;
+}
+
+static void write_uclamp_sysfs(char *filename, unsigned int val)
+{
+       if (ul_path_write_u64(NULL, val, filename) != 0)
+               err(EXIT_FAILURE, _("cannot write %s"), filename);
+}
+
+static void show_uclamp_system_info(void)
+{
+       unsigned int min, max;
+
+       min = read_uclamp_sysfs(_PATH_PROC_UCLAMP_MIN);
+       max = read_uclamp_sysfs(_PATH_PROC_UCLAMP_MAX);
+
+       printf(_("System util_clamp: min: %u 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, NULL);
+
+               proc_close_tasks(ts);
+       } else {
+               show_uclamp_pid_info(ctl->pid, ctl->cmd);
+       }
+}
+
+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_set)
+               sa.sched_util_min = ctl->util_min;
+       if (ctl->util_max_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;
+
+       if (ctl->reset_on_fork)
+               sa.sched_flags |= SCHED_FLAG_RESET_ON_FORK;
+
+       return sched_setattr(pid, &sa, 0);
+}
+
+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_set)
+               ctl->util_min = read_uclamp_sysfs(_PATH_PROC_UCLAMP_MIN);
+
+       if (!ctl->util_max_set)
+               ctl->util_max = read_uclamp_sysfs(_PATH_PROC_UCLAMP_MAX);
+
+       if (ctl->util_min > ctl->util_max) {
+               errno = EINVAL;
+               err(EXIT_FAILURE, _("util_min must be <= util_max"));
+       }
+
+       write_uclamp_sysfs(_PATH_PROC_UCLAMP_MIN, ctl->util_min);
+       write_uclamp_sysfs(_PATH_PROC_UCLAMP_MAX, ctl->util_max);
+}
+
+static void validate_util(int val)
+{
+       if (val > 1024 || val < -1) {
+               errno = EINVAL;
+               err(EXIT_FAILURE, _("%d out of range"), val);
+       }
+}
+
+int main(int argc, char **argv)
+{
+       struct uclampset _ctl = {
+               .pid = -1,
+               .util_min = NOT_SET,
+               .util_max = NOT_SET,
+               .cmd = NULL
+       };
+       struct uclampset *ctl = &_ctl;
+       int c;
+
+       static const struct option longopts[] = {
+               { "all-tasks",          no_argument, NULL, 'a' },
+               { "pid",                required_argument, NULL, 'p' },
+               { "system",             no_argument, NULL, 's' },
+               { "reset-on-fork",      no_argument, NULL, 'R' },
+               { "help",               no_argument, NULL, 'h' },
+               { "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, "+asRp:hm:M:vV", longopts, NULL)) != -1)
+       {
+               switch (c) {
+               case 'a':
+                       ctl->all_tasks = 1;
+                       break;
+               case 'p':
+                       errno = 0;
+                       ctl->pid = strtos32_or_err(optarg, _("invalid PID argument"));
+                       optind++;
+                       break;
+               case 's':
+                       ctl->system = 1;
+                       break;
+               case 'R':
+                       ctl->reset_on_fork = 1;
+                       break;
+               case 'v':
+                       ctl->verbose = 1;
+                       break;
+               case 'm':
+                       ctl->util_min = strtos32_or_err(optarg, _("invalid util_min argument"));
+                       ctl->util_min_set = 1;
+                       validate_util(ctl->util_min);
+                       optind++;
+                       break;
+               case 'M':
+                       ctl->util_max = strtos32_or_err(optarg, _("invalid util_max argument"));
+                       ctl->util_max_set = 1;
+                       validate_util(ctl->util_max);
+                       optind++;
+                       break;
+               case 'V':
+                       print_version(EXIT_SUCCESS);
+                       /* fallthrough */
+               case 'h':
+                       usage();
+               default:
+                       errtryhelp(EXIT_FAILURE);
+               }
+       }
+
+       if (argc == 1) {
+               usage();
+               exit(EXIT_FAILURE);
+       }
+
+       /* all_tasks implies --pid */
+       if (ctl->all_tasks && ctl->pid == -1) {
+               errno = EINVAL;
+               err(EXIT_FAILURE, _("missing -p option"));
+       }
+
+       if (!ctl->util_min_set && !ctl->util_max_set) {
+               /* -p or -s must be passed */
+               if (!ctl->system && ctl->pid == -1) {
+                       usage();
+                       exit(EXIT_FAILURE);
+               }
+
+               show_uclamp_info(ctl);
+               return EXIT_SUCCESS;
+       }
+
+       /* ensure there's a command to execute if no -s or -p */
+       if (!ctl->system && ctl->pid == -1) {
+               if (argc <= optind) {
+                       errno = EINVAL;
+                       err(EXIT_FAILURE, _("no cmd to execute"));
+               }
+
+               argv += optind;
+               ctl->cmd = argv[0];
+       }
+
+       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->cmd) {
+               execvp(ctl->cmd, argv);
+               errexec(ctl->cmd);
+       }
+
+       return EXIT_SUCCESS;
+}