- Introduced some instrumented test dependencies
- Fixed a problem in the LatencyNodeProvider#updateNode(FullNode) method - Fixed a bug in the LatencyNodeProvider#getSortedNodes() method - Introducing the NodeLatencyVerifier class
This commit is contained in:
parent
ccf61858e4
commit
776630dd57
8 changed files with 251 additions and 9 deletions
|
@ -23,6 +23,10 @@ android {
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
defaultConfig {
|
||||||
|
multiDexEnabled true
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -32,6 +36,10 @@ dependencies {
|
||||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
|
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
|
||||||
implementation group: "org.tukaani", name: "xz", version: "1.6"
|
implementation group: "org.tukaani", name: "xz", version: "1.6"
|
||||||
|
|
||||||
|
androidTestImplementation 'com.android.support:support-annotations:27.1.1'
|
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
androidTestImplementation 'com.android.support.test:rules:1.0.2'
|
||||||
|
|
||||||
// Rx dependencies
|
// Rx dependencies
|
||||||
api 'io.reactivex.rxjava2:rxandroid:2.0.2'
|
api 'io.reactivex.rxjava2:rxandroid:2.0.2'
|
||||||
api 'io.reactivex.rxjava2:rxjava:2.1.16'
|
api 'io.reactivex.rxjava2:rxjava:2.1.16'
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
package cy.agorise.graphenej;
|
||||||
|
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import cy.agorise.graphenej.api.bitshares.Nodes;
|
||||||
|
import cy.agorise.graphenej.network.FullNode;
|
||||||
|
import cy.agorise.graphenej.network.LatencyNodeProvider;
|
||||||
|
import cy.agorise.graphenej.network.NodeLatencyVerifier;
|
||||||
|
import cy.agorise.graphenej.network.NodeProvider;
|
||||||
|
import io.reactivex.Observer;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.subjects.PublishSubject;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class NodeLatencyVerifierTest {
|
||||||
|
private final String TAG = this.getClass().getName();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNodeLatencyTest() throws Exception {
|
||||||
|
ArrayList<FullNode> nodeList = new ArrayList<>();
|
||||||
|
nodeList.add(new FullNode(Nodes.NODE_URLS[0]));
|
||||||
|
nodeList.add(new FullNode(Nodes.NODE_URLS[1]));
|
||||||
|
nodeList.add(new FullNode(Nodes.NODE_URLS[2]));
|
||||||
|
final NodeLatencyVerifier nodeLatencyVerifier = new NodeLatencyVerifier(nodeList);
|
||||||
|
PublishSubject subject = nodeLatencyVerifier.start();
|
||||||
|
final NodeProvider nodeProvider = new LatencyNodeProvider();
|
||||||
|
subject.subscribe(new Observer<FullNode>() {
|
||||||
|
int counter = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSubscribe(Disposable d) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(FullNode fullNode) {
|
||||||
|
Log.i(TAG,String.format("Avg latency: %.2f, url: %s", fullNode.getLatencyValue(), fullNode.getUrl()));
|
||||||
|
|
||||||
|
// Updating node provider
|
||||||
|
nodeProvider.updateNode(fullNode);
|
||||||
|
List<FullNode> sortedNodes = nodeProvider.getSortedNodes();
|
||||||
|
for(FullNode node : sortedNodes){
|
||||||
|
Log.d(TAG,String.format("> %.2f, url: %s", node.getLatencyValue(), node.getUrl()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish test after certain amount of rounds
|
||||||
|
if(counter > 3){
|
||||||
|
synchronized (NodeLatencyVerifierTest.this){
|
||||||
|
nodeLatencyVerifier.stop();
|
||||||
|
NodeLatencyVerifierTest.this.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable e) {
|
||||||
|
Log.e(TAG,"onError.Msg: "+e.getMessage());
|
||||||
|
synchronized (NodeLatencyVerifierTest.this){
|
||||||
|
NodeLatencyVerifierTest.this.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplete() {
|
||||||
|
Log.d(TAG,"onComplete");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
synchronized(this) {
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
package="cy.agorise.graphenej">
|
package="cy.agorise.graphenej">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="1" />
|
<uses-sdk android:minSdkVersion="1" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application>
|
<application>
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -68,7 +68,7 @@ import okhttp3.WebSocketListener;
|
||||||
public class NetworkService extends Service {
|
public class NetworkService extends Service {
|
||||||
private final String TAG = this.getClass().getName();
|
private final String TAG = this.getClass().getName();
|
||||||
|
|
||||||
private static final int NORMAL_CLOSURE_STATUS = 1000;
|
public static final int NORMAL_CLOSURE_STATUS = 1000;
|
||||||
|
|
||||||
public static final String KEY_USERNAME = "key_username";
|
public static final String KEY_USERNAME = "key_username";
|
||||||
|
|
||||||
|
@ -535,7 +535,7 @@ public class NetworkService extends Service {
|
||||||
* Returns a list of {@link FullNode} instances
|
* Returns a list of {@link FullNode} instances
|
||||||
* @return List of full nodes
|
* @return List of full nodes
|
||||||
*/
|
*/
|
||||||
List<FullNode> getSortedNodes(){
|
public List<FullNode> getNodes(){
|
||||||
return nodeProvider.getSortedNodes();
|
return nodeProvider.getSortedNodes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
package cy.agorise.graphenej.network;
|
package cy.agorise.graphenej.network;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.PriorityQueue;
|
import java.util.PriorityQueue;
|
||||||
|
|
||||||
public class LatencyNodeProvider implements NodeProvider {
|
public class LatencyNodeProvider implements NodeProvider {
|
||||||
|
private final String TAG = this.getClass().getName();
|
||||||
private PriorityQueue<FullNode> mFullNodeHeap;
|
private PriorityQueue<FullNode> mFullNodeHeap;
|
||||||
|
|
||||||
public LatencyNodeProvider(){
|
public LatencyNodeProvider(){
|
||||||
|
@ -23,11 +28,8 @@ public class LatencyNodeProvider implements NodeProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean updateNode(FullNode fullNode) {
|
public boolean updateNode(FullNode fullNode) {
|
||||||
if(mFullNodeHeap.remove(fullNode)){
|
mFullNodeHeap.remove(fullNode);
|
||||||
return mFullNodeHeap.offer(fullNode);
|
return mFullNodeHeap.offer(fullNode);
|
||||||
}else{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -49,7 +51,15 @@ public class LatencyNodeProvider implements NodeProvider {
|
||||||
@Override
|
@Override
|
||||||
public List<FullNode> getSortedNodes() {
|
public List<FullNode> getSortedNodes() {
|
||||||
FullNode[] nodeArray = mFullNodeHeap.toArray(new FullNode[mFullNodeHeap.size()]);
|
FullNode[] nodeArray = mFullNodeHeap.toArray(new FullNode[mFullNodeHeap.size()]);
|
||||||
Arrays.sort(nodeArray);
|
ArrayList<FullNode> nodeList = new ArrayList<>();
|
||||||
|
for(FullNode fullNode : nodeList){
|
||||||
|
if(fullNode != null){
|
||||||
|
nodeList.add(fullNode);
|
||||||
|
}else{
|
||||||
|
Log.d(TAG,"Found a null node in getSortedNodes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(nodeList);
|
||||||
return Arrays.asList(nodeArray);
|
return Arrays.asList(nodeArray);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
package cy.agorise.graphenej.network;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import cy.agorise.graphenej.api.android.NetworkService;
|
||||||
|
import io.reactivex.subjects.PublishSubject;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.WebSocket;
|
||||||
|
import okhttp3.WebSocketListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that encapsulates the node latency verification task
|
||||||
|
*/
|
||||||
|
public class NodeLatencyVerifier {
|
||||||
|
|
||||||
|
public static final int DEFAULT_LATENCY_VERIFICATION_PERIOD = 5 * 1000;
|
||||||
|
|
||||||
|
// Variable used to store the list of nodes that should be verified
|
||||||
|
private List<FullNode> mNodeList;
|
||||||
|
|
||||||
|
// Variable used to store the desired verification period
|
||||||
|
private long verificationPeriod;
|
||||||
|
|
||||||
|
// Subject used to publish the result to interested parties
|
||||||
|
private PublishSubject<FullNode> subject = PublishSubject.create();
|
||||||
|
|
||||||
|
private HashMap<String, FullNode> nodeURLMap = new HashMap<>();
|
||||||
|
|
||||||
|
// private WebSocket webSocket;
|
||||||
|
|
||||||
|
// Map used to store the first timestamp required for a RTT (Round Trip Time) measurement.
|
||||||
|
// If:
|
||||||
|
// RTT = t2 - t1
|
||||||
|
// This map will hold the value of t1 for each one of the nodes to be measured.
|
||||||
|
private HashMap<FullNode, Long> timestamps = new HashMap<>();
|
||||||
|
|
||||||
|
private HashMap<String, Request> requestMap = new HashMap<>();
|
||||||
|
|
||||||
|
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
private OkHttpClient client;
|
||||||
|
|
||||||
|
public NodeLatencyVerifier(List<FullNode> nodes){
|
||||||
|
this(nodes, DEFAULT_LATENCY_VERIFICATION_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeLatencyVerifier(List<FullNode> nodes, long period){
|
||||||
|
mNodeList = nodes;
|
||||||
|
verificationPeriod = period;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used to start the latency verification task.
|
||||||
|
* <p>
|
||||||
|
* The returning object can be used for interested parties to receive constant updates
|
||||||
|
* regarding new latency measurements for every full node.
|
||||||
|
* </p>
|
||||||
|
* @return A {@link PublishSubject} class instance.
|
||||||
|
*/
|
||||||
|
public PublishSubject start(){
|
||||||
|
mHandler.post(mVerificationTask);
|
||||||
|
return subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method used to cancel the verification task.
|
||||||
|
*/
|
||||||
|
public void stop(){
|
||||||
|
mHandler.removeCallbacks(mVerificationTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node latency verification task.
|
||||||
|
*/
|
||||||
|
private final Runnable mVerificationTask = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for(FullNode fullNode : mNodeList){
|
||||||
|
long before = System.currentTimeMillis();
|
||||||
|
timestamps.put(fullNode, before);
|
||||||
|
|
||||||
|
// We want to reuse the same OkHttoClient instance if possible
|
||||||
|
if(client == null) client = new OkHttpClient();
|
||||||
|
|
||||||
|
// Same thing with the Request instance, we want to reuse them. But since
|
||||||
|
// we might have one request per node, we keep them in a map.
|
||||||
|
Request request;
|
||||||
|
if(requestMap.containsKey(fullNode.getUrl())){
|
||||||
|
request = requestMap.get(fullNode.getUrl());
|
||||||
|
}else{
|
||||||
|
// If the map had no entry for the request we want, we create one
|
||||||
|
// and add it to the map.
|
||||||
|
request = new Request.Builder().url(fullNode.getUrl()).build();
|
||||||
|
requestMap.put(fullNode.getUrl(), request);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.newWebSocket(request, mWebSocketListener);
|
||||||
|
if(!nodeURLMap.containsKey(fullNode.getUrl())){
|
||||||
|
nodeURLMap.put(fullNode.getUrl(), fullNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mHandler.postDelayed(this, verificationPeriod);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener that will be called upon a server response.
|
||||||
|
*/
|
||||||
|
private WebSocketListener mWebSocketListener = new WebSocketListener() {
|
||||||
|
@Override
|
||||||
|
public void onOpen(WebSocket webSocket, Response response) {
|
||||||
|
super.onOpen(webSocket, response);
|
||||||
|
handleResponse(webSocket, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||||
|
super.onFailure(webSocket, t, response);
|
||||||
|
handleResponse(webSocket, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleResponse(WebSocket webSocket, Response response){
|
||||||
|
String url = "wss://" + webSocket.request().url().host() + webSocket.request().url().encodedPath();
|
||||||
|
FullNode fullNode = nodeURLMap.get(url);
|
||||||
|
long after = System.currentTimeMillis();
|
||||||
|
long before = timestamps.get(fullNode);
|
||||||
|
long delay = after - before;
|
||||||
|
fullNode.addLatencyValue(delay);
|
||||||
|
subject.onNext(fullNode);
|
||||||
|
webSocket.close(NetworkService.NORMAL_CLOSURE_STATUS, null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -21,6 +21,6 @@ public class ExampleInstrumentedTest {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
assertEquals("com.luminiasoft.labs.sample", appContext.getPackageName());
|
assertEquals("cy.agorise.labs.sample", appContext.getPackageName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.support.v7.app.AppCompatActivity;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import cy.agorise.graphenej.api.android.NetworkService;
|
import cy.agorise.graphenej.api.android.NetworkService;
|
||||||
|
import cy.agorise.graphenej.network.NodeLatencyVerifier;
|
||||||
|
|
||||||
public abstract class ConnectedActivity extends AppCompatActivity implements ServiceConnection {
|
public abstract class ConnectedActivity extends AppCompatActivity implements ServiceConnection {
|
||||||
private final String TAG = this.getClass().getName();
|
private final String TAG = this.getClass().getName();
|
||||||
|
|
Loading…
Reference in a new issue