- 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'
|
||||
}
|
||||
}
|
||||
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'
|
||||
|
|
|
@ -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">
|
||||
|
||||
<uses-sdk android:minSdkVersion="1" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)){
|
||||
return mFullNodeHeap.offer(fullNode);
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
mFullNodeHeap.remove(fullNode);
|
||||
return mFullNodeHeap.offer(fullNode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 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 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();
|
||||
|
|
Loading…
Reference in a new issue