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..a8491f84fe 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 @@ -165,8 +166,8 @@ 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 +179,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..1f1493a3a7 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -224,18 +224,22 @@ void Usage(const BuildConfig& config) { " -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" +" -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" +" -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: depdending on your scheduler\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", +" -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); } @@ -1447,7 +1451,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 +1496,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..2654a5054a 100644 --- a/src/subprocess-posix.cc +++ b/src/subprocess-posix.cc @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include #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)); @@ -117,12 +121,37 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { if (err != 0) Fatal("posix_spawnattr_setflags: %s", strerror(err)); + int prev_priority; + if (priority != INT_MIN) { + // We set the priority of our own process so that the spawned child process + // directly inherits the wanted priority. Because of that, we have to save + // our current priority and reset it after the subprocess was spawned. + + errno = 0; + prev_priority = getpriority(PRIO_PROCESS, 0); + if (errno != 0) { + Fatal("getpriority: %s", strerror(errno)); + } + + err = setpriority(PRIO_PROCESS, 0, priority); + if (err != 0) { + Fatal("setpriority: %s", strerror(errno)); + } + } + const char* spawned_args[] = { "/bin/sh", "-c", command.c_str(), NULL }; err = posix_spawn(&pid_, "/bin/sh", &action, &attr, const_cast(spawned_args), environ); if (err != 0) Fatal("posix_spawn: %s", strerror(err)); + if (priority != INT_MIN) { + err = setpriority(PRIO_PROCESS, 0, prev_priority); + if (err != 0) { + Fatal("setpriority: %s", strerror(errno)); + } + } + err = posix_spawnattr_destroy(&attr); if (err != 0) Fatal("posix_spawnattr_destroy: %s", strerror(err)); @@ -238,9 +267,9 @@ 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..ff993f003d 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,9 @@ 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..e8ad70db62 100644 --- a/src/subprocess.h +++ b/src/subprocess.h @@ -18,6 +18,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -52,7 +53,11 @@ struct Subprocess { private: Subprocess(bool use_console); - bool Start(struct SubprocessSet* set, const std::string& command); + // priority: + // - The range of possible values is platform dependent + // - POSIX: variable between scheduling policies between sched_get_priority_min() and sched_get_priority_max() + // - 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 +88,8 @@ struct SubprocessSet { SubprocessSet(); ~SubprocessSet(); - Subprocess* Add(const std::string& command, bool use_console = false); + 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..b77ebcb78d 100644 --- a/src/subprocess_test.cc +++ b/src/subprocess_test.cc @@ -150,7 +150,7 @@ 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 +161,17 @@ TEST_F(SubprocessTest, Console) { } } +TEST_F(SubprocessTest, Priority) { + Subprocess* subproc = subprocs_.Add("ps -o nice -p $$", /*priority=*/10); + ASSERT_NE((Subprocess*)0, subproc); + + while (!subproc->Done()) { + subprocs_.DoWork(); + } + + EXPECT_EQ(" NI\n 10\n", subproc->GetOutput()); +} + #endif TEST_F(SubprocessTest, SetWithSingle) {