1*760c253cSXin Li /*
2*760c253cSXin Li * Copyright 2023 The ChromiumOS Authors
3*760c253cSXin Li * Use of this source code is governed by a BSD-style license that can be
4*760c253cSXin Li * found in the LICENSE file.
5*760c253cSXin Li */
6*760c253cSXin Li #include <err.h>
7*760c253cSXin Li #include <errno.h>
8*760c253cSXin Li #include <linux/ioprio.h>
9*760c253cSXin Li #include <sched.h>
10*760c253cSXin Li #include <stdio.h>
11*760c253cSXin Li #include <stdlib.h>
12*760c253cSXin Li #include <sys/resource.h>
13*760c253cSXin Li #include <sys/syscall.h>
14*760c253cSXin Li #include <sys/sysinfo.h>
15*760c253cSXin Li #include <unistd.h>
16*760c253cSXin Li
17*760c253cSXin Li #include <string_view>
18*760c253cSXin Li
19*760c253cSXin Li static const char description[] = R"(bgtask - `nice -n19` but more.
20*760c253cSXin Li
21*760c253cSXin Li This program exists to set a few process attributes, and exec another program.
22*760c253cSXin Li
23*760c253cSXin Li Intended usage is:
24*760c253cSXin Li $ bgtask ./my_long_running_build --extra-optimizations --and-more
25*760c253cSXin Li
26*760c253cSXin Li Specifically, `bgtask`:
27*760c253cSXin Li - sets its own priority so essentially any other task will take priority, then
28*760c253cSXin Li - sets its I/O priority so any regular task will take priority, then
29*760c253cSXin Li - sets its CPU mask so it may only run on 9/10ths of your cores (this step is
30*760c253cSXin Li skipped if you've already got a more restrictive mask), then
31*760c253cSXin Li - execs the command you gave it.
32*760c253cSXin Li )";
33*760c253cSXin Li
print_help_and_exit(int exit_code)34*760c253cSXin Li static void print_help_and_exit(int exit_code) {
35*760c253cSXin Li fputs(description, stderr);
36*760c253cSXin Li exit(exit_code);
37*760c253cSXin Li }
38*760c253cSXin Li
deprioritize_nice_or_warn()39*760c253cSXin Li static void deprioritize_nice_or_warn() {
40*760c253cSXin Li constexpr int current_process = 0;
41*760c253cSXin Li constexpr int max_nice_priority = 19;
42*760c253cSXin Li if (setpriority(PRIO_PROCESS, current_process, max_nice_priority)) {
43*760c253cSXin Li warn("setting priority failed");
44*760c253cSXin Li }
45*760c253cSXin Li }
46*760c253cSXin Li
deprioritize_io_or_warn()47*760c253cSXin Li static void deprioritize_io_or_warn() {
48*760c253cSXin Li // Glibc provides no wrapper here.
49*760c253cSXin Li constexpr int current_process = 0;
50*760c253cSXin Li const int background_io_prio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0);
51*760c253cSXin Li if (syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, current_process,
52*760c253cSXin Li background_io_prio) == -1) {
53*760c253cSXin Li warn("setting ioprio failed");
54*760c253cSXin Li }
55*760c253cSXin Li }
56*760c253cSXin Li
restrict_cpu_mask_or_warn()57*760c253cSXin Li static void restrict_cpu_mask_or_warn() {
58*760c253cSXin Li const int available_cpus = get_nprocs();
59*760c253cSXin Li // Use 9/10ths of CPUs, as requested in the mask.
60*760c253cSXin Li const int cpus_to_use = (available_cpus * 9) / 10;
61*760c253cSXin Li // Erm, single-core systems need not apply.
62*760c253cSXin Li if (cpus_to_use == 0) {
63*760c253cSXin Li return;
64*760c253cSXin Li }
65*760c253cSXin Li
66*760c253cSXin Li // Note pid==0 means "the current thread," for sched_*affinity calls.
67*760c253cSXin Li constexpr int current_process = 0;
68*760c253cSXin Li cpu_set_t current_mask;
69*760c253cSXin Li CPU_ZERO(¤t_mask);
70*760c253cSXin Li if (sched_getaffinity(current_process, sizeof(current_mask), ¤t_mask) ==
71*760c253cSXin Li -1) {
72*760c253cSXin Li if (errno == EINVAL) {
73*760c253cSXin Li // This can only happen if a machine has >1K cores. That can be handled,
74*760c253cSXin Li // but is extra complexity that I can't test & isn't expected to
75*760c253cSXin Li // realistically be a problem in the next few years.
76*760c253cSXin Li warnx("statically-allocated cpu affinity mask is too small");
77*760c253cSXin Li } else {
78*760c253cSXin Li warn("sched_getaffinity failed");
79*760c253cSXin Li }
80*760c253cSXin Li return;
81*760c253cSXin Li }
82*760c253cSXin Li
83*760c253cSXin Li const int cpus_in_current_mask = CPU_COUNT(¤t_mask);
84*760c253cSXin Li int cpus_to_disable = cpus_in_current_mask - cpus_to_use;
85*760c253cSXin Li if (cpus_to_disable <= 0) {
86*760c253cSXin Li // Don't warn; this probably isn't useful informtion to the user.
87*760c253cSXin Li return;
88*760c253cSXin Li }
89*760c253cSXin Li
90*760c253cSXin Li for (int i = 0; i < available_cpus; ++i) {
91*760c253cSXin Li if (CPU_ISSET(i, ¤t_mask)) {
92*760c253cSXin Li CPU_CLR(i, ¤t_mask);
93*760c253cSXin Li --cpus_to_disable;
94*760c253cSXin Li if (cpus_to_disable == 0) {
95*760c253cSXin Li break;
96*760c253cSXin Li }
97*760c253cSXin Li }
98*760c253cSXin Li }
99*760c253cSXin Li
100*760c253cSXin Li if (cpus_to_disable != 0) {
101*760c253cSXin Li warnx("Internal error: iterated through CPU mask but had %d CPUs left to "
102*760c253cSXin Li "disable.",
103*760c253cSXin Li cpus_to_disable);
104*760c253cSXin Li return;
105*760c253cSXin Li }
106*760c253cSXin Li
107*760c253cSXin Li if (sched_setaffinity(current_process, sizeof(current_mask), ¤t_mask) ==
108*760c253cSXin Li -1) {
109*760c253cSXin Li warn("sched_setaffinity failed");
110*760c253cSXin Li }
111*760c253cSXin Li }
112*760c253cSXin Li
main(int argc,char ** argv)113*760c253cSXin Li __attribute__((noreturn)) int main(int argc, char **argv) {
114*760c253cSXin Li if (argc == 1) {
115*760c253cSXin Li print_help_and_exit(/*exit_code=*/1);
116*760c253cSXin Li }
117*760c253cSXin Li
118*760c253cSXin Li std::string_view argv1 = argv[1];
119*760c253cSXin Li // Do minimal option parsing, since the user is likely to be passing `-flags`
120*760c253cSXin Li // and `--flags` to the program they're invoking.
121*760c253cSXin Li if (argv1 == "-h" || argv1 == "--help") {
122*760c253cSXin Li print_help_and_exit(/*exit_code=*/0);
123*760c253cSXin Li }
124*760c253cSXin Li
125*760c253cSXin Li deprioritize_nice_or_warn();
126*760c253cSXin Li deprioritize_io_or_warn();
127*760c253cSXin Li restrict_cpu_mask_or_warn();
128*760c253cSXin Li
129*760c253cSXin Li execvp(argv[1], &argv[1]);
130*760c253cSXin Li err(1, "execvp failed");
131*760c253cSXin Li }
132