diff --git a/graphenej/src/main/java/cy/agorise/graphenej/network/FullNode.java b/graphenej/src/main/java/cy/agorise/graphenej/network/FullNode.java new file mode 100644 index 0000000..71beea3 --- /dev/null +++ b/graphenej/src/main/java/cy/agorise/graphenej/network/FullNode.java @@ -0,0 +1,63 @@ +package cy.agorise.graphenej.network; +import cy.agorise.graphenej.stats.ExponentialMovingAverage; + +/** + * Class that represents a full node and is used to keep track of its round-trip time measured in milliseconds. + */ +public class FullNode implements Comparable { + private String mUrl; + private ExponentialMovingAverage latency; + + private FullNode(){} + + public FullNode(String url){ + latency = new ExponentialMovingAverage(ExponentialMovingAverage.DEFAULT_ALPHA); + this.mUrl = url; + } + + /** + * Full node URL getter + * @return + */ + public String getUrl() { + return mUrl; + } + + /** + * Full node URL setter + * @param mUrl + */ + public void setUrl(String mUrl) { + this.mUrl = mUrl; + } + + /** + * + * @return The exponential moving average object instance + */ + public ExponentialMovingAverage getLatencyAverage(){ + return latency; + } + + /** + * + * @return The latest latency average value + */ + public double getLatencyValue() { + return latency.getAverage(); + } + + /** + * Method that updates the latency average with a new value. + * @param latency Most recent latency sample to be added to the exponential average + */ + public void addLatencyValue(double latency) { + this.latency.updateValue(latency); + } + + @Override + public int compareTo(Object o) { + FullNode node = (FullNode) o; + return (int) Math.ceil(latency.getAverage() - node.getLatencyValue()); + } +} diff --git a/graphenej/src/main/java/cy/agorise/graphenej/stats/ExponentialMovingAverage.java b/graphenej/src/main/java/cy/agorise/graphenej/stats/ExponentialMovingAverage.java new file mode 100644 index 0000000..65bec39 --- /dev/null +++ b/graphenej/src/main/java/cy/agorise/graphenej/stats/ExponentialMovingAverage.java @@ -0,0 +1,49 @@ +package cy.agorise.graphenej.stats; + +/** + * Class used to compute the Exponential Moving Average of a sequence of values. + * For more details see here. + */ +public class ExponentialMovingAverage { + public static final double DEFAULT_ALPHA = 0.5; + private double alpha; + private Double accumulatedValue; + + /** + * Constructor, which takes only the alpha parameter as an argument. + * + * @param alpha The coefficient alpha represents the degree of weighting decrease, a constant + * smoothing factor between 0 and 1. A higher alpha discounts older observations faster. + */ + public ExponentialMovingAverage(double alpha) { + this.alpha = alpha; + } + + /** + * Method that updates the average with a new sample + * @param value New value + * @return The updated average value + */ + public double updateValue(double value) { + if (accumulatedValue == null) { + accumulatedValue = value; + return value; + } + double newValue = accumulatedValue + alpha * (value - accumulatedValue); + accumulatedValue = newValue; + return newValue; + } + + /** + * + * @return Returns the current average value + */ + public double getAverage(){ + return accumulatedValue == null ? 0 : accumulatedValue; + } + + public void setAlpha(double alpha){ + this.alpha = alpha; + this.accumulatedValue = null; + } +} \ No newline at end of file diff --git a/graphenej/src/test/java/cy/agorise/graphenej/network/FullNodeTest.java b/graphenej/src/test/java/cy/agorise/graphenej/network/FullNodeTest.java new file mode 100644 index 0000000..56d47cc --- /dev/null +++ b/graphenej/src/test/java/cy/agorise/graphenej/network/FullNodeTest.java @@ -0,0 +1,31 @@ +package cy.agorise.graphenej.network; + +import junit.framework.Assert; + +import org.junit.Test; + +public class FullNodeTest { + + @Test + public void testFullNodeComparable(){ + FullNode nodeA = new FullNode("wss://dummy"); + FullNode nodeB = new FullNode("wss://dummy"); + FullNode nodeC = new FullNode("wss://dummy"); + nodeA.addLatencyValue(100); + nodeB.addLatencyValue(200); + nodeC.addLatencyValue(100); + Assert.assertTrue("Makes sure the node nodeA.compareTo(nodeB) returns a negative value", nodeA.compareTo(nodeB) < 0); + Assert.assertTrue("Makes sure nodeA.compareTo(nodeB) returns zero", nodeA.compareTo(nodeC) == 0); + Assert.assertTrue("Makes sure nodeB.compareTo(nodeA) returns a positive value", nodeB.compareTo(nodeA) > 0); + } + + @Test + public void testFullNodeAverageLatency(){ + FullNode fullNode = new FullNode("wss://dummy"); + fullNode.getLatencyAverage().setAlpha(0.5); + fullNode.addLatencyValue(100); + Assert.assertEquals(100.0, fullNode.getLatencyValue()); + fullNode.addLatencyValue(50); + Assert.assertEquals(75.0, fullNode.getLatencyValue()); + } +}