From 378c18e5f024ac5a8aef4cb40d7c9aa9633d144c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 16:30:35 +0200 Subject: Adding upstream version 2.38.1. Signed-off-by: Daniel Baumann --- schedutils/Makemodule.am | 31 +++ schedutils/chrt.1 | 192 +++++++++++++++++ schedutils/chrt.1.adoc | 153 ++++++++++++++ schedutils/chrt.c | 493 ++++++++++++++++++++++++++++++++++++++++++++ schedutils/ionice.1 | 165 +++++++++++++++ schedutils/ionice.1.adoc | 101 +++++++++ schedutils/ionice.c | 269 ++++++++++++++++++++++++ schedutils/sched_attr.h | 127 ++++++++++++ schedutils/taskset.1 | 141 +++++++++++++ schedutils/taskset.1.adoc | 125 +++++++++++ schedutils/taskset.c | 254 +++++++++++++++++++++++ schedutils/uclampset.1 | 154 ++++++++++++++ schedutils/uclampset.1.adoc | 141 +++++++++++++ schedutils/uclampset.c | 338 ++++++++++++++++++++++++++++++ 14 files changed, 2684 insertions(+) create mode 100644 schedutils/Makemodule.am create mode 100644 schedutils/chrt.1 create mode 100644 schedutils/chrt.1.adoc create mode 100644 schedutils/chrt.c create mode 100644 schedutils/ionice.1 create mode 100644 schedutils/ionice.1.adoc create mode 100644 schedutils/ionice.c create mode 100644 schedutils/sched_attr.h create mode 100644 schedutils/taskset.1 create mode 100644 schedutils/taskset.1.adoc create mode 100644 schedutils/taskset.c create mode 100644 schedutils/uclampset.1 create mode 100644 schedutils/uclampset.1.adoc create mode 100644 schedutils/uclampset.c (limited to 'schedutils') diff --git a/schedutils/Makemodule.am b/schedutils/Makemodule.am new file mode 100644 index 0000000..1040da8 --- /dev/null +++ b/schedutils/Makemodule.am @@ -0,0 +1,31 @@ +if BUILD_CHRT +usrbin_exec_PROGRAMS += chrt +MANPAGES += schedutils/chrt.1 +dist_noinst_DATA += schedutils/chrt.1.adoc +chrt_SOURCES = schedutils/chrt.c schedutils/sched_attr.h +chrt_LDADD = $(LDADD) libcommon.la +endif + +if BUILD_IONICE +usrbin_exec_PROGRAMS += ionice +MANPAGES += schedutils/ionice.1 +dist_noinst_DATA += schedutils/ionice.1.adoc +ionice_SOURCES = schedutils/ionice.c +ionice_LDADD = $(LDADD) libcommon.la +endif + +if BUILD_TASKSET +usrbin_exec_PROGRAMS += taskset +MANPAGES += schedutils/taskset.1 +dist_noinst_DATA += schedutils/taskset.1.adoc +taskset_SOURCES = schedutils/taskset.c +taskset_LDADD = $(LDADD) libcommon.la +endif + +if BUILD_UCLAMPSET +usrbin_exec_PROGRAMS += uclampset +MANPAGES += schedutils/uclampset.1 +dist_noinst_DATA += schedutils/uclampset.1.adoc +uclampset_SOURCES = schedutils/uclampset.c schedutils/sched_attr.h +uclampset_LDADD = $(LDADD) libcommon.la +endif diff --git a/schedutils/chrt.1 b/schedutils/chrt.1 new file mode 100644 index 0000000..e9bb863 --- /dev/null +++ b/schedutils/chrt.1 @@ -0,0 +1,192 @@ +'\" t +.\" Title: chrt +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "CHRT" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +chrt \- manipulate the real\-time attributes of a process +.SH "SYNOPSIS" +.sp +\fBchrt\fP [options] \fIpriority command argument\fP ... +.sp +\fBchrt\fP [options] \fB\-p\fP [\fIpriority\fP] \fIPID\fP +.SH "DESCRIPTION" +.sp +\fBchrt\fP sets or retrieves the real\-time scheduling attributes of an existing \fIPID\fP, or runs \fIcommand\fP with the given attributes. +.SH "POLICIES" +.sp +\fB\-o\fP, \fB\-\-other\fP +.RS 4 +Set scheduling policy to \fBSCHED_OTHER\fP (time\-sharing scheduling). This is the default Linux scheduling policy. +.RE +.sp +\fB\-f\fP, \fB\-\-fifo\fP +.RS 4 +Set scheduling policy to \fBSCHED_FIFO\fP (first in\-first out). +.RE +.sp +\fB\-r\fP, \fB\-\-rr\fP +.RS 4 +Set scheduling policy to \fBSCHED_RR\fP (round\-robin scheduling). When no policy is defined, the \fBSCHED_RR\fP is used as the default. +.RE +.sp +\fB\-b\fP, \fB\-\-batch\fP +.RS 4 +Set scheduling policy to \fBSCHED_BATCH\fP (scheduling batch processes). Linux\-specific, supported since 2.6.16. The priority argument has to be set to zero. +.RE +.sp +\fB\-i\fP, \fB\-\-idle\fP +.RS 4 +Set scheduling policy to \fBSCHED_IDLE\fP (scheduling very low priority jobs). Linux\-specific, supported since 2.6.23. The priority argument has to be set to zero. +.RE +.sp +\fB\-d\fP, \fB\-\-deadline\fP +.RS 4 +Set scheduling policy to \fBSCHED_DEADLINE\fP (sporadic task model deadline scheduling). Linux\-specific, supported since 3.14. The priority argument has to be set to zero. See also \fB\-\-sched\-runtime\fP, \fB\-\-sched\-deadline\fP and \fB\-\-sched\-period\fP. The relation between the options required by the kernel is runtime \(lA deadline \(lA period. \fBchrt\fP copies \fIperiod\fP to \fIdeadline\fP if \fB\-\-sched\-deadline\fP is not specified and \fIdeadline\fP to \fIruntime\fP if \fB\-\-sched\-runtime\fP is not specified. It means that at least \fB\-\-sched\-period\fP has to be specified. See \fBsched\fP(7) for more details. +.RE +.SH "SCHEDULING OPTIONS" +.sp +\fB\-T\fP, \fB\-\-sched\-runtime\fP \fInanoseconds\fP +.RS 4 +Specifies runtime parameter for \fBSCHED_DEADLINE\fP policy (Linux\-specific). +.RE +.sp +\fB\-P\fP, \fB\-\-sched\-period\fP \fInanoseconds\fP +.RS 4 +Specifies period parameter for \fBSCHED_DEADLINE\fP policy (Linux\-specific). +.RE +.sp +\fB\-D\fP, \fB\-\-sched\-deadline\fP \fInanoseconds\fP +.RS 4 +Specifies deadline parameter for \fBSCHED_DEADLINE\fP policy (Linux\-specific). +.RE +.sp +\fB\-R\fP, \fB\-\-reset\-on\-fork\fP +.RS 4 +Use \fBSCHED_RESET_ON_FORK\fP or \fBSCHED_FLAG_RESET_ON_FORK\fP flag. Linux\-specific, supported since 2.6.31. +.RE +.sp +Each thread has a \fIreset\-on\-fork\fP scheduling flag. When this flag is set, children created by \fBfork\fP(2) do not inherit privileged scheduling policies. After the \fIreset\-on\-fork\fP flag has been enabled, it can be reset only if the thread has the \fBCAP_SYS_NICE\fP capability. This flag is disabled in child processes created by \fBfork\fP(2). +.sp +More precisely, if the \fIreset\-on\-fork\fP flag is set, the following rules apply for subsequently created children: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +If the calling thread has a scheduling policy of \fBSCHED_FIFO\fP or \fBSCHED_RR\fP, the policy is reset to \fBSCHED_OTHER\fP in child processes. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +If the calling process has a negative nice value, the nice value is reset to zero in child processes. +.RE +.SH "OPTIONS" +.sp +\fB\-a\fP, \fB\-\-all\-tasks\fP +.RS 4 +Set or retrieve the scheduling attributes of all the tasks (threads) for a given PID. +.RE +.sp +\fB\-m\fP, \fB\-\-max\fP +.RS 4 +Show minimum and maximum valid priorities, then exit. +.RE +.sp +\fB\-p\fP, \fB\-\-pid\fP +.RS 4 +Operate on an existing PID and do not launch a new task. +.RE +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Show status information. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "USAGE" +.sp +The default behavior is to run a new command: +.RS 4 +\fBchrt\fP \fIpriority\fP \fIcommand\fP [\fIarguments\fP] +.RE +.sp +You can also retrieve the real\-time attributes of an existing task: +.RS 4 +\fBchrt \-p\fP \fIPID\fP +.RE +.sp +Or set them: +.RS 4 +\fBchrt \-r \-p\fP \fIpriority PID\fP +.RE +.SH "PERMISSIONS" +.sp +A user must possess \fBCAP_SYS_NICE\fP to change the scheduling attributes of a process. Any user can retrieve the scheduling information. +.SH "NOTES" +.sp +Only \fBSCHED_FIFO\fP, \fBSCHED_OTHER\fP and \fBSCHED_RR\fP are part of POSIX 1003.1b Process Scheduling. The other scheduling attributes may be ignored on some systems. +.sp +Linux\(aq default scheduling policy is \fBSCHED_OTHER\fP. +.SH "AUTHORS" +.sp +.MTO "rml\(attech9.net" "Robert Love" "," +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.SH "SEE ALSO" +.sp +\fBnice\fP(1), +\fBrenice\fP(1), +\fBtaskset\fP(1), +\fBsched\fP(7) +.sp +See \fBsched_setscheduler\fP(2) for a description of the Linux scheduling scheme. +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBchrt\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file diff --git a/schedutils/chrt.1.adoc b/schedutils/chrt.1.adoc new file mode 100644 index 0000000..6ccba7d --- /dev/null +++ b/schedutils/chrt.1.adoc @@ -0,0 +1,153 @@ +//po4a: entry man manual +//// +chrt(1) manpage + +Copyright (C) 2004 Robert Love +Copyright (C) 2015 Karel Zak + +This is free documentation; 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. + +The GNU General Public License's references to "object code" +and "executables" are to be interpreted as the output of any +document formatting or typesetting system, including +intermediate and printed output. + +This manual 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. +//// += chrt(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: chrt +:colon: : + +== NAME + +chrt - manipulate the real-time attributes of a process + +== SYNOPSIS + +*chrt* [options] _priority command argument_ ... + +*chrt* [options] *-p* [_priority_] _PID_ + +== DESCRIPTION + +*chrt* sets or retrieves the real-time scheduling attributes of an existing _PID_, or runs _command_ with the given attributes. + +== POLICIES + +*-o*, *--other*:: +Set scheduling policy to *SCHED_OTHER* (time-sharing scheduling). This is the default Linux scheduling policy. + +*-f*, *--fifo*:: +Set scheduling policy to *SCHED_FIFO* (first in-first out). + +*-r*, *--rr*:: +Set scheduling policy to *SCHED_RR* (round-robin scheduling). When no policy is defined, the *SCHED_RR* is used as the default. + +*-b*, *--batch*:: +Set scheduling policy to *SCHED_BATCH* (scheduling batch processes). Linux-specific, supported since 2.6.16. The priority argument has to be set to zero. + +*-i*, *--idle*:: +Set scheduling policy to *SCHED_IDLE* (scheduling very low priority jobs). Linux-specific, supported since 2.6.23. The priority argument has to be set to zero. + +*-d*, *--deadline*:: +Set scheduling policy to *SCHED_DEADLINE* (sporadic task model deadline scheduling). Linux-specific, supported since 3.14. The priority argument has to be set to zero. See also *--sched-runtime*, *--sched-deadline* and *--sched-period*. The relation between the options required by the kernel is runtime <= deadline <= period. *chrt* copies _period_ to _deadline_ if *--sched-deadline* is not specified and _deadline_ to _runtime_ if *--sched-runtime* is not specified. It means that at least *--sched-period* has to be specified. See *sched*(7) for more details. + +== SCHEDULING OPTIONS + +*-T*, *--sched-runtime* _nanoseconds_:: +Specifies runtime parameter for *SCHED_DEADLINE* policy (Linux-specific). + +*-P*, *--sched-period* _nanoseconds_:: +Specifies period parameter for *SCHED_DEADLINE* policy (Linux-specific). + +*-D*, *--sched-deadline* _nanoseconds_:: +Specifies deadline parameter for *SCHED_DEADLINE* policy (Linux-specific). + +*-R*, *--reset-on-fork*:: +Use *SCHED_RESET_ON_FORK* or *SCHED_FLAG_RESET_ON_FORK* flag. Linux-specific, supported since 2.6.31. + +Each thread has a _reset-on-fork_ scheduling flag. When this flag is set, children created by *fork*(2) do not inherit privileged scheduling policies. After the _reset-on-fork_ flag has been enabled, it can be reset only if the thread has the *CAP_SYS_NICE* capability. This flag is disabled in child processes created by *fork*(2). + +More precisely, if the _reset-on-fork_ flag is set, the following rules apply for subsequently created children: + +* If the calling thread has a scheduling policy of *SCHED_FIFO* or *SCHED_RR*, the policy is reset to *SCHED_OTHER* in child processes. + +* If the calling process has a negative nice value, the nice value is reset to zero in child processes. + +== OPTIONS + +*-a*, *--all-tasks*:: +Set or retrieve the scheduling attributes of all the tasks (threads) for a given PID. + +*-m*, *--max*:: +Show minimum and maximum valid priorities, then exit. + +*-p*, *--pid*:: +Operate on an existing PID and do not launch a new task. + +*-v*, *--verbose*:: +Show status information. + +include::man-common/help-version.adoc[] + +== USAGE + +//TRANSLATORS: Keep {colon} untranslated +The default behavior is to run a new command{colon}:: + +*chrt* _priority_ _command_ [_arguments_] + +//TRANSLATORS: Keep {colon} untranslated +You can also retrieve the real-time attributes of an existing task{colon}:: + +*chrt -p* _PID_ + +//TRANSLATORS: Keep {colon} untranslated +Or set them{colon}:: + +*chrt -r -p* _priority PID_ + +== PERMISSIONS + +A user must possess *CAP_SYS_NICE* to change the scheduling attributes of a process. Any user can retrieve the scheduling information. + +== NOTES + +Only *SCHED_FIFO*, *SCHED_OTHER* and *SCHED_RR* are part of POSIX 1003.1b Process Scheduling. The other scheduling attributes may be ignored on some systems. + +Linux' default scheduling policy is *SCHED_OTHER*. + +== AUTHORS + +mailto:rml@tech9.net[Robert Love], +mailto:kzak@redhat.com[Karel Zak] + +== SEE ALSO + +*nice*(1), +*renice*(1), +*taskset*(1), +*sched*(7) + +See *sched_setscheduler*(2) for a description of the Linux scheduling scheme. + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/schedutils/chrt.c b/schedutils/chrt.c new file mode 100644 index 0000000..250d878 --- /dev/null +++ b/schedutils/chrt.c @@ -0,0 +1,493 @@ +/* + * chrt.c - manipulate a task's real-time attributes + * + * 27-Apr-2002: initial version - Robert Love + * 04-May-2011: make it thread-aware - Davidlohr Bueso + * + * 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) 2004 Robert Love + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "c.h" +#include "nls.h" +#include "closestream.h" +#include "strutils.h" +#include "procfs.h" +#include "sched_attr.h" + + +/* control struct */ +struct chrt_ctl { + pid_t pid; + int policy; /* SCHED_* */ + int priority; + + uint64_t runtime; /* --sched-* options */ + uint64_t deadline; + uint64_t period; + + unsigned int all_tasks : 1, /* all threads of the PID */ + reset_on_fork : 1, /* SCHED_RESET_ON_FORK or SCHED_FLAG_RESET_ON_FORK */ + altered : 1, /* sched_set**() used */ + verbose : 1; /* verbose output */ +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + + fputs(_("Show or change the real-time scheduling attributes of a process.\n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(_("Set policy:\n" + " chrt [options] [...]\n" + " chrt [options] --pid \n"), out); + fputs(USAGE_SEPARATOR, out); + fputs(_("Get policy:\n" + " chrt [options] -p \n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Policy options:\n"), out); + fputs(_(" -b, --batch set policy to SCHED_BATCH\n"), out); + fputs(_(" -d, --deadline set policy to SCHED_DEADLINE\n"), out); + fputs(_(" -f, --fifo set policy to SCHED_FIFO\n"), out); + fputs(_(" -i, --idle set policy to SCHED_IDLE\n"), out); + fputs(_(" -o, --other set policy to SCHED_OTHER\n"), out); + fputs(_(" -r, --rr set policy to SCHED_RR (default)\n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Scheduling options:\n"), out); + fputs(_(" -R, --reset-on-fork set reset-on-fork flag\n"), out); + fputs(_(" -T, --sched-runtime runtime parameter for DEADLINE\n"), out); + fputs(_(" -P, --sched-period period parameter for DEADLINE\n"), out); + fputs(_(" -D, --sched-deadline deadline parameter for DEADLINE\n"), out); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Other options:\n"), out); + fputs(_(" -a, --all-tasks operate on all the tasks (threads) for a given pid\n"), out); + fputs(_(" -m, --max show min and max valid priorities\n"), out); + fputs(_(" -p, --pid operate on existing given pid\n"), out); + fputs(_(" -v, --verbose display status information\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(22)); + + printf(USAGE_MAN_TAIL("chrt(1)")); + exit(EXIT_SUCCESS); +} + +static const char *get_policy_name(int policy) +{ +#ifdef SCHED_RESET_ON_FORK + policy &= ~SCHED_RESET_ON_FORK; +#endif + switch (policy) { + case SCHED_OTHER: + return "SCHED_OTHER"; + case SCHED_FIFO: + return "SCHED_FIFO"; +#ifdef SCHED_IDLE + case SCHED_IDLE: + return "SCHED_IDLE"; +#endif + case SCHED_RR: + return "SCHED_RR"; +#ifdef SCHED_BATCH + case SCHED_BATCH: + return "SCHED_BATCH"; +#endif +#ifdef SCHED_DEADLINE + case SCHED_DEADLINE: + return "SCHED_DEADLINE"; +#endif + default: + break; + } + + return _("unknown"); +} + +static void show_sched_pid_info(struct chrt_ctl *ctl, pid_t pid) +{ + int policy = -1, reset_on_fork = 0, prio = 0; +#ifdef SCHED_DEADLINE + uint64_t deadline = 0, runtime = 0, period = 0; +#endif + + /* don't display "pid 0" as that is confusing */ + if (!pid) + pid = getpid(); + + errno = 0; + + /* + * New way + */ +#ifdef HAVE_SCHED_SETATTR + { + struct sched_attr sa; + + if (sched_getattr(pid, &sa, sizeof(sa), 0) != 0) { + if (errno == ENOSYS) + goto fallback; + err(EXIT_FAILURE, _("failed to get pid %d's policy"), pid); + } + + policy = sa.sched_policy; + prio = sa.sched_priority; + reset_on_fork = sa.sched_flags & SCHED_FLAG_RESET_ON_FORK; + deadline = sa.sched_deadline; + runtime = sa.sched_runtime; + period = sa.sched_period; + } + + /* + * Old way + */ +fallback: + if (errno == ENOSYS) +#endif + { + struct sched_param sp; + + policy = sched_getscheduler(pid); + if (policy == -1) + err(EXIT_FAILURE, _("failed to get pid %d's policy"), pid); + + if (sched_getparam(pid, &sp) != 0) + err(EXIT_FAILURE, _("failed to get pid %d's attributes"), pid); + else + prio = sp.sched_priority; +# ifdef SCHED_RESET_ON_FORK + if (policy & SCHED_RESET_ON_FORK) + reset_on_fork = 1; +# endif + } + + if (ctl->altered) + printf(_("pid %d's new scheduling policy: %s"), pid, get_policy_name(policy)); + else + printf(_("pid %d's current scheduling policy: %s"), pid, get_policy_name(policy)); + + if (reset_on_fork) + printf("|SCHED_RESET_ON_FORK"); + putchar('\n'); + + if (ctl->altered) + printf(_("pid %d's new scheduling priority: %d\n"), pid, prio); + else + printf(_("pid %d's current scheduling priority: %d\n"), pid, prio); + +#ifdef SCHED_DEADLINE + if (policy == SCHED_DEADLINE) { + if (ctl->altered) + printf(_("pid %d's new runtime/deadline/period parameters: %ju/%ju/%ju\n"), + pid, runtime, deadline, period); + else + printf(_("pid %d's current runtime/deadline/period parameters: %ju/%ju/%ju\n"), + pid, runtime, deadline, period); + } +#endif +} + + +static void show_sched_info(struct chrt_ctl *ctl) +{ + if (ctl->all_tasks) { +#ifdef __linux__ + DIR *sub = NULL; + pid_t tid; + struct path_cxt *pc = ul_new_procfs_path(ctl->pid, NULL); + + while (pc && procfs_process_next_tid(pc, &sub, &tid) == 0) + show_sched_pid_info(ctl, tid); + + ul_unref_path(pc); +#else + err(EXIT_FAILURE, _("cannot obtain the list of tasks")); +#endif + } else + show_sched_pid_info(ctl, ctl->pid); +} + +static void show_min_max(void) +{ + unsigned long i; + int policies[] = { + SCHED_OTHER, + SCHED_FIFO, + SCHED_RR, +#ifdef SCHED_BATCH + SCHED_BATCH, +#endif +#ifdef SCHED_IDLE + SCHED_IDLE, +#endif +#ifdef SCHED_DEADLINE + SCHED_DEADLINE, +#endif + }; + + for (i = 0; i < ARRAY_SIZE(policies); i++) { + int plc = policies[i]; + int max = sched_get_priority_max(plc); + int min = sched_get_priority_min(plc); + + if (max >= 0 && min >= 0) + printf(_("%s min/max priority\t: %d/%d\n"), + get_policy_name(plc), min, max); + else + printf(_("%s not supported?\n"), get_policy_name(plc)); + } +} + +static int set_sched_one_by_setscheduler(struct chrt_ctl *ctl, pid_t pid) +{ + struct sched_param sp = { .sched_priority = ctl->priority }; + int policy = ctl->policy; + + errno = 0; +# ifdef SCHED_RESET_ON_FORK + if (ctl->reset_on_fork) + policy |= SCHED_RESET_ON_FORK; +# endif + +#if defined (__linux__) && defined(SYS_sched_setscheduler) + /* musl libc returns ENOSYS for its sched_setscheduler library + * function, because the sched_setscheduler Linux kernel system call + * does not conform to Posix; so we use the system call directly + */ + return syscall(SYS_sched_setscheduler, pid, policy, &sp); +#else + return sched_setscheduler(pid, policy, &sp); +#endif +} + + +#ifndef HAVE_SCHED_SETATTR +static int set_sched_one(struct chrt_ctl *ctl, pid_t pid) +{ + return set_sched_one_by_setscheduler(ctl, pid); +} + +#else /* !HAVE_SCHED_SETATTR */ +static int set_sched_one(struct chrt_ctl *ctl, pid_t pid) +{ + struct sched_attr sa = { .size = sizeof(struct sched_attr) }; + + /* old API is good enough for non-deadline */ + if (ctl->policy != SCHED_DEADLINE) + return set_sched_one_by_setscheduler(ctl, pid); + + /* no changeed by chrt, follow the current setting */ + sa.sched_nice = getpriority(PRIO_PROCESS, pid); + + /* use main() to check if the setting makes sense */ + sa.sched_policy = ctl->policy; + sa.sched_priority = ctl->priority; + sa.sched_runtime = ctl->runtime; + sa.sched_period = ctl->period; + sa.sched_deadline = ctl->deadline; + +# ifdef SCHED_FLAG_RESET_ON_FORK + /* Don't use SCHED_RESET_ON_FORK for sched_setattr()! */ + if (ctl->reset_on_fork) + sa.sched_flags |= SCHED_FLAG_RESET_ON_FORK; +# endif + errno = 0; + return sched_setattr(pid, &sa, 0); +} +#endif /* HAVE_SCHED_SETATTR */ + +static void set_sched(struct chrt_ctl *ctl) +{ + if (ctl->all_tasks) { +#ifdef __linux__ + DIR *sub = NULL; + pid_t tid; + struct path_cxt *pc = ul_new_procfs_path(ctl->pid, NULL); + + if (!pc) + err(EXIT_FAILURE, _("cannot obtain the list of tasks")); + + while (procfs_process_next_tid(pc, &sub, &tid) == 0) { + if (set_sched_one(ctl, tid) == -1) + err(EXIT_FAILURE, _("failed to set tid %d's policy"), tid); + } + ul_unref_path(pc); +#else + err(EXIT_FAILURE, _("cannot obtain the list of tasks")); +#endif + } else if (set_sched_one(ctl, ctl->pid) == -1) + err(EXIT_FAILURE, _("failed to set pid %d's policy"), ctl->pid); + + ctl->altered = 1; +} + +int main(int argc, char **argv) +{ + struct chrt_ctl _ctl = { .pid = -1, .policy = SCHED_RR }, *ctl = &_ctl; + int c; + + static const struct option longopts[] = { + { "all-tasks", no_argument, NULL, 'a' }, + { "batch", no_argument, NULL, 'b' }, + { "deadline", no_argument, NULL, 'd' }, + { "fifo", no_argument, NULL, 'f' }, + { "idle", no_argument, NULL, 'i' }, + { "pid", no_argument, NULL, 'p' }, + { "help", no_argument, NULL, 'h' }, + { "max", no_argument, NULL, 'm' }, + { "other", no_argument, NULL, 'o' }, + { "rr", no_argument, NULL, 'r' }, + { "sched-runtime", required_argument, NULL, 'T' }, + { "sched-period", required_argument, NULL, 'P' }, + { "sched-deadline", required_argument, NULL, 'D' }, + { "reset-on-fork", no_argument, NULL, 'R' }, + { "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, "+abdD:fiphmoP:T:rRvV", longopts, NULL)) != -1) + { + switch (c) { + case 'a': + ctl->all_tasks = 1; + break; + case 'b': +#ifdef SCHED_BATCH + ctl->policy = SCHED_BATCH; +#endif + break; + + case 'd': +#ifdef SCHED_DEADLINE + ctl->policy = SCHED_DEADLINE; +#endif + break; + case 'f': + ctl->policy = SCHED_FIFO; + break; + case 'R': + ctl->reset_on_fork = 1; + break; + case 'i': +#ifdef SCHED_IDLE + ctl->policy = SCHED_IDLE; +#endif + break; + case 'm': + show_min_max(); + return EXIT_SUCCESS; + case 'o': + ctl->policy = SCHED_OTHER; + break; + case 'p': + errno = 0; + ctl->pid = strtos32_or_err(argv[argc - 1], _("invalid PID argument")); + break; + case 'r': + ctl->policy = SCHED_RR; + break; + case 'v': + ctl->verbose = 1; + break; + case 'T': + ctl->runtime = strtou64_or_err(optarg, _("invalid runtime argument")); + break; + case 'P': + ctl->period = strtou64_or_err(optarg, _("invalid period argument")); + break; + case 'D': + ctl->deadline = strtou64_or_err(optarg, _("invalid deadline argument")); + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if (((ctl->pid > -1) && argc - optind < 1) || + ((ctl->pid == -1) && argc - optind < 2)) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); +} + + if ((ctl->pid > -1) && (ctl->verbose || argc - optind == 1)) { + show_sched_info(ctl); + if (argc - optind == 1) + return EXIT_SUCCESS; + } + + errno = 0; + ctl->priority = strtos32_or_err(argv[optind], _("invalid priority argument")); + +#ifdef SCHED_DEADLINE + if ((ctl->runtime || ctl->deadline || ctl->period) && ctl->policy != SCHED_DEADLINE) + errx(EXIT_FAILURE, _("--sched-{runtime,deadline,period} options " + "are supported for SCHED_DEADLINE only")); + if (ctl->policy == SCHED_DEADLINE) { + /* The basic rule is runtime <= deadline <= period, so we can + * make deadline and runtime optional on command line. Note we + * don't check any values or set any defaults, it's kernel + * responsibility. + */ + if (ctl->deadline == 0) + ctl->deadline = ctl->period; + if (ctl->runtime == 0) + ctl->runtime = ctl->deadline; + } +#else + if (ctl->runtime || ctl->deadline || ctl->period) + errx(EXIT_FAILURE, _("SCHED_DEADLINE is unsupported")); +#endif + if (ctl->pid == -1) + ctl->pid = 0; + if (ctl->priority < sched_get_priority_min(ctl->policy) || + sched_get_priority_max(ctl->policy) < ctl->priority) + errx(EXIT_FAILURE, + _("unsupported priority value for the policy: %d: see --max for valid range"), + ctl->priority); + set_sched(ctl); + + if (ctl->verbose) + show_sched_info(ctl); + + if (!ctl->pid) { + argv += optind + 1; + execvp(argv[0], argv); + errexec(argv[0]); + } + + return EXIT_SUCCESS; +} diff --git a/schedutils/ionice.1 b/schedutils/ionice.1 new file mode 100644 index 0000000..dd122d4 --- /dev/null +++ b/schedutils/ionice.1 @@ -0,0 +1,165 @@ +'\" t +.\" Title: ionice +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "IONICE" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +ionice \- set or get process I/O scheduling class and priority +.SH "SYNOPSIS" +.sp +\fBionice\fP [\fB\-c\fP \fIclass\fP] [\fB\-n\fP \fIlevel\fP] [\fB\-t\fP] \fB\-p\fP \fIPID\fP +.sp +\fBionice\fP [\fB\-c\fP \fIclass\fP] [\fB\-n\fP \fIlevel\fP] [\fB\-t\fP] \fB\-P\fP \fIPGID\fP +.sp +\fBionice\fP [\fB\-c\fP \fIclass\fP] [\fB\-n\fP \fIlevel\fP] [\fB\-t\fP] \fB\-u\fP \fIUID\fP +.sp +\fBionice\fP [\fB\-c\fP \fIclass\fP] [\fB\-n\fP \fIlevel\fP] [\fB\-t\fP] \fIcommand\fP [argument] ... +.SH "DESCRIPTION" +.sp +This program sets or gets the I/O scheduling class and priority for a program. If no arguments or just \fB\-p\fP is given, \fBionice\fP will query the current I/O scheduling class and priority for that process. +.sp +When \fIcommand\fP is given, \fBionice\fP will run this command with the given arguments. If no \fIclass\fP is specified, then \fIcommand\fP will be executed with the "best\-effort" scheduling class. The default priority level is 4. +.sp +As of this writing, a process can be in one of three scheduling classes: +.sp +\fBIdle\fP +.RS 4 +A program running with idle I/O priority will only get disk time when no other program has asked for disk I/O for a defined grace period. The impact of an idle I/O process on normal system activity should be zero. This scheduling class does not take a priority argument. Presently, this scheduling class is permitted for an ordinary user (since kernel 2.6.25). +.RE +.sp +\fBBest\-effort\fP +.RS 4 +This is the effective scheduling class for any process that has not asked for a specific I/O priority. This class takes a priority argument from \fI0\-7\fP, with a lower number being higher priority. Programs running at the same best\-effort priority are served in a round\-robin fashion. +.sp +Note that before kernel 2.6.26 a process that has not asked for an I/O priority formally uses "\fBnone\fP" as scheduling class, but the I/O scheduler will treat such processes as if it were in the best\-effort class. The priority within the best\-effort class will be dynamically derived from the CPU nice level of the process: io_priority = (cpu_nice + 20) / 5. +.sp +For kernels after 2.6.26 with the CFQ I/O scheduler, a process that has not asked for an I/O priority inherits its CPU scheduling class. The I/O priority is derived from the CPU nice level of the process (same as before kernel 2.6.26). +.RE +.sp +\fBRealtime\fP +.RS 4 +The RT scheduling class is given first access to the disk, regardless of what else is going on in the system. Thus the RT class needs to be used with some care, as it can starve other processes. As with the best\-effort class, 8 priority levels are defined denoting how big a time slice a given process will receive on each scheduling window. This scheduling class is not permitted for an ordinary (i.e., non\-root) user. +.RE +.SH "OPTIONS" +.sp +\fB\-c\fP, \fB\-\-class\fP \fIclass\fP +.RS 4 +Specify the name or number of the scheduling class to use; \f(CR0\fP for none, \f(CR1\fP for realtime, \f(CR2\fP for best\-effort, \f(CR3\fP for idle. +.RE +.sp +\fB\-n\fP, \fB\-\-classdata\fP \fIlevel\fP +.RS 4 +Specify the scheduling class data. This only has an effect if the class accepts an argument. For realtime and best\-effort, \fI0\-7\fP are valid data (priority levels), and \f(CR0\fP represents the highest priority level. +.RE +.sp +\fB\-p\fP, \fB\-\-pid\fP \fIPID\fP... +.RS 4 +Specify the process IDs of running processes for which to get or set the scheduling parameters. +.RE +.sp +\fB\-P\fP, \fB\-\-pgid\fP \fIPGID\fP... +.RS 4 +Specify the process group IDs of running processes for which to get or set the scheduling parameters. +.RE +.sp +\fB\-t\fP, \fB\-\-ignore\fP +.RS 4 +Ignore failure to set the requested priority. If \fIcommand\fP was specified, run it even in case it was not possible to set the desired scheduling priority, which can happen due to insufficient privileges or an old kernel version. +.RE +.sp +\fB\-u\fP, \fB\-\-uid\fP \fIUID\fP... +.RS 4 +Specify the user IDs of running processes for which to get or set the scheduling parameters. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "NOTES" +.sp +Linux supports I/O scheduling priorities and classes since 2.6.13 with the CFQ I/O scheduler. +.SH "EXAMPLES" +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +# \fBionice\fP \-c 3 \-p 89 +.RE +.sp +Sets process with PID 89 as an idle I/O process. +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +# \fBionice\fP \-c 2 \-n 0 bash +.RE +.sp +Runs \(aqbash\(aq as a best\-effort program with highest priority. +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +# \fBionice\fP \-p 89 91 +.RE +.sp +Prints the class and priority of the processes with PID 89 and 91. +.SH "AUTHORS" +.sp +.MTO "jens\(ataxboe.dk" "Jens Axboe" "," +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.SH "SEE ALSO" +.sp +\fBioprio_set\fP(2) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBionice\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file diff --git a/schedutils/ionice.1.adoc b/schedutils/ionice.1.adoc new file mode 100644 index 0000000..622c62e --- /dev/null +++ b/schedutils/ionice.1.adoc @@ -0,0 +1,101 @@ +//po4a: entry man manual += ionice(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: ionice +:plus: + + +== NAME + +ionice - set or get process I/O scheduling class and priority + +== SYNOPSIS + +*ionice* [*-c* _class_] [*-n* _level_] [*-t*] *-p* _PID_ + +*ionice* [*-c* _class_] [*-n* _level_] [*-t*] *-P* _PGID_ + +*ionice* [*-c* _class_] [*-n* _level_] [*-t*] *-u* _UID_ + +*ionice* [*-c* _class_] [*-n* _level_] [*-t*] _command_ [argument] ... + +== DESCRIPTION + +This program sets or gets the I/O scheduling class and priority for a program. If no arguments or just *-p* is given, *ionice* will query the current I/O scheduling class and priority for that process. + +When _command_ is given, *ionice* will run this command with the given arguments. If no _class_ is specified, then _command_ will be executed with the "best-effort" scheduling class. The default priority level is 4. + +As of this writing, a process can be in one of three scheduling classes: + +*Idle*:: +A program running with idle I/O priority will only get disk time when no other program has asked for disk I/O for a defined grace period. The impact of an idle I/O process on normal system activity should be zero. This scheduling class does not take a priority argument. Presently, this scheduling class is permitted for an ordinary user (since kernel 2.6.25). + +*Best-effort*:: +This is the effective scheduling class for any process that has not asked for a specific I/O priority. This class takes a priority argument from _0-7_, with a lower number being higher priority. Programs running at the same best-effort priority are served in a round-robin fashion. ++ +Note that before kernel 2.6.26 a process that has not asked for an I/O priority formally uses "*none*" as scheduling class, but the I/O scheduler will treat such processes as if it were in the best-effort class. The priority within the best-effort class will be dynamically derived from the CPU nice level of the process: io_priority = (cpu_nice {plus} 20) / 5. +//TRANSLATORS: Keep {plus} untranslated. ++ +For kernels after 2.6.26 with the CFQ I/O scheduler, a process that has not asked for an I/O priority inherits its CPU scheduling class. The I/O priority is derived from the CPU nice level of the process (same as before kernel 2.6.26). + +*Realtime*:: +The RT scheduling class is given first access to the disk, regardless of what else is going on in the system. Thus the RT class needs to be used with some care, as it can starve other processes. As with the best-effort class, 8 priority levels are defined denoting how big a time slice a given process will receive on each scheduling window. This scheduling class is not permitted for an ordinary (i.e., non-root) user. + +== OPTIONS + +*-c*, *--class* _class_:: +Specify the name or number of the scheduling class to use; `0` for none, `1` for realtime, `2` for best-effort, `3` for idle. + +*-n*, *--classdata* _level_:: +Specify the scheduling class data. This only has an effect if the class accepts an argument. For realtime and best-effort, _0-7_ are valid data (priority levels), and `0` represents the highest priority level. + +*-p*, *--pid* _PID_...:: +Specify the process IDs of running processes for which to get or set the scheduling parameters. + +*-P*, *--pgid* _PGID_...:: +Specify the process group IDs of running processes for which to get or set the scheduling parameters. + +*-t*, *--ignore*:: +Ignore failure to set the requested priority. If _command_ was specified, run it even in case it was not possible to set the desired scheduling priority, which can happen due to insufficient privileges or an old kernel version. + +*-u*, *--uid* _UID_...:: +Specify the user IDs of running processes for which to get or set the scheduling parameters. + +include::man-common/help-version.adoc[] + +== NOTES + +Linux supports I/O scheduling priorities and classes since 2.6.13 with the CFQ I/O scheduler. + +== EXAMPLES + +* # *ionice* -c 3 -p 89 + +Sets process with PID 89 as an idle I/O process. + +* # *ionice* -c 2 -n 0 bash + +Runs 'bash' as a best-effort program with highest priority. + +* # *ionice* -p 89 91 + +Prints the class and priority of the processes with PID 89 and 91. + +== AUTHORS + +mailto:jens@axboe.dk[Jens Axboe], +mailto:kzak@redhat.com[Karel Zak] + +== SEE ALSO + +*ioprio_set*(2) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/schedutils/ionice.c b/schedutils/ionice.c new file mode 100644 index 0000000..8be83df --- /dev/null +++ b/schedutils/ionice.c @@ -0,0 +1,269 @@ +/* + * ionice: set or get process io scheduling class and priority + * + * Copyright (C) 2005 Jens Axboe + * + * Released under the terms of the GNU General Public License version 2 + * + */ +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_SYSCALL_H +#include +#endif +#include + +#include "nls.h" +#include "strutils.h" +#include "c.h" +#include "closestream.h" + +static int tolerant; + +static inline int ioprio_set(int which, int who, int ioprio) +{ + return syscall(SYS_ioprio_set, which, who, ioprio); +} + +static inline int ioprio_get(int which, int who) +{ + return syscall(SYS_ioprio_get, which, who); +} + +enum { + IOPRIO_CLASS_NONE, + IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, + IOPRIO_CLASS_IDLE, +}; + +enum { + IOPRIO_WHO_PROCESS = 1, + IOPRIO_WHO_PGRP, + IOPRIO_WHO_USER, +}; + +#define IOPRIO_CLASS_SHIFT (13) +#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) + +#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) +#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) +#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) + +static const char *to_prio[] = { + [IOPRIO_CLASS_NONE] = "none", + [IOPRIO_CLASS_RT] = "realtime", + [IOPRIO_CLASS_BE] = "best-effort", + [IOPRIO_CLASS_IDLE] = "idle" +}; + +static int parse_ioclass(const char *str) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(to_prio); i++) + if (!strcasecmp(str, to_prio[i])) + return i; + return -1; +} + +static void ioprio_print(int pid, int who) +{ + int ioprio = ioprio_get(who, pid); + + if (ioprio == -1) + err(EXIT_FAILURE, _("ioprio_get failed")); + else { + int ioclass = IOPRIO_PRIO_CLASS(ioprio); + const char *name = _("unknown"); + + if (ioclass >= 0 && (size_t) ioclass < ARRAY_SIZE(to_prio)) + name = to_prio[ioclass]; + + if (ioclass != IOPRIO_CLASS_IDLE) + printf(_("%s: prio %lu\n"), name, + IOPRIO_PRIO_DATA(ioprio)); + else + printf("%s\n", name); + } +} + +static void ioprio_setid(int which, int ioclass, int data, int who) +{ + int rc = ioprio_set(who, which, + IOPRIO_PRIO_VALUE(ioclass, data)); + + if (rc == -1 && !tolerant) + err(EXIT_FAILURE, _("ioprio_set failed")); +} + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fputs(USAGE_HEADER, out); + fprintf(out, _(" %1$s [options] -p ...\n" + " %1$s [options] -P ...\n" + " %1$s [options] -u ...\n" + " %1$s [options] \n"), program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Show or change the I/O-scheduling class and priority of a process.\n"), out); + + fputs(USAGE_OPTIONS, out); + fputs(_(" -c, --class name or number of scheduling class,\n" + " 0: none, 1: realtime, 2: best-effort, 3: idle\n"), out); + fputs(_(" -n, --classdata priority (0..7) in the specified scheduling class,\n" + " only for the realtime and best-effort classes\n"), out); + fputs(_(" -p, --pid ... act on these already running processes\n"), out); + fputs(_(" -P, --pgid ... act on already running processes in these groups\n"), out); + fputs(_(" -t, --ignore ignore failures\n"), out); + fputs(_(" -u, --uid ... act on already running processes owned by these users\n"), out); + + fputs(USAGE_SEPARATOR, out); + printf(USAGE_HELP_OPTIONS(24)); + + printf(USAGE_MAN_TAIL("ionice(1)")); + + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) +{ + int data = 4, set = 0, ioclass = IOPRIO_CLASS_BE, c; + int which = 0, who = 0; + const char *invalid_msg = NULL; + + static const struct option longopts[] = { + { "classdata", required_argument, NULL, 'n' }, + { "class", required_argument, NULL, 'c' }, + { "help", no_argument, NULL, 'h' }, + { "ignore", no_argument, NULL, 't' }, + { "pid", required_argument, NULL, 'p' }, + { "pgid", required_argument, NULL, 'P' }, + { "uid", required_argument, NULL, 'u' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + while ((c = getopt_long(argc, argv, "+n:c:p:P:u:tVh", longopts, NULL)) != EOF) + switch (c) { + case 'n': + data = strtos32_or_err(optarg, _("invalid class data argument")); + set |= 1; + break; + case 'c': + if (isdigit(*optarg)) + ioclass = strtos32_or_err(optarg, + _("invalid class argument")); + else { + ioclass = parse_ioclass(optarg); + if (ioclass < 0) + errx(EXIT_FAILURE, + _("unknown scheduling class: '%s'"), + optarg); + } + set |= 2; + break; + case 'p': + if (who) + errx(EXIT_FAILURE, + _("can handle only one of pid, pgid or uid at once")); + invalid_msg = _("invalid PID argument"); + which = strtos32_or_err(optarg, invalid_msg); + who = IOPRIO_WHO_PROCESS; + break; + case 'P': + if (who) + errx(EXIT_FAILURE, + _("can handle only one of pid, pgid or uid at once")); + invalid_msg = _("invalid PGID argument"); + which = strtos32_or_err(optarg, invalid_msg); + who = IOPRIO_WHO_PGRP; + break; + case 'u': + if (who) + errx(EXIT_FAILURE, + _("can handle only one of pid, pgid or uid at once")); + invalid_msg = _("invalid UID argument"); + which = strtos32_or_err(optarg, invalid_msg); + who = IOPRIO_WHO_USER; + break; + case 't': + tolerant = 1; + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + + switch (ioclass) { + case IOPRIO_CLASS_NONE: + if ((set & 1) && !tolerant) + warnx(_("ignoring given class data for none class")); + data = 0; + break; + case IOPRIO_CLASS_RT: + case IOPRIO_CLASS_BE: + break; + case IOPRIO_CLASS_IDLE: + if ((set & 1) && !tolerant) + warnx(_("ignoring given class data for idle class")); + data = 7; + break; + default: + if (!tolerant) + warnx(_("unknown prio class %d"), ioclass); + break; + } + + if (!set && !which && optind == argc) + /* + * ionice without options, print the current ioprio + */ + ioprio_print(0, IOPRIO_WHO_PROCESS); + else if (!set && who) { + /* + * ionice -p|-P|-u ID [ID ...] + */ + ioprio_print(which, who); + + for(; argv[optind]; ++optind) { + which = strtos32_or_err(argv[optind], invalid_msg); + ioprio_print(which, who); + } + } else if (set && who) { + /* + * ionice -c CLASS -p|-P|-u ID [ID ...] + */ + ioprio_setid(which, ioclass, data, who); + + for(; argv[optind]; ++optind) { + which = strtos32_or_err(argv[optind], invalid_msg); + ioprio_setid(which, ioclass, data, who); + } + } else if (argv[optind]) { + /* + * ionice [-c CLASS] COMMAND + */ + ioprio_setid(0, ioclass, data, IOPRIO_WHO_PROCESS); + execvp(argv[optind], &argv[optind]); + errexec(argv[optind]); + } else { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + + return EXIT_SUCCESS; +} diff --git a/schedutils/sched_attr.h b/schedutils/sched_attr.h new file mode 100644 index 0000000..b755943 --- /dev/null +++ b/schedutils/sched_attr.h @@ -0,0 +1,127 @@ +/* + * 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) 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 + +/* the SCHED_BATCH is supported since Linux 2.6.16 + * -- temporary workaround for people with old glibc headers + */ +#if defined (__linux__) && !defined(SCHED_BATCH) +# define SCHED_BATCH 3 +#endif + +/* the SCHED_IDLE is supported since Linux 2.6.23 + * commit id 0e6aca43e08a62a48d6770e9a159dbec167bf4c6 + * -- temporary workaround for people with old glibc headers + */ +#if defined (__linux__) && !defined(SCHED_IDLE) +# define SCHED_IDLE 5 +#endif + +/* flag by sched_getscheduler() */ +#if defined(__linux__) && !defined(SCHED_RESET_ON_FORK) +# define SCHED_RESET_ON_FORK 0x40000000 +#endif + +/* flag by sched_getattr() */ +#if defined(__linux__) && !defined(SCHED_FLAG_RESET_ON_FORK) +# 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 + +#ifdef HAVE_SYS_SYSCALL_H +# include +#endif + +/* usable kernel-headers, but old glibc-headers */ +#if defined (__linux__) && !defined(SYS_sched_setattr) && defined(__NR_sched_setattr) +# define SYS_sched_setattr __NR_sched_setattr +#endif + +#if defined (__linux__) && !defined(SYS_sched_getattr) && defined(__NR_sched_getattr) +# define SYS_sched_getattr __NR_sched_getattr +#endif + +#if defined (__linux__) && !defined(HAVE_SCHED_SETATTR) && defined(SYS_sched_setattr) +# define HAVE_SCHED_SETATTR + +struct sched_attr { + uint32_t size; + uint32_t sched_policy; + uint64_t sched_flags; + + /* SCHED_NORMAL, SCHED_BATCH */ + int32_t sched_nice; + + /* SCHED_FIFO, SCHED_RR */ + uint32_t sched_priority; + + /* SCHED_DEADLINE (nsec) */ + 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) +{ + return syscall(SYS_sched_setattr, pid, attr, flags); +} + +static int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags) +{ + return syscall(SYS_sched_getattr, pid, attr, size, flags); +} +#endif + +/* the SCHED_DEADLINE is supported since Linux 3.14 + * commit id aab03e05e8f7e26f51dee792beddcb5cca9215a5 + * -- sched_setattr() is required for this policy! + */ +#if defined (__linux__) && !defined(SCHED_DEADLINE) && defined(HAVE_SCHED_SETATTR) +# define SCHED_DEADLINE 6 +#endif + +#endif /* UTIL_LINUX_SCHED_ATTR_H */ diff --git a/schedutils/taskset.1 b/schedutils/taskset.1 new file mode 100644 index 0000000..579d9be --- /dev/null +++ b/schedutils/taskset.1 @@ -0,0 +1,141 @@ +'\" t +.\" Title: taskset +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "TASKSET" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +taskset \- set or retrieve a process\(aqs CPU affinity +.SH "SYNOPSIS" +.sp +\fBtaskset\fP [options] \fImask command\fP [\fIargument\fP...] +.sp +\fBtaskset\fP [options] \fB\-p\fP [\fImask\fP] \fIpid\fP +.SH "DESCRIPTION" +.sp +The \fBtaskset\fP command is used to set or retrieve the CPU affinity of a running process given its \fIpid\fP, or to launch a new \fIcommand\fP with a given CPU affinity. CPU affinity is a scheduler property that "bonds" a process to a given set of CPUs on the system. The Linux scheduler will honor the given CPU affinity and the process will not run on any other CPUs. Note that the Linux scheduler also supports natural CPU affinity: the scheduler attempts to keep processes on the same CPU as long as practical for performance reasons. Therefore, forcing a specific CPU affinity is useful only in certain applications. +.sp +The CPU affinity is represented as a bitmask, with the lowest order bit corresponding to the first logical CPU and the highest order bit corresponding to the last logical CPU. Not all CPUs may exist on a given system but a mask may specify more CPUs than are present. A retrieved mask will reflect only the bits that correspond to CPUs physically on the system. If an invalid mask is given (i.e., one that corresponds to no valid CPUs on the current system) an error is returned. The masks may be specified in hexadecimal (with or without a leading "0x"), or as a CPU list with the \fB\-\-cpu\-list\fP option. For example, +.sp +\fB0x00000001\fP +.RS 4 +is processor #0, +.RE +.sp +\fB0x00000003\fP +.RS 4 +is processors #0 and #1, +.RE +.sp +\fB0xFFFFFFFF\fP +.RS 4 +is processors #0 through #31, +.RE +.sp +\fB32\fP +.RS 4 +is processors #1, #4, and #5, +.RE +.sp +\fB\-\-cpu\-list 0\-2,6\fP +.RS 4 +is processors #0, #1, #2, and #6. +.RE +.sp +\fB\-\-cpu\-list 0\-10:2\fP +.RS 4 +is processors #0, #2, #4, #6, #8 and #10. The suffix ":N" specifies stride in the range, for example 0\-10:3 is interpreted as 0,3,6,9 list. +.RE +.sp +When \fBtaskset\fP returns, it is guaranteed that the given program has been scheduled to a legal CPU. +.SH "OPTIONS" +.sp +\fB\-a\fP, \fB\-\-all\-tasks\fP +.RS 4 +Set or retrieve the CPU affinity of all the tasks (threads) for a given PID. +.RE +.sp +\fB\-c\fP, \fB\-\-cpu\-list\fP +.RS 4 +Interpret \fImask\fP as numerical list of processors instead of a bitmask. Numbers are separated by commas and may include ranges. For example: \fB0,5,8\-11\fP. +.RE +.sp +\fB\-p\fP, \fB\-\-pid\fP +.RS 4 +Operate on an existing PID and do not launch a new task. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "USAGE" +.sp +The default behavior is to run a new command with a given affinity mask: +.RS 4 +\fBtaskset\fP \fImask\fP \fIcommand\fP [\fIarguments\fP] +.RE +.sp +You can also retrieve the CPU affinity of an existing task: +.RS 4 +\fBtaskset \-p\fP \fIpid\fP +.RE +.sp +Or set it: +.RS 4 +\fBtaskset \-p\fP \fImask pid\fP +.RE +.SH "PERMISSIONS" +.sp +A user can change the CPU affinity of a process belonging to the same user. A user must possess \fBCAP_SYS_NICE\fP to change the CPU affinity of a process belonging to another user. A user can retrieve the affinity mask of any process. +.SH "AUTHORS" +.sp +Written by Robert M. Love. +.SH "COPYRIGHT" +.sp +Copyright © 2004 Robert M. Love. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.SH "SEE ALSO" +.sp +\fBchrt\fP(1), +\fBnice\fP(1), +\fBrenice\fP(1), +\fBsched_getaffinity\fP(2), +\fBsched_setaffinity\fP(2) +.sp +See \fBsched\fP(7) for a description of the Linux scheduling scheme. +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBtaskset\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file diff --git a/schedutils/taskset.1.adoc b/schedutils/taskset.1.adoc new file mode 100644 index 0000000..c10cc62 --- /dev/null +++ b/schedutils/taskset.1.adoc @@ -0,0 +1,125 @@ +//po4a: entry man manual +//// +taskset(1) manpage +Copyright (C) 2004 Robert Love + +This is free documentation; 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. + +The GNU General Public License's references to "object code" +and "executables" are to be interpreted as the output of any +document formatting or typesetting system, including +intermediate and printed output. + +This manual 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. +//// += taskset(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: taskset +:colon: : +:copyright: © + +== NAME + +taskset - set or retrieve a process's CPU affinity + +== SYNOPSIS + +*taskset* [options] _mask command_ [_argument_...] + +*taskset* [options] *-p* [_mask_] _pid_ + +== DESCRIPTION + +The *taskset* command is used to set or retrieve the CPU affinity of a running process given its _pid_, or to launch a new _command_ with a given CPU affinity. CPU affinity is a scheduler property that "bonds" a process to a given set of CPUs on the system. The Linux scheduler will honor the given CPU affinity and the process will not run on any other CPUs. Note that the Linux scheduler also supports natural CPU affinity: the scheduler attempts to keep processes on the same CPU as long as practical for performance reasons. Therefore, forcing a specific CPU affinity is useful only in certain applications. + +The CPU affinity is represented as a bitmask, with the lowest order bit corresponding to the first logical CPU and the highest order bit corresponding to the last logical CPU. Not all CPUs may exist on a given system but a mask may specify more CPUs than are present. A retrieved mask will reflect only the bits that correspond to CPUs physically on the system. If an invalid mask is given (i.e., one that corresponds to no valid CPUs on the current system) an error is returned. The masks may be specified in hexadecimal (with or without a leading "0x"), or as a CPU list with the *--cpu-list* option. For example, + +*0x00000001*:: +is processor #0, + +*0x00000003*:: +is processors #0 and #1, + +*0xFFFFFFFF*:: +is processors #0 through #31, + +*32*:: +is processors #1, #4, and #5, + +*--cpu-list 0-2,6*:: +is processors #0, #1, #2, and #6. + +*--cpu-list 0-10:2*:: +is processors #0, #2, #4, #6, #8 and #10. The suffix ":N" specifies stride in the range, for example 0-10:3 is interpreted as 0,3,6,9 list. + +When *taskset* returns, it is guaranteed that the given program has been scheduled to a legal CPU. + +== OPTIONS + +*-a*, *--all-tasks*:: +Set or retrieve the CPU affinity of all the tasks (threads) for a given PID. + +*-c*, *--cpu-list*:: +Interpret _mask_ as numerical list of processors instead of a bitmask. Numbers are separated by commas and may include ranges. For example: *0,5,8-11*. + +*-p*, *--pid*:: +Operate on an existing PID and do not launch a new task. + +include::man-common/help-version.adoc[] + +== USAGE + +//TRANSLATORS: Keep {colon} untranslated. +The default behavior is to run a new command with a given affinity mask{colon}:: +*taskset* _mask_ _command_ [_arguments_] + +//TRANSLATORS: Keep {colon} untranslated. +You can also retrieve the CPU affinity of an existing task{colon}:: +*taskset -p* _pid_ + +//TRANSLATORS: Keep {colon} untranslated. +Or set it{colon}:: +*taskset -p* _mask pid_ + +== PERMISSIONS + +A user can change the CPU affinity of a process belonging to the same user. A user must possess *CAP_SYS_NICE* to change the CPU affinity of a process belonging to another user. A user can retrieve the affinity mask of any process. + +== AUTHORS + +Written by Robert M. Love. + +== COPYRIGHT + +//TRANSLATORS: Keep {copyright} untranslated. +Copyright {copyright} 2004 Robert M. Love. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +== SEE ALSO + +*chrt*(1), +*nice*(1), +*renice*(1), +*sched_getaffinity*(2), +*sched_setaffinity*(2) + +See *sched*(7) for a description of the Linux scheduling scheme. + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/schedutils/taskset.c b/schedutils/taskset.c new file mode 100644 index 0000000..0ab7d12 --- /dev/null +++ b/schedutils/taskset.c @@ -0,0 +1,254 @@ +/* + * taskset.c - set or retrieve a task's CPU affinity + * + * 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) 2004 Robert Love + * Copyright (C) 2010 Karel Zak + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cpuset.h" +#include "nls.h" +#include "strutils.h" +#include "xalloc.h" +#include "procfs.h" +#include "c.h" +#include "closestream.h" + +struct taskset { + pid_t pid; /* task PID */ + cpu_set_t *set; /* task CPU mask */ + size_t setsize; + char *buf; /* buffer for conversion from mask to string */ + size_t buflen; + unsigned int use_list:1, /* use list rather than masks */ + get_only:1; /* print the mask, but not modify */ +}; + +static void __attribute__((__noreturn__)) usage(void) +{ + FILE *out = stdout; + fprintf(out, + _("Usage: %s [options] [mask | cpu-list] [pid|cmd [args...]]\n\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Show or change the CPU affinity of a process.\n"), out); + fputs(USAGE_SEPARATOR, out); + + fprintf(out, _( + "Options:\n" + " -a, --all-tasks operate on all the tasks (threads) for a given pid\n" + " -p, --pid operate on existing given pid\n" + " -c, --cpu-list display and specify cpus in list format\n" + )); + printf(USAGE_HELP_OPTIONS(25)); + + fputs(USAGE_SEPARATOR, out); + fprintf(out, _( + "The default behavior is to run a new command:\n" + " %1$s 03 sshd -b 1024\n" + "You can retrieve the mask of an existing task:\n" + " %1$s -p 700\n" + "Or set it:\n" + " %1$s -p 03 700\n" + "List format uses a comma-separated list instead of a mask:\n" + " %1$s -pc 0,3,7-11 700\n" + "Ranges in list format can take a stride argument:\n" + " e.g. 0-31:2 is equivalent to mask 0x55555555\n"), + program_invocation_short_name); + + printf(USAGE_MAN_TAIL("taskset(1)")); + exit(EXIT_SUCCESS); +} + +static void print_affinity(struct taskset *ts, int isnew) +{ + char *str, *msg; + + if (ts->use_list) { + str = cpulist_create(ts->buf, ts->buflen, ts->set, ts->setsize); + msg = isnew ? _("pid %d's new affinity list: %s\n") : + _("pid %d's current affinity list: %s\n"); + } else { + str = cpumask_create(ts->buf, ts->buflen, ts->set, ts->setsize); + msg = isnew ? _("pid %d's new affinity mask: %s\n") : + _("pid %d's current affinity mask: %s\n"); + } + + if (!str) + errx(EXIT_FAILURE, _("internal error: conversion from cpuset to string failed")); + + printf(msg, ts->pid ? ts->pid : getpid(), str); +} + +static void __attribute__((__noreturn__)) err_affinity(pid_t pid, int set) +{ + char *msg; + + msg = set ? _("failed to set pid %d's affinity") : + _("failed to get pid %d's affinity"); + + err(EXIT_FAILURE, msg, pid ? pid : getpid()); +} + +static void do_taskset(struct taskset *ts, size_t setsize, cpu_set_t *set) +{ + /* read the current mask */ + if (ts->pid) { + if (sched_getaffinity(ts->pid, ts->setsize, ts->set) < 0) + err_affinity(ts->pid, 0); + print_affinity(ts, FALSE); + } + + if (ts->get_only) + return; + + /* set new mask */ + if (sched_setaffinity(ts->pid, setsize, set) < 0) + err_affinity(ts->pid, 1); + + /* re-read the current mask */ + if (ts->pid) { + if (sched_getaffinity(ts->pid, ts->setsize, ts->set) < 0) + err_affinity(ts->pid, 0); + print_affinity(ts, TRUE); + } +} + +int main(int argc, char **argv) +{ + cpu_set_t *new_set; + pid_t pid = 0; + int c, all_tasks = 0; + int ncpus; + size_t new_setsize, nbits; + struct taskset ts; + + static const struct option longopts[] = { + { "all-tasks", 0, NULL, 'a' }, + { "pid", 0, NULL, 'p' }, + { "cpu-list", 0, NULL, 'c' }, + { "help", 0, NULL, 'h' }, + { "version", 0, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + close_stdout_atexit(); + + memset(&ts, 0, sizeof(ts)); + + while ((c = getopt_long(argc, argv, "+apchV", longopts, NULL)) != -1) { + switch (c) { + case 'a': + all_tasks = 1; + break; + case 'p': + pid = strtos32_or_err(argv[argc - 1], + _("invalid PID argument")); + break; + case 'c': + ts.use_list = 1; + break; + + case 'V': + print_version(EXIT_SUCCESS); + case 'h': + usage(); + default: + errtryhelp(EXIT_FAILURE); + } + } + + if ((!pid && argc - optind < 2) + || (pid && (argc - optind < 1 || argc - optind > 2))) { + warnx(_("bad usage")); + errtryhelp(EXIT_FAILURE); + } + + ncpus = get_max_number_of_cpus(); + if (ncpus <= 0) + errx(EXIT_FAILURE, _("cannot determine NR_CPUS; aborting")); + + /* + * the ts->set is always used for the sched_getaffinity call + * On the sched_getaffinity the kernel demands a user mask of + * at least the size of its own cpumask_t. + */ + ts.set = cpuset_alloc(ncpus, &ts.setsize, &nbits); + if (!ts.set) + err(EXIT_FAILURE, _("cpuset_alloc failed")); + + /* buffer for conversion from mask to string */ + ts.buflen = 7 * nbits; + ts.buf = xmalloc(ts.buflen); + + /* + * new_set is always used for the sched_setaffinity call + * On the sched_setaffinity the kernel will zero-fill its + * cpumask_t if the user's mask is shorter. + */ + new_set = cpuset_alloc(ncpus, &new_setsize, NULL); + if (!new_set) + err(EXIT_FAILURE, _("cpuset_alloc failed")); + + if (argc - optind == 1) + ts.get_only = 1; + + else if (ts.use_list) { + if (cpulist_parse(argv[optind], new_set, new_setsize, 0)) + errx(EXIT_FAILURE, _("failed to parse CPU list: %s"), + argv[optind]); + } else if (cpumask_parse(argv[optind], new_set, new_setsize)) { + errx(EXIT_FAILURE, _("failed to parse CPU mask: %s"), + argv[optind]); + } + + if (all_tasks && pid) { + DIR *sub = NULL; + struct path_cxt *pc = ul_new_procfs_path(pid, NULL); + + while (pc && procfs_process_next_tid(pc, &sub, &ts.pid) == 0) + do_taskset(&ts, new_setsize, new_set); + + ul_unref_path(pc); + } else { + ts.pid = pid; + do_taskset(&ts, new_setsize, new_set); + } + + free(ts.buf); + cpuset_free(ts.set); + cpuset_free(new_set); + + if (!pid) { + argv += optind + 1; + execvp(argv[0], argv); + errexec(argv[0]); + } + + return EXIT_SUCCESS; +} diff --git a/schedutils/uclampset.1 b/schedutils/uclampset.1 new file mode 100644 index 0000000..2f20ef3 --- /dev/null +++ b/schedutils/uclampset.1 @@ -0,0 +1,154 @@ +'\" t +.\" Title: uclampset +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "UCLAMPSET" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +uclampset \- manipulate the utilization clamping attributes of the system or a process +.SH "SYNOPSIS" +.sp +\fBuclampset\fP [options] [\fB\-m\fP \fIuclamp_min\fP] [\fB\-M\fP \fIuclamp_max] _command argument\fP +.sp +\fBuclampset\fP [options] [\fB\-m\fP \fIuclamp_min\fP] [\fB\-M\fP \fIuclamp_max\fP] \fB\-p\fP \fIPID\fP +.SH "DESCRIPTION" +.sp +\fBuclampset\fP sets or retrieves the utilization clamping attributes of an existing \fIPID\fP, or runs \fIcommand\fP with the given attributes. +.sp +Utilization clamping is a new feature added in v5.3. It gives a hint to the scheduler about the allowed range of utilization the task should be operating at. +.sp +The utilization of the task affects frequency selection and task placement. Only schedutil cpufreq governor understands handling util clamp hints at the time of writing. Consult your kernel docs for further info about other cpufreq governors support. +.sp +If you\(cqre running on asymmetric heterogeneous system like Arm\(cqs big.LITTLE. Utilization clamping can help bias task placement. If the task is boosted such that \fIutil_min\fP value is higher than the little cores\(aq capacity, then the scheduler will do its best to place it on a big core. +.sp +Similarly, if \fIutil_max\fP is smaller than or equal the capacity of the little cores, then the scheduler can still choose to place it there even if the actual utilization of the task is at max. +.sp +Setting a task\(cqs \fIuclamp_min\fP to a none zero value will effectively boost the task as when it runs it\(cqll always start from this utilization value. +.sp +By setting a task\(cqs \fIuclamp_max\fP below 1024, this will effectively cap the task as when it runs it\(cqll never be able to go above this utilization value. +.sp +The full utilization range is: [0:1024]. The special value \-1 is used to reset to system\(cqs default. +.SH "OPTIONS" +.sp +\fB\-m\fP +.RS 4 +Set \fIutil_min\fP value. +.RE +.sp +\fB\-M\fP +.RS 4 +Set \fIutil_max\fP value. +.RE +.sp +\fB\-a\fP, \fB\-\-all\-tasks\fP +.RS 4 +Set or retrieve the utilization clamping attributes of all the tasks (threads) for a given PID. +.RE +.sp +\fB\-p\fP, \fB\-\-pid\fP +.RS 4 +Operate on an existing PID and do not launch a new task. +.RE +.sp +\fB\-s\fP, \fB\-\-system\fP +.RS 4 +Set or retrieve the system\-wide utilization clamping attributes. +.RE +.sp +\fB\-R\fP, \fB\-\-reset\-on\-fork\fP +.RS 4 +Set \fBSCHED_FLAG_RESET_ON_FORK\fP flag. +.RE +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Show status information. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "USAGE" +.sp +The default behavior is to run a new command: +.RS 4 +\fBuclampset\fP \fI[\-m uclamp_min]\fP \fI[\-M uclamp_max]\fP \fIcommand\fP [\fIarguments\fP] +.RE +.sp +You can also retrieve the utilization clamping attributes of an existing task: +.RS 4 +\fBuclampset \-p\fP \fIPID\fP +.RE +.sp +Or set them: +.RS 4 +\fBuclampset \-p\fP \fIPID\fP \fI[\-m uclamp_min]\fP \fI[\-M uclamp_max]\fP +.RE +.sp +Or control the system\-wide attributes: +.RS 4 +\fBuclampset \-s\fP \fI[\-m uclamp_min]\fP \fI[\-M uclamp_max]\fP +.RE +.SH "PERMISSIONS" +.sp +A user must possess \fBCAP_SYS_NICE\fP to change the scheduling attributes of a process. Any user can retrieve the scheduling information. +.SH "NOTES" +.sp +The system wide utilization clamp attributes are there to control the \fIallowed\fP range the tasks can use. By default both \fIuclamp_min\fP and \fIuclamp_max\fP are set to 1024. This means users can set the utilization clamp values for their task across the full range [0:1024]. +.sp +For example: +.RS 4 +\fBuclampset \-s\fP \f(CR\-m 512\fP \f(CR\-M 700\fP +.RE +.sp +will prevent any task from being boosted higher than 512. And all tasks in the systems are capped to a utilization of 700. Effectively rendering the maximum performance of the system to 700. +.sp +Consult your kernel docs for the exact expected behavior on that kernel. +.SH "AUTHORS" +.sp +.MTO "qais.yousef\(atarm.com" "Qais Yousef" "" +.SH "SEE ALSO" +.sp +\fBnice\fP(1), +\fBrenice\fP(1), +\fBtaskset\fP(1), +\fBsched\fP(7) +.sp +See \fBsched_setscheduler\fP(2) and \fBsched_setattr\fP(2) for a description of the Linux scheduling scheme. +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBuclampset\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "." \ No newline at end of file diff --git a/schedutils/uclampset.1.adoc b/schedutils/uclampset.1.adoc new file mode 100644 index 0000000..62295d3 --- /dev/null +++ b/schedutils/uclampset.1.adoc @@ -0,0 +1,141 @@ +//po4a: entry man manual +//// +uclampset(1) manpage + +Copyright (C) 2020-2021 Qais Yousef +Copyright (C) 2020-2021 Arm Ltd + +This is free documentation; 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. + +The GNU General Public License's references to "object code" +and "executables" are to be interpreted as the output of any +document formatting or typesetting system, including +intermediate and printed output. + +This manual 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. +//// += uclampset(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: uclampset +:colon: : + +== NAME + +uclampset - +manipulate the utilization clamping attributes of the system or a process + +== SYNOPSIS + +*uclampset* [options] [*-m* _uclamp_min_] [*-M* _uclamp_max] _command argument_ + +*uclampset* [options] [*-m* _uclamp_min_] [*-M* _uclamp_max_] *-p* _PID_ + +== DESCRIPTION + +*uclampset* sets or retrieves the utilization clamping attributes of an existing _PID_, or runs _command_ with the given attributes. + +Utilization clamping is a new feature added in v5.3. It gives a hint to the scheduler about the allowed range of utilization the task should be operating at. + +The utilization of the task affects frequency selection and task placement. Only schedutil cpufreq governor understands handling util clamp hints at the time of writing. Consult your kernel docs for further info about other cpufreq governors support. + +If you're running on asymmetric heterogeneous system like Arm's big.LITTLE. Utilization clamping can help bias task placement. If the task is boosted such that _util_min_ value is higher than the little cores' capacity, then the scheduler will do its best to place it on a big core. + +Similarly, if _util_max_ is smaller than or equal the capacity of the little cores, then the scheduler can still choose to place it there even if the actual utilization of the task is at max. + +Setting a task's _uclamp_min_ to a none zero value will effectively boost the task as when it runs it'll always start from this utilization value. + +By setting a task's _uclamp_max_ below 1024, this will effectively cap the task as when it runs it'll never be able to go above this utilization value. + +The full utilization range is: [0:1024]. The special value -1 is used to reset to system's default. + +== OPTIONS + +*-m*:: +Set _util_min_ value. + +*-M*:: +Set _util_max_ value. + +*-a*, *--all-tasks*:: +Set or retrieve the utilization clamping attributes of all the tasks (threads) for a given PID. + +*-p*, *--pid*:: +Operate on an existing PID and do not launch a new task. + +*-s*, *--system*:: +Set or retrieve the system-wide utilization clamping attributes. + +*-R*, *--reset-on-fork*:: +Set *SCHED_FLAG_RESET_ON_FORK* flag. + +*-v*, *--verbose*:: +Show status information. + +include::man-common/help-version.adoc[] + +== USAGE + +//TRANSLATORS: Keep {colon} untranslated. +The default behavior is to run a new command{colon}:: +*uclampset* _[-m uclamp_min]_ _[-M uclamp_max]_ _command_ [_arguments_] + +//TRANSLATORS: Keep {colon} untranslated. +You can also retrieve the utilization clamping attributes of an existing task{colon}:: +*uclampset -p* _PID_ + +//TRANSLATORS: Keep {colon} untranslated. +Or set them{colon}:: +*uclampset -p* _PID_ _[-m uclamp_min]_ _[-M uclamp_max]_ + +//TRANSLATORS: Keep {colon} untranslated. +Or control the system-wide attributes{colon}:: +*uclampset -s* _[-m uclamp_min]_ _[-M uclamp_max]_ + +== PERMISSIONS + +A user must possess *CAP_SYS_NICE* to change the scheduling attributes of a process. Any user can retrieve the scheduling information. + +== NOTES + +The system wide utilization clamp attributes are there to control the _allowed_ range the tasks can use. By default both _uclamp_min_ and _uclamp_max_ are set to 1024. This means users can set the utilization clamp values for their task across the full range [0:1024]. + +//TRANSLATORS: Keep {colon} untranslated. +For example{colon}:: +*uclampset -s* `-m 512` `-M 700` + +will prevent any task from being boosted higher than 512. And all tasks in the systems are capped to a utilization of 700. Effectively rendering the maximum performance of the system to 700. + +Consult your kernel docs for the exact expected behavior on that kernel. + +== AUTHORS + +mailto:qais.yousef@arm.com[Qais Yousef] + +== SEE ALSO + +*nice*(1), +*renice*(1), +*taskset*(1), +*sched*(7) + +See *sched_setscheduler*(2) and *sched_setattr*(2) for a description of the Linux scheduling scheme. + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/schedutils/uclampset.c b/schedutils/uclampset.c new file mode 100644 index 0000000..0de806a --- /dev/null +++ b/schedutils/uclampset.c @@ -0,0 +1,338 @@ +/* + * 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 +#include +#include +#include +#include + +#include "closestream.h" +#include "path.h" +#include "pathnames.h" +#include "procfs.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; + + fputs(USAGE_HEADER, out); + fprintf(out, + _(" %1$s [options]\n" + " %1$s [options] --pid | --system | ...\n"), + program_invocation_short_name); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Show or change the utilization clamping attributes.\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); + + printf(USAGE_HELP_OPTIONS(22)); + + fputs(USAGE_SEPARATOR, out); + fputs(_("Utilization value range is [0:1024]. Use special -1 value to " + "reset to system's default.\n"), out); + + 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 = pid_get_cmdname(pid); + + printf(_("%s (%d) util_clamp: min: %d max: %d\n"), + comm ? : "unknown", 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) { + DIR *sub = NULL; + pid_t tid; + struct path_cxt *pc = ul_new_procfs_path(ctl->pid, NULL); + + if (!pc) + err(EXIT_FAILURE, _("cannot obtain the list of tasks")); + + while (procfs_process_next_tid(pc, &sub, &tid) == 0) + show_uclamp_pid_info(tid, NULL); + + ul_unref_path(pc); + } 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) { + DIR *sub = NULL; + pid_t tid; + struct path_cxt *pc = ul_new_procfs_path(ctl->pid, NULL); + + if (!pc) + err(EXIT_FAILURE, _("cannot obtain the list of tasks")); + + while (procfs_process_next_tid(pc, &sub, &tid) == 0) { + if (set_uclamp_one(ctl, tid) == -1) + err(EXIT_FAILURE, _("failed to set tid %d's uclamp values"), tid); + } + ul_unref_path(pc); + + } 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")); + 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); + break; + case 'M': + ctl->util_max = strtos32_or_err(optarg, _("invalid util_max argument")); + ctl->util_max_set = 1; + validate_util(ctl->util_max); + 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; +} -- cgit v1.2.3