blob: 06f3c20aae2ab88273bb9bb03a78195fd42c154d [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"
#ifdef _WIN32
#include "win32port.h"
#else
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#include <cinttypes>
#endif
#include <stdarg.h>
#include <stdlib.h>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif
#include "debug_flags.h"
using namespace std;
StatusPrinter::StatusPrinter(const BuildConfig& config)
: config_(config), started_edges_(0), finished_edges_(0), total_edges_(0),
running_edges_(0), progress_status_format_(NULL),
current_rate_(config.parallelism) {
// Don't do anything fancy in verbose mode.
if (config_.verbosity != BuildConfig::NORMAL)
printer_.set_smart_terminal(false);
progress_status_format_ = getenv("NINJA_STATUS");
if (!progress_status_format_)
progress_status_format_ = "[%f/%t] ";
}
void StatusPrinter::EdgeAddedToPlan(const Edge* edge) {
++total_edges_;
// Do we know how long did this edge take last time?
if (edge->prev_elapsed_time_millis != -1) {
++eta_predictable_edges_total_;
++eta_predictable_edges_remaining_;
eta_predictable_cpu_time_total_millis_ += edge->prev_elapsed_time_millis;
eta_predictable_cpu_time_remaining_millis_ +=
edge->prev_elapsed_time_millis;
} else
++eta_unpredictable_edges_remaining_;
}
void StatusPrinter::EdgeRemovedFromPlan(const Edge* edge) {
--total_edges_;
// Do we know how long did this edge take last time?
if (edge->prev_elapsed_time_millis != -1) {
--eta_predictable_edges_total_;
--eta_predictable_edges_remaining_;
eta_predictable_cpu_time_total_millis_ -= edge->prev_elapsed_time_millis;
eta_predictable_cpu_time_remaining_millis_ -=
edge->prev_elapsed_time_millis;
} else
--eta_unpredictable_edges_remaining_;
}
void StatusPrinter::BuildEdgeStarted(const Edge* edge,
int64_t start_time_millis) {
++started_edges_;
++running_edges_;
time_millis_ = start_time_millis;
if (edge->use_console() || printer_.is_smart_terminal())
PrintStatus(edge, start_time_millis);
if (edge->use_console())
printer_.SetConsoleLocked(true);
}
void StatusPrinter::RecalculateProgressPrediction() {
time_predicted_percentage_ = 0.0;
// Sometimes, the previous and actual times may be wildly different.
// For example, the previous build may have been fully recovered from ccache,
// so it was blazing fast, while the new build no longer gets hits from ccache
// for whatever reason, so it actually compiles code, which takes much longer.
// We should detect such cases, and avoid using "wrong" previous times.
// Note that we will only use the previous times if there are edges with
// previous time knowledge remaining.
bool use_previous_times = eta_predictable_edges_remaining_ &&
eta_predictable_cpu_time_remaining_millis_;
// Iff we have sufficient statistical information for the current run,
// that is, if we have took at least 15 sec AND finished at least 5% of edges,
// we can check whether our performance so far matches the previous one.
if (use_previous_times && total_edges_ && finished_edges_ &&
(time_millis_ >= 15 * 1e3) &&
(((double)finished_edges_ / total_edges_) >= 0.05)) {
// Over the edges we've just run, how long did they take on average?
double actual_average_cpu_time_millis =
(double)cpu_time_millis_ / finished_edges_;
// What is the previous average, for the edges with such knowledge?
double previous_average_cpu_time_millis =
(double)eta_predictable_cpu_time_total_millis_ /
eta_predictable_edges_total_;
double ratio = std::max(previous_average_cpu_time_millis,
actual_average_cpu_time_millis) /
std::min(previous_average_cpu_time_millis,
actual_average_cpu_time_millis);
// Let's say that the average times should differ by less than 10x
use_previous_times = ratio < 10;
}
int edges_with_known_runtime = finished_edges_;
if (use_previous_times)
edges_with_known_runtime += eta_predictable_edges_remaining_;
if (edges_with_known_runtime == 0)
return;
int edges_with_unknown_runtime = use_previous_times
? eta_unpredictable_edges_remaining_
: (total_edges_ - finished_edges_);
// Given the time elapsed on the edges we've just run,
// and the runtime of the edges for which we know previous runtime,
// what's the edge's average runtime?
int64_t edges_known_runtime_total_millis = cpu_time_millis_;
if (use_previous_times)
edges_known_runtime_total_millis +=
eta_predictable_cpu_time_remaining_millis_;
double average_cpu_time_millis =
(double)edges_known_runtime_total_millis / edges_with_known_runtime;
// For the edges for which we do not have the previous runtime,
// let's assume that their average runtime is the same as for the other edges,
// and we therefore can predict their remaining runtime.
double unpredictable_cpu_time_remaining_millis =
average_cpu_time_millis * edges_with_unknown_runtime;
// And therefore we can predict the remaining and total runtimes.
double total_cpu_time_remaining_millis =
unpredictable_cpu_time_remaining_millis;
if (use_previous_times)
total_cpu_time_remaining_millis +=
eta_predictable_cpu_time_remaining_millis_;
double total_cpu_time_millis =
cpu_time_millis_ + total_cpu_time_remaining_millis;
if (total_cpu_time_millis == 0.0)
return;
// After that we can tell how much work we've completed, in time units.
time_predicted_percentage_ = cpu_time_millis_ / total_cpu_time_millis;
}
void StatusPrinter::BuildEdgeFinished(Edge* edge, int64_t start_time_millis,
int64_t end_time_millis, bool success,
const string& output) {
time_millis_ = end_time_millis;
++finished_edges_;
int64_t elapsed = end_time_millis - start_time_millis;
cpu_time_millis_ += elapsed;
// Do we know how long did this edge take last time?
if (edge->prev_elapsed_time_millis != -1) {
--eta_predictable_edges_remaining_;
eta_predictable_cpu_time_remaining_millis_ -=
edge->prev_elapsed_time_millis;
} else
--eta_unpredictable_edges_remaining_;
if (edge->use_console())
printer_.SetConsoleLocked(false);
if (config_.verbosity == BuildConfig::QUIET)
return;
if (!edge->use_console())
PrintStatus(edge, end_time_millis);
--running_edges_;
// 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)
printer_.PrintOnNewLine("");
}
void StatusPrinter::BuildStarted() {
started_edges_ = 0;
finished_edges_ = 0;
running_edges_ = 0;
}
void StatusPrinter::BuildFinished() {
printer_.SetConsoleLocked(false);
printer_.PrintOnNewLine("");
}
string StatusPrinter::FormatProgressStatus(const char* progress_status_format,
int64_t time_millis) const {
string out;
char buf[32];
for (const char* s = progress_status_format; *s != '\0'; ++s) {
if (*s == '%') {
++s;
switch (*s) {
case '%':
out.push_back('%');
break;
// Started edges.
case 's':
snprintf(buf, sizeof(buf), "%d", started_edges_);
out += buf;
break;
// Total edges.
case 't':
snprintf(buf, sizeof(buf), "%d", total_edges_);
out += buf;
break;
// Running edges.
case 'r': {
snprintf(buf, sizeof(buf), "%d", running_edges_);
out += buf;
break;
}
// Unstarted edges.
case 'u':
snprintf(buf, sizeof(buf), "%d", total_edges_ - started_edges_);
out += buf;
break;
// Finished edges.
case 'f':
snprintf(buf, sizeof(buf), "%d", finished_edges_);
out += buf;
break;
// Overall finished edges per second.
case 'o':
SnprintfRate(finished_edges_ / (time_millis_ / 1e3), buf, "%.1f");
out += buf;
break;
// Current rate, average over the last '-j' jobs.
case 'c':
current_rate_.UpdateRate(finished_edges_, time_millis_);
SnprintfRate(current_rate_.rate(), buf, "%.1f");
out += buf;
break;
// Percentage of edges completed
case 'p': {
int percent = 0;
if (finished_edges_ != 0 && total_edges_ != 0)
percent = (100 * finished_edges_) / total_edges_;
snprintf(buf, sizeof(buf), "%3i%%", percent);
out += buf;
break;
}
#define FORMAT_TIME_HMMSS(t) \
"%" PRId64 ":%02" PRId64 ":%02" PRId64 "", (t) / 3600, ((t) % 3600) / 60, \
(t) % 60
#define FORMAT_TIME_MMSS(t) "%02" PRId64 ":%02" PRId64 "", (t) / 60, (t) % 60
// Wall time
case 'e': // elapsed, seconds
case 'w': // elapsed, human-readable
case 'E': // ETA, seconds
case 'W': // ETA, human-readable
{
double elapsed_sec = time_millis_ / 1e3;
double eta_sec = -1; // To be printed as "?".
if (time_predicted_percentage_ != 0.0) {
// So, we know that we've spent time_millis_ wall clock,
// and that is time_predicted_percentage_ percent.
// How much time will we need to complete 100%?
double total_wall_time = time_millis_ / time_predicted_percentage_;
// Naturally, that gives us the time remaining.
eta_sec = (total_wall_time - time_millis_) / 1e3;
}
const bool print_with_hours =
elapsed_sec >= 60 * 60 || eta_sec >= 60 * 60;
double sec = -1;
switch (*s) {
case 'e': // elapsed, seconds
case 'w': // elapsed, human-readable
sec = elapsed_sec;
break;
case 'E': // ETA, seconds
case 'W': // ETA, human-readable
sec = eta_sec;
break;
}
if (sec < 0)
snprintf(buf, sizeof(buf), "?");
else {
switch (*s) {
case 'e': // elapsed, seconds
case 'E': // ETA, seconds
snprintf(buf, sizeof(buf), "%.3f", sec);
break;
case 'w': // elapsed, human-readable
case 'W': // ETA, human-readable
if (print_with_hours)
snprintf(buf, sizeof(buf), FORMAT_TIME_HMMSS((int64_t)sec));
else
snprintf(buf, sizeof(buf), FORMAT_TIME_MMSS((int64_t)sec));
break;
}
}
out += buf;
break;
}
// Percentage of time spent out of the predicted time total
case 'P': {
snprintf(buf, sizeof(buf), "%3i%%",
(int)(100. * time_predicted_percentage_));
out += buf;
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;
RecalculateProgressPrediction();
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(progress_status_format_, time_millis)
+ to_print;
printer_.Print(to_print,
force_full_command ? LinePrinter::FULL : LinePrinter::ELIDE);
}
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);
}