initial impl (public domain, yadda yadda yadda)
This commit is contained in:
77
apps/netmonitor/java/build.xml
Normal file
77
apps/netmonitor/java/build.xml
Normal file
@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project basedir="." default="all" name="netmonitor">
|
||||
<target name="all" depends="clean, build" />
|
||||
<target name="build" depends="builddep, jar" />
|
||||
<target name="builddep">
|
||||
<ant dir="../../../core/java/" target="build" />
|
||||
</target>
|
||||
<target name="fetchJfreechart">
|
||||
<mkdir dir="./lib" />
|
||||
<get src="http://www.jfree.org/jfreechart/jfreechart-0.9.17.zip" verbose="true" dest="lib/jfreechart-0.9.17.zip" />
|
||||
<unzip src="lib/jfreechart-0.9.17.zip" dest="lib/" />
|
||||
</target>
|
||||
<target name="buildGUI" depends="build, jfreechart, jarGUI" />
|
||||
<target name="compile">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac srcdir="./src" debug="true" destdir="./build/obj" includes="net/**/*.java" excludes="net/i2p/netmonitor/gui/**" classpath="../../../core/java/build/i2p.jar" />
|
||||
</target>
|
||||
|
||||
<target name="compileGUI" depends="builddep">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/obj" />
|
||||
<javac debug="true" destdir="./build/obj">
|
||||
<src path="src/" />
|
||||
<classpath path="../../../core/java/build/i2p.jar" />
|
||||
<classpath path="lib/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" />
|
||||
<classpath path="lib/jfreechart-0.9.17/lib/log4j-1.2.8.jar" />
|
||||
<classpath path="lib/jfreechart-0.9.17/jfreechart-0.9.17.jar" />
|
||||
</javac>
|
||||
</target>
|
||||
<target name="jfreechart">
|
||||
<ant dir="./lib/jfreechart-0.9.17/" antfile="ant/build.xml" target="compile" />
|
||||
</target>
|
||||
|
||||
<target name="jarGUI" depends="compileGUI">
|
||||
<copy file="lib/jfreechart-0.9.17/jfreechart-0.9.17.jar" todir="build/" />
|
||||
<copy file="lib/jfreechart-0.9.17/lib/log4j-1.2.8.jar" todir="build/" />
|
||||
<copy file="lib/jfreechart-0.9.17/lib/jcommon-0.9.2.jar" todir="build/" />
|
||||
<jar destfile="./build/netviewer.jar" basedir="./build/obj" includes="**">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.netmonitor.gui.NetViewer" />
|
||||
<attribute name="Class-Path" value="log4j-1.2.8.jar jcommon-0.9.2.jar jfreechart-0.9.17.jar netviewer.jar i2p.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
<echo message="You will need to copy the log4j, jcommon, and jfreechart jar files from build/ into your lib dir" />
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
<jar destfile="./build/netmonitor.jar" basedir="./build/obj" includes="**/*.class">
|
||||
<manifest>
|
||||
<attribute name="Main-Class" value="net.i2p.netmonitor.NetMonitor" />
|
||||
<attribute name="Class-Path" value="i2p.jar netmonitor.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
<target name="javadoc">
|
||||
<mkdir dir="./build" />
|
||||
<mkdir dir="./build/javadoc" />
|
||||
<javadoc
|
||||
sourcepath="./src:../../../core/java/src:../../../core/java/test" destdir="./build/javadoc"
|
||||
packagenames="*"
|
||||
use="true"
|
||||
access="package"
|
||||
splitindex="true"
|
||||
windowtitle="I2P netmonitor" />
|
||||
</target>
|
||||
<target name="clean">
|
||||
<delete dir="./build" />
|
||||
</target>
|
||||
<target name="cleandep" depends="clean">
|
||||
<ant dir="../../../core/java/" target="cleandep" />
|
||||
</target>
|
||||
<target name="distclean" depends="clean">
|
||||
<ant dir="../../../core/java/" target="distclean" />
|
||||
<delete dir="./lib/" />
|
||||
</target>
|
||||
</project>
|
264
apps/netmonitor/java/src/net/i2p/netmonitor/DataHarvester.java
Normal file
264
apps/netmonitor/java/src/net/i2p/netmonitor/DataHarvester.java
Normal file
@ -0,0 +1,264 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import net.i2p.data.RouterInfo;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.Clock;
|
||||
import java.util.Properties;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.Locale;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Pull out important data from the published routerInfo and stash it away
|
||||
* in the netMonitor
|
||||
*
|
||||
*/
|
||||
class DataHarvester {
|
||||
private static final Log _log = new Log(DataHarvester.class);
|
||||
private static final DataHarvester _instance = new DataHarvester();
|
||||
public static final DataHarvester getInstance() { return _instance; }
|
||||
|
||||
protected DataHarvester() {}
|
||||
|
||||
/**
|
||||
* Harvest all of the data from the peers and store it in the monitor.
|
||||
*
|
||||
* @param peers list of RouterInfo structures to harvest from
|
||||
*/
|
||||
public void harvestData(NetMonitor monitor, List peers) {
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
harvestData(monitor, (RouterInfo)peers.get(i), peers);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull out all the data we can for the specified peer
|
||||
*
|
||||
* @param peer who are we focusing on in this pass
|
||||
* @param peers everyone on the network, to co
|
||||
*/
|
||||
private void harvestData(NetMonitor monitor, RouterInfo peer, List peers) {
|
||||
_log.info("Harvest the data from " + peer.getIdentity().getHash().toBase64());
|
||||
harvestRank(monitor, peer, peers);
|
||||
harvestRankAs(monitor, peer);
|
||||
harvestEncryptionTime(monitor, peer);
|
||||
harvestDroppedJobs(monitor, peer);
|
||||
harvestProcessingTime(monitor, peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* How does the peer rank other routers? Stored in the peer summary as
|
||||
* "rankAs", containing 4 longs (numFast, numReliable, numNotFailing, numFailing)
|
||||
*
|
||||
* @param peer who is doing the ranking
|
||||
*/
|
||||
private void harvestRankAs(NetMonitor monitor, RouterInfo peer) {
|
||||
int numFast = 0;
|
||||
int numReliable = 0;
|
||||
int numNotFailing = 0;
|
||||
int numFailing = 0;
|
||||
|
||||
Properties props = peer.getOptions();
|
||||
for (Iterator iter = props.keySet().iterator(); iter.hasNext(); ) {
|
||||
String key = (String)iter.next();
|
||||
if (key.startsWith("profile.")) {
|
||||
String val = (String)props.get(key);
|
||||
if (val.indexOf("fastReliable") != -1)
|
||||
numFast++;
|
||||
else if (val.indexOf("reliable") != -1)
|
||||
numReliable++;
|
||||
else if (val.indexOf("notFailing") != -1)
|
||||
numNotFailing++;
|
||||
else if (val.indexOf("failing") != -1)
|
||||
numFailing++;
|
||||
}
|
||||
}
|
||||
|
||||
long rankAs[] = new long[4];
|
||||
rankAs[0] = numFast;
|
||||
rankAs[1] = numReliable;
|
||||
rankAs[2] = numNotFailing;
|
||||
rankAs[3] = numFailing;
|
||||
String description = "how we rank peers";
|
||||
String valDescr[] = new String[4];
|
||||
valDescr[0] = "# peers we rank as fast";
|
||||
valDescr[1] = "# peers we rank as reliable";
|
||||
valDescr[2] = "# peers we rank as not failing";
|
||||
valDescr[3] = "# peers we rank as failing";
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), "rankAs", description, valDescr, peer.getPublished(), rankAs);
|
||||
}
|
||||
|
||||
/**
|
||||
* How do other peers rank the current peer? Stored in the peer summary as
|
||||
* "rank", containing 4 longs (numFast, numReliable, numNotFailing, numFailing)
|
||||
*
|
||||
* @param peer who do we want to check the network's perception of
|
||||
* @param peers peers whose rankings we will use
|
||||
*/
|
||||
private void harvestRank(NetMonitor monitor, RouterInfo peer, List peers) {
|
||||
int numFast = 0;
|
||||
int numReliable = 0;
|
||||
int numNotFailing = 0;
|
||||
int numFailing = 0;
|
||||
|
||||
// now count 'em
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
RouterInfo cur = (RouterInfo)peers.get(i);
|
||||
if (peer == cur) continue;
|
||||
String prop = "profile." + peer.getIdentity().getHash().toBase64().replace('=', '_');
|
||||
String val = cur.getOptions().getProperty(prop);
|
||||
if ( (val == null) || (val.length() <= 0) ) continue;
|
||||
if (val.indexOf("fastReliable") != -1)
|
||||
numFast++;
|
||||
else if (val.indexOf("reliable") != -1)
|
||||
numReliable++;
|
||||
else if (val.indexOf("notFailing") != -1)
|
||||
numNotFailing++;
|
||||
else if (val.indexOf("failing") != -1)
|
||||
numFailing++;
|
||||
}
|
||||
|
||||
long rank[] = new long[4];
|
||||
rank[0] = numFast;
|
||||
rank[1] = numReliable;
|
||||
rank[2] = numNotFailing;
|
||||
rank[3] = numFailing;
|
||||
String description = "how peers rank us";
|
||||
String valDescr[] = new String[4];
|
||||
valDescr[0] = "# peers ranking us as fast";
|
||||
valDescr[1] = "# peers ranking us as reliable";
|
||||
valDescr[2] = "# peers ranking us as not failing";
|
||||
valDescr[3] = "# peers ranking us as failing";
|
||||
// we use the current date, not the published date, since this sample doesnt come from them
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), "rank", description, valDescr, Clock.getInstance().now(), rank);
|
||||
}
|
||||
|
||||
/**
|
||||
* How long does it take the peer to perform an elGamal encryption? Stored in
|
||||
* the peer summary as "encryptTime", containing 4 doubles (numMs for 1 minute,
|
||||
* quantity in the last minute, numMs for 1 hour, quantity in the last hour)
|
||||
*
|
||||
* @param peer who are we checking the encryption time of
|
||||
*/
|
||||
private void harvestEncryptionTime(NetMonitor monitor, RouterInfo peer) {
|
||||
double minuteMs = getDouble(peer, "stat_crypto.elGamal.encrypt.60s", 0);
|
||||
double hourMs = getDouble(peer, "stat_crypto.elGamal.encrypt.60m", 0);
|
||||
double minuteQuantity = getDouble(peer, "stat_crypto.elGamal.encrypt.60s", 7);
|
||||
double hourQuantity = getDouble(peer, "stat_crypto.elGamal.encrypt.60m", 7);
|
||||
if ( (minuteMs == -1) || (hourMs == -1) || (minuteQuantity == -1) || (hourQuantity == -1) )
|
||||
return;
|
||||
|
||||
double times[] = new double[4];
|
||||
times[0] = minuteMs;
|
||||
times[1] = minuteQuantity;
|
||||
times[2] = hourMs;
|
||||
times[3] = hourQuantity;
|
||||
|
||||
String description = "how long it takes to do an ElGamal encryption";
|
||||
String valDescr[] = new String[4];
|
||||
valDescr[0] = "encryption time avg ms (minute)";
|
||||
valDescr[1] = "# encryptions (minute)";
|
||||
valDescr[2] = "encryption time avg ms (hour)";
|
||||
valDescr[3] = "# encryptions (hour)";
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), "encryptTime", description, valDescr, peer.getPublished(), times);
|
||||
}
|
||||
|
||||
/**
|
||||
* How jobs has the peer dropped in the last minute / hour? Stored in
|
||||
* the peer summary as "droppedJobs", containing 2 doubles (num jobs for 1 minute,
|
||||
* num jobs for 1 hour)
|
||||
*
|
||||
* @param peer who are we checking the frequency of dropping jobs for
|
||||
*/
|
||||
private void harvestDroppedJobs(NetMonitor monitor, RouterInfo peer) {
|
||||
double minute = getDouble(peer, "stat_jobQueue.droppedJobs.60s", 0);
|
||||
double hour = getDouble(peer, "stat_jobQueue.droppedJobs.60m", 0);
|
||||
double quantity[] = new double[2];
|
||||
quantity[0] = minute;
|
||||
quantity[1] = hour;
|
||||
if ( (minute == -1) || (hour == -1) )
|
||||
return;
|
||||
|
||||
String valDescr[] = new String[2];
|
||||
valDescr[0] = "# dropped jobs (minute)";
|
||||
valDescr[1] = "# dropped jobs (hour)";
|
||||
String description = "how many dropped jobs";
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), "droppedJobs", description, valDescr, peer.getPublished(), quantity);
|
||||
}
|
||||
|
||||
/**
|
||||
* How long does it take to process an outbound message? Stored in
|
||||
* the peer summary as "processingTime", containing 4 doubles (avg ms for 1 minute,
|
||||
* num messages for 1 minute, avg ms for 1 hour, num messages for 1 hour)
|
||||
*
|
||||
* @param peer who are we checking the frequency of dropping jobs for
|
||||
*/
|
||||
private void harvestProcessingTime(NetMonitor monitor, RouterInfo peer) {
|
||||
double minuteMs = getDouble(peer, "stat_transport.sendProcessingTime.60s", 0);
|
||||
double minuteFreq = getDouble(peer, "stat_transport.sendProcessingTime.60s", 7);
|
||||
double hourMs = getDouble(peer, "stat_transport.sendProcessingTime.60m", 0);
|
||||
double hourFreq = getDouble(peer, "stat_transport.sendProcessingTime.60m", 7);
|
||||
if ( (minuteMs == -1) || (hourMs == -1) || (minuteFreq == -1) || (hourFreq == -1) )
|
||||
return;
|
||||
|
||||
double times[] = new double[4];
|
||||
times[0] = minuteMs;
|
||||
times[1] = minuteFreq;
|
||||
times[2] = hourMs;
|
||||
times[3] = hourFreq;
|
||||
|
||||
String valDescr[] = new String[4];
|
||||
valDescr[0] = "process time avg ms (minute)";
|
||||
valDescr[1] = "process events (minute)";
|
||||
valDescr[2] = "process time avg ms (hour)";
|
||||
valDescr[3] = "process events (hour)";
|
||||
String description = "how long does it take to process a message";
|
||||
monitor.addData(peer.getIdentity().getHash().toBase64(), "processingTime", description, valDescr, peer.getPublished(), times);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull a value from the peer's option as a double, assuming the standard semicolon
|
||||
* delimited formatting
|
||||
*
|
||||
* @param peer peer to query
|
||||
* @param key peer option to check
|
||||
* @param index 0-based index into the semicolon delimited values to pull out
|
||||
* @return value, or -1 if there was an error
|
||||
*/
|
||||
private static final double getDouble(RouterInfo peer, String key, int index) {
|
||||
String val = peer.getOptions().getProperty(key);
|
||||
if (val == null) return -1;
|
||||
StringTokenizer tok = new StringTokenizer(val, ";");
|
||||
for (int i = 0; i < index; i++) {
|
||||
if (!tok.hasMoreTokens()) return -1;
|
||||
tok.nextToken(); // ignore
|
||||
}
|
||||
if (!tok.hasMoreTokens()) return -1;
|
||||
String cur = tok.nextToken();
|
||||
try {
|
||||
return getDoubleValue(cur);
|
||||
} catch (ParseException pe) {
|
||||
_log.warn("Unable to parse out the double from field " + index + " out of " + val + " for " + key, pe);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/** this mimics the format used in the router's StatisticsManager */
|
||||
private static final DecimalFormat _numFmt = new DecimalFormat("###,###,###,###,##0.00", new DecimalFormatSymbols(Locale.UK));
|
||||
|
||||
/**
|
||||
* Converts a number (double) to text
|
||||
* @param val the number to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
private static final double getDoubleValue(String val) throws ParseException {
|
||||
synchronized (_numFmt) {
|
||||
Number n = _numFmt.parse(val);
|
||||
return n.doubleValue();
|
||||
}
|
||||
}
|
||||
}
|
212
apps/netmonitor/java/src/net/i2p/netmonitor/NetMonitor.java
Normal file
212
apps/netmonitor/java/src/net/i2p/netmonitor/NetMonitor.java
Normal file
@ -0,0 +1,212 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.io.IOException;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Main driver for the app that harvests data about the performance of the network,
|
||||
* building summaries for each peer that change over time. <p />
|
||||
*
|
||||
* Usage: <code>NetMonitor [configFilename]</code> <p />
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class NetMonitor {
|
||||
private static final Log _log = new Log(NetMonitor.class);
|
||||
public static final String CONFIG_LOCATION_DEFAULT = "netmonitor.config";
|
||||
public static final String HARVEST_DELAY_PROP = "harvestDelaySeconds";
|
||||
public static final int HARVEST_DELAY_DEFAULT = 60;
|
||||
public static final String EXPORT_DELAY_PROP = "exportDelaySeconds";
|
||||
public static final int EXPORT_DELAY_DEFAULT = 120;
|
||||
public static final String SUMMARY_DURATION_PROP = "summaryDurationHours";
|
||||
public static final int SUMMARY_DURATION_DEFAULT = 72;
|
||||
public static final String NETDB_DIR_PROP = "netDbDir";
|
||||
public static final String NETDB_DIR_DEFAULT = "netDb";
|
||||
public static final String EXPORT_DIR_PROP = "exportDir";
|
||||
public static final String EXPORT_DIR_DEFAULT = "monitorData";
|
||||
private String _configLocation;
|
||||
private int _harvestDelay;
|
||||
private int _exportDelay;
|
||||
private String _exportDir;
|
||||
private String _netDbDir;
|
||||
private int _summaryDurationHours;
|
||||
private boolean _isRunning;
|
||||
private Map _peerSummaries;
|
||||
|
||||
public NetMonitor() {
|
||||
this(CONFIG_LOCATION_DEFAULT);
|
||||
}
|
||||
public NetMonitor(String configLocation) {
|
||||
_configLocation = configLocation;
|
||||
_peerSummaries = new HashMap(32);
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
/** read and call parse */
|
||||
private void loadConfig() {
|
||||
Properties props = new Properties();
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(_configLocation);
|
||||
props.load(fis);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Error loading the net monitor config", ioe);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
parseConfig(props);
|
||||
}
|
||||
|
||||
/** interpret the config elements and shove 'em in the vars */
|
||||
private void parseConfig(Properties props) {
|
||||
String val = props.getProperty(HARVEST_DELAY_PROP, ""+HARVEST_DELAY_DEFAULT);
|
||||
try {
|
||||
_harvestDelay = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Error parsing the harvest delay [" + val + "]", nfe);
|
||||
_harvestDelay = HARVEST_DELAY_DEFAULT;
|
||||
}
|
||||
|
||||
val = props.getProperty(EXPORT_DELAY_PROP, ""+EXPORT_DELAY_DEFAULT);
|
||||
try {
|
||||
_exportDelay = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Error parsing the export delay [" + val + "]", nfe);
|
||||
_exportDelay = EXPORT_DELAY_DEFAULT;
|
||||
}
|
||||
|
||||
val = props.getProperty(SUMMARY_DURATION_PROP, ""+SUMMARY_DURATION_DEFAULT);
|
||||
try {
|
||||
_summaryDurationHours = Integer.parseInt(val);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.warn("Error parsing the summary duration [" + val + "]", nfe);
|
||||
_summaryDurationHours = SUMMARY_DURATION_DEFAULT;
|
||||
}
|
||||
|
||||
_netDbDir = props.getProperty(NETDB_DIR_PROP, NETDB_DIR_DEFAULT);
|
||||
_exportDir = props.getProperty(EXPORT_DIR_PROP, EXPORT_DIR_DEFAULT);
|
||||
}
|
||||
|
||||
public void startMonitor() {
|
||||
_isRunning = true;
|
||||
I2PThread t = new I2PThread(new NetMonitorRunner(this));
|
||||
t.setName("DataHarvester");
|
||||
t.setPriority(I2PThread.MIN_PRIORITY);
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
|
||||
public void stopMonitor() { _isRunning = false; }
|
||||
public boolean isRunning() { return _isRunning; }
|
||||
/** how many seconds should we wait between harvestings? */
|
||||
public int getHarvestDelay() { return _harvestDelay; }
|
||||
/** how many seconds should we wait between exporting the data? */
|
||||
public int getExportDelay() { return _exportDelay; }
|
||||
/** where should we export the data? */
|
||||
public String getExportDir() { return _exportDir; }
|
||||
public void setExportDir(String dir) { _exportDir = dir; }
|
||||
public int getSummaryDurationHours() { return _summaryDurationHours; }
|
||||
/** where should we read the data from? */
|
||||
public String getNetDbDir() { return _netDbDir; }
|
||||
/**
|
||||
* what peers are we keeping track of?
|
||||
*
|
||||
* @return list of peer names (H(routerIdentity).toBase64())
|
||||
*/
|
||||
public List getPeers() {
|
||||
synchronized (_peerSummaries) {
|
||||
return new ArrayList(_peerSummaries.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/** what data do we have for the peer? */
|
||||
public PeerSummary getSummary(String peer) {
|
||||
synchronized (_peerSummaries) {
|
||||
return (PeerSummary)_peerSummaries.get(peer);
|
||||
}
|
||||
}
|
||||
|
||||
/** keep track of the given stat on the given peer */
|
||||
public void addData(String peer, String stat, String descr, String valDescr[], long sampleDate, double val[]) {
|
||||
synchronized (_peerSummaries) {
|
||||
if (!_peerSummaries.containsKey(peer))
|
||||
_peerSummaries.put(peer, new PeerSummary(peer));
|
||||
PeerSummary summary = (PeerSummary)_peerSummaries.get(peer);
|
||||
summary.addData(stat, descr, valDescr, sampleDate, val);
|
||||
}
|
||||
}
|
||||
|
||||
/** keep track of the given stat on the given peer */
|
||||
public void addData(String peer, String stat, String descr, String valDescr[], long sampleDate, long val[]) {
|
||||
synchronized (_peerSummaries) {
|
||||
if (!_peerSummaries.containsKey(peer))
|
||||
_peerSummaries.put(peer, new PeerSummary(peer));
|
||||
PeerSummary summary = (PeerSummary)_peerSummaries.get(peer);
|
||||
summary.addData(stat, descr, valDescr, sampleDate, val);
|
||||
}
|
||||
}
|
||||
|
||||
/** keep track of the loaded summary, overwriting any existing summary for the specified peer */
|
||||
public void addSummary(PeerSummary summary) {
|
||||
synchronized (_peerSummaries) {
|
||||
Object rv = _peerSummaries.put(summary.getPeer(), summary);
|
||||
if (rv != summary) _log.error("Updating the peer summary changed objects! old = " + rv + " new = " + summary);
|
||||
}
|
||||
}
|
||||
|
||||
public void importData() {
|
||||
_log.debug("Running import");
|
||||
File dataDir = new File(getExportDir());
|
||||
if (!dataDir.exists()) return;
|
||||
File dataFiles[] = dataDir.listFiles(new FilenameFilter() {
|
||||
public boolean accept(File f, String name) {
|
||||
return name.endsWith(".txt");
|
||||
}
|
||||
});
|
||||
if (dataFiles == null) return;
|
||||
for (int i = 0; i < dataFiles.length; i++) {
|
||||
FileInputStream fis = null;
|
||||
boolean delete = false;
|
||||
try {
|
||||
fis = new FileInputStream(dataFiles[i]);
|
||||
PeerSummaryReader.getInstance().read(this, fis);
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error reading the data file " + dataFiles[i].getAbsolutePath(), ioe);
|
||||
delete = true;
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
if (delete) dataFiles[i].delete();
|
||||
}
|
||||
}
|
||||
_log.debug(dataFiles.length + " summaries imported");
|
||||
}
|
||||
|
||||
/** drop all the old summary data */
|
||||
public void coallesceData() {
|
||||
synchronized (_peerSummaries) {
|
||||
for (Iterator iter = _peerSummaries.values().iterator(); iter.hasNext(); ) {
|
||||
PeerSummary summary = (PeerSummary)iter.next();
|
||||
summary.coallesceData(_summaryDurationHours * 60*60*1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static final void main(String args[]) {
|
||||
if (args.length == 1)
|
||||
new NetMonitor(args[0]).startMonitor();
|
||||
else
|
||||
new NetMonitor(CONFIG_LOCATION_DEFAULT).startMonitor();
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.data.DataFormatException;
|
||||
import net.i2p.data.RouterInfo;
|
||||
|
||||
/**
|
||||
* Active process that drives the monitoring by periodically rading the
|
||||
* netDb dir, pumping the router data throug the data harvester, updating
|
||||
* the state, and coordinating the export.
|
||||
*
|
||||
*/
|
||||
class NetMonitorRunner implements Runnable {
|
||||
private static final Log _log = new Log(NetMonitorRunner.class);
|
||||
private NetMonitor _monitor;
|
||||
/**
|
||||
* @param monitor who do we give our data to?
|
||||
*/
|
||||
public NetMonitorRunner(NetMonitor monitor) {
|
||||
_monitor = monitor;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
runImport();
|
||||
long now = Clock.getInstance().now();
|
||||
long nextHarvest = now;
|
||||
long nextExport = now + _monitor.getExportDelay() * 1000;
|
||||
while (_monitor.isRunning()) {
|
||||
now = Clock.getInstance().now();
|
||||
_monitor.coallesceData();
|
||||
if (now >= nextHarvest) {
|
||||
runHarvest();
|
||||
nextHarvest = now + _monitor.getHarvestDelay() * 1000;
|
||||
}
|
||||
if (now >= nextExport) {
|
||||
runExport();
|
||||
nextExport = now + _monitor.getExportDelay() * 1000;
|
||||
}
|
||||
pauseHarvesting();
|
||||
}
|
||||
}
|
||||
|
||||
private void runHarvest() {
|
||||
List routers = getRouters();
|
||||
DataHarvester.getInstance().harvestData(_monitor, routers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all of the available RouterInfo structures
|
||||
*
|
||||
*/
|
||||
private List getRouters() {
|
||||
File routers[] = listRouters();
|
||||
List rv = new ArrayList(64);
|
||||
if (routers != null) {
|
||||
for (int i = 0; i < routers.length; i++) {
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(routers[i]);
|
||||
RouterInfo ri = new RouterInfo();
|
||||
ri.readBytes(fis);
|
||||
rv.add(ri);
|
||||
} catch (DataFormatException dfe) {
|
||||
_log.warn("Unable to parse the routerInfo from " + routers[i].getAbsolutePath(), dfe);
|
||||
} catch (IOException ioe) {
|
||||
_log.warn("Unable to read the routerInfo from " + routers[i].getAbsolutePath(), ioe);
|
||||
} finally {
|
||||
if (fis != null) try { fis.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* dump the data to the filesystem
|
||||
*/
|
||||
private void runExport() {
|
||||
_log.info("Export");
|
||||
List peers = _monitor.getPeers();
|
||||
File exportDir = new File(_monitor.getExportDir());
|
||||
if (!exportDir.exists())
|
||||
exportDir.mkdirs();
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
String peerName = (String)peers.get(i);
|
||||
PeerSummary summary = (PeerSummary)_monitor.getSummary(peerName);
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
File summaryFile = new File(exportDir, peerName + ".txt");
|
||||
fos = new FileOutputStream(summaryFile);
|
||||
PeerSummaryWriter.getInstance().write(summary, fos);
|
||||
_log.debug("Peer summary written to " + summaryFile.getAbsolutePath());
|
||||
} catch (IOException ioe) {
|
||||
_log.error("Error exporting the peer summary for " + peerName, ioe);
|
||||
} finally {
|
||||
if (fos != null) try { fos.close(); } catch (IOException ioe) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read in all the peer summaries we had previously exported, overwriting any
|
||||
* existing ones in memory
|
||||
*
|
||||
*/
|
||||
private void runImport() {
|
||||
_monitor.importData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all of the routers to load
|
||||
*
|
||||
* @return list of File objects pointing at the routers around
|
||||
*/
|
||||
private File[] listRouters() {
|
||||
File dbDir = new File(_monitor.getNetDbDir());
|
||||
File files[] = dbDir.listFiles(new FilenameFilter() {
|
||||
public boolean accept(File f, String name) {
|
||||
return name.startsWith("routerInfo-");
|
||||
}
|
||||
});
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait the correct amount of time before harvesting again
|
||||
*
|
||||
*/
|
||||
private void pauseHarvesting() {
|
||||
try {
|
||||
Thread.sleep(_monitor.getHarvestDelay());
|
||||
} catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
46
apps/netmonitor/java/src/net/i2p/netmonitor/PeerStat.java
Normal file
46
apps/netmonitor/java/src/net/i2p/netmonitor/PeerStat.java
Normal file
@ -0,0 +1,46 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
/**
|
||||
* Actual data point (though its not fully normalized, but KISS)
|
||||
*
|
||||
*/
|
||||
public class PeerStat {
|
||||
private String _statName;
|
||||
private String _description;
|
||||
private String _valueDescriptions[];
|
||||
private long _sampleDate;
|
||||
private long _lvalues[];
|
||||
private double _dvalues[];
|
||||
|
||||
public PeerStat(String name, String description, String valueDescriptions[], long sampleDate, double values[]) {
|
||||
this(name, description, valueDescriptions, sampleDate, null, values);
|
||||
}
|
||||
public PeerStat(String name, String description, String valueDescriptions[], long sampleDate, long values[]) {
|
||||
this(name, description, valueDescriptions, sampleDate, values, null);
|
||||
}
|
||||
private PeerStat(String name, String description, String valueDescriptions[], long sampleDate, long lvalues[], double dvalues[]) {
|
||||
_statName = name;
|
||||
_description = description;
|
||||
_valueDescriptions = valueDescriptions;
|
||||
_sampleDate = sampleDate;
|
||||
_lvalues = lvalues;
|
||||
_dvalues = dvalues;
|
||||
}
|
||||
|
||||
/** unique name of the stat */
|
||||
public String getStatName() { return _statName; }
|
||||
/** one line summary of the stat */
|
||||
public String getDescription() { return _description; }
|
||||
/** description of each value */
|
||||
public String getValueDescription(int index) { return _valueDescriptions[index]; }
|
||||
/** description of all values */
|
||||
public String[] getValueDescriptions() { return _valueDescriptions; }
|
||||
/** when did the router publish the info being sampled? */
|
||||
public long getSampleDate() { return _sampleDate; }
|
||||
/** if the values are integers, this contains their values */
|
||||
public long[] getLongValues() { return _lvalues; }
|
||||
/** if the values are floating point numbers, this contains their values */
|
||||
public double[] getDoubleValues() { return _dvalues; }
|
||||
/** are the values floating poing numbers? */
|
||||
public boolean getIsDouble() { return _dvalues != null; }
|
||||
}
|
119
apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummary.java
Normal file
119
apps/netmonitor/java/src/net/i2p/netmonitor/PeerSummary.java
Normal file
@ -0,0 +1,119 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* coordinate the data points summarizing the performance of a particular peer
|
||||
* within the network
|
||||
*/
|
||||
public class PeerSummary {
|
||||
private static final Log _log = new Log(PeerSummary.class);
|
||||
private String _peer;
|
||||
/** statName to a List of PeerStat elements (sorted by sample date, earliest first) */
|
||||
private Map _stats;
|
||||
/** lock on this when accessing stat data */
|
||||
private Object _coallesceLock = new Object();
|
||||
|
||||
public PeerSummary(String peer) {
|
||||
_peer = peer;
|
||||
_stats = new HashMap(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a data point
|
||||
*
|
||||
* @param stat what data are we tracking?
|
||||
* @param description what does this data mean? (and what are the values?)
|
||||
* @param when what data set is this sample based off?
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, double val[]) {
|
||||
synchronized (_coallesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track a data point
|
||||
*
|
||||
* @param stat what data are we tracking?
|
||||
* @param description what does this data mean? (and what are the values?)
|
||||
* @param when what data set is this sample based off?
|
||||
* @param val actual data harvested
|
||||
*/
|
||||
public void addData(String stat, String description, String valueDescriptions[], long when, long val[]) {
|
||||
synchronized (_coallesceLock) {
|
||||
TreeMap stats = locked_getData(stat);
|
||||
stats.put(new Long(when), new PeerStat(stat, description, valueDescriptions, when, val));
|
||||
}
|
||||
}
|
||||
|
||||
/** get the peer's name (H(routerIdentity).toBase64()) */
|
||||
public String getPeer() { return _peer; }
|
||||
|
||||
/**
|
||||
* fetch the ordered list of PeerStat objects for the given stat (or null if
|
||||
* isn't being tracked or has no data)
|
||||
*
|
||||
*/
|
||||
public List getData(String statName) {
|
||||
synchronized (_coallesceLock) {
|
||||
return new ArrayList(((TreeMap)_stats.get(statName)).values());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the names of all of the stats that are being tracked
|
||||
*
|
||||
*/
|
||||
public Set getStatNames() {
|
||||
synchronized (_coallesceLock) {
|
||||
return new HashSet(_stats.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
/** drop old data points */
|
||||
public void coallesceData(long summaryDurationMs) {
|
||||
long earliest = Clock.getInstance().now() - summaryDurationMs;
|
||||
synchronized (_coallesceLock) {
|
||||
locked_coallesce(earliest);
|
||||
}
|
||||
}
|
||||
|
||||
/** go through all the stats and remove ones from before the given date */
|
||||
private void locked_coallesce(long earliestSampleDate) {
|
||||
if (true) return;
|
||||
for (Iterator iter = _stats.keySet().iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
TreeMap stats = (TreeMap)_stats.get(statName);
|
||||
while (stats.size() > 0) {
|
||||
Long when = (Long)stats.keySet().iterator().next();
|
||||
if (when.longValue() < earliestSampleDate) {
|
||||
stats.remove(when);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PeerStat elements, ordered by sample date (earliest first)
|
||||
*/
|
||||
private TreeMap locked_getData(String statName) {
|
||||
if (!_stats.containsKey(statName))
|
||||
_stats.put(statName, new TreeMap());
|
||||
return (TreeMap)_stats.get(statName);
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Set;
|
||||
import java.util.Locale;
|
||||
import java.util.Date;
|
||||
import java.util.StringTokenizer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.text.ParseException;
|
||||
|
||||
/**
|
||||
* Load up the peer summary
|
||||
*
|
||||
*/
|
||||
class PeerSummaryReader {
|
||||
private static final Log _log = new Log(PeerSummaryReader.class);
|
||||
private static final PeerSummaryReader _instance = new PeerSummaryReader();
|
||||
public static final PeerSummaryReader getInstance() { return _instance; }
|
||||
private PeerSummaryReader() {}
|
||||
|
||||
/** */
|
||||
public void read(NetMonitor monitor, InputStream in) throws IOException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
|
||||
String line = null;
|
||||
PeerSummary summary = null;
|
||||
String curDescription = null;
|
||||
List curArgs = null;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
if (line.startsWith("peer\t")) {
|
||||
String name = line.substring("peer\t".length()).trim();
|
||||
summary = monitor.getSummary(name);
|
||||
if (summary == null)
|
||||
summary = new PeerSummary(name);
|
||||
} else if (line.startsWith("## ")) {
|
||||
curDescription = line.substring("## ".length()).trim();
|
||||
curArgs = new ArrayList(4);
|
||||
} else if (line.startsWith("# param ")) {
|
||||
int start = line.indexOf(':');
|
||||
String arg = line.substring(start+1).trim();
|
||||
curArgs.add(arg);
|
||||
} else {
|
||||
StringTokenizer tok = new StringTokenizer(line);
|
||||
String name = tok.nextToken();
|
||||
try {
|
||||
long when = getTime(tok.nextToken());
|
||||
boolean isDouble = false;
|
||||
List argVals = new ArrayList(curArgs.size());
|
||||
while (tok.hasMoreTokens()) {
|
||||
String val = (String)tok.nextToken();
|
||||
if (val.indexOf('.') >= 0) {
|
||||
argVals.add(new Double(val));
|
||||
isDouble = true;
|
||||
} else {
|
||||
argVals.add(new Long(val));
|
||||
}
|
||||
}
|
||||
String valDescriptions[] = new String[curArgs.size()];
|
||||
for (int i = 0; i < curArgs.size(); i++)
|
||||
valDescriptions[i] = (String)curArgs.get(i);
|
||||
if (isDouble) {
|
||||
double values[] = new double[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Double)argVals.get(i)).doubleValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
} else {
|
||||
long values[] = new long[argVals.size()];
|
||||
for (int i = 0; i < argVals.size(); i++)
|
||||
values[i] = ((Long)argVals.get(i)).longValue();
|
||||
summary.addData(name, curDescription, valDescriptions, when, values);
|
||||
}
|
||||
} catch (ParseException pe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", pe);
|
||||
} catch (NumberFormatException nfe) {
|
||||
_log.error("Error parsing the data line [" + line + "]", nfe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
summary.coallesceData(monitor.getSummaryDurationHours() * 60*60*1000);
|
||||
monitor.addSummary(summary);
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
|
||||
/**
|
||||
* Converts a time (long) to text
|
||||
* @param when the time to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public long getTime(String when) throws ParseException {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.parse(when).getTime();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
package net.i2p.netmonitor;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import java.util.Set;
|
||||
import java.util.Locale;
|
||||
import java.util.Date;
|
||||
import java.text.SimpleDateFormat;
|
||||
|
||||
/**
|
||||
* Dump various peer summaries to disk (so external apps (or good ol' vi) can
|
||||
* peek into what we're harvesting
|
||||
*
|
||||
*/
|
||||
class PeerSummaryWriter {
|
||||
private static final Log _log = new Log(PeerSummaryWriter.class);
|
||||
private static final PeerSummaryWriter _instance = new PeerSummaryWriter();
|
||||
public static final PeerSummaryWriter getInstance() { return _instance; }
|
||||
private PeerSummaryWriter() {}
|
||||
|
||||
/** write out the peer summary to the stream specified */
|
||||
public void write(PeerSummary summary, OutputStream out) throws IOException {
|
||||
StringBuffer buf = new StringBuffer(4*1024);
|
||||
buf.append("peer\t").append(summary.getPeer()).append('\n');
|
||||
TreeSet names = new TreeSet(summary.getStatNames());
|
||||
for (Iterator iter = names.iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
List stats = summary.getData(statName);
|
||||
for (int i = 0; i < stats.size(); i++) {
|
||||
PeerStat stat = (PeerStat)stats.get(i);
|
||||
if (i == 0) {
|
||||
buf.append("## ").append(stat.getDescription()).append('\n');
|
||||
String descr[] = stat.getValueDescriptions();
|
||||
if (descr != null) {
|
||||
for (int j = 0; j < descr.length; j++)
|
||||
buf.append("# param ").append(j).append(": ").append(descr[j]).append('\n');
|
||||
}
|
||||
}
|
||||
buf.append(statName).append('\t');
|
||||
buf.append(getTime(stat.getSampleDate())).append('\t');
|
||||
|
||||
if (stat.getIsDouble()) {
|
||||
double vals[] = stat.getDoubleValues();
|
||||
if (vals != null) {
|
||||
for (int j = 0; j < vals.length; j++)
|
||||
buf.append(vals[j]).append('\t');
|
||||
}
|
||||
} else {
|
||||
long vals[] = stat.getLongValues();
|
||||
if (vals != null) {
|
||||
for (int j = 0; j < vals.length; j++)
|
||||
buf.append(vals[j]).append('\t');
|
||||
}
|
||||
}
|
||||
buf.append('\n');
|
||||
}
|
||||
}
|
||||
String data = buf.toString();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Stat: \n" + data);
|
||||
out.write(data.getBytes());
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK);
|
||||
|
||||
/**
|
||||
* Converts a time (long) to text
|
||||
* @param when the time to convert
|
||||
* @return the textual representation
|
||||
*/
|
||||
public String getTime(long when) {
|
||||
synchronized (_fmt) {
|
||||
return _fmt.format(new Date(when));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import net.i2p.netmonitor.PeerSummary;
|
||||
import net.i2p.netmonitor.PeerStat;
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.jfree.data.XYSeries;
|
||||
import org.jfree.data.XYSeriesCollection;
|
||||
import org.jfree.data.MovingAverage;
|
||||
import org.jfree.chart.JFreeChart;
|
||||
import org.jfree.chart.ChartPanel;
|
||||
import org.jfree.chart.plot.Plot;
|
||||
import org.jfree.chart.plot.XYPlot;
|
||||
import org.jfree.chart.axis.DateAxis;
|
||||
import org.jfree.chart.axis.NumberAxis;
|
||||
import org.jfree.chart.renderer.XYLineAndShapeRenderer;
|
||||
import org.jfree.chart.renderer.XYItemRenderer;
|
||||
import org.jfree.chart.renderer.XYDotRenderer;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import java.awt.Font;
|
||||
import java.awt.Color;
|
||||
|
||||
class JFreeChartAdapter {
|
||||
private final static Log _log = new Log(JFreeChartAdapter.class);
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
|
||||
ChartPanel createPanel(NetViewer state) {
|
||||
ChartPanel panel = new ChartPanel(createChart(state));
|
||||
panel.setDisplayToolTips(true);
|
||||
panel.setEnforceFileExtensions(true);
|
||||
panel.setHorizontalZoom(true);
|
||||
panel.setVerticalZoom(true);
|
||||
panel.setMouseZoomable(true, true);
|
||||
panel.getChart().setBackgroundPaint(WHITE);
|
||||
return panel;
|
||||
}
|
||||
|
||||
JFreeChart createChart(NetViewer state) {
|
||||
Plot plot = createPlot(state);
|
||||
JFreeChart chart = new JFreeChart("I2P network performance", Font.getFont("arial"), plot, true);
|
||||
return chart;
|
||||
}
|
||||
|
||||
void updateChart(ChartPanel panel, NetViewer state) {
|
||||
XYPlot plot = (XYPlot)panel.getChart().getPlot();
|
||||
plot.setDataset(getCollection(state));
|
||||
}
|
||||
|
||||
Plot createPlot(NetViewer state) {
|
||||
XYItemRenderer renderer = new XYLineAndShapeRenderer(); // new XYDotRenderer(); //
|
||||
XYPlot plot = new XYPlot(getCollection(state), new DateAxis(), new NumberAxis("val"), renderer);
|
||||
return plot;
|
||||
}
|
||||
|
||||
XYSeriesCollection getCollection(NetViewer state) {
|
||||
XYSeriesCollection col = new XYSeriesCollection();
|
||||
if (state != null) {
|
||||
List names = state.getConfigNames();
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
addPeer(col, state.getConfig((String)names.get(i)));
|
||||
}
|
||||
} else {
|
||||
XYSeries series = new XYSeries("latency", false, false);
|
||||
series.add(System.currentTimeMillis(), 0);
|
||||
col.addSeries(series);
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
void addPeer(XYSeriesCollection col, PeerPlotConfig config) {
|
||||
Set statNames = config.getSummary().getStatNames();
|
||||
for (Iterator iter = statNames.iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
List statData = config.getSummary().getData(statName);
|
||||
if (statData.size() > 0) {
|
||||
PeerStat data = (PeerStat)statData.get(0);
|
||||
String args[] = data.getValueDescriptions();
|
||||
if (args != null) {
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (config.getSeriesConfig().getShouldPlotValue(statName, args[i], false))
|
||||
addLine(col, config.getPeerName(), statName, args[i], statData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
void addLine(XYSeriesCollection col, String peer, String statName, String argName, List dataPoints) {
|
||||
XYSeries series = new XYSeries(peer.substring(0, 4) + ": " + argName, false, false);
|
||||
for (int i = 0; i < dataPoints.size(); i++) {
|
||||
PeerStat data = (PeerStat)dataPoints.get(i);
|
||||
String argNames[] = data.getValueDescriptions();
|
||||
int argIndex = -1;
|
||||
for (int j = 0; j < argNames.length; j++) {
|
||||
if (argNames[j].equals(argName)) {
|
||||
argIndex = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (data.getIsDouble())
|
||||
series.add(data.getSampleDate(), data.getDoubleValues()[argIndex]);
|
||||
else
|
||||
series.add(data.getSampleDate(), data.getLongValues()[argIndex]);
|
||||
}
|
||||
col.addSeries(series);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JLabel;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
import org.jfree.chart.ChartPanel;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*
|
||||
*/
|
||||
class JFreeChartHeartbeatPlotPane extends NetViewerPlotPane {
|
||||
private final static Log _log = new Log(JFreeChartHeartbeatPlotPane.class);
|
||||
private ChartPanel _panel;
|
||||
private JFreeChartAdapter _adapter;
|
||||
|
||||
public JFreeChartHeartbeatPlotPane(NetViewerGUI gui) {
|
||||
super(gui);
|
||||
}
|
||||
|
||||
public void stateUpdated() {
|
||||
if (_panel == null) {
|
||||
remove(0); // remove the dummy
|
||||
|
||||
_adapter = new JFreeChartAdapter();
|
||||
_panel = _adapter.createPanel(_gui.getViewer());
|
||||
_panel.setBackground(_gui.getBackground());
|
||||
JScrollPane pane = new JScrollPane(_panel);
|
||||
pane.setBackground(_gui.getBackground());
|
||||
add(pane, BorderLayout.CENTER);
|
||||
} else {
|
||||
_adapter.updateChart(_panel, _gui.getViewer());
|
||||
//_gui.pack();
|
||||
}
|
||||
}
|
||||
|
||||
protected void initializeComponents() {
|
||||
// noop
|
||||
setLayout(new BorderLayout());
|
||||
add(new JLabel(), BorderLayout.CENTER);
|
||||
//dummy.setBackground(_gui.getBackground());
|
||||
//dummy.setPreferredSize(new Dimension(800,600));
|
||||
//add(dummy);
|
||||
|
||||
//add(_panel);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import net.i2p.netmonitor.NetMonitor;
|
||||
import net.i2p.netmonitor.PeerSummary;
|
||||
import net.i2p.netmonitor.PeerStat;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.I2PThread;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Coordinate the visualization of the network monitor. <p />
|
||||
*
|
||||
* <b>Usage: <code>NetViewer [exportDir]</code></b> <br />
|
||||
* (exportDir is where the NetMonitor exports its state, "monitorData" by default)
|
||||
*/
|
||||
public class NetViewer {
|
||||
private static final Log _log = new Log(NetViewer.class);
|
||||
private NetMonitor _monitor;
|
||||
private NetViewerGUI _gui;
|
||||
private Map _plotConfigs;
|
||||
private boolean _isClosed;
|
||||
|
||||
public NetViewer() {
|
||||
this(NetMonitor.EXPORT_DIR_DEFAULT);
|
||||
}
|
||||
public NetViewer(String dataDir) {
|
||||
_monitor = new NetMonitor();
|
||||
_monitor.setExportDir(dataDir);
|
||||
_isClosed = false;
|
||||
_gui = new NetViewerGUI(this);
|
||||
_plotConfigs = new HashMap();
|
||||
}
|
||||
|
||||
public void runViewer() {
|
||||
I2PThread t = new I2PThread(new NetViewerRunner(this));
|
||||
t.setName("NetViewer");
|
||||
t.setDaemon(false);
|
||||
t.start();
|
||||
}
|
||||
|
||||
void refreshGUI() {
|
||||
_gui.stateUpdated();
|
||||
}
|
||||
|
||||
void reloadData() {
|
||||
_log.debug("Reloading data");
|
||||
_monitor.importData();
|
||||
refreshGUI();
|
||||
}
|
||||
|
||||
public void addConfig(String peerName, PeerPlotConfig config) {
|
||||
synchronized (_plotConfigs) {
|
||||
_plotConfigs.put(peerName, config);
|
||||
}
|
||||
}
|
||||
|
||||
public PeerPlotConfig getConfig(String peerName) {
|
||||
synchronized (_plotConfigs) {
|
||||
return (PeerPlotConfig)_plotConfigs.get(peerName);
|
||||
}
|
||||
}
|
||||
|
||||
public List getConfigNames() {
|
||||
synchronized (_plotConfigs) {
|
||||
return new ArrayList(_plotConfigs.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
public NetMonitor getMonitor() { return _monitor; }
|
||||
|
||||
long getDataLoadDelay() { return _monitor.getExportDelay(); }
|
||||
|
||||
/** has the viewer been closed? */
|
||||
public boolean getIsClosed() { return _isClosed; }
|
||||
public void setIsClosed(boolean closed) { _isClosed = closed; }
|
||||
|
||||
public static final void main(String args[]) {
|
||||
if (args.length == 1)
|
||||
new NetViewer(args[0]).runViewer();
|
||||
else
|
||||
new NetViewer().runViewer();
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.ItemListener;
|
||||
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
class NetViewerCommandBar extends JPanel {
|
||||
private NetViewerGUI _gui;
|
||||
private JComboBox _refreshRate;
|
||||
private JTextField _location;
|
||||
|
||||
/**
|
||||
* Constructs a command bar onto the gui
|
||||
* @param gui the gui the command bar is associated with
|
||||
*/
|
||||
public NetViewerCommandBar(NetViewerGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
private void refreshChanged(ItemEvent evt) {}
|
||||
private void loadCalled() {
|
||||
//_gui.getMonitor().load(_location.getText());
|
||||
}
|
||||
|
||||
private void browseCalled() {
|
||||
JFileChooser chooser = new JFileChooser(_location.getText());
|
||||
chooser.setBackground(_gui.getBackground());
|
||||
chooser.setMultiSelectionEnabled(false);
|
||||
int rv = chooser.showDialog(this, "Load");
|
||||
//if (rv == JFileChooser.APPROVE_OPTION)
|
||||
// _gui.getMonitor().load(chooser.getSelectedFile().getAbsolutePath());
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
_refreshRate = new JComboBox(new DefaultComboBoxModel(new Object[] {"10 second refresh", "30 second refresh", "1 minute refresh", "5 minute refresh"}));
|
||||
_refreshRate.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent evt) { refreshChanged(evt); } });
|
||||
_refreshRate.setEnabled(false);
|
||||
_refreshRate.setBackground(_gui.getBackground());
|
||||
//add(_refreshRate);
|
||||
JLabel loadLabel = new JLabel("Load from: ");
|
||||
loadLabel.setBackground(_gui.getBackground());
|
||||
add(loadLabel);
|
||||
_location = new JTextField(20);
|
||||
_location.setToolTipText("Either specify a local filename or a fully qualified URL");
|
||||
_location.setBackground(_gui.getBackground());
|
||||
_location.setEnabled(false);
|
||||
add(_location);
|
||||
JButton browse = new JButton("Browse...");
|
||||
browse.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { browseCalled(); } });
|
||||
browse.setBackground(_gui.getBackground());
|
||||
browse.setEnabled(false);
|
||||
add(browse);
|
||||
JButton load = new JButton("Load");
|
||||
load.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadCalled(); } });
|
||||
load.setBackground(_gui.getBackground());
|
||||
load.setEnabled(false);
|
||||
add(load);
|
||||
setBackground(_gui.getBackground());
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTabbedPane;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the control widgets (refresh/load/snapshot and the
|
||||
* tabbed panel with the plot config data)
|
||||
*
|
||||
*/
|
||||
class NetViewerControlPane extends JPanel {
|
||||
private final static Log _log = new Log(NetViewerControlPane.class);
|
||||
private NetViewerGUI _gui;
|
||||
private JTabbedPane _configPane;
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private final static Color LIGHT_BLUE = new Color(180, 180, 255);
|
||||
private final static Color BLACK = new Color(0, 0, 0);
|
||||
private Color _background = WHITE;
|
||||
private Color _foreground = BLACK;
|
||||
|
||||
/**
|
||||
* Constructs a control panel onto the gui
|
||||
* @param gui the gui the panel is associated with
|
||||
*/
|
||||
public NetViewerControlPane(NetViewerGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/** the settings have changed - revise */
|
||||
void refreshView() {
|
||||
_gui.refreshView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when tests have changed
|
||||
*/
|
||||
public synchronized void stateUpdated() {
|
||||
List knownNames = new ArrayList(8);
|
||||
List peers = _gui.getViewer().getMonitor().getPeers();
|
||||
_log.debug("GUI updated with peers: " + peers);
|
||||
for (int i = 0; i < peers.size(); i++) {
|
||||
String name = (String)peers.get(i);
|
||||
String shortName = name.substring(0,4);
|
||||
knownNames.add(shortName);
|
||||
if (_configPane.indexOfTab(shortName) >= 0) {
|
||||
JScrollPane pane = (JScrollPane)_configPane.getComponentAt(_configPane.indexOfTab(shortName));
|
||||
PeerPlotConfigPane cfgPane = (PeerPlotConfigPane)pane.getViewport().getView();
|
||||
cfgPane.stateUpdated();
|
||||
_log.debug("We already know about [" + name + "]");
|
||||
} else {
|
||||
_log.info("The peer [" + name + "] is new to us");
|
||||
PeerPlotConfig cfg = new PeerPlotConfig(_gui.getViewer().getMonitor().getSummary(name));
|
||||
_gui.getViewer().addConfig(name, cfg);
|
||||
PeerPlotConfigPane pane = new PeerPlotConfigPane(cfg, this);
|
||||
JScrollPane p = new JScrollPane(pane);
|
||||
p.setBackground(_background);
|
||||
_configPane.addTab(shortName, null, p, "Peer " + name);
|
||||
_configPane.setBackgroundAt(_configPane.getTabCount()-1, _background);
|
||||
_configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground);
|
||||
}
|
||||
}
|
||||
List toRemove = new ArrayList(4);
|
||||
for (int i = 0; i < _configPane.getTabCount(); i++) {
|
||||
if (knownNames.contains(_configPane.getTitleAt(i))) {
|
||||
// noop
|
||||
} else {
|
||||
toRemove.add(_configPane.getTitleAt(i));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < toRemove.size(); i++) {
|
||||
String title = (String)toRemove.get(i);
|
||||
_log.info("Removing peer [" + title + "]");
|
||||
_configPane.removeTabAt(_configPane.indexOfTab(title));
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
if (_gui != null)
|
||||
setBackground(_gui.getBackground());
|
||||
else
|
||||
setBackground(_background);
|
||||
setLayout(new BorderLayout());
|
||||
NetViewerCommandBar bar = new NetViewerCommandBar(_gui);
|
||||
bar.setBackground(getBackground());
|
||||
add(bar, BorderLayout.NORTH);
|
||||
_configPane = new JTabbedPane(JTabbedPane.LEFT);
|
||||
_configPane.setBackground(_background);
|
||||
JScrollPane pane = new JScrollPane(_configPane);
|
||||
pane.setBackground(_background);
|
||||
add(pane, BorderLayout.CENTER);
|
||||
//setPreferredSize(new Dimension(800, 400));
|
||||
//setMinimumSize(new Dimension(800, 400));
|
||||
//setMaximumSize(new Dimension(800, 400));
|
||||
}
|
||||
|
||||
NetViewer getViewer() { return _gui.getViewer(); }
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JMenu;
|
||||
import javax.swing.JMenuBar;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JSplitPane;
|
||||
|
||||
class NetViewerGUI extends JFrame {
|
||||
private NetViewer _viewer;
|
||||
private NetViewerPlotPane _plotPane;
|
||||
private NetViewerControlPane _controlPane;
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private Color _background = WHITE;
|
||||
|
||||
/**
|
||||
* Creates the GUI for all youz who be too shoopid for text based shitz
|
||||
* @param monitor the monitor the gui operates over
|
||||
*/
|
||||
public NetViewerGUI(NetViewer viewer) {
|
||||
super("Network Viewer");
|
||||
_viewer = viewer;
|
||||
initializeComponents();
|
||||
pack();
|
||||
//setResizable(false);
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
NetViewer getViewer() { return _viewer; }
|
||||
|
||||
/** build up all our widgets */
|
||||
private void initializeComponents() {
|
||||
getContentPane().setLayout(new BorderLayout());
|
||||
|
||||
setBackground(_background);
|
||||
|
||||
_plotPane = new JFreeChartHeartbeatPlotPane(this); // // new NetViewerPlotPane(this); //
|
||||
_plotPane.setBackground(_background);
|
||||
JScrollPane pane = new JScrollPane(_plotPane);
|
||||
pane.setBackground(_background);
|
||||
//getContentPane().add(pane, BorderLayout.CENTER);
|
||||
|
||||
_controlPane = new NetViewerControlPane(this);
|
||||
_controlPane.setBackground(_background);
|
||||
//getContentPane().add(_controlPane, BorderLayout.SOUTH);
|
||||
|
||||
JSplitPane split = new JSplitPane(JSplitPane.VERTICAL_SPLIT, pane, new JScrollPane(_controlPane));
|
||||
getContentPane().add(split, BorderLayout.CENTER);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
initializeMenus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when the state of the world changes . . .
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
_controlPane.stateUpdated();
|
||||
_plotPane.stateUpdated();
|
||||
pack();
|
||||
}
|
||||
|
||||
public void refreshView() {
|
||||
_plotPane.stateUpdated();
|
||||
}
|
||||
|
||||
private void exitCalled() {
|
||||
_viewer.setIsClosed(true);
|
||||
setVisible(false);
|
||||
System.exit(0);
|
||||
}
|
||||
private void loadConfigCalled() {}
|
||||
private void saveConfigCalled() {}
|
||||
private void loadSnapshotCalled() {}
|
||||
private void saveSnapshotCalled() {}
|
||||
|
||||
private void initializeMenus() {
|
||||
JMenuBar bar = new JMenuBar();
|
||||
JMenu fileMenu = new JMenu("File");
|
||||
JMenuItem loadConfig = new JMenuItem("Load config");
|
||||
loadConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadConfigCalled(); } });
|
||||
JMenuItem saveConfig = new JMenuItem("Save config");
|
||||
saveConfig.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveConfigCalled(); } });
|
||||
JMenuItem saveSnapshot = new JMenuItem("Save snapshot");
|
||||
saveSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { saveSnapshotCalled(); } });
|
||||
JMenuItem loadSnapshot = new JMenuItem("Load snapshot");
|
||||
loadSnapshot.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadSnapshotCalled(); } });
|
||||
JMenuItem exit = new JMenuItem("Exit");
|
||||
exit.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { exitCalled(); } });
|
||||
|
||||
fileMenu.add(loadConfig);
|
||||
fileMenu.add(saveConfig);
|
||||
fileMenu.add(loadSnapshot);
|
||||
fileMenu.add(saveSnapshot);
|
||||
fileMenu.add(exit);
|
||||
bar.add(fileMenu);
|
||||
setJMenuBar(bar);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextArea;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
|
||||
/**
|
||||
* Render the graph and legend
|
||||
*/
|
||||
class NetViewerPlotPane extends JPanel {
|
||||
private final static Log _log = new Log(NetViewerPlotPane.class);
|
||||
protected NetViewerGUI _gui;
|
||||
private JTextArea _text;
|
||||
|
||||
/**
|
||||
* Constructs the plot pane
|
||||
* @param gui the gui the pane is attached to
|
||||
*/
|
||||
public NetViewerPlotPane(NetViewerGUI gui) {
|
||||
_gui = gui;
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: when things change . . .
|
||||
*/
|
||||
public void stateUpdated() {
|
||||
StringBuffer buf = new StringBuffer(32*1024);
|
||||
buf.append("moo");
|
||||
_text.setText(buf.toString());
|
||||
}
|
||||
|
||||
protected void initializeComponents() {
|
||||
setBackground(_gui.getBackground());
|
||||
//Dimension size = new Dimension(800, 600);
|
||||
_text = new JTextArea("",30,80); // 16, 60);
|
||||
_text.setAutoscrolls(true);
|
||||
_text.setEditable(false);
|
||||
// _text.setLineWrap(true);
|
||||
// add(new JScrollPane(_text));
|
||||
add(_text);
|
||||
// add(new JScrollPane(_text, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS));
|
||||
// setPreferredSize(size);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
class NetViewerRunner implements Runnable {
|
||||
private NetViewer _viewer;
|
||||
|
||||
public NetViewerRunner(NetViewer viewer) {
|
||||
_viewer = viewer;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
while (!_viewer.getIsClosed()) {
|
||||
_viewer.reloadData();
|
||||
try { Thread.sleep(_viewer.getDataLoadDelay()*1000); } catch (InterruptedException ie) {}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,218 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.netmonitor.PeerSummary;
|
||||
|
||||
/**
|
||||
* Configure how we want to render a particular peerSummary in the GUI
|
||||
*/
|
||||
class PeerPlotConfig {
|
||||
private final static Log _log = new Log(PeerPlotConfig.class);
|
||||
/** where can we find the current state/data (either as a filename or a URL)? */
|
||||
private String _location;
|
||||
/** what test are we defining the plot data for? */
|
||||
private PeerSummary _summary;
|
||||
/** how should we render the current data set? */
|
||||
private PlotSeriesConfig _seriesConfig;
|
||||
private Set _listeners;
|
||||
private boolean _disabled;
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param location the name of the file/URL to get the data from
|
||||
*/
|
||||
public PeerPlotConfig(String location) {
|
||||
this(location, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegating constructor . . .
|
||||
* @param location the name of the file/URL to get the data from
|
||||
*/
|
||||
public PeerPlotConfig(PeerSummary summary) {
|
||||
this(null, summary, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a config =)
|
||||
* @param location the location of the file/URL to get the data from
|
||||
* @param config the client's configuration
|
||||
* @param seriesConfig the series config
|
||||
*/
|
||||
public PeerPlotConfig(String location, PeerSummary summary, PlotSeriesConfig seriesConfig) {
|
||||
_location = location;
|
||||
_summary = summary;
|
||||
if (seriesConfig != null)
|
||||
_seriesConfig = seriesConfig;
|
||||
else
|
||||
_seriesConfig = new PlotSeriesConfig();
|
||||
|
||||
_listeners = Collections.synchronizedSet(new HashSet(2));
|
||||
_disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Where is the current state data supposed to be found? This must either be a
|
||||
* local file path or a URL
|
||||
* @return the current location
|
||||
*/
|
||||
public String getLocation() { return _location; }
|
||||
|
||||
/**
|
||||
* The location the current state data is supposed to be found. This must either be
|
||||
* a local file path or a URL
|
||||
* @param location the location
|
||||
*/
|
||||
public void setLocation(String location) {
|
||||
_location = location;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* What are we configuring?
|
||||
* @return the client configuration
|
||||
*/
|
||||
public PeerSummary getSummary() { return _summary; }
|
||||
|
||||
/**
|
||||
* Sets what we are currently configuring
|
||||
* @param config the new config
|
||||
*/
|
||||
public void setPeerSummary(PeerSummary summary) {
|
||||
_summary = summary;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* How do we want to render the current data set?
|
||||
* @return the way we currently render the data
|
||||
*/
|
||||
public PlotSeriesConfig getSeriesConfig() { return _seriesConfig; }
|
||||
|
||||
/**
|
||||
* Sets how we want to render the current data set.
|
||||
* @param config the new config
|
||||
*/
|
||||
public void setSeriesConfig(PlotSeriesConfig config) {
|
||||
_seriesConfig = config;
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* four char description of the peer
|
||||
* @return the name
|
||||
*/
|
||||
public String getPeerName() { return _summary.getPeer(); }
|
||||
|
||||
/**
|
||||
* we've got someone who wants to be notified of changes to the plot config
|
||||
* @param lsnr the listener to be added
|
||||
*/
|
||||
public void addListener(UpdateListener lsnr) { _listeners.add(lsnr); }
|
||||
|
||||
/**
|
||||
* remove a listener
|
||||
* @param lsnr the listener to remove
|
||||
*/
|
||||
public void removeListener(UpdateListener lsnr) { _listeners.remove(lsnr); }
|
||||
|
||||
void fireUpdate() {
|
||||
if (_disabled) return;
|
||||
for (Iterator iter = _listeners.iterator(); iter.hasNext(); ) {
|
||||
((UpdateListener)iter.next()).configUpdated(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables notification of events listeners
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
public void disableEvents() { _disabled = true; }
|
||||
|
||||
/**
|
||||
* Enables notification of events listeners
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
public void enableEvents() { _disabled = false; }
|
||||
|
||||
/**
|
||||
* How do we want to render a particular dataset (either the current or the averaged values)?
|
||||
*/
|
||||
public class PlotSeriesConfig {
|
||||
private Map _shouldPlot;
|
||||
private Map _plotColors;
|
||||
|
||||
/**
|
||||
* Creates a config for the rendering of a particular dataset)
|
||||
* @param plotLost do we plot lost packets?
|
||||
* @param plotColor in what color?
|
||||
*/
|
||||
public PlotSeriesConfig() {
|
||||
_shouldPlot = new HashMap(16);
|
||||
_plotColors = new HashMap(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the plot config this plot series config is a part of
|
||||
* @return the plot config
|
||||
*/
|
||||
public PeerPlotConfig getPlotConfig() { return PeerPlotConfig.this; }
|
||||
|
||||
public boolean getShouldPlotValue(String statName, String argName, boolean defaultVal) {
|
||||
Boolean val = (Boolean)_shouldPlot.get(statName + argName);
|
||||
if (val == null)
|
||||
return defaultVal;
|
||||
else
|
||||
return val.booleanValue();
|
||||
}
|
||||
|
||||
public void setShouldPlotValue(String statName, String argName, boolean shouldPlot) {
|
||||
_shouldPlot.put(statName + argName, new Boolean(shouldPlot));
|
||||
fireUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* What color should we plot the data with?
|
||||
* @return the color
|
||||
*/
|
||||
public Color getPlotLineColor(String statName, String argName) {
|
||||
return (Color)_plotColors.get(statName + argName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the color we should plot the data with
|
||||
* @param color the color to use
|
||||
*/
|
||||
public void setPlotLineColor(String statName, String argName, Color color) {
|
||||
if (color == null)
|
||||
_plotColors.remove(statName + argName);
|
||||
else
|
||||
_plotColors.put(statName + argName, color);
|
||||
fireUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface for listening to updates . . .
|
||||
*/
|
||||
public interface UpdateListener {
|
||||
/**
|
||||
* @param config the peer plot config that changes
|
||||
* @see PeerPlotConfig#fireUpdate()
|
||||
*/
|
||||
void configUpdated(PeerPlotConfig config);
|
||||
}
|
||||
}
|
@ -0,0 +1,280 @@
|
||||
package net.i2p.netmonitor.gui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.TreeMap;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
import java.util.Iterator;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JColorChooser;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.netmonitor.PeerStat;
|
||||
import net.i2p.netmonitor.PeerSummary;
|
||||
|
||||
class PeerPlotConfigPane extends JPanel implements PeerPlotConfig.UpdateListener {
|
||||
private final static Log _log = new Log(PeerPlotConfigPane.class);
|
||||
private PeerPlotConfig _config;
|
||||
private NetViewerControlPane _parent;
|
||||
private JLabel _peer;
|
||||
private JButton _toggleAll;
|
||||
private OptionGroup _options[];
|
||||
private Random _rnd = new Random();
|
||||
private final static Color WHITE = new Color(255, 255, 255);
|
||||
private Color _background = WHITE;
|
||||
|
||||
/**
|
||||
* Constructs a pane
|
||||
* @param config the plot config it represents
|
||||
* @param pane the pane this one is attached to
|
||||
*/
|
||||
public PeerPlotConfigPane(PeerPlotConfig config, NetViewerControlPane pane) {
|
||||
_config = config;
|
||||
_parent = pane;
|
||||
if (_parent != null)
|
||||
_background = _parent.getBackground();
|
||||
_config.addListener(this);
|
||||
initializeComponents();
|
||||
}
|
||||
|
||||
private void initializeComponents() {
|
||||
buildComponents();
|
||||
placeComponents(this);
|
||||
refreshView();
|
||||
//setBorder(new BevelBorder(BevelBorder.RAISED));
|
||||
setBackground(_background);
|
||||
}
|
||||
|
||||
/**
|
||||
* place all the gui components onto the given panel
|
||||
* @param body the panel to place the components on
|
||||
*/
|
||||
private void placeComponents(JPanel body) {
|
||||
body.setLayout(new GridBagLayout());
|
||||
GridBagConstraints cts = new GridBagConstraints();
|
||||
|
||||
// row 0: peer name + toggle All
|
||||
cts.gridx = 0;
|
||||
cts.gridy = 0;
|
||||
cts.gridwidth = 2;
|
||||
cts.anchor = GridBagConstraints.WEST;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_peer, cts);
|
||||
cts.gridx = 2;
|
||||
cts.gridy = 0;
|
||||
cts.gridwidth = 1;
|
||||
cts.anchor = GridBagConstraints.EAST;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(_toggleAll, cts);
|
||||
|
||||
int row = 0;
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
row++;
|
||||
cts.gridx = 0;
|
||||
cts.gridy = row;
|
||||
cts.gridwidth = 3;
|
||||
cts.weightx = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
cts.anchor = GridBagConstraints.WEST;
|
||||
body.add(_options[i]._statName, cts);
|
||||
|
||||
for (int j = 0; j < _options[i]._statAttributes.size(); j++) {
|
||||
row++;
|
||||
cts.gridx = 0;
|
||||
cts.gridy = row;
|
||||
cts.gridwidth = 1;
|
||||
cts.weightx = 1;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
body.add(new JLabel(" "), cts);
|
||||
cts.gridx = 1;
|
||||
cts.gridy = row;
|
||||
cts.gridwidth = 1;
|
||||
cts.weightx = 5;
|
||||
cts.fill = GridBagConstraints.BOTH;
|
||||
JCheckBox box = (JCheckBox)_options[i]._statAttributes.get(j);
|
||||
box.setBackground(_background);
|
||||
body.add(box, cts);
|
||||
cts.gridx = 2;
|
||||
cts.fill = GridBagConstraints.NONE;
|
||||
cts.anchor = GridBagConstraints.EAST;
|
||||
JButton toggleAttr = new JButton("Toggle all");
|
||||
toggleAttr.setBackground(_background);
|
||||
toggleAttr.addActionListener(new ToggleAttribute(_options[i].getStatName(), box.getText(), box));
|
||||
body.add(toggleAttr, cts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ToggleAttribute implements ActionListener {
|
||||
private String _statName;
|
||||
private String _attrName;
|
||||
private JCheckBox _box;
|
||||
public ToggleAttribute(String statName, String attrName, JCheckBox box) {
|
||||
_statName = statName;
|
||||
_attrName = attrName;
|
||||
_box = box;
|
||||
}
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
boolean setActive = true;
|
||||
if (_box.isSelected()) {
|
||||
setActive = false;
|
||||
}
|
||||
|
||||
List names = _parent.getViewer().getConfigNames();
|
||||
for (int i = 0; i < names.size(); i++) {
|
||||
String name = (String)names.get(i);
|
||||
PeerPlotConfig cfg = _parent.getViewer().getConfig(name);
|
||||
cfg.getSeriesConfig().setShouldPlotValue(_statName, _attrName, setActive);
|
||||
_log.debug("Setting " + _statName + "." + _attrName + " to " + setActive + " for " + name);
|
||||
}
|
||||
_parent.stateUpdated();
|
||||
_parent.refreshView();
|
||||
}
|
||||
}
|
||||
|
||||
/** build all of the gui components */
|
||||
private void buildComponents() {
|
||||
_peer = new JLabel("Router: " + _config.getPeerName());
|
||||
|
||||
_toggleAll = new JButton("Show all");
|
||||
_toggleAll.setBackground(_background);
|
||||
_toggleAll.addActionListener(new ActionListener() {
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
_config.disableEvents();
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
for (int j = 0; j < _options[i]._statAttributes.size(); j++) {
|
||||
JCheckBox box = (JCheckBox)_options[i]._statAttributes.get(j);
|
||||
String statName = _options[i].getStatName();
|
||||
String attrName = box.getText();
|
||||
if (_toggleAll.getText().equals("Show all")) {
|
||||
box.setSelected(true);
|
||||
_config.getSeriesConfig().setShouldPlotValue(statName, attrName, true);
|
||||
} else {
|
||||
box.setSelected(false);
|
||||
_config.getSeriesConfig().setShouldPlotValue(statName, attrName, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_toggleAll.getText().equals("Show all"))
|
||||
_toggleAll.setText("Hide all");
|
||||
else
|
||||
_toggleAll.setText("Show all");
|
||||
_config.enableEvents();
|
||||
_config.fireUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
Set statNames = _config.getSummary().getStatNames();
|
||||
List options = new ArrayList(statNames.size());
|
||||
for (Iterator iter = statNames.iterator(); iter.hasNext(); ) {
|
||||
String statName = (String)iter.next();
|
||||
List data = _config.getSummary().getData(statName);
|
||||
if ( (data != null) && (data.size() > 0) ) {
|
||||
PeerStat stat = (PeerStat)data.get(0);
|
||||
String attributes[] = stat.getValueDescriptions();
|
||||
OptionGroup group = new OptionGroup(statName, attributes);
|
||||
options.add(group);
|
||||
}
|
||||
}
|
||||
TreeMap orderedOptions = new TreeMap();
|
||||
for (int i = 0; i < options.size(); i++) {
|
||||
OptionGroup grp = (OptionGroup)options.get(i);
|
||||
orderedOptions.put(grp.getStatName(), grp);
|
||||
}
|
||||
_options = new OptionGroup[options.size()];
|
||||
int i = 0;
|
||||
for (Iterator iter = orderedOptions.values().iterator(); iter.hasNext(); ) {
|
||||
OptionGroup grp = (OptionGroup)iter.next();
|
||||
_options[i] = grp;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/** the settings have changed - revise */
|
||||
private void refreshView() {
|
||||
_parent.refreshView();
|
||||
}
|
||||
|
||||
/**
|
||||
* notified that the config has been updated
|
||||
* @param config the config that was been updated
|
||||
*/
|
||||
public void configUpdated(PeerPlotConfig config) { refreshView(); }
|
||||
|
||||
public void stateUpdated() {
|
||||
for (int i = 0; i < _options.length; i++) {
|
||||
for (int j = 0; j < _options[i]._statAttributes.size(); j++) {
|
||||
JCheckBox box = (JCheckBox)_options[i]._statAttributes.get(j);
|
||||
if (_config.getSeriesConfig().getShouldPlotValue(_options[i].getStatName(), box.getText(), false))
|
||||
box.setSelected(true);
|
||||
else
|
||||
box.setSelected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class OptionGroup {
|
||||
JTextField _statName;
|
||||
List _statAttributes;
|
||||
|
||||
public String getStatName() { return _statName.getText(); }
|
||||
|
||||
/**
|
||||
* Creates an OptionLine.
|
||||
* @param statName statistic group in question
|
||||
* @param statAttributes list of attributes we keep about this stat
|
||||
*/
|
||||
public OptionGroup(String statName, String statAttributes[]) {
|
||||
_statName = new JTextField(statName);
|
||||
_statName.setEditable(false);
|
||||
_statName.setBackground(_background);
|
||||
_statAttributes = new ArrayList(4);
|
||||
if (statAttributes != null) {
|
||||
for (int i = 0; i < statAttributes.length; i++) {
|
||||
JCheckBox box = new JCheckBox(statAttributes[i]);
|
||||
box.addActionListener(new UpdateListener(OptionGroup.this));
|
||||
_statAttributes.add(box);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class UpdateListener implements ActionListener {
|
||||
private OptionGroup _group;
|
||||
|
||||
/**
|
||||
* Update Listener constructor . . .
|
||||
* @param group the group of stats to watch
|
||||
*/
|
||||
public UpdateListener(OptionGroup group) {
|
||||
_group = group;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
|
||||
*/
|
||||
public void actionPerformed(ActionEvent evt) {
|
||||
_config.disableEvents();
|
||||
|
||||
for (int i = 0; i < _group._statAttributes.size(); i++) {
|
||||
JCheckBox box = (JCheckBox)_group._statAttributes.get(i);
|
||||
_config.getSeriesConfig().setShouldPlotValue(_group.getStatName(), box.getText(), box.isSelected());
|
||||
}
|
||||
_config.enableEvents();
|
||||
_config.fireUpdate();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user