/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.node; import static java.util.concurrent.TimeUnit.MINUTES; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.DecimalFormat; import freenet.support.Fields; import freenet.support.Logger; import freenet.support.Ticker; import freenet.support.io.Closer; /** * A class to estimate the node's average uptime. Every 5 minutes (with a fixed offset), we write * an integer (the number of minutes since the epoch) to the end of a file. We rotate it when it * gets huge. We read it to figure out how many of the 5 minute slots in the last X period are occupied. * * @author toad */ public class UptimeEstimator implements Runnable { /** * Five minutes in milliseconds. */ static final long PERIOD = MINUTES.toMillis(5); Ticker ticker; /** For each 5 minute slot in the last 48 hours, were we online? */ private boolean[] wasOnline = new boolean[48*12]; //48 hours * 12 5-minute slots/hour /** Whether the node was online for each 5 minute slot in the last week, */ private boolean[] wasOnlineWeek = new boolean[7*24*12]; //7 days/week * 24 hours/day * 12 5-minute slots/hour /** Which slot are we up to? We rotate around the array. Slots before us are before us, * slots after us are also before us (it wraps around). */ private int slot; /** The file we are writing to */ private File logFile; /** The previous file. We have read this. When logFile reaches 48 hours, we dump the prevFile, * move the logFile over it, and write to a new logFile. */ private File prevFile; /** We write to disk every 5 minutes. The offset is derived from the node's identity. */ private long timeOffset; public UptimeEstimator(ProgramDirectory runDir, Ticker ticker, byte[] bs) { this.ticker = ticker; logFile = runDir.file("uptime.dat"); prevFile = runDir.file("uptime.old.dat"); timeOffset = (int) ((((double)(Math.abs(Fields.hashCode(bs, bs.length / 2, bs.length - bs.length / 2)))) / Integer.MAX_VALUE) * PERIOD); } public void start() { long now = System.currentTimeMillis(); int fiveMinutesSinceEpoch = (int)(now / PERIOD); int base = fiveMinutesSinceEpoch - wasOnlineWeek.length; // Read both files. readData(prevFile, base); readData(logFile, base); schedule(System.currentTimeMillis()); System.out.println("Created uptime estimator, time offset is "+timeOffset+" uptime at startup is "+new DecimalFormat("0.00").format(getUptime())); } private void readData(File file, int base) { FileInputStream fis = null; try { fis = new FileInputStream(file); DataInputStream dis = new DataInputStream(fis); try { while(true) { int offset = dis.readInt(); if(offset < base) continue; int slotNo = offset - base; if(slotNo == wasOnlineWeek.length) break; // Reached the end, restarted within the same timeslot. if(slotNo > wasOnlineWeek.length || slotNo < 0) { Logger.error(this, "Corrupt data read from uptime file "+file+": 5-minutes-from-epoch is now "+(base+wasOnlineWeek.length)+" but read "+slotNo); break; } wasOnline[slotNo % wasOnline.length] = wasOnlineWeek[slotNo] = true; } } catch (EOFException e) { // Finished } finally { Closer.close(dis); } } catch (IOException e) { Logger.error(this, "Unable to read old uptime file: "+file+" - we will assume we weren't online during that period"); } finally { Closer.close(fis); } } @Override public void run() { synchronized(this) { wasOnlineWeek[slot] = true; wasOnline[slot % wasOnline.length] = true; slot = (slot + 1) % wasOnlineWeek.length; } long now = System.currentTimeMillis(); if(logFile.length() > wasOnlineWeek.length*4L) { prevFile.delete(); logFile.renameTo(prevFile); } FileOutputStream fos = null; DataOutputStream dos = null; int fiveMinutesSinceEpoch = (int)(now / PERIOD); try { fos = new FileOutputStream(logFile, true); dos = new DataOutputStream(fos); dos.writeInt(fiveMinutesSinceEpoch); } catch (FileNotFoundException e) { Logger.error(this, "Unable to create or access "+logFile+" : "+e, e); } catch (IOException e) { Logger.error(this, "Unable to write to uptime estimator log file: "+logFile); } finally { Closer.close(dos); Closer.close(fos); // Schedule next time schedule(now); } } private void schedule(long now) { long nextTime = (((now / PERIOD)) * (PERIOD)) + timeOffset; if(nextTime < now) nextTime += PERIOD; ticker.queueTimedJob(this, nextTime - System.currentTimeMillis()); } /** * Get the node's uptime fraction over the selected uptime array. * @return fraction between 0.0 and 1.0. */ private synchronized double getUptime(boolean[] uptime) { int upCount = 0; for(boolean sample : uptime) if(sample) upCount++; return ((double) upCount) / ((double) uptime.length); } /** * Get the node's uptime fraction over the past 48 hours. * @return fraction between 0.0 and 1.0. */ public synchronized double getUptime() { return getUptime(wasOnline); } /** * Get the node's uptime fraction over the past 168 hours. * @return fraction between 0.0 and 1.0. */ public synchronized double getUptimeWeek() { return getUptime(wasOnlineWeek); } }