| /* |
| * 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; |
| } |
| } |