Feature: Speedtest#3440
Merged
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a new Cloudflare-based Speed Test widget to the Dashboard, including the underlying Cloudflare speed test implementation, UI (with live charts), and associated documentation/localization updates.
Changes:
- Introduces a Cloudflare
speed.cloudflare.comspeed test service implementation and result/progress models. - Adds a new Dashboard widget (view + view model) to run and visualize speed tests (download/upload/latency/jitter).
- Updates docs, external services listing, localization resources, and bumps the assembly version; adds a LiveCharts2 dependency.
Reviewed changes
Copilot reviewed 21 out of 23 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| Website/src/pages/download.mdx | Documents Cloudflare Speed Test as an optional third-party service. |
| Website/docs/application/dashboard.md | Adds Dashboard documentation for the new Speed Test widget (behavior + privacy note). |
| Source/NETworkManager/Views/WiFiView.xaml | Minor formatting-only XAML changes. |
| Source/NETworkManager/Views/SpeedTestWidgetView.xaml.cs | Adds code-behind for the new Speed Test widget view. |
| Source/NETworkManager/Views/SpeedTestWidgetView.xaml | Adds the Speed Test widget UI (disclaimer, status, charts, metrics). |
| Source/NETworkManager/Views/PingMonitorHostView.xaml | Minor formatting-only XAML changes. |
| Source/NETworkManager/Views/NetworkConnectionWidgetView.xaml | Adjusts widget layout/borders (likely to accommodate dashboard layout changes). |
| Source/NETworkManager/Views/IPApiIPGeolocationWidgetView.xaml | Minor layout tweak (spacing/column width). |
| Source/NETworkManager/Views/IPApiDNSResolverWidgetView.xaml | Minor layout tweak (spacing). |
| Source/NETworkManager/Views/DashboardView.xaml.cs | Loads the new Speed Test widget into the Dashboard. |
| Source/NETworkManager/Views/DashboardView.xaml | Places the Speed Test widget in the Dashboard layout grid. |
| Source/NETworkManager/ViewModels/SpeedTestWidgetViewModel.cs | Implements widget state, commands, progress handling, and chart series. |
| Source/NETworkManager/NETworkManager.csproj | Adds LiveCharts2 WPF dependency for sparkline charts. |
| Source/NETworkManager.Models/Cloudflare/SpeedTestService.cs | Implements Cloudflare speed test network logic (meta/latency/download/upload). |
| Source/NETworkManager.Models/Cloudflare/SpeedTestResult.cs | Adds a model for final speed test results. |
| Source/NETworkManager.Models/Cloudflare/SpeedTestProgress.cs | Adds progress reporting types (phase + live metrics). |
| Source/NETworkManager.Models/Cloudflare/SpeedTestMetaInfo.cs | Adds model types for Cloudflare /meta response. |
| Source/NETworkManager.Localization/Resources/Strings.resx | Adds localized strings for speed test UI/phases/disclaimer/external service description. |
| Source/NETworkManager.Localization/Resources/Strings.Designer.cs | Updates generated strongly-typed accessors for new localized strings. |
| Source/NETworkManager.Documentation/ExternalServicesManager.cs | Registers speed.cloudflare.com in the external services list. |
| Source/GlobalAssemblyInfo.cs | Bumps assembly version. |
| README.md | Documents Cloudflare Speed Test as an optional third-party service. |
Files not reviewed (1)
- Source/NETworkManager.Localization/Resources/Strings.Designer.cs: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+332
to
+359
| var payload = ArrayPool<byte>.Shared.Rent(bytes); | ||
| try | ||
| { | ||
| using var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/__up"); | ||
| using var content = new ByteArrayContent(payload, 0, bytes); | ||
| content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); | ||
| request.Content = content; | ||
|
|
||
| var sw = Stopwatch.StartNew(); | ||
| using var response = await _client | ||
| .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken) | ||
| .ConfigureAwait(false); | ||
| sw.Stop(); | ||
| var ttfb = sw.Elapsed.TotalMilliseconds; | ||
| response.EnsureSuccessStatusCode(); | ||
| await response.Content.CopyToAsync(Stream.Null, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| var uploadDurationMs = ttfb; | ||
| if (uploadDurationMs <= 0) | ||
| return (0.0, uploadDurationMs); | ||
|
|
||
| var bps = 8.0 * bytes * EstimatedHeaderFraction / (uploadDurationMs / 1000.0); | ||
| return (bps, uploadDurationMs); | ||
| } | ||
| finally | ||
| { | ||
| ArrayPool<byte>.Shared.Return(payload); | ||
| } |
|
|
||
| public SpeedTestService() | ||
| { | ||
| _client = new HttpClient { Timeout = TimeSpan.FromSeconds(60) }; |
Comment on lines
+270
to
+277
| if (DownloadSamples.Count == 0) | ||
| DownloadSamples.Add(p.NewDownloadSampleMbps.Value); | ||
| DownloadSamples.Add(p.NewDownloadSampleMbps.Value); | ||
| } | ||
| if (p.NewUploadSampleMbps.HasValue) | ||
| { | ||
| if (UploadSamples.Count == 0) | ||
| UploadSamples.Add(p.NewUploadSampleMbps.Value); |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Comment on lines
+329
to
+362
| private async Task<(double Bps, double DurationMs)> MeasureUploadAsync(int bytes, | ||
| CancellationToken cancellationToken) | ||
| { | ||
| var payload = ArrayPool<byte>.Shared.Rent(bytes); | ||
| Array.Clear(payload, 0, bytes); | ||
| try | ||
| { | ||
| using var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/__up"); | ||
| using var content = new ByteArrayContent(payload, 0, bytes); | ||
| content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); | ||
| request.Content = content; | ||
|
|
||
| var sw = Stopwatch.StartNew(); | ||
| using var response = await _client | ||
| .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken) | ||
| .ConfigureAwait(false); | ||
| sw.Stop(); | ||
| var ttfb = sw.Elapsed.TotalMilliseconds; | ||
| response.EnsureSuccessStatusCode(); | ||
| await response.Content.CopyToAsync(Stream.Null, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| var uploadDurationMs = ttfb; | ||
| if (uploadDurationMs <= 0) | ||
| return (0.0, uploadDurationMs); | ||
|
|
||
| var bps = 8.0 * bytes * EstimatedHeaderFraction / (uploadDurationMs / 1000.0); | ||
| return (bps, uploadDurationMs); | ||
| } | ||
| finally | ||
| { | ||
| ArrayPool<byte>.Shared.Return(payload, clearArray: true); | ||
| } | ||
| } | ||
|
|
Comment on lines
+64
to
+71
| private readonly HttpClient _client; | ||
|
|
||
| public SpeedTestService() | ||
| { | ||
| _client = new HttpClient { Timeout = Timeout.InfiniteTimeSpan }; | ||
| _client.DefaultRequestHeaders.Add("Origin", Origin); | ||
| _client.DefaultRequestHeaders.UserAgent.ParseAdd("NETworkManager"); | ||
| } |
Comment on lines
+244
to
+248
| _cts?.Dispose(); | ||
| _cts = new CancellationTokenSource(); | ||
|
|
||
| using var service = new SpeedTestService(); | ||
| var progress = new Progress<SpeedTestProgress>(p => |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Comment on lines
39
to
43
| <Grid> | ||
| <Grid.RowDefinitions> | ||
| <RowDefinition Height="Auto" /> | ||
| <RowDefinition Height="10" /> | ||
| <RowDefinition Height="Auto" /> | ||
| <RowDefinition Height="*" /> | ||
| </Grid.RowDefinitions> |
Comment on lines
35
to
39
| <Grid> | ||
| <Grid.RowDefinitions> | ||
| <RowDefinition Height="Auto" /> | ||
| <RowDefinition Height="10" /> | ||
| <RowDefinition Height="Auto" /> | ||
| <RowDefinition Height="*" /> | ||
| </Grid.RowDefinitions> |
Comment on lines
38
to
42
| <Grid> | ||
| <Grid.RowDefinitions> | ||
| <RowDefinition Height="Auto" /> | ||
| <RowDefinition Height="10" /> | ||
| <RowDefinition Height="*" /> | ||
| </Grid.RowDefinitions> |
Comment on lines
+228
to
+233
| <TextBox Grid.Column="2" Grid.Row="4" | ||
| Text="{Binding CurrentLatencyMs, StringFormat={}{0:F0} ms, TargetNullValue=-/-, FallbackValue=-/-, Mode=OneWay}" /> | ||
| <TextBlock Grid.Column="0" Grid.Row="6" | ||
| Text="{x:Static localization:Strings.Jitter}" /> | ||
| <TextBox Grid.Column="2" Grid.Row="6" | ||
| Text="{Binding CurrentJitterMs, StringFormat={}{0:F1} ms, TargetNullValue=-/-, FallbackValue=-/-, Mode=OneWay}" /> |
Comment on lines
+242
to
+245
| </Rectangle> | ||
| <TextBlock Text="{Binding CurrentDownloadMbps, StringFormat={}{0:F1} Mbps, TargetNullValue=-/-, FallbackValue=-/-}" | ||
| Style="{StaticResource MessageTextBlock}" | ||
| Margin="10,0,0,0" /> |
Comment on lines
+261
to
+264
| </Rectangle> | ||
| <TextBlock Text="{Binding CurrentUploadMbps, StringFormat={}{0:F1} Mbps, TargetNullValue=-/-, FallbackValue=-/-}" | ||
| Style="{StaticResource MessageTextBlock}" | ||
| Margin="10,0,0,0" /> |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…Root/NETworkManager into feature/2592-speed-test
Comment on lines
+221
to
+223
| <Binding Path="Result.ServerCity" /> | ||
| <Binding Path="Result.ServerCountry" /> | ||
| <Binding Path="Result.ServerIata" /> |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…Root/NETworkManager into feature/2592-speed-test
| <data name="Jitter" xml:space="preserve"> | ||
| <value>Jitter</value> | ||
| </data> | ||
| <data name="ExternalService_Cloudflare_SpeedTest_Description" xml:space="preserve"> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes proposed in this pull request
Related issue(s)
Copilot generated summary
Provide a Copilot generated summary of the changes in this pull request.
Copilot summary
This pull request introduces a new integration with Cloudflare's speed test service, allowing users to measure their download/upload speeds, latency, and jitter directly from the application. It adds the necessary data models, progress reporting, and localized strings to support this feature. Additionally, it updates external service documentation, bumps the assembly version, and enhances localization resources for the new speed test functionality.
Cloudflare Speed Test Integration
SpeedTestMetaInfo,SpeedTestMetaColo,SpeedTestProgress, andSpeedTestResultto represent Cloudflare speed test metadata, progress, and results. [1] [2] [3]speed.cloudflare.com) as an optional third-party service in both the documentation and theExternalServicesManager. [1] [2]Localization and UI Support
Project and Dependency Updates
2026.5.24.0to reflect the new feature release.LiveChartsCore.SkiaSharpView.WPFas a new dependency to support advanced charting for the speed test UI.To-Do