/*
*
* Copyright (c) 2013 - 2017 Lijun Liao
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation with the addition of the
* following permission added to Section 15 as permitted in Section 7(a):
*
* FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
* THE AUTHOR LIJUN LIAO. LIJUN LIAO DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
* OF THIRD PARTY RIGHTS.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License.
*
* You can be released from the requirements of the license by purchasing
* a commercial license. Buying such a license is mandatory as soon as you
* develop commercial activities involving the XiPKI software without
* disclosing the source code of your own applications.
*
* For more information, please contact Lijun Liao at this
* address: lijun.liao@gmail.com
*/
package org.xipki.commons.common;
import java.util.Calendar;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.xipki.commons.common.qa.MeasurePoint;
import org.xipki.commons.common.util.StringUtil;
/**
* @author Lijun Liao
* @since 2.0.0
*/
public class ProcessLog {
private static final long MS_900 = 900L;
private static final long DAY_IN_SEC = 24L * 60 * 60;
private static final int MIN_LEN = 12;
private final long total;
private final boolean hasTotal;
private long startTimeMs;
private AtomicLong numProcessed;
private AtomicLong lastPrintTimeMs;
private final AtomicBoolean finished = new AtomicBoolean(false);
private long totalElapsedTimeMs;
private int totalAverageSpeed;
private final ConcurrentLinkedDeque<MeasurePoint> measureDeque = new ConcurrentLinkedDeque<>();
public ProcessLog(final long total) {
this.total = total;
this.hasTotal = total > 0;
reset();
}
public void printHeader() {
StringBuilder sb = new StringBuilder();
// first header line
final int n = hasTotal ? 7 : 4;
for (int i = 0; i < n * MIN_LEN; i++) {
sb.append('-');
}
sb.append('\n');
// second header line
sb.append(formatText("total"));
if (hasTotal) {
sb.append(formatText("%"));
}
sb.append(formatText("average"));
sb.append(formatText("current"));
sb.append(formatText("time"));
if (hasTotal) {
sb.append(formatText("time"));
sb.append(formatText("finish"));
}
sb.append('\n');
// third header line
sb.append(formatText(""));
if (hasTotal) {
sb.append(formatText(""));
}
sb.append(formatText("speed"));
sb.append(formatText("speed"));
sb.append(formatText("spent"));
if (hasTotal) {
sb.append(formatText("left"));
sb.append(formatText("at"));
}
sb.append('\n');
System.out.println(sb.toString());
System.out.flush();
} // method printHeader
public void finish() {
finished.set(true);
totalElapsedTimeMs = System.currentTimeMillis() - startTimeMs;
totalAverageSpeed = 0;
if (totalElapsedTimeMs > 0) {
totalAverageSpeed = (int) (numProcessed.get() * 1000 / totalElapsedTimeMs);
}
}
public void printTrailer() {
finish();
printStatus(true);
StringBuilder sb = new StringBuilder();
sb.append('\n');
final int n = hasTotal ? 7 : 4;
for (int i = 0; i < n * MIN_LEN; i++) {
sb.append('-');
}
System.out.println(sb.toString());
System.out.flush();
}
public long getNumProcessed() {
return numProcessed.get();
}
public long getTotal() {
return total;
}
public void reset() {
startTimeMs = System.currentTimeMillis();
numProcessed = new AtomicLong(0);
lastPrintTimeMs = new AtomicLong(0);
measureDeque.clear();
measureDeque.add(new MeasurePoint(startTimeMs, 0));
}
public long getStartTime() {
return startTimeMs;
}
public long addNumProcessed(final long numProcessed) {
return this.numProcessed.addAndGet(numProcessed);
}
public void printStatus() {
printStatus(false);
}
private void printStatus(final boolean forcePrint) {
final long nowMs = System.currentTimeMillis();
final long tmpNumProcessed = numProcessed.get();
if (!forcePrint && nowMs - lastPrintTimeMs.get() < MS_900) {
return;
}
measureDeque.addLast(new MeasurePoint(nowMs, numProcessed.get()));
lastPrintTimeMs.set(nowMs);
int numMeasurePoints = measureDeque.size();
// CHECKSTYLE:SKIP
MeasurePoint referenceMeasurePoint = (numMeasurePoints > 10) ? measureDeque.removeFirst()
: measureDeque.getFirst();
StringBuilder sb = new StringBuilder("\r");
// processed number
sb.append(StringUtil.formatAccount(tmpNumProcessed, true));
// processed percent
if (hasTotal) {
int percent = (int) (tmpNumProcessed * 100 / total);
String percentS = Integer.toString(percent);
sb.append(formatText(percentS));
}
// average speed
long averageSpeed = 0;
long elapsedTimeMs = nowMs - startTimeMs;
if (elapsedTimeMs > 0) {
averageSpeed = tmpNumProcessed * 1000 / elapsedTimeMs;
}
sb.append(StringUtil.formatAccount(averageSpeed, true));
// current speed
long currentSpeed = 0;
long t2inms = nowMs - referenceMeasurePoint.getMeasureTime(); // in ms
if (t2inms > 0) {
currentSpeed =
(tmpNumProcessed - referenceMeasurePoint.getMeasureAccount()) * 1000 / t2inms;
}
sb.append(StringUtil.formatAccount(currentSpeed, true));
// elapsed time
sb.append(StringUtil.formatTime(elapsedTimeMs / 1000, true));
// remaining time and finish at
if (hasTotal) {
long remaingTimeMs = -1;
if (currentSpeed > 0) {
remaingTimeMs = (total - tmpNumProcessed) * 1000 / currentSpeed;
}
long finishAtMs = -1;
if (remaingTimeMs != -1) {
finishAtMs = nowMs + remaingTimeMs;
}
if (remaingTimeMs == -1) {
sb.append(formatText("--"));
} else {
sb.append(StringUtil.formatTime(remaingTimeMs / 1000, true));
}
if (finishAtMs == -1) {
sb.append(formatText("--"));
} else {
sb.append(buildDateTime(finishAtMs));
}
}
System.out.print(sb.toString());
System.out.flush();
} // method printStatus
public long getTotalElapsedTime() {
if (finished.get()) {
return totalElapsedTimeMs;
}
return System.currentTimeMillis() - startTimeMs;
}
public int getTotalAverageSpeed() {
if (finished.get()) {
return totalAverageSpeed;
}
long elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
int averageSpeed = 0;
if (elapsedTimeMs > 0) {
averageSpeed = (int) (numProcessed.get() * 1000 / elapsedTimeMs);
}
return averageSpeed;
}
private static String formatText(String text) {
return StringUtil.formatText(text, MIN_LEN);
}
private static String buildDateTime(long timeMs) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(timeMs);
StringBuilder sb = new StringBuilder();
int hour = cal.get(Calendar.HOUR_OF_DAY);
if (hour < 10) {
sb.append('0');
}
sb.append(hour);
int minute = cal.get(Calendar.MINUTE);
sb.append(":");
if (minute < 10) {
sb.append('0');
}
sb.append(minute);
int second = cal.get(Calendar.SECOND);
sb.append(":");
if (second < 10) {
sb.append('0');
}
sb.append(second);
cal.setTimeInMillis(System.currentTimeMillis());
cal.set(Calendar.HOUR, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
long midNightSec = cal.getTimeInMillis() / 1000;
long days = (timeMs / 1000 - midNightSec) / DAY_IN_SEC;
if (days > 0) {
sb.append('+').append(days);
}
int size = sb.length();
for (int i = 0; i < (MIN_LEN - size); i++) {
sb.insert(0, ' ');
}
return sb.toString();
} // method buildDateTime
}