initial impl (public domain, yadda yadda yadda)

This commit is contained in:
jrandom
2004-04-16 02:16:55 +00:00
committed by zzz
parent 7b824e6178
commit 8fbd7acf2a
18 changed files with 2148 additions and 0 deletions

View 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>

View 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();
}
}
}

View 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();
}
}

View File

@ -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) {}
}
}

View 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; }
}

View 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);
}
}

View File

@ -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();
}
}
}

View File

@ -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));
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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());
}
}

View File

@ -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(); }
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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) {}
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}