/*
* Copyright 2015 MiLaboratory.com
*
* 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.milaboratory.util;
import cc.redberry.pipe.util.CountLimitingOutputPort;
import cc.redberry.pipe.util.CountingOutputPort;
import java.io.PrintStream;
import java.text.DecimalFormat;
public class SmartProgressReporter implements Runnable {
private static final DecimalFormat percentFormat = new DecimalFormat("##.#'%'");
private final PrintStream stream;
private final CanReportProgressAndStage reporter;
private double progressPeriod = 0.10, timePeriod = 120_000;
private boolean detectStageChange = true;
public SmartProgressReporter(CanReportProgressAndStage reporter, PrintStream stream) {
this.stream = stream;
this.reporter = reporter;
}
public SmartProgressReporter(CanReportProgressAndStage reporter) {
this(reporter, System.out);
}
public SmartProgressReporter(final String prefix, final CanReportProgress reporter) {
this(prefix, reporter, System.out);
}
public SmartProgressReporter(final String prefix, final CanReportProgress reporter, PrintStream stream) {
this.stream = stream;
this.reporter = new CanReportProgressAndStage() {
@Override
public String getStage() {
return prefix;
}
@Override
public double getProgress() {
return reporter.getProgress();
}
@Override
public boolean isFinished() {
return reporter.isFinished();
}
};
}
public double getProgressPeriod() {
return progressPeriod;
}
public void setProgressPeriod(double progressPeriod) {
this.progressPeriod = progressPeriod;
}
public double getTimePeriod() {
return timePeriod;
}
public void setTimePeriod(double timePeriod) {
this.timePeriod = timePeriod;
}
public boolean isDetectStageChange() {
return detectStageChange;
}
public void setDetectStageChange(boolean detectStageChange) {
this.detectStageChange = detectStageChange;
}
@Override
public void run() {
long currentStamp, lastStamp = System.currentTimeMillis(), deltaTime, et;
double currentProgress, lastProgress = Double.NaN, deltaValue;
String currentStage, lastStage = null, etStr;
boolean trigger;
try {
while (!reporter.isFinished()) {
synchronized (reporter) {
currentProgress = reporter.getProgress();
currentStage = reporter.getStage();
}
currentStamp = System.currentTimeMillis();
deltaValue = currentProgress - lastProgress;
deltaTime = currentStamp - lastStamp;
trigger = false;
if (detectStageChange && !currentStage.equals(lastStage))
trigger = true;
if (Double.isNaN(currentProgress) ^ Double.isNaN(lastProgress))
trigger = true;
if (deltaValue >= progressPeriod
|| deltaTime >= timePeriod)
trigger = true;
if (deltaValue < 0.0) {
deltaValue = Double.NaN;
trigger = true;
}
long hours, minutes, seconds;
if (trigger) {
if (Double.isNaN(deltaValue) || deltaTime == 0 || deltaValue == 0.0)
etStr = "";
else {
et = (long) ((1.0 - currentProgress) * deltaTime / deltaValue);
et /= 1000;
hours = et / 3600;
et -= hours * 3600;
minutes = (et) / 60;
et -= minutes * 60;
seconds = et;
etStr = " ETA: " + timeString(hours) + ":" + timeString(minutes) + ":" + timeString(seconds);
}
if (currentStage == null)
currentStage = "null";
String sProgress;
if (Double.isNaN(currentProgress))
sProgress = "progress unknown";
else
sProgress = percentFormat.format(currentProgress * 100.0);
stream.println(currentStage + ": " + sProgress + etStr);
lastProgress = currentProgress;
lastStamp = currentStamp;
lastStage = currentStage;
}
Thread.sleep(1000);
}
} catch (InterruptedException e) {
}
}
private static String timeString(long time) {
String timeStr = Long.toString(time);
return timeStr.length() < 2 ? ("0" + timeStr) : timeStr;
}
public static void startProgressReport(SmartProgressReporter reporter) {
Thread thread = new Thread(reporter);
thread.setDaemon(true);
thread.start();
}
public static void startProgressReport(CanReportProgressAndStage reporter, PrintStream stream) {
startProgressReport(new SmartProgressReporter(reporter, stream));
}
public static void startProgressReport(CanReportProgressAndStage reporter) {
startProgressReport(new SmartProgressReporter(reporter));
}
public static void startProgressReport(final String prefix, final CanReportProgress reporter) {
startProgressReport(new SmartProgressReporter(prefix, reporter));
}
public static void startProgressReport(final String prefix, final CanReportProgress reporter, PrintStream stream) {
startProgressReport(new SmartProgressReporter(prefix, reporter, stream));
}
public static CanReportProgress extractProgress(final CountingOutputPort<?> countingOutputPort, final long size) {
return new CanReportProgress() {
@Override
public double getProgress() {
return 1.0 * countingOutputPort.getCount() / size;
}
@Override
public boolean isFinished() {
return countingOutputPort.getCount() >= size;
}
};
}
public static CanReportProgress extractProgress(final CountLimitingOutputPort<?> countLimitingOutputPort) {
return new CanReportProgress() {
@Override
public double getProgress() {
long limit = countLimitingOutputPort.getLimit();
long done = limit - countLimitingOutputPort.getElementsLeft();
return 1.0 * done / limit;
}
@Override
public boolean isFinished() {
return countLimitingOutputPort.getElementsLeft() == 0;
}
};
}
}