Implement calculation of a moving average of ping times. Improve PeerMonitor by making columns sortable.

This commit is contained in:
Mike Hearn 2012-12-07 14:45:41 +01:00
parent 3f89eda933
commit 26f63550be
3 changed files with 68 additions and 11 deletions

View File

@ -88,7 +88,8 @@ public class Peer {
// Outstanding pings against this peer and how long the last one took to complete. Locked under the Peer lock.
private List<PendingPing> pendingPings;
private long lastPingTime;
private long[] lastPingTimes;
private static final int PING_MOVING_AVERAGE_WINDOW = 20;
private Channel channel;
private VersionMessage peerVersionMessage;
@ -110,7 +111,7 @@ public class Peer {
this.isAcked = false;
this.handler = new PeerHandler();
this.pendingPings = Lists.newLinkedList();
this.lastPingTime = Long.MAX_VALUE;
this.lastPingTimes = null;
}
/**
@ -775,15 +776,27 @@ public class Peer {
public void complete() {
Preconditions.checkNotNull(future, "Already completed");
Long elapsed = Long.valueOf(Utils.now().getTime() - startTimeMsec);
synchronized (Peer.this) {
Peer.this.lastPingTime = elapsed.longValue();
}
Peer.this.addPingTimeData(elapsed.longValue());
log.debug("{}: ping time is {} msec", Peer.this.toString(), elapsed);
future.set(elapsed);
future = null;
}
}
/** Adds a ping time sample to the averaging window. */
private synchronized void addPingTimeData(long sample) {
if (lastPingTimes == null) {
lastPingTimes = new long[PING_MOVING_AVERAGE_WINDOW];
// Initialize the averaging window to the first sample.
Arrays.fill(lastPingTimes, sample);
} else {
// Shift all elements backwards by one.
System.arraycopy(lastPingTimes, 1, lastPingTimes, 0, lastPingTimes.length - 1);
// And append the new sample to the end.
lastPingTimes[lastPingTimes.length - 1] = sample;
}
}
/**
* Sends the peer a ping message and returns a future that will be invoked when the pong is received back.
* The future provides a number which is the number of milliseconds elapsed between the ping and the pong.
@ -806,7 +819,22 @@ public class Peer {
* been called or we did not hear back the "pong" message yet, returns {@link Long#MAX_VALUE}.
*/
public long getLastPingTime() {
return lastPingTime;
if (lastPingTimes == null)
return Long.MAX_VALUE;
return lastPingTimes[lastPingTimes.length - 1];
}
/**
* Returns a moving average of the last N ping/pong cycles. If {@link com.google.bitcoin.core.Peer#ping()} has never
* been called or we did not hear back the "pong" message yet, returns {@link Long#MAX_VALUE}. The moving average
* window is 5 buckets.
*/
public long getPingTime() {
if (lastPingTimes == null)
return Long.MAX_VALUE;
long sum = 0;
for (long i : lastPingTimes) sum += i;
return (long)((double) sum / lastPingTimes.length);
}
private void processPong(Pong m) {

View File

@ -446,16 +446,28 @@ public class PeerTest extends TestWithNetworkConnections {
Utils.rollMockClock(0);
// No ping pong happened yet.
assertEquals(Long.MAX_VALUE, peer.getLastPingTime());
assertEquals(Long.MAX_VALUE, peer.getPingTime());
ListenableFuture<Long> future = peer.ping();
Ping pingMsg = (Ping) outbound();
assertEquals(Long.MAX_VALUE, peer.getLastPingTime());
assertEquals(Long.MAX_VALUE, peer.getPingTime());
assertFalse(future.isDone());
Utils.rollMockClock(5);
// The pong is returned.
inbound(peer, new Pong(pingMsg.getNonce()));
assertTrue(future.isDone());
long elapsed = future.get();
assertTrue("" + elapsed, elapsed > 1000);
assertEquals(elapsed, peer.getLastPingTime());
assertEquals(elapsed, peer.getPingTime());
// Do it again and make sure it affects the average.
future = peer.ping();
outbound();
Utils.rollMockClock(50);
inbound(peer, new Pong(pingMsg.getNonce()));
elapsed = future.get();
assertEquals(elapsed, peer.getLastPingTime());
assertEquals(14000, peer.getPingTime());
}
private Message outbound() {

View File

@ -100,6 +100,7 @@ public class PeerMonitor {
peerTableModel = new PeerTableModel();
JTable peerTable = new JTable(peerTableModel);
peerTable.setAutoCreateRowSorter(true);
JScrollPane scrollPane = new JScrollPane(peerTable);
window.getContentPane().add(scrollPane, BorderLayout.CENTER);
window.pack();
@ -120,6 +121,7 @@ public class PeerMonitor {
private final int USER_AGENT = 2;
private final int CHAIN_HEIGHT = 3;
private final int PING_TIME = 4;
private final int LAST_PING_TIME = 5;
public int getRowCount() {
return peerGroup.numConnectedPeers();
@ -132,13 +134,27 @@ public class PeerMonitor {
case PROTOCOL_VERSION: return "Protocol version";
case USER_AGENT: return "User Agent";
case CHAIN_HEIGHT: return "Chain height";
case PING_TIME: return "Ping time";
case PING_TIME: return "Average ping";
case LAST_PING_TIME: return "Last ping";
default: throw new RuntimeException();
}
}
public int getColumnCount() {
return 5;
return 6;
}
public Class<?> getColumnClass(int column) {
switch (column) {
case PROTOCOL_VERSION:
return Integer.class;
case CHAIN_HEIGHT:
case PING_TIME:
case LAST_PING_TIME:
return Long.class;
default:
return String.class;
}
}
public Object getValueAt(int row, int col) {
@ -154,11 +170,12 @@ public class PeerMonitor {
case CHAIN_HEIGHT:
return peer.getBestHeight();
case PING_TIME:
long time = peer.getLastPingTime();
case LAST_PING_TIME:
long time = col == PING_TIME ? peer.getPingTime() : peer.getLastPingTime();
if (time == Long.MAX_VALUE)
return "";
return 0L;
else
return String.format("%d ms", time);
return time;
default: throw new RuntimeException();
}