feature: mirror speedtest

This commit is contained in:
harlekin
2021-11-15 14:09:41 +00:00
committed by Sheepit Renderfarm
parent 6c5e252a1f
commit 2e46685068
7 changed files with 324 additions and 35 deletions

View File

@@ -35,7 +35,11 @@ import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import com.sheepit.client.datamodel.SpeedTestTarget;
import com.sheepit.client.datamodel.SpeedTestResult;
import com.sheepit.client.datamodel.SpeedTestTargetResult;
import lombok.Getter;
import org.simpleframework.xml.core.Persister;
@@ -74,6 +78,8 @@ import com.sheepit.client.os.OS;
public class Server extends Thread {
private static final int NUMBER_OF_SPEEDTEST_RESULTS = 3;
final private String HTTP_USER_AGENT = "Java/" + System.getProperty("java.version");
private String base_url;
private final OkHttpClient httpClient;
@@ -136,8 +142,8 @@ public class Server extends Thread {
String in = response.body().string();
try {
HeartBeatInfos heartBeartInfos = new Persister().read(HeartBeatInfos.class, in);
ServerCode serverCode = ServerCode.fromInt(heartBeartInfos.getStatus());
HeartBeatInfos heartBeatInfos = new Persister().read(HeartBeatInfos.class, in);
ServerCode serverCode = ServerCode.fromInt(heartBeatInfos.getStatus());
if (serverCode == ServerCode.KEEPMEALIVE_STOP_RENDERING) {
this.log.debug("Server::stayAlive server asked to kill local render process");
// kill the current process, it will generate an error but it's okay
@@ -252,6 +258,41 @@ public class Server extends Thread {
return Error.Type.UNKNOWN;
}
if (serverConfig.getSpeedTestTargets() != null && serverConfig.getSpeedTestTargets().isEmpty() == false) {
try {
client.getGui().status("Checking mirror connection speeds...");
Speedtest speedtest = new Speedtest(log);
List<SpeedTestTarget> bestSpeedTestTargets = speedtest.doSpeedtests(serverConfig.getSpeedTestTargets().stream().map(m -> m.getUrl()).collect(Collectors.toList()),
NUMBER_OF_SPEEDTEST_RESULTS);
SpeedTestResult result = new SpeedTestResult();
result.setResults(bestSpeedTestTargets.stream().map(m -> {
SpeedTestTargetResult targetResult = new SpeedTestTargetResult();
targetResult.setTarget(m.getUrl());
targetResult.setSpeed(m.getSpeedtest());
targetResult.setPing((int) (m.getPing().getAverage()));
return targetResult;
}).collect(Collectors.toList()));
final Persister persister = new Persister();
try (StringWriter writer = new StringWriter()) {
persister.write(result, writer);
HttpUrl.Builder urlBuilder = Objects.requireNonNull(HttpUrl.parse(this.getPage("speedtest-answer"))).newBuilder();
Response response = this.HTTPRequest(urlBuilder, RequestBody.create(MediaType.parse("application/xml"), writer.toString()));
if (response.code() != HttpURLConnection.HTTP_OK) {
throw new IOException("Server::getConfiguration Speedtest unexpected response");
}
}
catch (final Exception e) {
throw new IOException("Server::getConfiguration Speedtest failed to generate payload");
}
}
catch (IOException e) {
this.log.error("Server::getConfiguration Speedtest failed: " + e);
return Error.Type.NETWORK_ISSUE;
}
}
client.setSessionStarted(true);
this.client.getGui().successfulAuthenticationEvent(publickey);

View File

@@ -0,0 +1,129 @@
package com.sheepit.client;
import com.sheepit.client.datamodel.SpeedTestTarget;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.Socket;
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;
}
/**
* @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>
* if <code>numberOfResults > urls.size()</code>
*/
public List<SpeedTestTarget> doSpeedtests(List<String> urls, int numberOfResults) {
List<SpeedTestTarget> pingResult = (urls
.stream()
.map(this::measure)
.sorted(ORDERED)
.collect(Collectors.toList())
);
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;
}
private SpeedTestTarget measure(String mirror) {
long pingCount = 12;
var pingStatistics = LongStream
.range(0, pingCount)
.map(i -> {
try {
return runTimed(() -> ping(mirror, PORT)).first;
}
catch (Exception e) {
this.log.error("Speedtest::ping Exception " + e);
return Long.MAX_VALUE;
}
})
.summaryStatistics();
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);
}
}
private static int ping(String url, int port) {
try (Socket socket = new Socket(new URL(url).getHost(), port)) {
}
catch (IOException e) {
throw new RuntimeException("Unable to open a socket to " + url + ":" + port, e);
}
return -1;
}
}

View File

@@ -1,6 +1,7 @@
package com.sheepit.client.datamodel;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.ElementList;
@@ -16,6 +17,9 @@ import java.util.List;
@ElementList(name = "request", inline = true, required = false) private List<RequestEndPoint> requestEndPoints;
@Getter @Setter
@ElementList(name = "speedtest", required = false) private List<SpeedTestTarget> speedTestTargets;
public ServerConfig() {
}

View File

@@ -0,0 +1,18 @@
package com.sheepit.client.datamodel;
import lombok.Data;
import lombok.ToString;
import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;
import java.util.List;
@Root(strict = false, name = "speedtest") @Data @ToString public class SpeedTestResult {
@ElementList(inline = true) private List<SpeedTestTargetResult> results;
public SpeedTestResult() {
}
}

View File

@@ -0,0 +1,22 @@
package com.sheepit.client.datamodel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Root;
import java.util.LongSummaryStatistics;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Root(name = "target")
public class SpeedTestTarget {
@Attribute(name = "url")
private String url;
private long speedtest;
private LongSummaryStatistics ping;
}

View File

@@ -0,0 +1,18 @@
package com.sheepit.client.datamodel;
import lombok.Data;
import lombok.ToString;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Root;
@Root(strict = false, name = "result") @Data @ToString public class SpeedTestTargetResult {
@Attribute private String target;
@Attribute private Long speed;
@Attribute private Integer ping;
public SpeedTestTargetResult() {
}
}