package freenet.node;
import java.text.DecimalFormat;
import java.text.FieldPosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.math.TrivialRunningAverage;
/** A record of stats during a single hour */
public class HourlyStatsRecord {
private static final int N_DISTANCE_GROUPS = 16;
private final boolean completeHour;
private boolean finishedReporting;
/**(Logarithmic) routing distances grouped by HTL*/
private StatsLine[] byHTL;
/**HTL grouped by (logarithmic) routing distance*/
private StatsLine[] byDist;
private Date beginTime;
private final Node node;
/** Public constructor.
*
* @param completeHour Whether this record began at the start of the hour
*/
public HourlyStatsRecord(Node node, boolean completeHour) {
this.node = node;
this.completeHour = completeHour;
finishedReporting = false;
byHTL = new StatsLine[node.maxHTL() + 1];
for (int i = 0; i < byHTL.length; i++) byHTL[i] = new StatsLine();
byDist = new StatsLine[N_DISTANCE_GROUPS];
for (int i = 0; i < byDist.length; i++) byDist[i] = new StatsLine();
beginTime = new Date();
}
/** Mark this record as complete and stop recording. */
public synchronized void markFinal() {
finishedReporting = true;
}
/** Report an incoming accepted remote request.
*
* @param ssk Whether the request was an ssk
* @param successs Whether the request succeeded
* @param local If the request succeeded, whether it succeeded locally
* @param htl The htl counter the request had when it arrived
* @param location The routing location of the request
*/
public synchronized void remoteRequest(boolean ssk, boolean success, boolean local,
int htl, double location) {
if (finishedReporting) throw new IllegalStateException(
"Attempted to modify completed stats record.");
if (htl < 0) throw new IllegalArgumentException("Invalid HTL.");
if (location < 0 || location > 1)
throw new IllegalArgumentException("Invalid location.");
htl = Math.min(htl, node.maxHTL());
double rawDist = Location.distance(node.getLocation(), location);
if (rawDist <= 0.0) rawDist = Double.MIN_VALUE;
double logDist = Math.log(rawDist) / Math.log(2.0);
assert logDist < (-1.0 + 0x1.0p-1022/* Double.MIN_NORMAL */);
int distBucket = ((int)Math.floor(-1 * logDist));
if (distBucket >= byDist.length) distBucket = byDist.length - 1;
if(ssk) {
byHTL[htl].locDiffSSK.report(logDist);
} else {
byHTL[htl].locDiffCHK.report(logDist);
}
if (success) {
if (ssk) {
if (local) {
byHTL[htl].sskLocalSuccess.report(logDist);
byDist[distBucket].sskLocalSuccess.report(htl);
} else {
byHTL[htl].sskRemoteSuccess.report(logDist);
byDist[distBucket].sskRemoteSuccess.report(htl);
}
} else {
if (local) {
byHTL[htl].chkLocalSuccess.report(logDist);
byDist[distBucket].chkLocalSuccess.report(htl);
} else {
byHTL[htl].chkRemoteSuccess.report(logDist);
byDist[distBucket].chkRemoteSuccess.report(htl);
}
}
} else {
if (ssk) {
byHTL[htl].sskFailure.report(logDist);
byDist[distBucket].sskFailure.report(htl);
} else {
byHTL[htl].chkFailure.report(logDist);
byDist[distBucket].chkFailure.report(htl);
}
}
}
public void log() {
Logger.normal(this, toString());
}
private static SimpleDateFormat utcDateTime;
static {
utcDateTime = new SimpleDateFormat("yyyyMMdd HH:mm:ss.SSS");
utcDateTime.setTimeZone(TimeZone.getTimeZone("UTC"));
}
private static final DecimalFormat fix3p3pct = new DecimalFormat("##0.000%");
private static final DecimalFormat fix4p = new DecimalFormat("#.0000");
private static double fixNaN(double d) {
if (Double.isNaN(d)) return 0.;
return d;
}
@Override
public synchronized String toString() {
StringBuilder s = new StringBuilder();
s.append("HourlyStats: Report for hour beginning with UTC ");
s.append(utcDateTime.format(beginTime, new StringBuffer(), new FieldPosition(0))).append("\n");
s.append("HourlyStats: Node uptime (ms):\t").append(node.getUptime()).append("\n");
s.append("HourlyStats: build:\t").append(Version.buildNumber()).append("\n");
s.append("HourlyStats: CompleteHour: ").append(completeHour);
s.append("\tFinished: ").append(finishedReporting).append("\n");
for (int i = byHTL.length - 1; i >= 0; i--) {
s.append("HourlyStats: HTL\t").append(i).append("\t");
s.append(byHTL[i].toString()).append("\n");
}
for (int i = 0; i < byDist.length; i++) {
s.append("HourlyStats: logDist\t").append(i).append("\t");
s.append(byDist[i].toString()).append("\n");
}
return s.toString();
}
public void fillRemoteRequestHTLsBox(HTMLNode html) {
HTMLNode table = html.addChild("table");
HTMLNode row = table.addChild("tr");
row.addChild("th", "HTL");
row.addChild("th", "CHKs");
row.addChild("th", "SSKs");
row = table.addChild("tr");
char nbsp = (char)160;
int totalCHKLS = 0;
int totalCHKRS = 0;
int totalCHKT = 0;
int totalSSKLS = 0;
int totalSSKRS = 0;
int totalSSKT = 0;
synchronized(this) {
for(int htl = byHTL.length - 1; htl >= 0; htl--) {
row = table.addChild("tr");
row.addChild("td", Integer.toString(htl));
StatsLine line = byHTL[htl];
int chkLS = (int)line.chkLocalSuccess.countReports();
int chkRS = (int)line.chkRemoteSuccess.countReports();
int chkF = (int)line.chkFailure.countReports();
int chkT = chkLS + chkRS + chkF;
int sskLS = (int)line.sskLocalSuccess.countReports();
int sskRS = (int)line.sskRemoteSuccess.countReports();
int sskF = (int)line.sskFailure.countReports();
int sskT = sskLS + sskRS + sskF;
double locdiffCHK = line.locDiffCHK.currentValue();
locdiffCHK = Math.pow(2.0, locdiffCHK);
double locdiffSSK = line.locDiffSSK.currentValue();
locdiffSSK = Math.pow(2.0, locdiffSSK);
double chkRate = 0.;
double sskRate = 0.;
if (chkT > 0) chkRate = ((double)(chkLS + chkRS)) / (chkT);
if (sskT > 0) sskRate = ((double)(sskLS + sskRS)) / (sskT);
row.addChild("td", fix3p3pct.format(chkRate) + nbsp + "(" + chkLS + "," + chkRS + "," + chkT + ")"+nbsp+"("+fix4p.format(locdiffCHK)+")");
row.addChild("td", fix3p3pct.format(sskRate) + nbsp + "(" + sskLS + "," + sskRS + "," + sskT + ")"+nbsp+"("+fix4p.format(locdiffSSK)+")");
totalCHKLS += chkLS;
totalCHKRS+= chkRS;
totalCHKT += chkT;
totalSSKLS += sskLS;
totalSSKRS += sskRS;
totalSSKT += sskT;
}
double totalCHKRate = 0.0;
double totalSSKRate = 0.0;
if (totalCHKT > 0) totalCHKRate = ((double)(totalCHKLS + totalCHKRS)) / totalCHKT;
if (totalSSKT > 0) totalSSKRate = ((double)(totalSSKLS + totalSSKRS)) / totalSSKT;
row = table.addChild("tr");
row.addChild("td", "Total");
row.addChild("td", fix3p3pct.format(totalCHKRate) + nbsp + "("+ totalCHKLS + "," + totalCHKRS + "," + totalCHKT + ")");
row.addChild("td", fix3p3pct.format(totalSSKRate) + nbsp + "("+ totalSSKLS + "," + totalSSKRS + "," + totalSSKT + ")");
}
}
private class StatsLine {
TrivialRunningAverage chkLocalSuccess;
TrivialRunningAverage chkRemoteSuccess;
TrivialRunningAverage chkFailure;
TrivialRunningAverage sskLocalSuccess;
TrivialRunningAverage sskRemoteSuccess;
TrivialRunningAverage sskFailure;
TrivialRunningAverage locDiffCHK;
TrivialRunningAverage locDiffSSK;
StatsLine() {
chkLocalSuccess = new TrivialRunningAverage();
chkRemoteSuccess = new TrivialRunningAverage();
chkFailure = new TrivialRunningAverage();
sskLocalSuccess = new TrivialRunningAverage();
sskRemoteSuccess = new TrivialRunningAverage();
sskFailure = new TrivialRunningAverage();
locDiffCHK = new TrivialRunningAverage();
locDiffSSK = new TrivialRunningAverage();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(chkLocalSuccess.countReports()).append("\t");
sb.append(chkRemoteSuccess.countReports()).append("\t");
sb.append(chkFailure.countReports()).append("\t");
sb.append(sskLocalSuccess.countReports()).append("\t");
sb.append(sskRemoteSuccess.countReports()).append("\t");
sb.append(sskFailure.countReports()).append("\t");
sb.append(fix4p.format(fixNaN(chkLocalSuccess.currentValue()))).append("\t");
sb.append(fix4p.format(fixNaN(chkRemoteSuccess.currentValue()))).append("\t");
sb.append(fix4p.format(fixNaN(chkFailure.currentValue()))).append("\t");
sb.append(fix4p.format(fixNaN(sskLocalSuccess.currentValue()))).append("\t");
sb.append(fix4p.format(fixNaN(sskRemoteSuccess.currentValue()))).append("\t");
sb.append(fix4p.format(fixNaN(sskFailure.currentValue()))).append("\t");
return sb.toString();
}
}
}