blob: 378edc7b53f2136872db3ed803cf018875fb8a0c [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "status.h"
#include <assert.h>
#include <stdarg.h>
#include <stdlib.h>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#undef min // min() is defined as a macro in minwindef.h
#endif
#include "async_loop.h"
#include "build_config.h"
#include "debug_flags.h"
using namespace std;
namespace {
void StringAppendRate(std::string& str, double rate) {
if (rate == -1)
str.push_back('?');
else
StringAppendFormat(str, "%1.f", rate);
}
} // namespace
StatusPrinter::StatusPrinter(const BuildConfig& config)
: config_(config), started_edges_(0),
finished_edges_(0), total_edges_(0), running_edges_(0), time_millis_(0),
current_rate_(config.parallelism), format_(config_.status_format()) {
// LinePrinter constructor uses these environment variables to determine
// properties of the current terminal.
printer_.Reset(config.environment.Get("TERM"),
config.environment.Get("CLICOLOR_FORCE"));
// Don't do anything fancy in verbose mode.
if (config_.verbosity != BuildConfig::NORMAL)
printer_.set_smart_terminal(false);
if (printer_.is_smart_terminal() && !config_.dry_run) {
max_pending_height_ = config_.status_max_commands();
// Since elapsed times are displayed to the first decimal digit
// only, there is no point in using a value smaller than 0.1 seconds
// for the refresh timeout.
const int64_t minimal_refresh_timeout_ms = 100;
int64_t config_refresh = config_.status_refresh_millis();
if (config_refresh && config_refresh > minimal_refresh_timeout_ms)
refresh_timeout_ms_ = config_refresh;
else
refresh_timeout_ms_ = minimal_refresh_timeout_ms;
}
}
StatusPrinter::~StatusPrinter() = default;
void StatusPrinter::PlanHasTotalEdges(int total) {
total_edges_ = total;
}
void StatusPrinter::BuildEdgeStarted(const Edge* edge) {
int64_t start_time_millis = GetCurrentBuildTimeMs();
++started_edges_;
++running_edges_;
time_millis_ = start_time_millis;
pending_edges_[edge] = start_time_millis;
if (edge->use_console()) {
// This command will print its output directly to stdout, so
// clear the pending edges to let Ninja update the status and
// lock the line printer.
DisableTimer();
ClearPendingEdges();
PrintStatus(edge, start_time_millis);
printer_.SetConsoleLocked(true);
} else if (printer_.is_smart_terminal()) {
PrintStatus(edge, start_time_millis);
BuildRefresh(start_time_millis);
}
}
void StatusPrinter::BuildEdgeFinished(Edge* edge, bool success,
const string& output) {
int64_t end_time_millis = GetCurrentBuildTimeMs();
time_millis_ = end_time_millis;
++finished_edges_;
auto it = pending_edges_.find(edge);
assert(it != pending_edges_.end());
pending_edges_.erase(it);
--running_edges_;
if (edge->use_console()) {
printer_.SetConsoleLocked(false);
EnableTimer();
}
if (config_.verbosity == BuildConfig::QUIET)
return;
if (!edge->use_console()) {
PrintStatus(edge, end_time_millis);
if (success && output.empty()) {
BuildRefresh(end_time_millis);
} else {
// Ninja is going to print something so clear
// any pending edges first.
ClearPendingEdges();
}
}
// Print the command that is spewing before printing its output.
if (!success) {
string outputs;
for (vector<Node*>::const_iterator o = edge->outputs_.begin();
o != edge->outputs_.end(); ++o)
outputs += (*o)->path() + " ";
if (printer_.supports_color()) {
printer_.PrintOnNewLine("\x1B[31m" "FAILED: " "\x1B[0m" + outputs + "\n");
} else {
printer_.PrintOnNewLine("FAILED: " + outputs + "\n");
}
printer_.PrintOnNewLine(edge->EvaluateCommand() + "\n");
}
if (!output.empty()) {
// ninja sets stdout and stderr of subprocesses to a pipe, to be able to
// check if the output is empty. Some compilers, e.g. clang, check
// isatty(stderr) to decide if they should print colored output.
// To make it possible to use colored output with ninja, subprocesses should
// be run with a flag that forces them to always print color escape codes.
// To make sure these escape codes don't show up in a file if ninja's output
// is piped to a file, ninja strips ansi escape codes again if it's not
// writing to a |smart_terminal_|.
// (Launching subprocesses in pseudo ttys doesn't work because there are
// only a few hundred available on some systems, and ninja can launch
// thousands of parallel compile commands.)
string final_output;
if (!printer_.supports_color())
final_output = StripAnsiEscapeCodes(output);
else
final_output = output;
#ifdef _WIN32
// Fix extra CR being added on Windows, writing out CR CR LF (#773)
_setmode(_fileno(stdout), _O_BINARY); // Begin Windows extra CR fix
#endif
printer_.PrintOnNewLine(final_output);
#ifdef _WIN32
_setmode(_fileno(stdout), _O_TEXT); // End Windows extra CR fix
#endif
}
}
void StatusPrinter::BuildLoadDyndeps() {
// The DependencyScan calls EXPLAIN() to print lines explaining why
// it considers a portion of the graph to be out of date. Normally
// this is done before the build starts, but our caller is about to
// load a dyndep file during the build. Doing so may generate more
// explanation lines (via fprintf directly to stderr), but in an
// interactive console the cursor is currently at the end of a status
// line. Start a new line so that the first explanation does not
// append to the status line. After the explanations are done a
// new build status line will appear.
if (g_explaining) {
ClearPendingEdges();
printer_.PrintOnNewLine("");
}
}
void StatusPrinter::DisableTimer() {
if (timer_)
timer_.Cancel();
}
void StatusPrinter::EnableTimer() {
if (timer_)
timer_.SetDurationMs(refresh_timeout_ms_);
}
int64_t StatusPrinter::GetCurrentBuildTimeMs() const {
return AsyncLoop::NowMs() - start_build_time_ms_;
}
void StatusPrinter::BuildStarted() {
started_edges_ = 0;
finished_edges_ = 0;
running_edges_ = 0;
if (refresh_timeout_ms_ > 0) {
if (!timer_) {
start_build_time_ms_ = AsyncLoop::NowMs();
timer_ = AsyncTimer::CreateWithDuration(
refresh_timeout_ms_, AsyncLoop::Get(), [this] {
int64_t cur_time_ms = GetCurrentBuildTimeMs();
BuildRefresh(cur_time_ms);
timer_.SetDurationMs(refresh_timeout_ms_);
});
}
EnableTimer();
}
// Reset last_update_time_ms_ because this function can be called
// multiple times, but with different starting epochs, which can
// confuse the logic in this code. As an example: Ninja starts
// a build then decides to launch the regenerator, which takes
// about 5 seconds to generate a new build plan. Ninja then starts
// another build. Without the reset, pending commands will not be
// displayed for about 5 seconds after the start of the second
// build!
last_update_time_ms_ = -1;
}
void StatusPrinter::BuildFinished() {
if (timer_) {
timer_.Close();
}
start_build_time_ms_ = 0;
printer_.SetConsoleLocked(false);
ClearPendingEdges();
printer_.PrintOnNewLine("");
}
string StatusPrinter::FormatProgressStatus(const char* progress_status_format,
int64_t time_millis) const {
string out;
for (const char* s = progress_status_format; *s != '\0'; ++s) {
if (*s == '%') {
++s;
switch (*s) {
case '%':
out.push_back('%');
break;
// Started edges.
case 's':
StringAppendFormat(out, "%d", started_edges_);
break;
// Total edges.
case 't':
StringAppendFormat(out, "%d", total_edges_);
break;
// Running edges.
case 'r':
StringAppendFormat(out, "%d", running_edges_);
break;
// Unstarted edges.
case 'u':
StringAppendFormat(out, "%d", total_edges_ - started_edges_);
break;
// Finished edges.
case 'f':
StringAppendFormat(out, "%d", finished_edges_);
break;
// Overall finished edges per second.
case 'o':
StringAppendRate(out, finished_edges_ / (time_millis_ / 1e3));
break;
// Current rate, average over the last '-j' jobs.
case 'c':
current_rate_.UpdateRate(finished_edges_, time_millis_);
StringAppendRate(out, current_rate_.rate());
break;
// Percentage
case 'p': {
int percent = (100 * finished_edges_) / total_edges_;
StringAppendFormat(out, "%3i%%", percent);
break;
}
case 'e':
StringAppendFormat(out, "%.3f", time_millis_ / 1e3);
break;
default:
Fatal("unknown placeholder '%%%c' in $NINJA_STATUS", *s);
return "";
}
} else {
out.push_back(*s);
}
}
return out;
}
void StatusPrinter::PrintStatus(const Edge* edge, int64_t time_millis) {
if (config_.verbosity == BuildConfig::QUIET
|| config_.verbosity == BuildConfig::NO_STATUS_UPDATE)
return;
bool force_full_command = config_.verbosity == BuildConfig::VERBOSE;
string to_print = edge->GetBinding("description");
if (to_print.empty() || force_full_command)
to_print = edge->GetBinding("command");
to_print = FormatProgressStatus(format_.c_str(), time_millis) +
to_print;
last_status_ = std::move(to_print);
printer_.Print(last_status_,
force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
}
void StatusPrinter::BuildRefresh(int64_t cur_time_ms) {
if (last_update_time_ms_ >= 0) {
int64_t since_last_ms = cur_time_ms - last_update_time_ms_;
if (since_last_ms < refresh_timeout_ms_) {
// No need to update more than necessary when tasks complete
// really really fast.
return;
}
}
last_update_time_ms_ = cur_time_ms;
PrintPendingEdges(cur_time_ms);
}
void StatusPrinter::ClearPendingEdges() {
if (last_pending_height_ == 0)
return;
// repeat "go down 1 line; erase whole line" |last_height_| times.
for (size_t n = 0; n < last_pending_height_; ++n) {
printf("\x1B[1B\x1B[2K");
}
// move up |last_height_| lines.
printf("\x1B[%dA", static_cast<int>(last_pending_height_));
fflush(stdout);
last_pending_height_ = 0;
}
void StatusPrinter::PrintPendingEdges(int64_t cur_time_ms) {
if (!max_pending_height_)
return;
// Find the N-th older running edges, where N is max_height_.
// Reuse the sorted_pending_edges_ vector between calls.
auto& sorted_edges = sorted_pending_edges_;
sorted_edges.assign(pending_edges_.begin(), pending_edges_.end());
auto less = [](const EdgeInfo& a, const EdgeInfo& b) -> bool {
return a.second < b.second;
};
size_t count = std::min(sorted_edges.size(), max_pending_height_);
std::partial_sort(sorted_edges.begin(), sorted_edges.begin() + count,
sorted_edges.end(), less);
std::string pending_line;
for (size_t n = 0; n < count; ++n) {
EdgeInfo& pair = sorted_edges[n];
// Format the elapsed time in a human friendly format.
std::string elapsed;
int64_t elapsed_ms = cur_time_ms - pair.second;
if (elapsed_ms < 0) {
elapsed = "??????";
} else {
if (elapsed_ms < 60000) {
StringAppendFormat(elapsed, "%d.%ds",
static_cast<int>((elapsed_ms / 1000)),
static_cast<int>((elapsed_ms % 1000) / 100));
} else {
StringAppendFormat(elapsed, "%dm%ds",
static_cast<int>((elapsed_ms / 60000)),
static_cast<int>((elapsed_ms % 60000) / 1000));
}
}
// Get edge description or command.
std::string description = pair.first->GetBinding("description");
if (description.empty())
description = pair.first->GetBinding("command");
// Format '<elapsed> | <description>' where <elapsed> is
// right-justified.
size_t justification_width = 6;
size_t justified_elapsed_width =
std::min(justification_width, elapsed.size());
size_t needed_capacity = justified_elapsed_width + 3 + description.size();
if (needed_capacity > pending_line.capacity())
pending_line.reserve(needed_capacity);
if (elapsed.size() < justification_width) {
pending_line.assign(justification_width - elapsed.size(), ' ');
} else {
pending_line.clear();
}
pending_line.append(elapsed);
pending_line.append(" | ", 3);
pending_line.append(description);
printf("\n");
printer_.Print(pending_line, LinePrinter::ELIDE);
}
// Clear previous lines that are not needed anymore.
size_t next_height = count;
for (; count < last_pending_height_; ++count) {
// Go to next line + clear entire line.
printf("\n\x1B[2K");
}
if (count > 0) {
// Move up to the top status line. Then print the status
// again to reposition the cursor to the right position.
// Note that using ASCII sequences to save/restore the
// cursor position does not work reliably in all terminals
// (and terminal emulators like mosh or asciinema).
printf("\x1B[%dA", static_cast<int>(count));
printer_.Print(last_status_, LinePrinter::ELIDE);
}
last_pending_height_ = next_height;
}
void StatusPrinter::Warning(const char* msg, ...) {
va_list ap;
va_start(ap, msg);
::Warning(msg, ap);
va_end(ap);
}
void StatusPrinter::Error(const char* msg, ...) {
va_list ap;
va_start(ap, msg);
::Error(msg, ap);
va_end(ap);
}
void StatusPrinter::Info(const char* msg, ...) {
va_list ap;
va_start(ap, msg);
::Info(msg, ap);
va_end(ap);
}