xref: /aosp_15_r20/external/toolchain-utils/contrib/gbiv/bgtask/bgtask.cc (revision 760c253c1ed00ce9abd48f8546f08516e57485fe)
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(&current_mask);
70*760c253cSXin Li   if (sched_getaffinity(current_process, sizeof(current_mask), &current_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(&current_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, &current_mask)) {
92*760c253cSXin Li       CPU_CLR(i, &current_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), &current_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