diff --git a/src/build.cc b/src/build.cc index fb29732a54..fbd6327986 100644 --- a/src/build.cc +++ b/src/build.cc @@ -606,7 +606,8 @@ size_t RealCommandRunner::CanRunMore() const { bool RealCommandRunner::StartCommand(Edge* edge) { string command = edge->EvaluateCommand(); - Subprocess* subproc = subprocs_.Add(command, edge->use_console()); + Subprocess* subproc = + subprocs_.Add(command, config_.subprocess_priority, edge->use_console()); if (!subproc) return false; subproc_to_edge_.insert(make_pair(subproc, edge)); diff --git a/src/build.h b/src/build.h index 471f0b2caa..3f5462aeb7 100644 --- a/src/build.h +++ b/src/build.h @@ -15,6 +15,7 @@ #ifndef NINJA_BUILD_H_ #define NINJA_BUILD_H_ +#include #include #include #include @@ -22,8 +23,8 @@ #include #include "depfile_parser.h" -#include "graph.h" #include "exit_status.h" +#include "graph.h" #include "util.h" // int64_t struct BuildLog; @@ -165,8 +166,9 @@ struct CommandRunner { /// Options (e.g. verbosity, parallelism) passed to a build. struct BuildConfig { - BuildConfig() : verbosity(NORMAL), dry_run(false), parallelism(1), - failures_allowed(1), max_load_average(-0.0f) {} + BuildConfig() + : verbosity(NORMAL), dry_run(false), parallelism(1), failures_allowed(1), + subprocess_priority(INT_MIN), max_load_average(-0.0f) {} enum Verbosity { QUIET, // No output -- used when testing. @@ -178,6 +180,7 @@ struct BuildConfig { bool dry_run; int parallelism; int failures_allowed; + int subprocess_priority; /// The maximum load average we must not exceed. A negative value /// means that we do not have any limit. double max_load_average; diff --git a/src/ninja.cc b/src/ninja.cc index 2902359f15..75675743ce 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -214,29 +214,41 @@ struct Tool { /// Print usage information. void Usage(const BuildConfig& config) { - fprintf(stderr, -"usage: ninja [options] [targets...]\n" -"\n" -"if targets are unspecified, builds the 'default' target (see manual).\n" -"\n" -"options:\n" -" --version print ninja version (\"%s\")\n" -" -v, --verbose show all command lines while building\n" -" --quiet don't show progress status, just command output\n" -"\n" -" -C DIR change to DIR before doing anything else\n" -" -f FILE specify input build file [default=build.ninja]\n" -"\n" -" -j N run N jobs in parallel (0 means infinity) [default=%d on this system]\n" -" -k N keep going until N jobs fail (0 means infinity) [default=1]\n" -" -l N do not start new jobs if the load average is greater than N\n" -" -n dry run (don't run commands but act like they succeeded)\n" -"\n" -" -d MODE enable debugging (use '-d list' to list modes)\n" -" -t TOOL run a subtool (use '-t list' to list subtools)\n" -" terminates toplevel options; further flags are passed to the tool\n" -" -w FLAG adjust warnings (use '-w list' to list warnings)\n", - kNinjaVersion, config.parallelism); + fprintf( + stderr, + "usage: ninja [options] [targets...]\n" + "\n" + "if targets are unspecified, builds the 'default' target (see manual).\n" + "\n" + "options:\n" + " --version print ninja version (\"%s\")\n" + " -v, --verbose show all command lines while building\n" + " --quiet don't show progress status, just command output\n" + "\n" + " -C DIR change to DIR before doing anything else\n" + " -f FILE specify input build file [default=build.ninja]\n" + "\n" + " -j N run N jobs in parallel (0 means infinity) [default=%d " + "on this system]\n" + " -k N keep going until N jobs fail (0 means infinity) " + "[default=1]\n" + " -l N do not start new jobs if the load average is greater " + "than N\n" + " -n dry run (don't run commands but act like they " + "succeeded)\n" + " -p PRIORITY set the priority/niceness of spawned processes\n" + " the range of possible values is platform dependent:\n" + " - POSIX: In -20..19 range, and -20..20 on some " + "systems, see 'man setpriority'n" + " - Windows: between -20 (highest priority) and 20 " + "(lowest priority)\n" + "\n" + " -d MODE enable debugging (use '-d list' to list modes)\n" + " -t TOOL run a subtool (use '-t list' to list subtools)\n" + " terminates toplevel options; further flags are passed " + "to the tool\n" + " -w FLAG adjust warnings (use '-w list' to list warnings)\n", + kNinjaVersion, config.parallelism); } /// Choose a default value for the -j (parallelism) flag. @@ -1447,7 +1459,7 @@ int ReadFlags(int* argc, char*** argv, int opt; while (!options->tool && - (opt = getopt_long(*argc, *argv, "d:f:j:k:l:nt:vw:C:h", kLongOptions, + (opt = getopt_long(*argc, *argv, "d:f:j:k:l:p:nt:vw:C:h", kLongOptions, NULL)) != -1) { switch (opt) { case 'd': @@ -1492,6 +1504,15 @@ int ReadFlags(int* argc, char*** argv, case 'n': config->dry_run = true; break; + case 'p': { + char* end; + int value = strtol(optarg, &end, 10); + if (*end != 0) + Fatal("-p parameter not numeric: did you mean -p 0?"); + + config->subprocess_priority = value; + break; + } case 't': options->tool = ChooseTool(optarg); if (!options->tool) diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc index 8e785406c9..c8de12c23d 100644 --- a/src/subprocess-posix.cc +++ b/src/subprocess-posix.cc @@ -12,17 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "subprocess.h" - -#include #include #include #include -#include +#include +#include #include #include +#include +#include +#include #include -#include +#include + +#include "subprocess.h" #if defined(USE_PPOLL) #include @@ -48,7 +51,8 @@ Subprocess::~Subprocess() { Finish(); } -bool Subprocess::Start(SubprocessSet* set, const string& command) { +bool Subprocess::Start(SubprocessSet* set, const string& command, + int priority) { int output_pipe[2]; if (pipe(output_pipe) < 0) Fatal("pipe: %s", strerror(errno)); @@ -123,6 +127,13 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { if (err != 0) Fatal("posix_spawn: %s", strerror(err)); + if (priority != INT_MIN) { + err = setpriority(PRIO_PROCESS, pid_, priority); + if (err != 0 && err != ESRCH) { + Fatal("setpriority: %s", strerror(errno)); + } + } + err = posix_spawnattr_destroy(&attr); if (err != 0) Fatal("posix_spawnattr_destroy: %s", strerror(err)); @@ -238,9 +249,10 @@ SubprocessSet::~SubprocessSet() { Fatal("sigprocmask: %s", strerror(errno)); } -Subprocess *SubprocessSet::Add(const string& command, bool use_console) { +Subprocess* SubprocessSet::Add(const string& command, int priority, + bool use_console) { Subprocess *subprocess = new Subprocess(use_console); - if (!subprocess->Start(this, command)) { + if (!subprocess->Start(this, command, priority)) { delete subprocess; return 0; } diff --git a/src/subprocess-win32.cc b/src/subprocess-win32.cc index ff3baaca7f..868375b525 100644 --- a/src/subprocess-win32.cc +++ b/src/subprocess-win32.cc @@ -74,7 +74,22 @@ HANDLE Subprocess::SetupPipe(HANDLE ioport) { return output_write_child; } -bool Subprocess::Start(SubprocessSet* set, const string& command) { +static DWORD POSIXPriorityToWin32(int unixPriority) { + if (unixPriority <= -20) { + return HIGH_PRIORITY_CLASS; // ..-20 + } else if (unixPriority < 0) { + return ABOVE_NORMAL_PRIORITY_CLASS; // -19..-1 + } else if (unixPriority == 0) { + return NORMAL_PRIORITY_CLASS; // 0 + } else if (unixPriority < 20) { + return BELOW_NORMAL_PRIORITY_CLASS; // 1..19 + } else { + return IDLE_PRIORITY_CLASS; // 20.. + } +} + +bool Subprocess::Start(SubprocessSet* set, const string& command, + int priority) { HANDLE child_pipe = SetupPipe(set->ioport_); SECURITY_ATTRIBUTES security_attributes; @@ -106,6 +121,8 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { // Ninja handles ctrl-c, except for subprocesses in console pools. DWORD process_flags = use_console_ ? 0 : CREATE_NEW_PROCESS_GROUP; + if (priority != INT_MIN) + process_flags |= POSIXPriorityToWin32(priority); // Do not prepend 'cmd /c' on Windows, this breaks command // lines greater than 8,191 chars. @@ -238,9 +255,10 @@ BOOL WINAPI SubprocessSet::NotifyInterrupted(DWORD dwCtrlType) { return FALSE; } -Subprocess *SubprocessSet::Add(const string& command, bool use_console) { +Subprocess* SubprocessSet::Add(const string& command, int priority, + bool use_console) { Subprocess *subprocess = new Subprocess(use_console); - if (!subprocess->Start(this, command)) { + if (!subprocess->Start(this, command, priority)) { delete subprocess; return 0; } diff --git a/src/subprocess.h b/src/subprocess.h index 9e3d2ee98f..a8f7f9793f 100644 --- a/src/subprocess.h +++ b/src/subprocess.h @@ -15,9 +15,11 @@ #ifndef NINJA_SUBPROCESS_H_ #define NINJA_SUBPROCESS_H_ +#include + +#include #include #include -#include #ifdef _WIN32 #include @@ -52,7 +54,13 @@ struct Subprocess { private: Subprocess(bool use_console); - bool Start(struct SubprocessSet* set, const std::string& command); + // priority: + // - INT_MIN denotes no intended priority change + // - The range of possible values is platform dependent + // - POSIX: typically between -20 and 19 but can be changed by RLIMIT_NICE + // - Windows: between -20 (highest priority) and 20 (lowest priority) + bool Start(struct SubprocessSet* set, const std::string& command, + int priority); void OnPipeReady(); std::string buf_; @@ -83,7 +91,9 @@ struct SubprocessSet { SubprocessSet(); ~SubprocessSet(); - Subprocess* Add(const std::string& command, bool use_console = false); + // priority is forwarded to Subprocess::Start + Subprocess* Add(const std::string& command, int priority = INT_MIN, + bool use_console = false); bool DoWork(); Subprocess* NextFinished(); void Clear(); diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc index 073fe86931..16e9e0c130 100644 --- a/src/subprocess_test.cc +++ b/src/subprocess_test.cc @@ -150,7 +150,8 @@ TEST_F(SubprocessTest, Console) { // Skip test if we don't have the console ourselves. if (isatty(0) && isatty(1) && isatty(2)) { Subprocess* subproc = - subprocs_.Add("test -t 0 -a -t 1 -a -t 2", /*use_console=*/true); + subprocs_.Add("test -t 0 -a -t 1 -a -t 2", /*priority=*/INT_MIN, + /*use_console=*/true); ASSERT_NE((Subprocess*)0, subproc); while (!subproc->Done()) { @@ -161,6 +162,20 @@ TEST_F(SubprocessTest, Console) { } } +TEST_F(SubprocessTest, Priority) { + // Field 19 of the stat file is the nice level according to: + // https://www.kernel.org/doc/html/latest/filesystems/proc.html + Subprocess* subproc = + subprocs_.Add("cat /proc/self/stat | cut -d' ' -f19", /*priority=*/10); + ASSERT_NE((Subprocess*)0, subproc); + + while (!subproc->Done()) { + subprocs_.DoWork(); + } + + EXPECT_EQ("10\n", subproc->GetOutput()); +} + #endif TEST_F(SubprocessTest, SetWithSingle) {