package com.limegroup.gnutella.statistics;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.limewire.core.settings.ApplicationSettings;
import org.limewire.i18n.I18nMarker;
import org.limewire.inject.EagerSingleton;
import org.limewire.lifecycle.Service;
import org.limewire.lifecycle.ServiceRegistry;
import org.limewire.util.Clock;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.limegroup.gnutella.Statistics;
/**
* Periodically updates the uptime statistics.
*/
@EagerSingleton
final class UptimeStatTimer implements Service {
/** Current uptime in seconds. */
private volatile long currentUptime = 0;
/** How often to update the uptime settings, in seconds. */
private static final int UPDATE_INTERVAL = 10;
/** How many uptimes and downtimes to record. */
protected static final int HISTORY_LENGTH = 20;
/** Downtime to use if this is the first session, in seconds. */
protected static final int DEFAULT_DOWNTIME = 24 * 60 * 60; // 24 hours
private long lastUpdateTime = 0;
private volatile ScheduledFuture<?> future = null;
private final AtomicBoolean firstUptimeUpdate = new AtomicBoolean(true);
private final ScheduledExecutorService backgroundExecutor;
private final Statistics stats;
private final Clock clock;
@Inject
UptimeStatTimer(@Named("backgroundExecutor") ScheduledExecutorService backgroundExecutor,
Statistics stats, Clock clock) {
this.backgroundExecutor = backgroundExecutor;
this.stats = stats;
this.clock = clock;
}
@Inject void register(ServiceRegistry registry) {
registry.register(this);
}
@Override
public String getServiceName() {
return I18nMarker.marktr("Uptime Statistics");
}
@Override
public void initialize() {
// Increment the session counter
int sessions = ApplicationSettings.SESSIONS.getValue();
ApplicationSettings.SESSIONS.setValue(sessions + 1);
// Record the time between sessions
long lastShutdown = ApplicationSettings.LAST_SHUTDOWN_TIME.getValue();
long downtime;
if(lastShutdown == 0)
downtime = DEFAULT_DOWNTIME;
else
downtime = Math.max(0, (clock.now() - lastShutdown) / 1000);
// If the number of downtimes is greater that the number of uptimes,
// the last session must have ended without recording the uptime or
// shutdown time. To avoid double-counting the downtime we should
// overwrite the last downtime instead of appending.
String[] downtimes = ApplicationSettings.DOWNTIME_HISTORY.get();
String[] uptimes = ApplicationSettings.UPTIME_HISTORY.get();
if(downtimes.length > uptimes.length)
downtimes = updateHistory(downtimes, Long.toString(downtime));
else
downtimes = appendToHistory(downtimes, Long.toString(downtime));
ApplicationSettings.DOWNTIME_HISTORY.set(downtimes);
// Measure the time between refreshes
lastUpdateTime = clock.now();
}
@Override
public void start() {
// Start the periodic update timer
future = backgroundExecutor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
refreshStats();
}
}, UPDATE_INTERVAL, UPDATE_INTERVAL, TimeUnit.SECONDS);
}
@Override
public void stop() {
// Update the stats for the last time before saving
refreshStats();
Future future = this.future;
if(future != null) {
future.cancel(false);
this.future = null;
}
}
/**
* Refreshes the uptime statistics. Package access for testing.
*/
void refreshStats() {
long now = clock.now();
long elapsed = (now - lastUpdateTime) / 1000;
if(elapsed > 0) {
currentUptime += elapsed;
updateUptimeHistory(currentUptime);
long totalUptime = ApplicationSettings.TOTAL_UPTIME.getValue() + elapsed;
ApplicationSettings.TOTAL_UPTIME.setValue(totalUptime);
int sessions = ApplicationSettings.SESSIONS.getValue();
if(sessions > 0) {
ApplicationSettings.AVERAGE_UPTIME.setValue(totalUptime / sessions);
}
ApplicationSettings.FRACTIONAL_UPTIME.setValue(stats.calculateFractionalUptime());
ApplicationSettings.LAST_SHUTDOWN_TIME.setValue(now); // Pessimistic
}
lastUpdateTime = now;
}
/**
* Updates the uptime history with the current uptime.
* Package access for testing.
*
* @param currentUptime the current uptime in seconds
*/
void updateUptimeHistory(long currentUptime) {
String[] uptimes = ApplicationSettings.UPTIME_HISTORY.get();
// The first update in each session should append to the array
if(firstUptimeUpdate.getAndSet(false))
uptimes = appendToHistory(uptimes, Long.toString(currentUptime));
else
uptimes = updateHistory(uptimes, Long.toString(currentUptime));
ApplicationSettings.UPTIME_HISTORY.set(uptimes);
}
private String[] appendToHistory(String[] original, String newItem) {
String[] copy;
if(original.length < HISTORY_LENGTH) {
copy = new String[original.length + 1];
System.arraycopy(original, 0, copy, 0, original.length);
} else {
copy = new String[HISTORY_LENGTH];
System.arraycopy(original, 1, copy, 0, copy.length - 1);
}
copy[copy.length - 1] = newItem;
return copy;
}
private String[] updateHistory(String[] history, String newItem) {
if(history.length == 0)
history = new String[1];
history[history.length - 1] = newItem;
return history;
}
}