Exposing an interface that allows the fine tuning of the alpha parameter used to calculate the exponential moving average of measured latencies

This commit is contained in:
Nelson R. Perez 2018-10-18 17:36:34 -05:00
parent 3a19808ac5
commit 63eebf11c4
5 changed files with 91 additions and 13 deletions

View file

@ -58,6 +58,7 @@ import cy.agorise.graphenej.network.NodeProvider;
import cy.agorise.graphenej.operations.CustomOperation; import cy.agorise.graphenej.operations.CustomOperation;
import cy.agorise.graphenej.operations.LimitOrderCreateOperation; import cy.agorise.graphenej.operations.LimitOrderCreateOperation;
import cy.agorise.graphenej.operations.TransferOperation; import cy.agorise.graphenej.operations.TransferOperation;
import cy.agorise.graphenej.stats.ExponentialMovingAverage;
import io.reactivex.Observer; import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.Nullable; import io.reactivex.annotations.Nullable;
@ -79,29 +80,69 @@ public class NetworkService extends Service {
public static final int NORMAL_CLOSURE_STATUS = 1000; public static final int NORMAL_CLOSURE_STATUS = 1000;
// Time to wait before retrying a connection attempt // Time to wait before retrying a connection attempt
private final int DEFAULT_RETRY_DELAY = 5000; private final int DEFAULT_RETRY_DELAY = 500;
/**
* Constant to be used as a key in order to pass the user name information, in case the
* provided API nodes might require this information.
*/
public static final String KEY_USERNAME = "key_username"; public static final String KEY_USERNAME = "key_username";
/**
* Constant to be used as a key in order to pass the password information, in case the
* provided API nodes might require this information.
* <p>
* This information should be passed as an intent extra when calling the bindService
* or startService methods.
*/
public static final String KEY_PASSWORD = "key_password"; public static final String KEY_PASSWORD = "key_password";
/**
* Constant used as a key in order to specify which APIs the application will be requiring.
* <p>
* This information should be passed as an intent extra when calling the bindService
* or startService methods.
*/
public static final String KEY_REQUESTED_APIS = "key_requested_apis"; public static final String KEY_REQUESTED_APIS = "key_requested_apis";
/**
* Constant used as a key in order to let the NetworkService know whether or not it should
* start a recurring node latency verification task.
* <p>
* This information should be passed as an intent extra when calling the bindService
* or startService methods.
*/
public static final String KEY_ENABLE_LATENCY_VERIFIER = "key_enable_latency_verifier"; public static final String KEY_ENABLE_LATENCY_VERIFIER = "key_enable_latency_verifier";
/**
* Constant used as a key in order to specify the alpha (or smoothing) factor to be used in
* the exponential moving average calculated from the different latency samples. This only
* makes sense if the latency verification feature is enabled of course.
* <p>
* This information should be passed as an intent extra when calling the bindService
* or startService methods.
*/
public static final String KEY_NODE_LATENCY_SMOOTHING_FACTOR = "key_node_latency_smoothing_factor";
/** /**
* Key used to pass via intent a boolean extra to specify whether the connection should * Key used to pass via intent a boolean extra to specify whether the connection should
* be automatically established. * be automatically established.
* <p>
* This information should be passed as an intent extra when calling the bindService
* or startService methods.
*/ */
public static final String KEY_AUTO_CONNECT = "key_auto_connect"; public static final String KEY_AUTO_CONNECT = "key_auto_connect";
/** /**
* Key used to pass via intent a list of node URLs. The value passed should be a String * Key used to pass via intent a list of node URLs. The value passed should be a String
* containing a simple comma separated list of URLs. * containing a simple comma separated list of URLs.
* * <p>
* For example: * For example:
* *
* wss://domain1.com/ws,wss://domain2.com/ws,wss://domain3.com/ws * wss://domain1.com/ws,wss://domain2.com/ws,wss://domain3.com/ws
* <p>
* This information should be passed as an intent extra when calling the bindService
* or startService methods.
*/ */
public static final String KEY_NODE_URLS = "key_node_urls"; public static final String KEY_NODE_URLS = "key_node_urls";
@ -264,9 +305,10 @@ public class NetworkService extends Service {
// a first round of measurements in order to be sure to select the // a first round of measurements in order to be sure to select the
// best node. // best node.
if(verifyNodeLatency){ if(verifyNodeLatency){
double alpha = extras.getDouble(KEY_NODE_LATENCY_SMOOTHING_FACTOR, ExponentialMovingAverage.DEFAULT_ALPHA);
ArrayList<FullNode> fullNodes = new ArrayList<>(); ArrayList<FullNode> fullNodes = new ArrayList<>();
for(String url : urls){ for(String url : urls){
fullNodes.add(new FullNode(url)); fullNodes.add(new FullNode(url, alpha));
} }
nodeLatencyVerifier = new NodeLatencyVerifier(fullNodes); nodeLatencyVerifier = new NodeLatencyVerifier(fullNodes);
fullNodePublishSubject = nodeLatencyVerifier.start(); fullNodePublishSubject = nodeLatencyVerifier.start();

View file

@ -15,6 +15,8 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import cy.agorise.graphenej.stats.ExponentialMovingAverage;
/** /**
* This class should be instantiated at the application level of the android app. * This class should be instantiated at the application level of the android app.
* *
@ -52,6 +54,7 @@ public class NetworkServiceManager implements Application.ActivityLifecycleCallb
private List<String> mCustomNodeUrls = new ArrayList<>(); private List<String> mCustomNodeUrls = new ArrayList<>();
private boolean mAutoConnect; private boolean mAutoConnect;
private boolean mVerifyLatency; private boolean mVerifyLatency;
private double alpha;
/** /**
* Runnable used to schedule a service disconnection once the app is not visible to the user for * Runnable used to schedule a service disconnection once the app is not visible to the user for
@ -194,6 +197,7 @@ public class NetworkServiceManager implements Application.ActivityLifecycleCallb
private List<String> customNodeUrls; private List<String> customNodeUrls;
private boolean autoconnect = true; private boolean autoconnect = true;
private boolean verifyNodeLatency; private boolean verifyNodeLatency;
private double alpha = ExponentialMovingAverage.DEFAULT_ALPHA;
/** /**
* Sets the user name, if required to connect to a node. * Sets the user name, if required to connect to a node.
@ -269,6 +273,17 @@ public class NetworkServiceManager implements Application.ActivityLifecycleCallb
return this; return this;
} }
/**
* Sets the node latency verification's exponential moving average alpha parameter.
* @param alpha The alpha parameter to use when computing the exponential moving average of the
* measured latencies.
* @return The Builder instance.
*/
public Builder setLatencyAverageAlpha(double alpha){
this.alpha = alpha;
return this;
}
/** /**
* Method used to build a {@link NetworkServiceManager} instance with all of the characteristics * Method used to build a {@link NetworkServiceManager} instance with all of the characteristics
* passed as parameters. * passed as parameters.

View file

@ -8,14 +8,35 @@ import cy.agorise.graphenej.stats.ExponentialMovingAverage;
public class FullNode implements Comparable { public class FullNode implements Comparable {
private String mUrl; private String mUrl;
private ExponentialMovingAverage latency; private ExponentialMovingAverage mLatency;
private boolean isConnected; private boolean isConnected;
private FullNode(){} private FullNode(){}
/**
* Constructor used to specify both the node URL and the alpha parameter that one wishes to set the
* exponential moving average with.
* <p>
* The alpha parameter represents the degree of weighting decrease, and can be specified as any value
* between 0 and 1. A higher alpha discounts older observations faster.
*
* @param url The node URL.
* @param alpha The alpha parameter used to compute the exponential moving average.
*/
public FullNode(String url, double alpha){
mLatency = new ExponentialMovingAverage(alpha);
mUrl = url;
}
/**
* Constructor used to specify only the node URL.
* <p>
* The alpha parameter is set to the value specified at {@link ExponentialMovingAverage#DEFAULT_ALPHA}
*
* @param url The node URL.
*/
public FullNode(String url){ public FullNode(String url){
latency = new ExponentialMovingAverage(ExponentialMovingAverage.DEFAULT_ALPHA); this(url, ExponentialMovingAverage.DEFAULT_ALPHA);
this.mUrl = url;
} }
/** /**
@ -39,7 +60,7 @@ public class FullNode implements Comparable {
* @return The exponential moving average object instance * @return The exponential moving average object instance
*/ */
public ExponentialMovingAverage getLatencyAverage(){ public ExponentialMovingAverage getLatencyAverage(){
return latency; return mLatency;
} }
/** /**
@ -47,7 +68,7 @@ public class FullNode implements Comparable {
* @return The latest latency average value * @return The latest latency average value
*/ */
public double getLatencyValue() { public double getLatencyValue() {
return latency.getAverage(); return mLatency.getAverage();
} }
public boolean isConnected() { public boolean isConnected() {
@ -59,17 +80,17 @@ public class FullNode implements Comparable {
} }
/** /**
* Method that updates the latency average with a new value. * Method that updates the mLatency average with a new value.
* @param latency Most recent latency sample to be added to the exponential average * @param latency Most recent mLatency sample to be added to the exponential average
*/ */
public void addLatencyValue(double latency) { public void addLatencyValue(double latency) {
this.latency.updateValue(latency); this.mLatency.updateValue(latency);
} }
@Override @Override
public int compareTo(Object o) { public int compareTo(Object o) {
FullNode node = (FullNode) o; FullNode node = (FullNode) o;
return (int) Math.ceil(latency.getAverage() - node.getLatencyValue()); return (int) Math.ceil(mLatency.getAverage() - node.getLatencyValue());
} }
@Override @Override

View file

@ -154,7 +154,6 @@ public class NodeLatencyVerifier {
long before = timestamps.get(fullNode); long before = timestamps.get(fullNode);
delay = after - before; delay = after - before;
} }
fullNode.addLatencyValue(delay); fullNode.addLatencyValue(delay);
subject.onNext(fullNode); subject.onNext(fullNode);
}else{ }else{

View file

@ -38,6 +38,7 @@ public class SampleApplication extends Application {
.setCustomNodeUrls(nodes) .setCustomNodeUrls(nodes)
.setAutoConnect(true) .setAutoConnect(true)
.setNodeLatencyVerification(true) .setNodeLatencyVerification(true)
.setLatencyAverageAlpha(0.1f)
.build(this); .build(this);
// Registering this class as a listener to all activity's callback cycle events, in order to // Registering this class as a listener to all activity's callback cycle events, in order to