Skip to content

Commit 3e9ddff

Browse files
authored
Select fastest server on a full latency test, but optimise the timeout given other server results. (#120)
1 parent dfbdced commit 3e9ddff

File tree

11 files changed

+86
-132
lines changed

11 files changed

+86
-132
lines changed

src/NetPace.Console/ConsoleWriters/DefaultConsoleWriter.cs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ public async Task PerformSpeedTestAsync(bool initialSpeedTest, IAnsiConsole cons
2828
var uploadResult = new SpeedTestResult();
2929

3030
// Perform speed test
31-
if (!(settings.NoLatency && settings.NoDownload && settings.NoUpload))
32-
{
31+
if (!(settings.NoDownload && settings.NoUpload))
32+
{
3333
await console.Progress()
3434
.AutoClear(false)
3535
.Columns(
@@ -40,13 +40,9 @@ await console.Progress()
4040
])
4141
.StartAsync(async progress =>
4242
{
43-
ProgressTask? latencyProgress = null; ProgressTask? downloadProgress = null; ProgressTask? uploadProgress = null;
43+
ProgressTask? downloadProgress = null; ProgressTask? uploadProgress = null;
4444

4545
// Create the graphical progress bars
46-
if (!settings.NoLatency)
47-
{
48-
latencyProgress = progress.AddTask("Latency", autoStart: true, maxValue: 100);
49-
}
5046
if (!settings.NoDownload)
5147
{
5248
downloadProgress = progress.AddTask("Downloading", autoStart: true, maxValue: 100);
@@ -57,14 +53,6 @@ await console.Progress()
5753
}
5854

5955
// Perform the speed tests and show progress
60-
if (!settings.NoLatency)
61-
{
62-
// Update the initial server probe result with a more accurate latency
63-
fastest = await speedTestClient.GetServerLatencyAsync(fastest.Server, (LatencyTestProgress progress) =>
64-
{
65-
latencyProgress!.Value = progress.PercentageComplete;
66-
}, cancellationToken);
67-
}
6856
if (!settings.NoDownload)
6957
{
7058
downloadResult = await speedTestClient.GetDownloadSpeedAsync(fastest.Server, settings.DownloadSizeMb, (SpeedTestProgress progress) =>
@@ -108,6 +96,13 @@ await console.Progress()
10896
}
10997

11098

99+
if ((settings.NoDownload && settings.NoUpload) && console.Profile.Capabilities.Interactive)
100+
{
101+
// Latency only test: Add an extra blank line for formatting.
102+
console.WriteLine("");
103+
}
104+
105+
111106
// Display speed test result.
112107
console.WriteLine(string.Join(", ", new[]
113108
{

src/NetPace.Console/ConsoleWriters/ServerSelector.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ internal static class ServerSelector
1010
/// <summary>
1111
/// Gets the server to use for speed testing based on settings.
1212
/// </summary>
13-
public static async Task<ServerLatencyResult> GetServerAsync(ISpeedTestService speedTestClient, SpeedTestCommandSettings settings, CancellationToken cancellationToken = default)
13+
public static async Task<LatencyTestResult> GetServerAsync(ISpeedTestService speedTestClient, SpeedTestCommandSettings settings, CancellationToken cancellationToken = default)
1414
{
1515
ArgumentNullException.ThrowIfNull(speedTestClient);
1616
ArgumentNullException.ThrowIfNull(settings);
@@ -26,13 +26,13 @@ public static async Task<ServerLatencyResult> GetServerAsync(ISpeedTestService s
2626
throw new Exception("No servers available");
2727
}
2828
var firstServer = servers.First();
29-
return new ServerLatencyResult { Server = firstServer, Latency = 0 };
29+
return new LatencyTestResult { Server = firstServer, Latency = 0 };
3030
}
3131
else
3232
{
3333
// Create a minimal speed test server without testing latency.
3434
var server = new Server() { Sponsor = "(Unknown)", Url = settings.ServerUrl };
35-
return new ServerLatencyResult { Server = server, Latency = 0 };
35+
return new LatencyTestResult { Server = server, Latency = 0 };
3636
}
3737
}
3838
else

src/NetPace.Core.Tests/OoklaSpeedtestTests.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -495,10 +495,10 @@ public async Task GetFastestServerByLatencyAsync_ShouldReturnServerWithLowestLat
495495
var httpClient = mockHttp.ToHttpClient();
496496
var settings = new OoklaSpeedtestSettings
497497
{
498-
ServerDiscovery = new()
498+
LatencyTest = new()
499499
{
500-
ServerProbeIterations = 1,
501-
ServerProbeIntervalMilliseconds = 0
500+
LatencyTestIterations = 1,
501+
LatencyTestIntervalMilliseconds = 0
502502
}
503503
};
504504

@@ -530,10 +530,10 @@ public async Task GetFastestServerByLatencyAsync_ShouldThrow_WhenAllServersFail(
530530
var httpClient = mockHttp.ToHttpClient();
531531
var settings = new OoklaSpeedtestSettings
532532
{
533-
ServerDiscovery = new()
533+
LatencyTest = new()
534534
{
535-
ServerProbeIterations = 1,
536-
ServerProbeIntervalMilliseconds = 0
535+
LatencyTestIterations = 1,
536+
LatencyTestIntervalMilliseconds = 0
537537
}
538538
};
539539

@@ -599,10 +599,10 @@ public async Task GetFastestServerByLatencyAsync_WithProgress_ShouldReportProgre
599599
var httpClient = mockHttp.ToHttpClient();
600600
var settings = new OoklaSpeedtestSettings
601601
{
602-
ServerDiscovery = new()
602+
LatencyTest = new()
603603
{
604-
ServerProbeIterations = 1,
605-
ServerProbeIntervalMilliseconds = 0
604+
LatencyTestIterations = 1,
605+
LatencyTestIntervalMilliseconds = 0
606606
}
607607
};
608608

@@ -634,10 +634,10 @@ public async Task GetFastestServerByLatencyAsync_WithProgress_ShouldReport100Per
634634
var httpClient = mockHttp.ToHttpClient();
635635
var settings = new OoklaSpeedtestSettings
636636
{
637-
ServerDiscovery = new()
637+
LatencyTest = new()
638638
{
639-
ServerProbeIterations = 1,
640-
ServerProbeIntervalMilliseconds = 0
639+
LatencyTestIterations = 1,
640+
LatencyTestIntervalMilliseconds = 0
641641
}
642642
};
643643

@@ -672,10 +672,10 @@ public async Task GetFastestServerByLatencyAsync_WithProgress_ShouldReportProgre
672672
var httpClient = mockHttp.ToHttpClient();
673673
var settings = new OoklaSpeedtestSettings
674674
{
675-
ServerDiscovery = new()
675+
LatencyTest = new()
676676
{
677-
ServerProbeIterations = 1,
678-
ServerProbeIntervalMilliseconds = 0
677+
LatencyTestIterations = 1,
678+
LatencyTestIntervalMilliseconds = 0
679679
}
680680
}; ;
681681

@@ -706,10 +706,10 @@ public async Task GetFastestServerByLatencyAsync_WithProgress_ShouldPropagateExc
706706
var httpClient = mockHttp.ToHttpClient();
707707
var settings = new OoklaSpeedtestSettings
708708
{
709-
ServerDiscovery = new()
709+
LatencyTest = new()
710710
{
711-
ServerProbeIterations = 1,
712-
ServerProbeIntervalMilliseconds = 0
711+
LatencyTestIterations = 1,
712+
LatencyTestIntervalMilliseconds = 0
713713
}
714714
};
715715

src/NetPace.Core/Clients/Ookla/OoklaSpeedtest.cs

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -43,63 +43,41 @@ public async Task<IServer[]> GetServersAsync(CancellationToken cancellationToken
4343
}
4444

4545
/// <inheritdoc/>
46-
public async Task<ServerLatencyResult> GetServerLatencyAsync(string serverUrl, CancellationToken cancellationToken = default)
46+
public async Task<LatencyTestResult> GetServerLatencyAsync(string serverUrl, CancellationToken cancellationToken = default)
4747
{
4848
return await GetServerLatencyAsync(serverUrl, (_) => { }, cancellationToken);
4949
}
5050

5151
/// <inheritdoc/>
52-
public async Task<ServerLatencyResult> GetServerLatencyAsync(string serverUrl, Action<LatencyTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
52+
public async Task<LatencyTestResult> GetServerLatencyAsync(string serverUrl, Action<LatencyTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
5353
{
5454
ArgumentException.ThrowIfNullOrWhiteSpace(serverUrl);
5555

5656
var server = new Server() { Sponsor = "(Unknown)", Url = serverUrl };
57-
return await internalGetServerLatencyAsync(server, httpClient, delayProvider, settings.LatencyTest.HttpTimeoutMilliseconds, settings.LatencyTest.LatencyTestIterations, settings.LatencyTest.LatencyTestIntervalMilliseconds, UpdateProgress, cancellationToken);
57+
return await GetServerLatencyAsync(server, UpdateProgress, cancellationToken);
5858
}
5959

6060
/// <inheritdoc/>
61-
public async Task<ServerLatencyResult> GetServerLatencyAsync(IServer server, CancellationToken cancellationToken = default)
61+
public async Task<LatencyTestResult> GetServerLatencyAsync(IServer server, CancellationToken cancellationToken = default)
6262
{
6363
return await GetServerLatencyAsync(server, (_) => { }, cancellationToken);
6464
}
6565

6666
/// <inheritdoc/>
67-
public async Task<ServerLatencyResult> GetServerLatencyAsync(IServer server, Action<LatencyTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
67+
public async Task<LatencyTestResult> GetServerLatencyAsync(IServer server, Action<LatencyTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
6868
{
6969
ArgumentNullException.ThrowIfNull(server);
7070
ArgumentException.ThrowIfNullOrWhiteSpace(server.Url);
71-
72-
return await internalGetServerLatencyAsync(server, httpClient, delayProvider, settings.LatencyTest.HttpTimeoutMilliseconds, settings.LatencyTest.LatencyTestIterations, settings.LatencyTest.LatencyTestIntervalMilliseconds, UpdateProgress, cancellationToken);
73-
}
74-
75-
private static async Task<ServerLatencyResult> internalGetServerLatencyAsync(IServer server, HttpClient httpClient, IDelayProvider delayProvider, int httpTimeoutMilliseconds, int maxIterations, int intervalMilliseconds, Action<LatencyTestProgress> UpdateProgress, CancellationToken cancellationToken)
76-
{
77-
// Validate inputs to avoid invalid operation during the latency test
78-
ArgumentNullException.ThrowIfNull(server);
79-
ArgumentException.ThrowIfNullOrWhiteSpace(server.Url);
80-
ArgumentNullException.ThrowIfNull(httpClient);
81-
ArgumentNullException.ThrowIfNull(delayProvider);
8271
ArgumentNullException.ThrowIfNull(UpdateProgress);
8372

84-
if (maxIterations < 1)
85-
{
86-
throw new ArgumentOutOfRangeException(nameof(maxIterations), "maxIterations must be at least 1.");
87-
}
88-
89-
if (httpTimeoutMilliseconds <= 0)
90-
{
91-
throw new ArgumentOutOfRangeException(nameof(httpTimeoutMilliseconds), "httpTimeoutMilliseconds must be greater than 0.");
92-
}
93-
94-
if (intervalMilliseconds < 0)
95-
{
96-
throw new ArgumentOutOfRangeException(nameof(intervalMilliseconds), "intervalMilliseconds cannot be negative.");
97-
}
98-
9973
var latencyUrl = GetBaseUrl(server.Url) + "latency.txt";
10074
var pings = new List<int>();
10175
var stopwatch = new Stopwatch();
10276

77+
var maxIterations = settings.LatencyTest.LatencyTestIterations;
78+
var intervalMilliseconds = settings.LatencyTest.LatencyTestIntervalMilliseconds;
79+
var httpTimeoutMilliseconds = settings.LatencyTest.HttpTimeoutMilliseconds;
80+
10381
for (var iteration = 0; iteration < maxIterations; iteration++)
10482
{
10583
cancellationToken.ThrowIfCancellationRequested();
@@ -132,7 +110,7 @@ private static async Task<ServerLatencyResult> internalGetServerLatencyAsync(ISe
132110
}
133111

134112
// Calculate the average server latency.
135-
var latencyResult = new ServerLatencyResult
113+
var latencyResult = new LatencyTestResult
136114
{
137115
Server = server,
138116
Latency = (int)pings.Average()
@@ -142,21 +120,21 @@ private static async Task<ServerLatencyResult> internalGetServerLatencyAsync(ISe
142120
}
143121

144122
/// <inheritdoc/>
145-
public async Task<ServerLatencyResult> GetFastestServerByLatencyAsync(IServer[] servers, CancellationToken cancellationToken = default)
123+
public async Task<LatencyTestResult> GetFastestServerByLatencyAsync(IServer[] servers, CancellationToken cancellationToken = default)
146124
{
147125
return await GetFastestServerByLatencyAsync(servers, _ => { }, cancellationToken);
148126
}
149127

150128
/// <inheritdoc/>
151-
public async Task<ServerLatencyResult> GetFastestServerByLatencyAsync(IServer[] servers, Action<SpeedTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
129+
public async Task<LatencyTestResult> GetFastestServerByLatencyAsync(IServer[] servers, Action<SpeedTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
152130
{
153131
ArgumentNullException.ThrowIfNull(servers);
154132
if (servers.Length == 0)
155133
{
156134
throw new ArgumentException("At least one server must be provided.", nameof(servers));
157135
}
158136

159-
var serverProbes = new List<ServerLatencyResult>();
137+
var serverProbes = new List<LatencyTestResult>();
160138

161139
for (int i = 0; i < servers.Length; i++)
162140
{
@@ -178,15 +156,7 @@ public async Task<ServerLatencyResult> GetFastestServerByLatencyAsync(IServer[]
178156

179157
try
180158
{
181-
var latencyResult = await internalGetServerLatencyAsync(
182-
servers[i],
183-
httpClient,
184-
delayProvider,
185-
settings.LatencyTest.HttpTimeoutMilliseconds,
186-
settings.ServerDiscovery.ServerProbeIterations,
187-
settings.ServerDiscovery.ServerProbeIntervalMilliseconds,
188-
_ => { },
189-
linkedCts.Token);
159+
var latencyResult = await GetServerLatencyAsync(servers[i], _ => { }, linkedCts.Token);
190160

191161
serverProbes.Add(latencyResult);
192162
}

src/NetPace.Core/Clients/Ookla/Settings/ServerDiscoverySettings.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,4 @@ public sealed record ServerDiscoverySettings
1717
/// The timeout duration in milliseconds when probing a server.
1818
/// </summary>
1919
public int ServerTimeoutMilliseconds { get; init; } = 2000;
20-
21-
/// <summary>
22-
/// The number of times to probe a server.
23-
/// </summary>
24-
public int ServerProbeIterations { get; init; } = 4;
25-
26-
/// <summary>
27-
/// The delay in milliseconds between each server probe.
28-
/// Set to 0 to disable delay between iterations.
29-
/// </summary>
30-
public int ServerProbeIntervalMilliseconds { get; init; } = 0;
3120
}

src/NetPace.Core/Clients/Testing/FaultySpeedTester.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,43 +50,43 @@ public Task<IServer[]> GetServersAsync(CancellationToken cancellationToken = def
5050
}
5151

5252
/// <inheritdoc/>
53-
public Task<ServerLatencyResult> GetServerLatencyAsync(IServer server, CancellationToken cancellationToken = default)
53+
public Task<LatencyTestResult> GetServerLatencyAsync(IServer server, CancellationToken cancellationToken = default)
5454
{
5555
AssertNotFaulted(server, nameof(GetServerLatencyAsync));
5656
return inner.GetServerLatencyAsync(server, cancellationToken);
5757
}
5858

5959
/// <inheritdoc/>
60-
public Task<ServerLatencyResult> GetServerLatencyAsync(IServer server, Action<LatencyTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
60+
public Task<LatencyTestResult> GetServerLatencyAsync(IServer server, Action<LatencyTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
6161
{
6262
AssertNotFaulted(server, nameof(GetServerLatencyAsync));
6363
return inner.GetServerLatencyAsync(server, UpdateProgress, cancellationToken);
6464
}
6565

6666
/// <inheritdoc/>
67-
public async Task<ServerLatencyResult> GetServerLatencyAsync(string serverUrl, CancellationToken cancellationToken = default)
67+
public async Task<LatencyTestResult> GetServerLatencyAsync(string serverUrl, CancellationToken cancellationToken = default)
6868
{
6969
var result = await inner.GetServerLatencyAsync(serverUrl, cancellationToken);
7070
AssertNotFaulted(result.Server, nameof(GetServerLatencyAsync));
7171
return result;
7272
}
7373

7474
/// <inheritdoc/>
75-
public async Task<ServerLatencyResult> GetServerLatencyAsync(string serverUrl, Action<LatencyTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
75+
public async Task<LatencyTestResult> GetServerLatencyAsync(string serverUrl, Action<LatencyTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
7676
{
7777
var result = await inner.GetServerLatencyAsync(serverUrl, UpdateProgress, cancellationToken);
7878
AssertNotFaulted(result.Server, nameof(GetServerLatencyAsync));
7979
return result;
8080
}
8181

8282
/// <inheritdoc/>
83-
public Task<ServerLatencyResult> GetFastestServerByLatencyAsync(IServer[] servers, CancellationToken cancellationToken = default)
83+
public Task<LatencyTestResult> GetFastestServerByLatencyAsync(IServer[] servers, CancellationToken cancellationToken = default)
8484
{
8585
return inner.GetFastestServerByLatencyAsync(servers, cancellationToken);
8686
}
8787

8888
/// <inheritdoc/>
89-
public Task<ServerLatencyResult> GetFastestServerByLatencyAsync(IServer[] servers, Action<SpeedTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
89+
public Task<LatencyTestResult> GetFastestServerByLatencyAsync(IServer[] servers, Action<SpeedTestProgress> UpdateProgress, CancellationToken cancellationToken = default)
9090
{
9191
return inner.GetFastestServerByLatencyAsync(servers, UpdateProgress, cancellationToken);
9292
}

0 commit comments

Comments
 (0)