From 7c155545ae28839b6af1bcee0bb97f3046c830b3 Mon Sep 17 00:00:00 2001 From: jrandom <jrandom> Date: Mon, 12 Apr 2004 02:44:18 +0000 Subject: [PATCH] partial impl of the gui, still a few things left to do: - implement some validation on the state files loaded - reenable delete and updates to refresh - integrate the real chart code (currently just plain text instead of the graphs) - gui updates i wont spend more than another day on this during the testnet, but i want to get it plotting before continuing. --- apps/heartbeat/java/build.xml | 16 +- .../src/net/i2p/heartbeat/ClientConfig.java | 38 +- .../java/src/net/i2p/heartbeat/PeerData.java | 7 +- .../src/net/i2p/heartbeat/PeerDataWriter.java | 25 +- .../heartbeat/gui/HeartbeatControlPane.java | 93 +++++ .../i2p/heartbeat/gui/HeartbeatMonitor.java | 64 ++++ .../gui/HeartbeatMonitorCommandBar.java | 62 ++++ .../heartbeat/gui/HeartbeatMonitorGUI.java | 89 +++++ .../heartbeat/gui/HeartbeatMonitorRunner.java | 26 ++ .../heartbeat/gui/HeartbeatMonitorState.java | 67 ++++ .../i2p/heartbeat/gui/HeartbeatPlotPane.java | 59 +++ .../net/i2p/heartbeat/gui/PeerPlotConfig.java | 209 ++++++++++- .../i2p/heartbeat/gui/PeerPlotConfigPane.java | 339 +++++++++++++++++ .../net/i2p/heartbeat/gui/PeerPlotState.java | 58 +++ .../heartbeat/gui/PeerPlotStateFetcher.java | 350 ++++++++++++++++++ .../net/i2p/heartbeat/gui/StaticPeerData.java | 95 +++++ 16 files changed, 1572 insertions(+), 25 deletions(-) create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatControlPane.java create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitor.java create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorCommandBar.java create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorGUI.java create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorRunner.java create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorState.java create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatPlotPane.java create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfigPane.java create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotState.java create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotStateFetcher.java create mode 100644 apps/heartbeat/java/src/net/i2p/heartbeat/gui/StaticPeerData.java diff --git a/apps/heartbeat/java/build.xml b/apps/heartbeat/java/build.xml index 567c476d70..d506b0edf5 100644 --- a/apps/heartbeat/java/build.xml +++ b/apps/heartbeat/java/build.xml @@ -1,11 +1,17 @@ <?xml version="1.0" encoding="UTF-8"?> <project basedir="." default="all" name="heartbeat"> - <target name="all" depends="clean, build" /> + <target name="all" depends="clean, buildGUI" /> <target name="build" depends="builddep, jar" /> + <target name="buildGUI" depends="build, jarGUI" /> <target name="builddep"> <ant dir="../../../core/java/" target="build" /> </target> <target name="compile"> + <mkdir dir="./build" /> + <mkdir dir="./build/obj" /> + <javac srcdir="./src" debug="true" destdir="./build/obj" excludes="src/net/i2p/heartbeat/gui/**/*.java" classpath="../../../core/java/build/i2p.jar" /> + </target> + <target name="compileGUI"> <mkdir dir="./build" /> <mkdir dir="./build/obj" /> <javac srcdir="./src" debug="true" destdir="./build/obj" classpath="../../../core/java/build/i2p.jar" /> @@ -18,6 +24,14 @@ </manifest> </jar> </target> + <target name="jarGUI" depends="compileGUI"> + <jar destfile="./build/heartbeatGUI.jar" basedir="./build/obj" includes="**/*.class"> + <manifest> + <attribute name="Main-Class" value="net.i2p.heartbeat.gui.HeartbeatMonitor" /> + <attribute name="Class-Path" value="i2p.jar heartbeatGUI.jar" /> + </manifest> + </jar> + </target> <target name="javadoc"> <mkdir dir="./build" /> <mkdir dir="./build/javadoc" /> diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/ClientConfig.java b/apps/heartbeat/java/src/net/i2p/heartbeat/ClientConfig.java index e0a836a479..23bb0d9e51 100644 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/ClientConfig.java +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/ClientConfig.java @@ -1,6 +1,7 @@ package net.i2p.heartbeat; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Properties; import java.util.StringTokenizer; @@ -85,10 +86,19 @@ public class ClientConfig { public ClientConfig() { this(null, null, null, -1, -1, -1, -1, 0, null, null); } + + /** + * Create a dummy client config to be fetched from the specified location + * + */ + public ClientConfig(String location) { + this(null, null, location, -1, -1, -1, -1, 0, null, null); + } /** * @param peer who we will test against * @param us who we are + * @param statLocation where the stat data should be stored/fetched * @param duration how many minutes to keep events for * @param statFreq how often to write out stats * @param sendFreq how often to send pings @@ -97,11 +107,11 @@ public class ClientConfig { * @param comment describe this test * @param averagePeriods list of minutes to summarize over */ - public ClientConfig(Destination peer, Destination us, String statFile, int duration, int statFreq, int sendFreq, + public ClientConfig(Destination peer, Destination us, String statLocation, int duration, int statFreq, int sendFreq, int sendSize, int numHops, String comment, int averagePeriods[]) { _peer = peer; _us = us; - _statFile = statFile; + _statFile = statLocation; _statDuration = duration; _statFrequency = statFreq; _sendFrequency = sendFreq; @@ -273,6 +283,30 @@ public class ClientConfig { public void setAveragePeriods(int periods[]) { _averagePeriods = periods; } + + /** + * Make sure we're keeping track of the average over the given time period. + * + * @param minutes how many minutes to monitor + */ + public void addAveragePeriod(int minutes) { + if (_averagePeriods != null) { + for (int i = 0; i < _averagePeriods.length; i++) { + if (_averagePeriods[i] == minutes) + return; + } + } + + int numPeriods = 1; + if (_averagePeriods != null) + numPeriods += _averagePeriods.length; + int periods[] = new int[numPeriods]; + if (_averagePeriods != null) + System.arraycopy(_averagePeriods, 0, periods, 0, _averagePeriods.length); + periods[periods.length-1] = minutes; + Arrays.sort(periods); + _averagePeriods = periods; + } /** * Retrieves how many hops this test engine is configured to use for its outbound and inbound tunnels diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/PeerData.java b/apps/heartbeat/java/src/net/i2p/heartbeat/PeerData.java index 1148348e76..dd0faf6000 100644 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/PeerData.java +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/PeerData.java @@ -103,6 +103,7 @@ public class PeerData { public long getSessionStart() { return _sessionStart; } + public void setSessionStart(long when) { _sessionStart = when; } /** * how many pings have we sent for this test? @@ -211,13 +212,17 @@ public class PeerData { data.setPongReceived(now); data.setPongSent(pongSent); data.setWasPonged(true); - _dataPoints.put(new Long(dateSent), data); + addDataPoint(data); } } _sendRate.addData(pongSent - dateSent, 0); _receiveRate.addData(now - pongSent, 0); _lifetimeReceived++; } + + protected void addDataPoint(EventDataPoint data) { + _dataPoints.put(new Long(data.getPingSent()), data); + } /** * drop all datapoints outside the window we're watching, and timeout all diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/PeerDataWriter.java b/apps/heartbeat/java/src/net/i2p/heartbeat/PeerDataWriter.java index d2d6b11256..ed621a5a84 100644 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/PeerDataWriter.java +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/PeerDataWriter.java @@ -2,6 +2,7 @@ package net.i2p.heartbeat; import java.io.File; import java.io.FileOutputStream; +import java.io.OutputStream; import java.io.IOException; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -17,7 +18,7 @@ import net.i2p.util.Log; * Actually write out the stats for peer test * */ -class PeerDataWriter { +public class PeerDataWriter { private final static Log _log = new Log(PeerDataWriter.class); /** @@ -28,18 +29,11 @@ class PeerDataWriter { */ public boolean persist(PeerData data) { String filename = data.getConfig().getStatFile(); - String header = getHeader(data); File statFile = new File(filename); FileOutputStream fos = null; try { fos = new FileOutputStream(statFile); - fos.write(header.getBytes()); - fos.write("#action\tstatus\tdate and time sent \tsendMs\treplyMs\n".getBytes()); - for (Iterator iter = data.getDataPoints().iterator(); iter.hasNext();) { - PeerData.EventDataPoint point = (PeerData.EventDataPoint) iter.next(); - String line = getEvent(point); - fos.write(line.getBytes()); - } + persist(data, fos); } catch (IOException ioe) { if (_log.shouldLog(Log.ERROR)) _log.error("Error persisting the peer data for " @@ -53,6 +47,19 @@ class PeerDataWriter { } return true; } + + public boolean persist(PeerData data, OutputStream out) throws IOException { + String header = getHeader(data); + + out.write(header.getBytes()); + out.write("#action\tstatus\tdate and time sent \tsendMs\treplyMs\n".getBytes()); + for (Iterator iter = data.getDataPoints().iterator(); iter.hasNext();) { + PeerData.EventDataPoint point = (PeerData.EventDataPoint) iter.next(); + String line = getEvent(point); + out.write(line.getBytes()); + } + return true; + } private String getHeader(PeerData data) { StringBuffer buf = new StringBuffer(1024); diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatControlPane.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatControlPane.java new file mode 100644 index 0000000000..4f2a76c4d5 --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatControlPane.java @@ -0,0 +1,93 @@ +package net.i2p.heartbeat.gui; + +import javax.swing.JPanel; +import javax.swing.JLabel; +import javax.swing.JTabbedPane; +import javax.swing.JScrollPane; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; + +import java.util.List; +import java.util.ArrayList; + +import net.i2p.util.Log; + +/** + * Render the control widgets (refresh/load/snapshot and the + * tabbed panel with the plot config data) + * + */ +class HeartbeatControlPane extends JPanel { + private final static Log _log = new Log(HeartbeatControlPane.class); + private HeartbeatMonitorGUI _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; + + public HeartbeatControlPane(HeartbeatMonitorGUI gui) { + _gui = gui; + initializeComponents(); + } + + public void addTest(PeerPlotConfig config) { + _configPane.addTab(config.getTitle(), null, new JScrollPane(new PeerPlotConfigPane(config, this)), config.getSummary()); + _configPane.setBackgroundAt(_configPane.getTabCount()-1, _background); + _configPane.setForegroundAt(_configPane.getTabCount()-1, _foreground); + } + public void removeTest(PeerPlotConfig config) { + _gui.getMonitor().getState().removeTest(config); + int index = _configPane.indexOfTab(config.getTitle()); + if (index >= 0) + _configPane.removeTabAt(index); + } + + public void testsUpdated() { + List knownNames = new ArrayList(8); + for (int i = 0; i < _gui.getMonitor().getState().getTestCount(); i++) { + PeerPlotState state = _gui.getMonitor().getState().getTest(i); + String title = state.getPlotConfig().getTitle(); + knownNames.add(state.getPlotConfig().getTitle()); + if (_configPane.indexOfTab(title) >= 0) { + _log.debug("We already know about [" + title + "]"); + } else { + _log.info("The test [" + title + "] is new to us"); + PeerPlotConfigPane pane = new PeerPlotConfigPane(state.getPlotConfig(), this); + _configPane.addTab(state.getPlotConfig().getTitle(), null, new JScrollPane(pane), state.getPlotConfig().getSummary()); + _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 test [" + title + "]"); + _configPane.removeTabAt(_configPane.indexOfTab(title)); + } + } + + private void initializeComponents() { + if (_gui != null) + setBackground(_gui.getBackground()); + else + setBackground(_background); + setLayout(new BorderLayout()); + HeartbeatMonitorCommandBar bar = new HeartbeatMonitorCommandBar(_gui); + bar.setBackground(getBackground()); + add(bar, BorderLayout.NORTH); + _configPane = new JTabbedPane(JTabbedPane.LEFT); + _configPane.setBackground(_background); + //add(_configPane, BorderLayout.CENTER); + add(_configPane, BorderLayout.SOUTH); + } +} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitor.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitor.java new file mode 100644 index 0000000000..73bcedfed6 --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitor.java @@ -0,0 +1,64 @@ +package net.i2p.heartbeat.gui; + +import net.i2p.util.I2PThread; +import net.i2p.util.Log; + +public class HeartbeatMonitor implements PeerPlotStateFetcher.FetchStateReceptor { + private final static Log _log = new Log(HeartbeatMonitor.class); + private HeartbeatMonitorState _state; + private HeartbeatMonitorGUI _gui; + + public HeartbeatMonitor() { this(null); } + public HeartbeatMonitor(String configFilename) { + _state = new HeartbeatMonitorState(configFilename); + _gui = new HeartbeatMonitorGUI(this); + } + + public void runMonitor() { + loadConfig(); + I2PThread t = new I2PThread(new HeartbeatMonitorRunner(this)); + t.setName("HeartbeatMonitor"); + t.setDaemon(false); + t.start(); + _log.debug("Monitor started"); + } + + /** give us all the data/config available */ + HeartbeatMonitorState getState() { return _state; } + + /** for all of the peer tests being monitored, refetch the data and rerender */ + void refetchData() { + _log.debug("Refetching data"); + for (int i = 0; i < _state.getTestCount(); i++) + PeerPlotStateFetcher.fetchPeerPlotState(this, _state.getTest(i)); + } + + /** (re)load the config defining what peer tests we are monitoring (and how to render) */ + void loadConfig() { + //for (int i = 0; i < 10; i++) { + // load("fake" + i); + //} + } + + public void load(String location) { + PeerPlotConfig cfg = new PeerPlotConfig(location); + PeerPlotState state = new PeerPlotState(cfg); + PeerPlotStateFetcher.fetchPeerPlotState(this, state); + } + + public synchronized void peerPlotStateFetched(PeerPlotState state) { + _state.addTest(state); + _gui.stateUpdated(); + } + + /** store the config defining what peer tests we are monitoring (and how to render) */ + void storeConfig() {} + + public static void main(String args[]) { + Thread.currentThread().setName("HeartbeatMonitor.main"); + if (args.length == 1) + new HeartbeatMonitor(args[0]).runMonitor(); + else + new HeartbeatMonitor().runMonitor(); + } +} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorCommandBar.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorCommandBar.java new file mode 100644 index 0000000000..c354a80328 --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorCommandBar.java @@ -0,0 +1,62 @@ +package net.i2p.heartbeat.gui; + +import javax.swing.JPanel; +import javax.swing.JLabel; +import javax.swing.JComboBox; +import javax.swing.JTextField; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.DefaultComboBoxModel; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +class HeartbeatMonitorCommandBar extends JPanel { + private HeartbeatMonitorGUI _gui; + private JComboBox _refreshRate; + private JTextField _location; + + public HeartbeatMonitorCommandBar(HeartbeatMonitorGUI 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()); + add(_location); + JButton browse = new JButton("Browse..."); + browse.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { browseCalled(); } }); + browse.setBackground(_gui.getBackground()); + add(browse); + JButton load = new JButton("Load"); + load.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { loadCalled(); } }); + load.setBackground(_gui.getBackground()); + add(load); + setBackground(_gui.getBackground()); + } +} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorGUI.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorGUI.java new file mode 100644 index 0000000000..d04baea5c7 --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorGUI.java @@ -0,0 +1,89 @@ +package net.i2p.heartbeat.gui; + +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JMenuBar; +import javax.swing.JScrollPane; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; + +class HeartbeatMonitorGUI extends JFrame { + private HeartbeatMonitor _monitor; + private HeartbeatPlotPane _plotPane; + private HeartbeatControlPane _controlPane; + private final static Color WHITE = new Color(255, 255, 255); + private Color _background = WHITE; + + public HeartbeatMonitorGUI(HeartbeatMonitor monitor) { + super("Heartbeat Monitor"); + _monitor = monitor; + initializeComponents(); + pack(); + setResizable(false); + setVisible(true); + } + + HeartbeatMonitor getMonitor() { return _monitor; } + + /** build up all our widgets */ + private void initializeComponents() { + getContentPane().setLayout(new BorderLayout()); + + setBackground(_background); + + _plotPane = new HeartbeatPlotPane(this); + _plotPane.setBackground(_background); + JScrollPane pane = new JScrollPane(_plotPane); + pane.setBackground(_background); + getContentPane().add(pane, BorderLayout.CENTER); + + _controlPane = new HeartbeatControlPane(this); + _controlPane.setBackground(_background); + getContentPane().add(_controlPane, BorderLayout.SOUTH); + + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + initializeMenus(); + } + + public void stateUpdated() { + _controlPane.testsUpdated(); + _plotPane.stateUpdated(); + } + + private void exitCalled() { + _monitor.getState().setWasKilled(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); + } +} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorRunner.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorRunner.java new file mode 100644 index 0000000000..7e95117e58 --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorRunner.java @@ -0,0 +1,26 @@ +package net.i2p.heartbeat.gui; + +import net.i2p.util.Log; + +/** + * Periodically fire off necessary events (instructing the heartbeat monitor when + * to refetch the data, etc). This is the only active thread in the heartbeat + * monitor (outside the swing/jvm threads) + * + */ +class HeartbeatMonitorRunner implements Runnable { + private final static Log _log = new Log(HeartbeatMonitorRunner.class); + private HeartbeatMonitor _monitor; + + public HeartbeatMonitorRunner(HeartbeatMonitor monitor) { + _monitor = monitor; + } + + public void run() { + while (!_monitor.getState().getWasKilled()) { + _monitor.refetchData(); + try { Thread.sleep(_monitor.getState().getRefreshRateMs()); } catch (InterruptedException ie) {} + } + _log.info("Stopping the heartbeat monitor runner"); + } +} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorState.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorState.java new file mode 100644 index 0000000000..1603a72a80 --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatMonitorState.java @@ -0,0 +1,67 @@ +package net.i2p.heartbeat.gui; + +import java.util.ArrayList; +import java.util.List; +import java.util.Collections; + +/** + * manage the current state of the GUI - all data points, as well as any + * rendering or configuration options. + * + */ +class HeartbeatMonitorState { + private String _configFile; + private List _peerPlotState; + private int _currentPeerPlotConfig; + private int _refreshRateMs; + private boolean _killed; + + /** by default, refresh every 30 seconds */ + private final static int DEFAULT_REFRESH_RATE = 30*1000; + /** where do we load/store config info from? */ + private final static String DEFAULT_CONFIG_FILE = "heartbeatMonitor.config"; + + public HeartbeatMonitorState() { this(DEFAULT_CONFIG_FILE); } + public HeartbeatMonitorState(String configFile) { + _peerPlotState = Collections.synchronizedList(new ArrayList()); + _refreshRateMs = DEFAULT_REFRESH_RATE; + _configFile = configFile; + _killed = false; + _currentPeerPlotConfig = 0; + } + + /** how many tests are we monitoring? */ + public int getTestCount() { return _peerPlotState.size(); } + public PeerPlotState getTest(int peer) { return (PeerPlotState)_peerPlotState.get(peer); } + public void addTest(PeerPlotState peerState) { + if (!_peerPlotState.contains(peerState)) + _peerPlotState.add(peerState); + } + public void removeTest(PeerPlotState peerState) { _peerPlotState.remove(peerState); } + + public void removeTest(PeerPlotConfig peerConfig) { + for (int i = 0; i < getTestCount(); i++) { + PeerPlotState state = getTest(i); + if (state.getPlotConfig() == peerConfig) { + removeTest(state); + return; + } + } + } + + /** which of the tests are we currently editing/viewing? */ + public int getPeerPlotConfig() { return _currentPeerPlotConfig; } + public void setPeerPlotConfig(int whichTest) { _currentPeerPlotConfig = whichTest; } + + /** how frequently should we update the data? */ + public int getRefreshRateMs() { return _refreshRateMs; } + public void setRefreshRateMs(int ms) { _refreshRateMs = ms; } + + /** where is our config stored? */ + public String getConfigFile() { return _configFile; } + public void setConfigFile(String filename) { _configFile = filename; } + + /** have we been shut down? */ + public boolean getWasKilled() { return _killed; } + public void setWasKilled(boolean killed) { _killed = killed; } +} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatPlotPane.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatPlotPane.java new file mode 100644 index 0000000000..1f914456c0 --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/HeartbeatPlotPane.java @@ -0,0 +1,59 @@ +package net.i2p.heartbeat.gui; + +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.JScrollPane; +import java.awt.Color; +import java.awt.Dimension; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import net.i2p.heartbeat.PeerDataWriter; +import net.i2p.util.Log; + +/** + * Render the graph and legend + * + */ +class HeartbeatPlotPane extends JPanel { + private final static Log _log = new Log(HeartbeatPlotPane.class); + private HeartbeatMonitorGUI _gui; + private JTextArea _text; + + public HeartbeatPlotPane(HeartbeatMonitorGUI gui) { + _gui = gui; + initializeComponents(); + } + + public void stateUpdated() { + StringBuffer buf = new StringBuffer(32*1024); + PeerDataWriter writer = new PeerDataWriter(); + + for (int i = 0; i < _gui.getMonitor().getState().getTestCount(); i++) { + StaticPeerData data = _gui.getMonitor().getState().getTest(i).getCurrentData(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(4096); + try { + writer.persist(data, baos); + } catch (IOException ioe) { + _log.error("wtf, error writing to a byte array?", ioe); + } + buf.append(new String(baos.toByteArray())).append("\n\n\n"); + } + + _text.setText(buf.toString()); + } + + private void initializeComponents() { + setBackground(new Color(255, 255, 255)); + //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); + } +} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfig.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfig.java index da32dfd7dc..d7cfc3ee7c 100644 --- a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfig.java +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfig.java @@ -1,5 +1,17 @@ package net.i2p.heartbeat.gui; +import java.util.List; +import java.util.ArrayList; +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Arrays; +import java.util.Collections; +import java.util.TreeMap; + +import java.awt.Color; + +import net.i2p.data.Destination; import net.i2p.heartbeat.ClientConfig; /** @@ -7,24 +19,197 @@ import net.i2p.heartbeat.ClientConfig; * */ class PeerPlotConfig { + /** 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 ClientConfig _config; + /** how should we render the current data set? */ + private PlotSeriesConfig _currentSeriesConfig; + /** how should we render the various averages available? */ + private List _averageSeriesConfigs; + private Set _listeners; + private boolean _disabled; - private final static void foo() { - // bar - if (true) { - // baz + public PeerPlotConfig(String location) { + this(location, null, null, null); + } + + public PeerPlotConfig(String location, ClientConfig config, PlotSeriesConfig currentSeriesConfig, List averageSeriesConfigs) { + _location = location; + if (config == null) + config = new ClientConfig(location); + _config = config; + if (currentSeriesConfig != null) + _currentSeriesConfig = currentSeriesConfig; + else + _currentSeriesConfig = new PlotSeriesConfig(0); + + if (averageSeriesConfigs != null) { + _averageSeriesConfigs = averageSeriesConfigs; + } else { + rebuildAverageSeriesConfigs(); } - // baf + _listeners = Collections.synchronizedSet(new HashSet(2)); + _disabled = false; + } + + public void rebuildAverageSeriesConfigs() { + int periods[] = _config.getAveragePeriods(); + if (periods == null) { + _averageSeriesConfigs = Collections.synchronizedList(new ArrayList(0)); + } else { + Arrays.sort(periods); + _averageSeriesConfigs = Collections.synchronizedList(new ArrayList(periods.length)); + for (int i = 0; i < periods.length; i++) { + _averageSeriesConfigs.add(new PlotSeriesConfig(periods[i]*60*1000)); + } + } + } + + public void addAverage(int minutes) { + _config.addAveragePeriod(minutes); + + TreeMap ordered = new TreeMap(); + for (int i = 0; i < _averageSeriesConfigs.size(); i++) { + PlotSeriesConfig cfg = (PlotSeriesConfig)_averageSeriesConfigs.get(i); + ordered.put(new Long(cfg.getPeriod()), cfg); + } + ordered.put(new Long(minutes*60*1000), new PlotSeriesConfig(minutes*60*1000)); + + List cfgs = Collections.synchronizedList(new ArrayList(ordered.size())); + for (Iterator iter = ordered.values().iterator(); iter.hasNext(); ) + cfgs.add(iter.next()); + + _averageSeriesConfigs = cfgs; + } + + /** + * Where is the current state data supposed to be found? This must either be a + * local file path or a URL + * + */ + public String getLocation() { return _location; } + public void setLocation(String location) { + _location = location; + fireUpdate(); + } + + /** What are we configuring? */ + public ClientConfig getClientConfig() { return _config; } + public void setClientConfig(ClientConfig config) { + _config = config; + fireUpdate(); + } + + /** How do we want to render the current data set? */ + public PlotSeriesConfig getCurrentSeriesConfig() { return _currentSeriesConfig; } + public void setCurrentSeriesConfig(PlotSeriesConfig config) { + _currentSeriesConfig = config; + fireUpdate(); + } + + /** How do we want to render the averages? */ + public List getAverageSeriesConfigs() { return _averageSeriesConfigs; } + public void setAverageSeriesConfigs(List configs) { _averageSeriesConfigs = configs; } + + /** four char description of the peer */ + public String getPeerName() { + Destination peer = getClientConfig().getPeer(); + if (peer == null) + return "????"; + else + return peer.calculateHash().toBase64().substring(0, 4); } - // moo + public String getTitle() { return getPeerName() + '.' + getSize() + '.' + getClientConfig().getSendFrequency(); } + public String getSummary() { + return "Send peer " + getPeerName() + ' ' + getSize() + " every " + + getClientConfig().getSendFrequency() + " seconds through " + + getClientConfig().getNumHops() + "-hop tunnels"; + } + + private String getSize() { + int bytes = getClientConfig().getSendSize(); + if (bytes < 1024) + return bytes + "b"; + else + return bytes/1024 + "kb"; + } - private final static void biff() { - // b0nk - if (false) { - // boink + /** we've got someone who wants to be notified of changes to the plot config */ + public void addListener(UpdateListener lsnr) { _listeners.add(lsnr); } + 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); + } + } + + public void disableEvents() { _disabled = true; } + 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 long _period; + private boolean _plotSendTime; + private boolean _plotReceiveTime; + private boolean _plotLostMessages; + private Color _plotLineColor; + + public PlotSeriesConfig(long period) { + this(period, false, false, false, null); + } + public PlotSeriesConfig(long period, boolean plotSend, boolean plotReceive, boolean plotLost, Color plotColor) { + _period = period; + _plotSendTime = plotSend; + _plotReceiveTime = plotReceive; + _plotLostMessages = plotLost; + _plotLineColor = plotColor; + } + + public PeerPlotConfig getPlotConfig() { return PeerPlotConfig.this; } + + /** + * What period is this series config describing? + * + * @return 0 for current, otherwise # milliseconds that are being averaged over + */ + public long getPeriod() { return _period; } + public void setPeriod(long period) { + _period = period; + fireUpdate(); } - // b00m + /** Should we render the time to send (ping to peer)? */ + public boolean getPlotSendTime() { return _plotSendTime; } + public void setPlotSendTime(boolean shouldPlot) { + _plotSendTime = shouldPlot; + fireUpdate(); + } + /** Should we render the time to receive (peer pong to us)? */ + public boolean getPlotReceiveTime() { return _plotReceiveTime; } + public void setPlotReceiveTime(boolean shouldPlot) { + _plotReceiveTime = shouldPlot; + fireUpdate(); + } + /** Should we render the number of messages lost (ping sent, no pong received in time)? */ + public boolean getPlotLostMessages() { return _plotLostMessages; } + public void setPlotLostMessages(boolean shouldPlot) { + _plotLostMessages = shouldPlot; + fireUpdate(); + } + /** What color should we plot the data with? */ + public Color getPlotLineColor() { return _plotLineColor; } + public void setPlotLineColor(Color color) { + _plotLineColor = color; + fireUpdate(); + } + } + + public interface UpdateListener { + void configUpdated(PeerPlotConfig config); } - // baa } \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfigPane.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfigPane.java new file mode 100644 index 0000000000..91e6a7ea3e --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotConfigPane.java @@ -0,0 +1,339 @@ +package net.i2p.heartbeat.gui; + +import net.i2p.util.Log; + +import javax.swing.JPanel; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JButton; +import javax.swing.JTextField; +import javax.swing.JTextArea; +import javax.swing.JScrollPane; +import javax.swing.DefaultComboBoxModel; +import javax.swing.ComboBoxModel; +import javax.swing.JColorChooser; +import javax.swing.border.LineBorder; +import javax.swing.border.BevelBorder; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import java.util.List; +import java.util.Random; + +class PeerPlotConfigPane extends JPanel implements PeerPlotConfig.UpdateListener { + private final static Log _log = new Log(PeerPlotConfigPane.class); + private PeerPlotConfig _config; + private HeartbeatControlPane _parent; + private JLabel _title; + private JButton _delete; + private JLabel _fromLabel; + private JTextField _from; + private JTextArea _comments; + private JLabel _peerLabel; + private JTextField _peerKey; + private JLabel _localLabel; + private JTextField _localKey; + private OptionLine _options[]; + private Random _rnd = new Random(); + private final static Color WHITE = new Color(255, 255, 255); + private Color _background = WHITE; + + public PeerPlotConfigPane(PeerPlotConfig config, HeartbeatControlPane pane) { + _config = config; + _parent = pane; + if (_parent != null) + _background = _parent.getBackground(); + _config.addListener(this); + initializeComponents(); + } + + /** called when the user wants to stop monitoring this test */ + private void delete() { + _parent.removeTest(_config); + } + + private void initializeComponents() { + buildComponents(); + placeComponents(this); + refreshView(); + //setBorder(new BevelBorder(BevelBorder.RAISED)); + setBackground(_background); + } + + /** place all the gui components onto the given panel */ + private void placeComponents(JPanel body) { + body.setLayout(new GridBagLayout()); + GridBagConstraints cts = new GridBagConstraints(); + + // row 0: title + delete + cts.gridx = 0; + cts.gridy = 0; + cts.gridwidth = 5; + cts.anchor = GridBagConstraints.WEST; + cts.fill = GridBagConstraints.NONE; + body.add(_title, cts); + cts.gridx = 5; + cts.gridwidth = 1; + cts.anchor = GridBagConstraints.NORTHWEST; + cts.fill = GridBagConstraints.BOTH; + body.add(_delete, cts); + + // row 1: from + location + cts.gridx = 0; + cts.gridy = 1; + cts.gridwidth = 1; + cts.fill = GridBagConstraints.NONE; + body.add(_fromLabel, cts); + cts.gridx = 1; + cts.gridwidth = 5; + cts.fill = GridBagConstraints.BOTH; + body.add(_from, cts); + + // row 2: comment + cts.gridx = 0; + cts.gridy = 2; + cts.gridwidth = 6; + cts.fill = GridBagConstraints.BOTH; + body.add(_comments, cts); + + // row 3: peer + peerKey + cts.gridx = 0; + cts.gridy = 3; + cts.gridwidth = 1; + cts.fill = GridBagConstraints.NONE; + body.add(_peerLabel, cts); + cts.gridx = 1; + cts.gridwidth = 5; + cts.fill = GridBagConstraints.BOTH; + body.add(_peerKey, cts); + + // row 4: local + localKey + cts.gridx = 0; + cts.gridy = 4; + cts.gridwidth = 1; + cts.fill = GridBagConstraints.NONE; + body.add(_localLabel, cts); + cts.gridx = 1; + cts.gridwidth = 5; + cts.fill = GridBagConstraints.BOTH; + body.add(_localKey, cts); + + // row 5-N: data row + for (int i = 0; i < _options.length; i++) { + cts.gridx = 0; + cts.gridy = 5 + i; + cts.gridwidth = 1; + cts.fill = GridBagConstraints.NONE; + cts.anchor = GridBagConstraints.WEST; + if (_options[i]._durationMinutes <= 0) + body.add(new JLabel("Data: "), cts); + else + body.add(new JLabel(_options[i]._durationMinutes + "m avg: "), cts); + + cts.gridx = 1; + body.add(_options[i]._send, cts); + cts.gridx = 2; + body.add(_options[i]._recv, cts); + cts.gridx = 3; + body.add(_options[i]._lost, cts); + cts.gridx = 4; + body.add(_options[i]._all, cts); + cts.gridx = 5; + body.add(_options[i]._color, cts); + } + } + + /** build all of the gui components */ + private void buildComponents() { + _title = new JLabel(_config.getSummary()); + _title.setBackground(_background); + _delete = new JButton("Delete"); + _delete.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { delete(); } }); + _delete.setEnabled(false); + _delete.setBackground(_background); + _fromLabel = new JLabel("Location: "); + _fromLabel.setBackground(_background); + _from = new JTextField(_config.getLocation()); + _from.setEditable(false); + _from.setBackground(_background); + _comments = new JTextArea(_config.getClientConfig().getComment(), 2, 20); + // _comments = new JTextArea(_config.getClientConfig().getComment(), 2, 40); + _comments.setEditable(false); + _comments.setBackground(_background); + _peerLabel = new JLabel("Peer: "); + _peerLabel.setBackground(_background); + _peerKey = new JTextField(_config.getClientConfig().getPeer().toBase64(), 8); + _peerKey.setBackground(_background); + _localLabel = new JLabel("Local: "); + _localLabel.setBackground(_background); + _localKey = new JTextField(_config.getClientConfig().getUs().toBase64(), 8); + _localKey.setBackground(_background); + + int averagedPeriods[] = _config.getClientConfig().getAveragePeriods(); + if (averagedPeriods == null) + averagedPeriods = new int[0]; + + _options = new OptionLine[1 + averagedPeriods.length]; + _options[0] = new OptionLine(0); + for (int i = 0; i < averagedPeriods.length; i++) { + _options[1+i] = new OptionLine(averagedPeriods[i]); + } + } + + /** the settings have changed - revise */ + private void refreshView() { + for (int i = 0; i < _options.length; i++) { + PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_options[i]._durationMinutes); + if (cfg == null) { + _log.warn("Config for minutes " + _options[i]._durationMinutes + " was not found?"); + continue; + } + _log.debug("Refreshing view for minutes ["+ _options[i]._durationMinutes + "]: send [" + + _options[i]._send.isSelected() + "/" + cfg.getPlotSendTime() + "] recv [" + + _options[i]._recv.isSelected() + "/" + cfg.getPlotReceiveTime() + "] lost [" + + _options[i]._lost.isSelected() + "/" + cfg.getPlotLostMessages() + "]"); + _options[i]._send.setSelected(cfg.getPlotSendTime()); + _options[i]._recv.setSelected(cfg.getPlotReceiveTime()); + _options[i]._lost.setSelected(cfg.getPlotLostMessages()); + if (cfg.getPlotLineColor() != null) + _options[i]._color.setBackground(cfg.getPlotLineColor()); + } + } + + /** find the right config for the given period, or null if none exist */ + private PeerPlotConfig.PlotSeriesConfig getConfig(int minutes) { + if (minutes <= 0) + return _config.getCurrentSeriesConfig(); + + List configs = _config.getAverageSeriesConfigs(); + for (int i = 0; i < configs.size(); i++) { + PeerPlotConfig.PlotSeriesConfig cfg = (PeerPlotConfig.PlotSeriesConfig)configs.get(i); + if (cfg.getPeriod() == minutes * 60*1000) + return cfg; + } + return null; + } + + /** notified that the config has been updated */ + public void configUpdated(PeerPlotConfig config) { refreshView(); } + + private class ChooseColor implements ActionListener { + private int _minutes; + private JButton _button; + + public ChooseColor(int minutes, JButton button) { + _minutes = minutes; + _button = button; + } + public void actionPerformed(ActionEvent evt) { + PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_minutes); + Color origColor = null; + if (cfg != null) + origColor = cfg.getPlotLineColor(); + Color color = JColorChooser.showDialog(PeerPlotConfigPane.this, "What color should this line be?", origColor); + if (color != null) { + if (cfg != null) + cfg.setPlotLineColor(color); + _button.setBackground(color); + } + } + } + + private class OptionLine { + int _durationMinutes; + JCheckBox _send; + JCheckBox _recv; + JCheckBox _lost; + JCheckBox _all; + JButton _color; + + public OptionLine(int durationMinutes) { + _durationMinutes = durationMinutes; + _send = new JCheckBox("send time"); + _send.setBackground(_background); + _recv = new JCheckBox("receive time"); + _recv.setBackground(_background); + _lost = new JCheckBox("lost messages"); + _lost.setBackground(_background); + _all = new JCheckBox("all"); + _all.setBackground(_background); + _color = new JButton("color"); + int r = _rnd.nextInt(255); + if (r < 0) r = -r; + int g = _rnd.nextInt(255); + if (g < 0) g = -g; + int b = _rnd.nextInt(255); + if (b < 0) b = -b; + _color.setBackground(new Color(r, g, b)); + + _send.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes)); + _recv.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes)); + _lost.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes)); + _all.addActionListener(new UpdateListener(OptionLine.this, _durationMinutes)); + _color.addActionListener(new ChooseColor(durationMinutes, _color)); + } + } + + private class UpdateListener implements ActionListener { + private OptionLine _line; + private int _minutes; + public UpdateListener(OptionLine line, int minutes) { + _line = line; + _minutes = minutes; + } + public void actionPerformed(ActionEvent evt) { + PeerPlotConfig.PlotSeriesConfig cfg = getConfig(_minutes); + if (cfg == null) { + _log.error("wtf, why is there no config for " + _minutes + "?"); + + List configs = _config.getAverageSeriesConfigs(); + for (int i = 0; i < configs.size(); i++) { + PeerPlotConfig.PlotSeriesConfig conf = (PeerPlotConfig.PlotSeriesConfig)configs.get(i); + _log.debug("We know about " + conf.getPeriod()); + } + return; + } + + cfg.getPlotConfig().disableEvents(); + _log.debug("Updating data for minutes ["+ _line._durationMinutes + "]: send [" + + _line._send.isSelected() + "/" + cfg.getPlotSendTime() + "] recv [" + + _line._recv.isSelected() + "/" + cfg.getPlotReceiveTime() + "] lost [" + + _line._lost.isSelected() + "/" + cfg.getPlotLostMessages() + "]"); + + boolean force = _line._all.isSelected(); + cfg.setPlotSendTime(_line._send.isSelected() || force); + cfg.setPlotReceiveTime(_line._recv.isSelected() || force); + cfg.setPlotLostMessages(_line._lost.isSelected() || force); + cfg.getPlotConfig().enableEvents(); + cfg.getPlotConfig().fireUpdate(); + } + } + + public final static void main(String args[]) { + Test t = new Test(); + t.runTest(); + } + + private final static class Test implements PeerPlotStateFetcher.FetchStateReceptor { + public void runTest() { + PeerPlotConfig cfg = new PeerPlotConfig("C:\\testnet\\r2\\heartbeatStat_10s_30kb.txt"); + PeerPlotState state = new PeerPlotState(cfg); + PeerPlotStateFetcher.fetchPeerPlotState(this, state); + try { Thread.sleep(60*1000); } catch (InterruptedException ie) {} + System.exit(-1); + } + + public void peerPlotStateFetched(PeerPlotState state) { + javax.swing.JFrame f = new javax.swing.JFrame("Test"); + f.getContentPane().add(new JScrollPane(new PeerPlotConfigPane(state.getPlotConfig(), null))); + f.pack(); + f.setVisible(true); + } + } +} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotState.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotState.java new file mode 100644 index 0000000000..f0c3f60062 --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotState.java @@ -0,0 +1,58 @@ +package net.i2p.heartbeat.gui; + +import net.i2p.heartbeat.PeerData; + +/** + * Current data + plot config for a particular test + * + */ +class PeerPlotState { + private StaticPeerData _currentData; + private PeerPlotConfig _plotConfig; + + public PeerPlotState() { + this(null, null); + } + public PeerPlotState(PeerPlotConfig config) { + this(config, new StaticPeerData(config.getClientConfig())); + } + public PeerPlotState(PeerPlotConfig config, StaticPeerData data) { + _plotConfig = config; + _currentData = data; + } + + public void addAverage(int minutes, int sendMs, int recvMs, int lost) { + // make sure we've got the config entry for the average + _plotConfig.addAverage(minutes); + // add the data point... + _currentData.addAverage(minutes, sendMs, recvMs, lost); + } + + /** + * we successfully got a ping/pong through + * + * @param sendTime when did the ping get sent? + * @param sendMs how much later did the peer receive the ping? + * @param recvMs how much later than that did we receive the pong? + */ + public void addSuccess(long sendTime, int sendMs, int recvMs) { + _currentData.addData(sendTime, sendMs, recvMs); + } + + /** + * we lost a ping/pong + * + * @param sendTime when did we send the ping? + */ + public void addLost(long sendTime) { + _currentData.addData(sendTime); + } + + /** data set to render */ + public StaticPeerData getCurrentData() { return _currentData; } + public void setCurrentData(StaticPeerData data) { _currentData = data; } + + /** configuration options on how to render the data set */ + public PeerPlotConfig getPlotConfig() { return _plotConfig; } + public void setPlotConfig(PeerPlotConfig config) { _plotConfig = config; } +} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotStateFetcher.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotStateFetcher.java new file mode 100644 index 0000000000..5fa598f6ac --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/PeerPlotStateFetcher.java @@ -0,0 +1,350 @@ +package net.i2p.heartbeat.gui; + +import net.i2p.util.Log; +import net.i2p.util.I2PThread; + +import net.i2p.data.Destination; +import net.i2p.data.DataFormatException; + +import net.i2p.heartbeat.ClientConfig; +import net.i2p.heartbeat.PeerData; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.FileInputStream; +import java.net.URL; +import java.net.MalformedURLException; + +import java.text.SimpleDateFormat; +import java.text.ParseException; +import java.util.Locale; +import java.util.List; +import java.util.ArrayList; +import java.util.StringTokenizer; + +class PeerPlotStateFetcher { + private final static Log _log = new Log(PeerPlotStateFetcher.class); + + /** + * Fetch and fill the specified state structure + * + */ + public static void fetchPeerPlotState(FetchStateReceptor receptor, PeerPlotState state) { + I2PThread t = new I2PThread(new Fetcher(receptor, state)); + t.setDaemon(true); + t.setName("Fetch state from " + state.getPlotConfig().getLocation()); + t.start(); + } + + public interface FetchStateReceptor { + void peerPlotStateFetched(PeerPlotState state); + } + + private static class Fetcher implements Runnable { + private PeerPlotState _state; + private FetchStateReceptor _receptor; + public Fetcher(FetchStateReceptor receptor, PeerPlotState state) { + _state = state; + _receptor = receptor; + } + public void run() { + String loc = _state.getPlotConfig().getLocation(); + _log.debug("Load called [" + loc + "]"); + InputStream in = null; + try { + try { + URL location = new URL(loc); + in = location.openStream(); + } catch (MalformedURLException mue) { + _log.debug("Not a url [" + loc + "]"); + in = null; + } + + if (in == null) + in = new FileInputStream(loc); + + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String line = null; + while ( (line = reader.readLine()) != null) { + handleLine(line); + } + + if (valid()) + _receptor.peerPlotStateFetched(_state); + } catch (IOException ioe) { + _log.error("Error retrieving from the location [" + loc + "]", ioe); + } finally { + if (in != null) try { in.close(); } catch (IOException ioe) {} + } + } + + /** + * check to make sure we've got everything we need + * + */ + boolean valid() { + return true; + } + + /** + * handle a line from the data set - these can be formatted in one of the + * following ways. <p /> + * + * <pre> + * peer khWYqCETu9YtPUvGV92ocsbEW5DezhKlIG7ci8RLX3g= + * local u-9hlR1ik2hemXf0HvKMfeRgrS86CbNQh25e7XBhaQE= + * peerDest [base 64 of the full destination] + * localDest [base 64 of the full destination] + * numTunnelHops 2 + * comment Test with localhost sending 30KB every 20 seconds + * sendFrequency 20 + * sendSize 30720 + * sessionStart 20040409.22:51:10.915 + * currentTime 20040409.23:31:39.607 + * numPending 2 + * lifetimeSent 118 + * lifetimeRecv 113 + * #averages minutes sendMs recvMs numLost + * periodAverage 1 1843 771 0 + * periodAverage 5 786 752 1 + * periodAverage 30 855 735 3 + * #action status date and time sent sendMs replyMs + * EVENT OK 20040409.23:21:44.742 691 670 + * EVENT OK 20040409.23:22:05.201 671 581 + * EVENT OK 20040409.23:22:26.301 1182 1452 + * EVENT OK 20040409.23:22:47.322 24304 1723 + * EVENT OK 20040409.23:23:08.232 2293 1081 + * EVENT OK 20040409.23:23:29.332 1392 641 + * EVENT OK 20040409.23:23:50.262 641 761 + * EVENT OK 20040409.23:24:11.102 651 701 + * EVENT OK 20040409.23:24:31.401 841 621 + * EVENT OK 20040409.23:24:52.061 651 681 + * EVENT OK 20040409.23:25:12.480 701 1623 + * EVENT OK 20040409.23:25:32.990 1442 1212 + * EVENT OK 20040409.23:25:54.230 591 631 + * EVENT OK 20040409.23:26:14.620 620 691 + * EVENT OK 20040409.23:26:35.199 1793 1432 + * EVENT OK 20040409.23:26:56.570 661 641 + * EVENT OK 20040409.23:27:17.200 641 660 + * EVENT OK 20040409.23:27:38.120 611 921 + * EVENT OK 20040409.23:27:58.699 831 621 + * EVENT OK 20040409.23:28:19.559 801 661 + * EVENT OK 20040409.23:28:40.279 601 611 + * EVENT OK 20040409.23:29:00.648 601 621 + * EVENT OK 20040409.23:29:21.288 701 661 + * EVENT LOST 20040409.23:29:41.828 + * EVENT LOST 20040409.23:30:02.327 + * EVENT LOST 20040409.23:30:22.656 + * EVENT OK 20040409.23:31:24.305 1843 771 + * </pre> + */ + private void handleLine(String line) { + if (line.startsWith("peerDest")) + handlePeerDest(line); + else if (line.startsWith("localDest")) + handleLocalDest(line); + else if (line.startsWith("numTunnelHops")) + handleNumTunnelHops(line); + else if (line.startsWith("comment")) + handleComment(line); + else if (line.startsWith("sendFrequency")) + handleSendFrequency(line); + else if (line.startsWith("sendSize")) + handleSendSize(line); + else if (line.startsWith("periodAverage")) + handlePeriodAverage(line); + else if (line.startsWith("EVENT")) + handleEvent(line); + else if (line.startsWith("numPending")) + handleNumPending(line); + else if (line.startsWith("sessionStart")) + handleSessionStart(line); + else + _log.debug("Not handled: " + line); + } + + private void handlePeerDest(String line) { + StringTokenizer tok = new StringTokenizer(line); + tok.nextToken(); // ignore; + String destKey = tok.nextToken(); + try { + Destination d = new Destination(); + d.fromBase64(destKey); + _state.getPlotConfig().getClientConfig().setPeer(d); + _log.debug("Setting the peer to " + d.calculateHash().toBase64()); + } catch (DataFormatException dfe) { + _log.error("Unable to parse the peerDest line: [" + line + "]", dfe); + } + } + + private void handleLocalDest(String line) { + StringTokenizer tok = new StringTokenizer(line); + tok.nextToken(); // ignore; + String destKey = tok.nextToken(); + try { + Destination d = new Destination(); + d.fromBase64(destKey); + _state.getPlotConfig().getClientConfig().setUs(d); + } catch (DataFormatException dfe) { + _log.error("Unable to parse the localDest line: [" + line + "]", dfe); + } + } + + private void handleComment(String line) { + StringTokenizer tok = new StringTokenizer(line); + tok.nextToken(); // ignore; + StringBuffer buf = new StringBuffer(line.length()-32); + while (tok.hasMoreTokens()) + buf.append(tok.nextToken()).append(' '); + _state.getPlotConfig().getClientConfig().setComment(buf.toString()); + } + + private void handleNumTunnelHops(String line) { + StringTokenizer tok = new StringTokenizer(line); + tok.nextToken(); // ignore; + String num = tok.nextToken(); + try { + int val = Integer.parseInt(num); + _state.getPlotConfig().getClientConfig().setNumHops(val); + } catch (NumberFormatException nfe) { + _log.error("Unable to parse the numTunnelHops line: [" + line + "]", nfe); + } + } + + private void handleNumPending(String line) { + StringTokenizer tok = new StringTokenizer(line); + tok.nextToken(); // ignore; + String num = tok.nextToken(); + try { + int val = Integer.parseInt(num); + _state.getCurrentData().setPendingCount(val); + } catch (NumberFormatException nfe) { + _log.error("Unable to parse the numPending line: [" + line + "]", nfe); + } + } + + private void handleSendFrequency(String line) { + StringTokenizer tok = new StringTokenizer(line); + tok.nextToken(); // ignore; + String num = tok.nextToken(); + try { + int val = Integer.parseInt(num); + _state.getPlotConfig().getClientConfig().setSendFrequency(val); + } catch (NumberFormatException nfe) { + _log.error("Unable to parse the sendFrequency line: [" + line + "]", nfe); + } + } + + private void handleSendSize(String line) { + StringTokenizer tok = new StringTokenizer(line); + tok.nextToken(); // ignore; + String num = tok.nextToken(); + try { + int val = Integer.parseInt(num); + _state.getPlotConfig().getClientConfig().setSendSize(val); + } catch (NumberFormatException nfe) { + _log.error("Unable to parse the sendSize line: [" + line + "]", nfe); + } + } + + private void handleSessionStart(String line) { + StringTokenizer tok = new StringTokenizer(line); + tok.nextToken(); // ignore; + String date = tok.nextToken(); + try { + long when = getDate(date); + _state.getCurrentData().setSessionStart(when); + } catch (NumberFormatException nfe) { + _log.error("Unable to parse the sessionStart line: [" + line + "]", nfe); + } + } + + private void handlePeriodAverage(String line) { + StringTokenizer tok = new StringTokenizer(line); + tok.nextToken(); // ignore; + try { + // periodAverage minutes sendMs recvMs numLost + int min = Integer.parseInt(tok.nextToken()); + int send = Integer.parseInt(tok.nextToken()); + int recv = Integer.parseInt(tok.nextToken()); + int lost = Integer.parseInt(tok.nextToken()); + _state.addAverage(min, send, recv, lost); + } catch (NumberFormatException nfe) { + _log.error("Unable to parse the sendSize line: [" + line + "]", nfe); + } + } + + private void handleEvent(String line) { + StringTokenizer tok = new StringTokenizer(line); + + // * EVENT OK 20040409.23:29:21.288 701 661 + // * EVENT LOST 20040409.23:29:41.828 + tok.nextToken(); // ignore first two + tok.nextToken(); + try { + long when = getDate(tok.nextToken()); + if (when < 0) { + _log.error("Invalid EVENT line: [" + line + "]"); + return; + } + if (tok.hasMoreTokens()) { + int sendMs = Integer.parseInt(tok.nextToken()); + int recvMs = Integer.parseInt(tok.nextToken()); + _state.addSuccess(when, sendMs, recvMs); + } else { + _state.addLost(when); + } + } catch (NumberFormatException nfe) { + _log.error("Unable to parse the EVENT line: [" + line + "]", nfe); + } + } + + private static final SimpleDateFormat _fmt = new SimpleDateFormat("yyyyMMdd.HH:mm:ss.SSS", Locale.UK); + private long getDate(String date) { + synchronized (_fmt) { + try { + return _fmt.parse(date).getTime(); + } catch (ParseException pe) { + _log.error("Unable to parse the date [" + date + "]", pe); + return -1; + } + } + } + + private void fakeRun() { + try { + Destination peer = new Destination(); + Destination us = new Destination(); + peer.fromBase64("3RPLOkQGlq8anNyNWhjbMyHxpAvUyUJKbiUejI80DnPR59T3blc7-XrBhQ2iPbf-BRAR~v1j34Kpba1eDyhPk2gevsE6ULO1irarJ3~C9WcQH2wAbNiVwfWqbh6onQ~YmkSpGNwGHD6ytwbvTyXeBJ" + + "cS8e6gmfNN-sYLn1aQu8UqWB3D6BmTfLtyS3eqWVk66Nrzmwy8E1Hvq5z~1lukYb~cyiDO1oZHAOLyUQtd9eN16yJY~2SRG8LiscpPMl9nSJUr6fmXMUubW-M7QGFH82Om-735PJUk6WMy1Hi9Vgh4Pxhdl7g" + + "fqGRWioFABdhcypb7p1Ca77p73uabLDFK-SjIYmdj7TwSdbNa6PCmzEvCEW~IZeZmnZC5B6pK30AdmD9vc641wUGce9xTJVfNRupf5L7pSsVIISix6FkKQk-FTW2RsZKLbuMCYMaPzLEx5gzODEqtI6Jf2teM" + + "d5xCz51RPayDJl~lJ-W0IWYfosnjM~KxYaqc4agviBuF5ZWeAAAA"); + us.fromBase64("W~JFpqSH8uopylox2V5hMbpcHSsb-dJkSKvdJ1vj~KQcUFJWXFyfbetBAukcGH5S559aK9oslU0qbVoMDlJITVC4OXfXSnVbJBP1IhsK8SvjSYicjmIi2fA~k4HvSh9Wxu~bg8yo~jgfHA8tjYpp" + + "K9QKc56BpkJb~hx0nNGy4Ny9eW~6A5AwAmHvwdt5NqcREYRMjRd63dMGm8BcEe-6FbOyMo3dnIFcETWAe8TCeoMxm~S1n~6Jlinw3ETxv-L6lQkhFFWnC5zyzQ~4JhVxxT3taTMYXg8td4CBGmrS078jcjW63" + + "rlSiQgZBlYfN3iEYmurhuIEV9NXRcmnMrBOQUAoXPpVuRIxJbaQNDL71FO2iv424n4YjKs84suAho34GGQKq7WoL5V5KQgihfcl0f~xne-qP3FtpoPFeyA9x-sA2JWDAsxoZlfvgkiP5eyOn23prT9TJK47HC" + + "VilHSV11uTVaC4Jc5YsjoBCZadWbgQnMCKlZ4jk-bLE1PSWLg7AAAA"); + _state.getPlotConfig().getClientConfig().setPeer(peer); + _state.getPlotConfig().getClientConfig().setUs(us); + _state.getPlotConfig().getClientConfig().setNumHops(2); + _state.getPlotConfig().getClientConfig().setComment("we do stuff\nreally nifty stuff. really"); + _state.getPlotConfig().getClientConfig().setAveragePeriods(new int[] { 1, 5, 30, 60 }); + int rnd = new java.util.Random().nextInt(); + if (rnd > 0) + rnd = rnd % 10; + else + rnd = (-rnd) % 10; + _state.getPlotConfig().getClientConfig().setSendFrequency(rnd); + _state.getPlotConfig().getClientConfig().setSendSize(16*1024); + _state.getPlotConfig().getClientConfig().setStatDuration(10); + _state.getPlotConfig().rebuildAverageSeriesConfigs(); + _state.setCurrentData(new StaticPeerData(_state.getPlotConfig().getClientConfig())); + + _receptor.peerPlotStateFetched(_state); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} \ No newline at end of file diff --git a/apps/heartbeat/java/src/net/i2p/heartbeat/gui/StaticPeerData.java b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/StaticPeerData.java new file mode 100644 index 0000000000..784debc8b7 --- /dev/null +++ b/apps/heartbeat/java/src/net/i2p/heartbeat/gui/StaticPeerData.java @@ -0,0 +1,95 @@ +package net.i2p.heartbeat.gui; + +import net.i2p.heartbeat.PeerData; +import net.i2p.heartbeat.ClientConfig; + +import java.util.Map; +import java.util.HashMap; + +/** + * Raw data points for a test + * + */ +class StaticPeerData extends PeerData { + private int _pending; + /** Integer (period, in minutes) to Integer (milliseconds) for sending a ping */ + private Map _averageSendTimes; + /** Integer (period, in minutes) to Integer (milliseconds) for receiving a pong */ + private Map _averageReceiveTimes; + /** Integer (period, in minutes) to Integer (num messages) of how many messages were lost on average */ + private Map _lostMessages; + + public StaticPeerData(ClientConfig config) { + super(config); + _averageSendTimes = new HashMap(4); + _averageReceiveTimes = new HashMap(4); + _lostMessages = new HashMap(4); + } + + + public void addAverage(int minutes, int sendMs, int recvMs, int lost) { + _averageSendTimes.put(new Integer(minutes), new Integer(sendMs)); + _averageReceiveTimes.put(new Integer(minutes), new Integer(recvMs)); + _lostMessages.put(new Integer(minutes), new Integer(lost)); + } + + public void setPendingCount(int numPending) { _pending = numPending; } + public void setSessionStart(long when) { super.setSessionStart(when); } + + public void addData(long sendTime, int sendMs, int recvMs) { + PeerData.EventDataPoint dataPoint = new PeerData.EventDataPoint(sendTime); + dataPoint.setPongSent(sendTime + sendMs); + dataPoint.setPongReceived(sendTime + sendMs + recvMs); + dataPoint.setWasPonged(true); + addDataPoint(dataPoint); + } + + public void addData(long sendTime) { + PeerData.EventDataPoint dataPoint = new PeerData.EventDataPoint(sendTime); + dataPoint.setWasPonged(false); + addDataPoint(dataPoint); + } + + + + /** + * how many pings are still outstanding? + * @return the number of pings outstanding + */ + public int getPendingCount() { return _pending; } + + + /** + * average time to send over the given period. + * + * @param period number of minutes to retrieve the average for + * @return milliseconds average, or -1 if we dont track that period + */ + public double getAverageSendTime(int period) { + return ((Integer)_averageSendTimes.get(new Integer(period))).doubleValue(); + } + + + /** + * average time to receive over the given period. + * + * @param period number of minutes to retrieve the average for + * @return milliseconds average, or -1 if we dont track that period + */ + public double getAverageReceiveTime(int period) { + return ((Integer)_averageReceiveTimes.get(new Integer(period))).doubleValue(); + } + + + /** + * number of lost messages over the given period. + * + * @param period number of minutes to retrieve the average for + * @return number of lost messages in the period, or -1 if we dont track that period + */ + public double getLostMessages(int period) { + return ((Integer)_lostMessages.get(new Integer(period))).doubleValue(); + } + + public void cleanup() {} +} \ No newline at end of file -- GitLab