
com.univocity.api.statistics.DataTransferStatistics Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of univocity-common-api Show documentation
Show all versions of univocity-common-api Show documentation
univocity public API - Common classes and utilites
The newest version!
/*
* Copyright (c) 2013 Univocity Software Pty Ltd. All rights reserved.
* This file is subject to the terms and conditions defined in file
* 'LICENSE.txt', which is part of this source code package.
*/
package com.univocity.api.statistics;
import com.univocity.api.common.*;
import java.sql.*;
import java.text.*;
import java.util.concurrent.*;
import static java.lang.System.*;
import static java.util.concurrent.TimeUnit.*;
/**
* A basic implementation of {@link DataTransfer} which collects statistics about a given data transfer process, and
* provides easy to use methods to report the progress and timing of the process.
*
* @param the source of data, where data is coming from
* @param the target of data, where data is being transferred into.
*
* @author Univocity Software Pty Ltd - [email protected]
* @see NotificationHandler
*/
public class DataTransferStatistics implements DataTransfer {
private S source;
private T target;
private double totalSize = -1L;
private double totalTransferredSoFar = -1L;
private double totalTransferredRecently = 0.0;
private double ratePerSecond;
private long startTime = -1L;
private long endTime = -1L;
private boolean aborted;
private Exception abortError;
private SimpleDateFormat dateFormat;
private String dateMask;
private String description;
private String unitDescription;
private double unitDivisor = 1;
private NotificationHandler> notificationHandler;
/**
* Creates a new data transfer statistics object that doesn't notify the user of updates. Users are expected to
* query this object at their discretion to obtain the process' status
*/
public DataTransferStatistics() {
this(0, null);
}
/**
* Creates a new data transfer statistics object that notifies the user of updates. Users can
* query this object at their discretion to obtain the process' status and will receive notification of
* any updates to this process as soon as they occur.
*
* @param notificationHandler the notification callback that will receive updates as soon as the statistics are changed.
*/
public DataTransferStatistics(NotificationHandler> notificationHandler) {
this(0, notificationHandler);
}
/**
* Creates a new data transfer statistics object that notifies the user of updates at given intervals, and when
* the process completes. Users can query this object at their discretion to obtain the process' status and will
* receive notification of updates to this process, after a given interval to avoid excessive processing.
*
* @param notificationInterval the minimum interval, in milliseconds, before notifying the provided callback of updates.
* @param handler the notification callback that will receive updates as soon as the statistics
* are changed.
*/
public DataTransferStatistics(long notificationInterval, final NotificationHandler> handler) {
setNotificationHandler(notificationInterval, handler);
}
/**
* Associates a notification handler callback to receive updates from the data transfer operation
*
* @param handler the callback that will be used every time the data transfer statistics are updated
*/
protected final void setNotificationHandler(final NotificationHandler> handler) {
setNotificationHandler(0, handler);
}
/**
* Associates a notification handler callback to receive updates from the data transfer operation at
* at most after a given amount of time has elapsed.
*
* @param notificationInterval the minimum interval in milliseconds to wait before the next call to
* {@link NotificationHandler#notify(Object, boolean)} should be performed.
* Prevents excessive notifications.
* @param handler the callback that will be used every time the data transfer statistics are updated
*/
protected final void setNotificationHandler(long notificationInterval, final NotificationHandler> handler) {
if (notificationInterval > 0) {
this.notificationHandler = new TimedNotificationHandler>(notificationInterval) {
@Override
protected void onNotification(DataTransferStatistics n, boolean lastNotification) {
handler.notify(n, lastNotification);
}
};
} else {
this.notificationHandler = handler;
}
}
/**
* Returns a description of the unit of data being transferred.
*
* @return a description of the unit of data being transferred.
*/
public final String getUnitDescription() {
return unitDescription == null ? "" : unitDescription;
}
/**
* Defines a description of the unit of data being transferred.
*
* @param unitDescription the description of what is being transferred
*/
public final void setUnit(String unitDescription) {
this.unitDescription = unitDescription;
}
/**
* Defines a description of the unit of data being transferred and updates the unit divisor to amounts will be returned
* taking into account the division of the totals by the given divisor. This allows you to convert totals accumulated as bytes
* into kilobytes ({@code unitDivisor = 1024}) or megabytes ({@code unitDivisor = 1024*1024}) for example.
*
* @param unitDescription the description of what unit of data is being transferred
* @param unitDivisor the divisor to be applied over the totals accumulated by this class, so that the units
*/
public void setUnit(String unitDescription, long unitDivisor) {
this.unitDescription = unitDescription;
if (unitDivisor == 0) {
unitDivisor = 1;
}
this.unitDivisor = unitDivisor;
}
/**
* Returns the unit divisor currently in use. The unit divisor allows you to convert totals accumulated as bytes
* into kilobytes ({@code unitDivisor = 1024}) or megabytes ({@code unitDivisor = 1024*1024}) for example.
*
* @return the divisor to be applied over the totals accumulated by this class
*/
public final long getUnitDivisor() {
return (long) unitDivisor;
}
@Override
public final void started(S source, long totalSize, T target) {
if (isRunning()) {
throw new IllegalStateException("Data transfer statistics already started: " + toString());
}
this.source = source;
this.target = target;
description = source + " -> " + target;
this.totalSize = totalSize;
startTime = currentTimeMillis();
totalTransferredSoFar = 0L;
totalTransferredRecently = 0L;
endTime = -1L;
}
long lastSecond = 0;
@Override
public final void transferred(S source, long transferred, T target) {
if (transferred > 0 && this.source == source && this.target == target) {
totalTransferredSoFar += transferred;
totalTransferredRecently += transferred;
notifyStatisticUpdates();
}
}
private void updateRatePerSecond() {
if (lastSecond <= 0) {
lastSecond = System.currentTimeMillis();
totalTransferredRecently = 0.0;
return;
}
long time = currentTimeMillis() - lastSecond;
if (time < 1000) {
return;
}
ratePerSecond = (totalTransferredRecently / unitDivisor) / ((double) time) * 1000.0;
totalTransferredRecently = 0.0;
lastSecond = System.currentTimeMillis();
}
private void notifyStatisticUpdates() {
if (notificationHandler != null) {
updateRatePerSecond();
notificationHandler.notify(this, endTime > 0);
}
}
@Override
public final void completed(S source, T target) {
if (this.source == source && this.target == target) {
endTime = currentTimeMillis();
notifyStatisticUpdates();
}
}
@Override
public final void aborted(S source, T target, Exception error) {
if (this.source == source && this.target == target) {
aborted = true;
abortError = error;
completed(source, target);
}
}
/**
* Returns the average transfer rate, in milliseconds.
*
* @return the number number of data elements measured by the underlying implementation transferred per millisecond.
*/
public final double getAverageRate() {
return getAverageRate(MILLISECONDS);
}
/**
* Returns the average transfer rate, using a given {@link TimeUnit}
*
* @param timeUnit the time unit used to calculate the transfer rate.
*
* @return the number number of data elements measured by the underlying implementation
* transferred per a given unit of time
*/
public final double getAverageRate(TimeUnit timeUnit) {
long time = getTimeElapsed();
time = timeUnit.convert(time, MILLISECONDS);
if (time > 0) {
return getTotalTransferredSoFar() / (double) time;
} else {
return getTotalTransferredSoFar();
}
}
/**
* Returns the number of milliseconds the data transfer took so far.
*
* @return the number of milliseconds the data transfer took so far.
*/
public final long getTimeElapsed() {
if (endTime > 0) {
return endTime - startTime;
}
return currentTimeMillis() - startTime;
}
/**
* Returns how long the data transfer took so far, using a given time unit.
*
* @param timeUnit the unit of time to use to represent the time elapsed.
*
* @return how long the data transfer took so far.
*/
public final double getTimeElapsed(TimeUnit timeUnit) {
return timeUnit.convert(getTimeElapsed(), MILLISECONDS);
}
/**
* Returns the total amount transferred already.
*
* @return the total amount transferred already.
*/
public final double getTotalTransferredSoFar() {
return totalTransferredSoFar / unitDivisor;
}
/**
* Returns the total size of the source to be transferred to the target, if available. If the process completed
* successfully, returns the amount transferred. The meaning of the amount returned depends on the underlying
* implementation: this can be bytes, number of records, etc. If negative, the total size is unknown ahead of
* time.
*
* @return the total size to transfer, if known. Otherwise, returns a negative value. If the process completed
* successfully, returns the amount transferred.
*/
public final double getTotalSize() {
if (!aborted && endTime > 0) {
return totalTransferredSoFar / unitDivisor;
}
return totalSize / unitDivisor;
}
/**
* Returns the data transfer start time, in milliseconds.
*
* @return the start time
*/
public final long getStartTime() {
return startTime;
}
private String getTime(long time, String dateMask, String valueOnError) {
if (this.dateMask != null && !this.dateMask.equals(dateMask)) {
dateFormat = null;
}
if (dateFormat == null) {
dateFormat = new SimpleDateFormat(dateMask);
this.dateMask = dateMask;
}
try {
return dateFormat.format(new Date(time));
} catch (Exception ex) {
return valueOnError;
}
}
/**
* Returns the data transfer start time, as a formatted date.
*
* @param dateMask a date mask following the rules specified by {@link SimpleDateFormat}
* @param valueOnError a default value to be returned in case of errors generating the formatted date.
*
* @return the start time
*/
public final String getStartTime(String dateMask, String valueOnError) {
return getTime(startTime, dateMask, valueOnError);
}
/**
* Returns the data transfer start time, as a formatted date.
*
* @param dateMask a date mask following the rules specified by {@link SimpleDateFormat}
*
* @return the start time
*/
public final String getStartTime(String dateMask) {
return getTime(startTime, dateMask, "N/A");
}
/**
* Returns the data transfer end time, in milliseconds. If it is still ongoing, returns {@code -1}
*
* @return the end time, or {@code -1} of the process is still running.
*/
public final long getEndTime() {
return endTime;
}
/**
* Returns the data transfer end time, in milliseconds. If it is still ongoing, returns {@code -1}
*
* @param dateMask a date mask following the rules specified by {@link SimpleDateFormat}
* @param valueOnError a default value to be returned in case of errors generating the formatted date.
*
* @return the end time, or {@code -1} of the process is still running.
*/
public final String getEndTime(String dateMask, String valueOnError) {
return getTime(endTime, dateMask, valueOnError);
}
/**
* Returns the data transfer end time, in milliseconds. If it is still ongoing, returns {@code -1}
*
* @param dateMask a date mask following the rules specified by {@link SimpleDateFormat}
*
* @return the end time, or {@code -1} of the process is still running.
*/
public final String getEndTime(String dateMask) {
return getTime(endTime, dateMask, "N/A");
}
/**
* Returns the percentage of data already transferred, (where 0.0 represents 0% and 1.0 represents 100%)
*
* @return the percentage of data already transferred.
*/
public final double getTransferPercentage() {
if (totalSize > 0) {
return getTotalTransferredSoFar() / getTotalSize();
}
if (aborted) {
return 0.0;
}
if (endTime > 0) {
return 1.0;
}
return 0.0;
}
/**
* Returns the source of data
*
* @return the source of data
*/
public final S getSource() {
return source;
}
/**
* Returns the destination of the data transferred from the source.
*
* @return the data destination.
*/
public final T getTarget() {
return target;
}
/**
* Returns the exception, if any, that caused the interruption of data transfer process.
*
* @return the cause of the interruption of this data transfer.
*/
public final Exception getAbortError() {
return abortError;
}
@Override
public final boolean isStarted() {
return totalTransferredSoFar != -1;
}
@Override
public final boolean isRunning() {
return isStarted() && endTime <= 0;
}
@Override
public final boolean isAborted() {
return aborted;
}
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DataTransferStatistics, ?> that = (DataTransferStatistics, ?>) o;
if (!source.equals(that.source)) {
return false;
}
return target.equals(that.target);
}
@Override
public final int hashCode() {
int result = source.hashCode();
result = 31 * result + target.hashCode();
return result;
}
@Override
public String toString() {
if (totalTransferredSoFar == -1L) {
return "Not started";
}
StringBuilder stats = new StringBuilder(" | ");
stats.append(getFormattedTotalTransferredSoFar());
stats.append(" of ").append(getFormattedTotalSize());
stats.append(' ').append('(');
stats.append(getFormattedTransferPercentage());
if (aborted) {
if (abortError != null) {
return description + stats.toString() + ") | Aborted with error " + abortError.getMessage();
} else {
return description + stats.toString() + ") | Aborted";
}
}
stats.append(' ').append('-').append(' ');
stats.append(getFormattedRatePerSecond());
stats.append(" (avg ").append(getFormattedAverageRate()).append(')');
if (endTime < 0) {
return description + stats.toString() + " | Transferring ";
} else {
return description + stats.toString() + " | Completed ";
}
}
/**
* Returns a formatted {@code String} representing the percentage of data already transferred
*
* @param format the numeric format to use to represent the percentage.
* @param valueIfUnknown the {@code String} to return if the percentage cannot be determined.
*
* @return the formatted percentage of data already transferred.
*/
public String getFormattedTransferPercentage(NumberFormat format, String valueIfUnknown) {
if (getTotalSize() > 0) {
return format.format(getTransferPercentage());
} else {
return valueIfUnknown;
}
}
/**
* Returns the percentage of data already transferred, formatted a decimal number between 0% and 100% inclusive,
* or ?% if unknown
*
* @return the formatted percentage of data already transferred.
*/
public String getFormattedTransferPercentage() {
return getFormattedTransferPercentage(NumberFormat.getPercentInstance(), "?%");
}
/**
* Returns the approximate transfer rate at which the data was transferred during the previous second.
*
* @return the number number of data elements measured by the underlying implementation
* transferred during the previous second.
*/
public final double getRatePerSecond() {
updateRatePerSecond();
if (ratePerSecond == 0.0) {
return getAverageRate();
}
return ratePerSecond;
}
/**
* Returns a formatted {@code String} representing the approximate rate at which the data was transferred
* during the previous second.
*
* @param format the numeric format to use to represent the rate.
* @param rateSymbol the symbol to be displayed after the rate
*
* @return the formatted approximate rate at which the data was transferred during the previous second.
*/
public String getFormattedRatePerSecond(NumberFormat format, String rateSymbol) {
return getFormattedRate(getRatePerSecond(), format, rateSymbol);
}
/**
* Returns a formatted {@code String} representing the approximate rate, per second, at which the data was being
* transferred to the target during the previous second.
*
* @return the formatted rate at which the data is being transferred during the previous second.
*/
public String getFormattedRatePerSecond() {
return getFormattedRatePerSecond(NumberFormat.getNumberInstance(), " /sec");
}
private String getFormattedRate(double rate, NumberFormat format, String rateSymbol) {
StringBuilder out = new StringBuilder();
out.append(format.format(rate));
if (!getUnitDescription().isEmpty()) {
out.append(getUnitDescription());
}
if (rateSymbol != null && !rateSymbol.isEmpty()) {
out.append(rateSymbol);
}
return out.toString();
}
/**
* Returns a formatted {@code String} representing the average rate at which the data is being transferred to the target.
*
* @param format the numeric format to use to represent thea average rate.
* @param timeUnit the unit of time to be used to calculate the average rate.
* @param rateSymbol the symbol to be displayed after the rate
*
* @return the formatted average rate at which the data is being transferred
*/
public String getFormattedAverageRate(NumberFormat format, TimeUnit timeUnit, String rateSymbol) {
return getFormattedRate(getAverageRate(timeUnit), format, rateSymbol);
}
/**
* Returns a formatted {@code String} representing the average rate, per second, at which the data is being transferred
* to the target
*
* @return the formatted average rate at which the data is being transferred
*/
public String getFormattedAverageRate() {
return getFormattedAverageRate(NumberFormat.getNumberInstance(), TimeUnit.SECONDS, " /sec");
}
/**
* Returns a formatted {@code String} representing the amount size of data already to be transferred to the target
*
* @param format the numeric format to use to represent the amount.
*
* @return the formatted total amount already transferred
*/
public String getFormattedTotalTransferredSoFar(NumberFormat format) {
StringBuilder out = new StringBuilder();
out.append(format.format(getTotalTransferredSoFar()));
if (!getUnitDescription().isEmpty()) {
out.append(' ');
out.append(getUnitDescription());
}
return out.toString();
}
/**
* Returns a formatted {@code String} representing the amount size of data already to be transferred to the target
*
* @return the formatted total amount already transferred
*/
public String getFormattedTotalTransferredSoFar() {
return getFormattedTotalTransferredSoFar(NumberFormat.getNumberInstance());
}
/**
* Returns a formatted {@code String} representing the total size of the source to be transferred to the target,
* if available.
*
* @param format the numeric format to use to represent the total.
* @param valueIfUnknown the {@code String} to return if the total size cannot be determined.
*
* @return the formatted total amount to be transferred
*/
public String getFormattedTotalSize(NumberFormat format, String valueIfUnknown) {
StringBuilder out = new StringBuilder();
if (getTotalSize() > 0) {
out.append(format.format(getTotalSize()));
} else {
out.append(valueIfUnknown);
}
if (!getUnitDescription().isEmpty()) {
out.append(' ');
out.append(getUnitDescription());
}
return out.toString();
}
/**
* Returns a formatted {@code String} representing the total size of the source to be transferred to the target,
* if available.
*
* @return the formatted total amount to be transferred
*/
public String getFormattedTotalSize() {
return getFormattedTotalSize(NumberFormat.getNumberInstance(), "?");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy