blob: 951c77da66f1b1e35b976f5886718315c4a5b686 [file] [log] [blame]
/*
* Copyright 2012 Google Inc.
*
* 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.
*/
package com.google.gwt.dev.javac;
import com.google.gwt.core.ext.TreeLogger;
import java.util.concurrent.TimeUnit;
/**
* Logs progress of an operation, attempting to calculate an estimated time remaining (ETR) for
* the entire operation for each log entry, assuming that progress is approximately linear.
*/
class ProgressLogger {
/**
* An interface for retrieving nanosecond times, useable ONLY for deltas.
*/
interface NanoSource {
/**
* Returns a number of nanoseconds. Subtracting the results of two invocations of this method
* yields the approximate number of nanoseconds between the invocations.
*/
long getNanos();
}
// Constant for dividing nanosecond time intervals by to get second time intervals. It is a double
// in order to allow fractional seconds.
private static final long NANOSECONDS_IN_SECOND = TimeUnit.SECONDS.toNanos(1);
// The minimum amount of progress that must be made before an ETR is calculated (where 1.0 is
// 100%). If this is too low, then early, inaccurate estimates will be shown.
private static final double ETR_ACCURACY_THRESHOLD = 0.05;
// The number of seconds before which no log is shown. This reduces log spam for things with
// fast progress.
private static final int LOG_BLACKOUT_PERIOD = 5;
// The number of nanoseconds before which no log is shown.
private static final long LOG_BLACKOUT_PERIOD_NANOS = LOG_BLACKOUT_PERIOD * NANOSECONDS_IN_SECOND;
private final TreeLogger logger;
private final TreeLogger.Type logLevel;
private final int maxProgressUnits;
private final int percentageIncrement;
private final NanoSource nanoSource;
private long startNanos = 0;
private boolean timerStarted = false;
private long nextPercentage = 0;
/**
* Creates a ProgressLogger that logs messages with log level {@code logLevel} to {@code logger}.
* It will log only every {@code percentageIncrement} percent of progress, where progress is
* measured from 0 to {@code maxProgressUnits}. Gets nano time offsets for ETR calculations from
* {@link System#nanoTime()}.
*/
ProgressLogger(TreeLogger logger, TreeLogger.Type logLevel, int maxProgressUnits,
int percentageIncrement) {
this.logger = logger;
this.logLevel = logLevel;
this.maxProgressUnits = maxProgressUnits;
this.percentageIncrement = percentageIncrement;
this.nanoSource = new NanoSource() {
@Override
public long getNanos() {
return System.nanoTime();
}
};
}
/**
* Returns true if {@link #startTimer()} has been called, and false otherwise.
*/
boolean isTimerStarted() {
return timerStarted;
}
/**
* Sets the start time against which ETR calculations are made.
*/
void startTimer() {
startNanos = nanoSource.getNanos();
timerStarted = true;
}
/**
* Notifies the ProgressLogger that progress has reached {@code progressUnits}. This may cause
* (depending on the ProgressLogger's settings) the emission of a log message in the form
* "[PERCENT]% complete (ETR: [ESTIMATED TIME REMAINING] seconds)" in most cases, or
* "[PERCENT]% complete (ETR: ?)" if not enough progress has been made to make a good estimate of
* remaining time.
*
* Successive calls to this method should provide monotonically increasing values of
* {@code progressUnits}. It does not support backwards progress.
*/
void updateProgress(int progressUnits) {
// Only do the percentage calculation if the result would be logged.
if (!logger.isLoggable(logLevel)) {
return;
}
if (!timerStarted) {
throw new IllegalStateException("#updateProgress() called before #startTimer().");
}
double progress = progressUnits / (double) maxProgressUnits;
double currentPercentage = 100 * progress;
long elapsedNanos = nanoSource.getNanos() - startNanos;
// Display the percent complete if progress has reached the
// next percentage increment, or if progress is 100%.
if (currentPercentage < nextPercentage && progressUnits != maxProgressUnits) {
return;
}
// Don't log anything if < LOG_BLACKOUT_PERIOD_NANOS nanoseconds have elapsed.
if (elapsedNanos < LOG_BLACKOUT_PERIOD_NANOS) {
return;
}
// Show the largest integer multiple of the percentage increment that is less than the
// actual percentage.
long displayedPercentage =
percentageIncrement * Math.round(Math.floor(currentPercentage / percentageIncrement));
// If progress is 100%, just show 100%.
if (progressUnits == maxProgressUnits) {
displayedPercentage = 100;
}
// Only attempt to estimate a time remaining if we have a reasonable amount of data to go
// on. Otherwise the estimates are wildly misleading.
if (progress >= ETR_ACCURACY_THRESHOLD) {
// Do linear extrapolation to estimate the amount of time remaining.
double estimatedTotalNanos = elapsedNanos / progress;
double estimatedNanosRemaining = estimatedTotalNanos - elapsedNanos;
// Convert nanos to seconds.
double estimatedSecondsRemaining = estimatedNanosRemaining / (double) NANOSECONDS_IN_SECOND;
logger.log(logLevel, String.format("%d%% complete (ETR: %d seconds)",
displayedPercentage, Math.round(estimatedSecondsRemaining)));
} else {
// Not enough information to estimate time remaining.
logger.log(logLevel, String.format("%d%% complete (ETR: ?)", displayedPercentage));
}
nextPercentage += percentageIncrement;
}
}