- 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:
Nelson R. Perez 2018-09-20 16:21:25 -05:00
parent ccf61858e4
commit 776630dd57
8 changed files with 251 additions and 9 deletions

View file

@ -23,6 +23,10 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
defaultConfig {
multiDexEnabled true
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
}
dependencies {
@ -32,6 +36,10 @@ dependencies {
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
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
api 'io.reactivex.rxjava2:rxandroid:2.0.2'
api 'io.reactivex.rxjava2:rxjava:2.1.16'

View file

@ -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();
}
}
}

View file

@ -3,6 +3,7 @@
package="cy.agorise.graphenej">
<uses-sdk android:minSdkVersion="1" />
<uses-permission android:name="android.permission.INTERNET" />
<application>
<service

View file

@ -68,7 +68,7 @@ import okhttp3.WebSocketListener;
public class NetworkService extends Service {
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";
@ -535,7 +535,7 @@ public class NetworkService extends Service {
* Returns a list of {@link FullNode} instances
* @return List of full nodes
*/
List<FullNode> getSortedNodes(){
public List<FullNode> getNodes(){
return nodeProvider.getSortedNodes();
}
}

View file

@ -1,10 +1,15 @@
package cy.agorise.graphenej.network;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;
public class LatencyNodeProvider implements NodeProvider {
private final String TAG = this.getClass().getName();
private PriorityQueue<FullNode> mFullNodeHeap;
public LatencyNodeProvider(){
@ -23,11 +28,8 @@ public class LatencyNodeProvider implements NodeProvider {
@Override
public boolean updateNode(FullNode fullNode) {
if(mFullNodeHeap.remove(fullNode)){
mFullNodeHeap.remove(fullNode);
return mFullNodeHeap.offer(fullNode);
}else{
return false;
}
}
/**
@ -49,7 +51,15 @@ public class LatencyNodeProvider implements NodeProvider {
@Override
public List<FullNode> getSortedNodes() {
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);
}
}

View file

@ -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);
}
};
}

View file

@ -21,6 +21,6 @@ public class ExampleInstrumentedTest {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.luminiasoft.labs.sample", appContext.getPackageName());
assertEquals("cy.agorise.labs.sample", appContext.getPackageName());
}
}

View file

@ -9,6 +9,7 @@ import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import cy.agorise.graphenej.api.android.NetworkService;
import cy.agorise.graphenej.network.NodeLatencyVerifier;
public abstract class ConnectedActivity extends AppCompatActivity implements ServiceConnection {
private final String TAG = this.getClass().getName();