2021-11-15 14:09:41 +00:00
|
|
|
package com.sheepit.client;
|
|
|
|
|
|
|
|
|
|
import com.sheepit.client.datamodel.SpeedTestTarget;
|
|
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.InputStream;
|
2021-12-04 10:32:10 +00:00
|
|
|
import java.net.InetAddress;
|
|
|
|
|
import java.net.InetSocketAddress;
|
2021-11-15 14:09:41 +00:00
|
|
|
import java.net.MalformedURLException;
|
|
|
|
|
import java.net.Socket;
|
2021-12-04 10:32:10 +00:00
|
|
|
import java.net.SocketAddress;
|
2021-11-15 14:09:41 +00:00
|
|
|
import java.net.URL;
|
|
|
|
|
import java.time.Duration;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Comparator;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.concurrent.Callable;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
import java.util.stream.LongStream;
|
|
|
|
|
|
|
|
|
|
public class Speedtest {
|
|
|
|
|
public static final int PORT = 443;
|
|
|
|
|
private static final Comparator<SpeedTestTarget> ORDERED = Comparator.comparing(speedTestTarget -> speedTestTarget.getPing().getAverage());
|
|
|
|
|
|
|
|
|
|
private Log log;
|
|
|
|
|
|
|
|
|
|
public Speedtest(Log log) {
|
|
|
|
|
this.log = log;
|
|
|
|
|
}
|
2021-12-04 10:32:10 +00:00
|
|
|
|
2021-11-15 14:09:41 +00:00
|
|
|
/**
|
2021-12-04 10:32:10 +00:00
|
|
|
* @param urls the urls to the speedtest payloads
|
|
|
|
|
* @param numberOfResults number of best mirrors to return
|
|
|
|
|
*
|
|
|
|
|
* @return An array of the mirrors with the best connection time. The size of the array is determined by <code>numberOfResults</code> or <code>urls.size()</code>
|
2021-11-15 14:09:41 +00:00
|
|
|
* if <code>numberOfResults > urls.size()</code>
|
2021-12-04 10:32:10 +00:00
|
|
|
*/
|
2021-11-15 14:09:41 +00:00
|
|
|
public List<SpeedTestTarget> doSpeedtests(List<String> urls, int numberOfResults) {
|
2021-12-04 10:32:10 +00:00
|
|
|
|
2021-11-15 14:09:41 +00:00
|
|
|
List<SpeedTestTarget> pingResult = (urls
|
2021-12-04 10:32:10 +00:00
|
|
|
.stream()
|
|
|
|
|
.map(this::measure)
|
|
|
|
|
.filter(target -> target.getPing().getAverage() > 0)
|
|
|
|
|
.sorted(ORDERED)
|
|
|
|
|
.collect(Collectors.toList())
|
2021-11-15 14:09:41 +00:00
|
|
|
);
|
2021-12-04 10:32:10 +00:00
|
|
|
|
2021-11-15 14:09:41 +00:00
|
|
|
numberOfResults = Math.min(numberOfResults, urls.size());
|
|
|
|
|
|
|
|
|
|
List<SpeedTestTarget> result = new ArrayList<>(numberOfResults);
|
|
|
|
|
|
|
|
|
|
int i = 0;
|
|
|
|
|
while (result.size() < numberOfResults && i < pingResult.size()) {
|
|
|
|
|
SpeedTestTarget m = pingResult.get(i);
|
|
|
|
|
try {
|
|
|
|
|
var speedtestResult = runTimed(() -> speedtest(m.getUrl()));
|
|
|
|
|
m.setSpeedtest(
|
|
|
|
|
Math.round(speedtestResult.second / (speedtestResult.first / (double) 1000L)) // number of bytes / time in seconds
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e) {
|
|
|
|
|
this.log.error("Speedtest::doSpeedtests Exception " + e);
|
|
|
|
|
i++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
result.add(m);
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.sort(Comparator.comparing(SpeedTestTarget::getSpeedtest).reversed());
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2021-12-04 10:32:10 +00:00
|
|
|
|
2021-11-15 14:09:41 +00:00
|
|
|
private SpeedTestTarget measure(String mirror) {
|
|
|
|
|
long pingCount = 12;
|
2021-12-04 10:32:10 +00:00
|
|
|
|
|
|
|
|
LongStream.Builder streamBuilder = LongStream.builder();
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < pingCount; i++) {
|
|
|
|
|
try {
|
|
|
|
|
streamBuilder.add(runTimed(() -> ping(mirror, PORT)).first);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e) {
|
|
|
|
|
this.log.error("Speedtest::ping Exception " + e);
|
|
|
|
|
pingCount /= 2;
|
|
|
|
|
streamBuilder.add(Long.MAX_VALUE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var pingStatistics = streamBuilder.build().summaryStatistics();
|
|
|
|
|
|
2021-11-15 14:09:41 +00:00
|
|
|
return new SpeedTestTarget(mirror, -1, pingStatistics);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Will return both the time it took to complete the task and the result of it
|
|
|
|
|
* @param task the task whose execution time we want to measure
|
|
|
|
|
* @param <T> the return value of the task
|
|
|
|
|
* @return A pair where the first value is the execution time in ms and the second value is the task result
|
|
|
|
|
* @throws Exception
|
|
|
|
|
*/
|
|
|
|
|
private <T> Pair<Long, T> runTimed(Callable<T> task) throws Exception {
|
|
|
|
|
long start, end;
|
|
|
|
|
start = System.nanoTime();
|
|
|
|
|
T callValue = task.call();
|
|
|
|
|
end = System.nanoTime();
|
|
|
|
|
return new Pair<>(Duration.ofNanos(end - start).toMillis(), callValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Downloads a payload from the given url and returns the number of downloaded bytes
|
|
|
|
|
* @param url the url pointing at the speedtest file
|
|
|
|
|
* @return the number of bytes read
|
|
|
|
|
*/
|
|
|
|
|
private int speedtest(String url) {
|
|
|
|
|
try (InputStream stream = new URL(url).openStream()) {
|
|
|
|
|
return stream.readAllBytes().length;
|
|
|
|
|
}
|
|
|
|
|
catch (MalformedURLException e) {
|
|
|
|
|
throw new RuntimeException("Invalid speedtest URL: " + url, e);
|
|
|
|
|
}
|
|
|
|
|
catch (IOException e) {
|
|
|
|
|
throw new RuntimeException("Unable to execute speedtest to: " + url, e);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-04 10:32:10 +00:00
|
|
|
|
2021-11-15 14:09:41 +00:00
|
|
|
private static int ping(String url, int port) {
|
2022-02-19 14:58:25 +00:00
|
|
|
InetAddress mirrorIP = null;
|
2021-12-04 10:32:10 +00:00
|
|
|
try (Socket socket = new Socket()) {
|
2022-02-19 14:58:25 +00:00
|
|
|
mirrorIP = InetAddress.getByName(new URL(url).getHost());
|
2021-12-04 10:32:10 +00:00
|
|
|
SocketAddress socketAddress = new InetSocketAddress(mirrorIP, port);
|
|
|
|
|
int maxWaitingTime_ms = 3000;
|
|
|
|
|
socket.connect(socketAddress, maxWaitingTime_ms);
|
2021-11-15 14:09:41 +00:00
|
|
|
}
|
|
|
|
|
catch (IOException e) {
|
2022-02-19 14:58:25 +00:00
|
|
|
String problemURL = mirrorIP != null ? mirrorIP + " (derived from: " + url + ")" : url;
|
|
|
|
|
throw new RuntimeException("Unable to connect to " + problemURL, e);
|
2021-11-15 14:09:41 +00:00
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|