mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
CmdPal: Aggregate net adapter stats and default adapter selection (#49678)
## Summary of the Pull Request This PR introduces a new virtual network adapter in performance monitor that sums up data from all physical adapters. That way users will see any network usage, and for most of them it might be good enough. For the rest, this PR adds a new command to set any network adapter as a default. - Switches from Performance Counters to raw GetIfTable2. - Adds a new virtual network adapter "All" and uses it as a default. - Adds a new command to set an actual default network adapter for the perfmon. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CoreWidgetProvider.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class NetworkStatsTests
|
||||
{
|
||||
private static readonly Guid EthernetGuid = new("11111111-1111-1111-1111-111111111111");
|
||||
private static readonly Guid WifiGuid = new("22222222-2222-2222-2222-222222222222");
|
||||
|
||||
[TestMethod]
|
||||
public void ApplySnapshots_AddsAggregateBeforeIndividualAdapters()
|
||||
{
|
||||
var stats = CreateNetworkStats();
|
||||
stats.ApplySnapshots(
|
||||
[
|
||||
new(1, EthernetGuid, "Ethernet", 10_000, 2_000, 1_000_000),
|
||||
new(2, WifiGuid, "Wi-Fi", 20_000, 4_000, 2_000_000),
|
||||
],
|
||||
0);
|
||||
|
||||
stats.ApplySnapshots(
|
||||
[
|
||||
new(1, EthernetGuid, "Ethernet", 60_000, 27_000, 1_000_000),
|
||||
new(2, WifiGuid, "Wi-Fi", 120_000, 29_000, 2_000_000),
|
||||
],
|
||||
1);
|
||||
|
||||
Assert.AreEqual("All physical network adapters", stats.GetNetworkName(0));
|
||||
Assert.AreEqual("Ethernet", stats.GetNetworkName(1));
|
||||
Assert.AreEqual("Wi-Fi", stats.GetNetworkName(2));
|
||||
|
||||
var aggregate = stats.GetNetworkUsage(0);
|
||||
Assert.AreEqual(50_000f, aggregate.Sent);
|
||||
Assert.AreEqual(150_000f, aggregate.Received);
|
||||
Assert.AreEqual(8f * 200_000 / 3_000_000, aggregate.Usage, 0.0001f);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ApplySnapshots_TreatsCounterResetAsZeroDelta()
|
||||
{
|
||||
var stats = CreateNetworkStats();
|
||||
stats.ApplySnapshots([new(1, EthernetGuid, "Ethernet", 10_000, 5_000, 1_000_000)], 0);
|
||||
|
||||
stats.ApplySnapshots([new(1, EthernetGuid, "Ethernet", 1_000, 5_500, 1_000_000)], 2);
|
||||
|
||||
var ethernet = stats.GetNetworkUsage(1);
|
||||
Assert.AreEqual(0f, ethernet.Received);
|
||||
Assert.AreEqual(250f, ethernet.Sent);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ApplySnapshots_ReappearingAdapterStartsWithZeroRate()
|
||||
{
|
||||
var stats = CreateNetworkStats();
|
||||
stats.ApplySnapshots([new(1, EthernetGuid, "USB Ethernet", 10_000, 5_000, 1_000_000)], 0);
|
||||
stats.ApplySnapshots([], 1);
|
||||
|
||||
stats.ApplySnapshots([new(1, EthernetGuid, "USB Ethernet", 100_000, 50_000, 1_000_000)], 1);
|
||||
|
||||
var ethernet = stats.GetNetworkUsage(1);
|
||||
Assert.AreEqual(0f, ethernet.Received);
|
||||
Assert.AreEqual(0f, ethernet.Sent);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void AdapterIds_DistinguishAggregateFromMissingAndResolveLateAdapter()
|
||||
{
|
||||
var stats = CreateNetworkStats();
|
||||
var wifiAdapterId = "network-interface:" + WifiGuid.ToString("D");
|
||||
|
||||
Assert.AreEqual(-1, stats.GetNetworkIndex(wifiAdapterId));
|
||||
|
||||
stats.ApplySnapshots(
|
||||
[
|
||||
new(1, EthernetGuid, "Ethernet", 10_000, 2_000, 1_000_000),
|
||||
new(2, WifiGuid, "Wi-Fi", 20_000, 4_000, 2_000_000),
|
||||
],
|
||||
0);
|
||||
|
||||
Assert.AreEqual(NetworkStats.AllPhysicalAdaptersId, stats.GetNetworkId(0));
|
||||
Assert.AreEqual(0, stats.GetNetworkIndex(NetworkStats.AllPhysicalAdaptersId));
|
||||
Assert.AreEqual(1, stats.GetNetworkIndex(stats.GetNetworkId(1)));
|
||||
Assert.AreEqual(2, stats.GetNetworkIndex(wifiAdapterId.ToUpperInvariant()));
|
||||
Assert.AreEqual(-1, stats.GetNetworkIndex("network-interface:missing"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ApplySnapshots_UnknownLinkSpeedStillContributesBytesAndCapsAggregateUsage()
|
||||
{
|
||||
var stats = CreateNetworkStats();
|
||||
stats.ApplySnapshots(
|
||||
[
|
||||
new(1, EthernetGuid, "Ethernet", 0, 0, 1_000_000),
|
||||
new(2, WifiGuid, "Unknown speed", 0, 0, 0),
|
||||
],
|
||||
0);
|
||||
|
||||
stats.ApplySnapshots(
|
||||
[
|
||||
new(1, EthernetGuid, "Ethernet", 100_000, 0, 1_000_000),
|
||||
new(2, WifiGuid, "Unknown speed", 50_000, 0, 0),
|
||||
],
|
||||
1);
|
||||
|
||||
var aggregate = stats.GetNetworkUsage(0);
|
||||
Assert.AreEqual(150_000f, aggregate.Received);
|
||||
Assert.AreEqual(1f, aggregate.Usage);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetKnownLinkSpeed_IgnoresUnknownSpeedSentinel()
|
||||
{
|
||||
Assert.AreEqual(1_000_000UL, PhysicalNetworkInterfaceSnapshotProvider.GetKnownLinkSpeed(ulong.MaxValue, 1_000_000));
|
||||
Assert.AreEqual(2_000_000UL, PhysicalNetworkInterfaceSnapshotProvider.GetKnownLinkSpeed(2_000_000, ulong.MaxValue));
|
||||
Assert.AreEqual(0UL, PhysicalNetworkInterfaceSnapshotProvider.GetKnownLinkSpeed(ulong.MaxValue, ulong.MaxValue));
|
||||
}
|
||||
|
||||
private static NetworkStats CreateNetworkStats()
|
||||
{
|
||||
return new NetworkStats(new UnusedSnapshotProvider(), "All physical network adapters");
|
||||
}
|
||||
|
||||
private sealed class UnusedSnapshotProvider : IPhysicalNetworkInterfaceSnapshotProvider
|
||||
{
|
||||
public IReadOnlyList<PhysicalNetworkInterfaceSnapshot> GetSnapshots()
|
||||
{
|
||||
throw new InvalidOperationException("This provider is not used by these tests.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using CoreWidgetProvider.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class SettingsManagerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void DefaultNetworkAdapterId_RoundTripsThroughSettingsFile()
|
||||
{
|
||||
var directory = Path.Combine(Path.GetTempPath(), $"PerformanceMonitorSettingsTests-{Guid.NewGuid():N}");
|
||||
var filePath = Path.Combine(directory, "performanceMonitor.settings.json");
|
||||
var adapterId = "network-interface:11111111-1111-1111-1111-111111111111";
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
var settings = new SettingsManager(filePath);
|
||||
Assert.AreEqual(NetworkStats.AllPhysicalAdaptersId, settings.DefaultNetworkAdapterId);
|
||||
|
||||
settings.SetDefaultNetworkAdapterId(adapterId);
|
||||
|
||||
var reloadedSettings = new SettingsManager(filePath);
|
||||
Assert.AreEqual(adapterId, reloadedSettings.DefaultNetworkAdapterId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(directory))
|
||||
{
|
||||
Directory.Delete(directory, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,179 +5,191 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Common;
|
||||
|
||||
namespace CoreWidgetProvider.Helpers;
|
||||
|
||||
internal sealed partial class NetworkStats : PerformanceCounterSourceBase, IDisposable
|
||||
internal sealed partial class NetworkStats
|
||||
{
|
||||
private readonly Dictionary<string, List<PerformanceCounter>> _networkCounters = new();
|
||||
private bool _networkCounterReadFailureLogged;
|
||||
|
||||
private Dictionary<string, Data> NetworkUsages { get; set; } = new();
|
||||
|
||||
private Dictionary<string, List<float>> NetChartValues { get; set; } = new();
|
||||
internal const string AllPhysicalAdaptersId = "all-physical-network-adapters";
|
||||
private const string PhysicalAdapterIdPrefix = "network-interface:";
|
||||
private readonly IPhysicalNetworkInterfaceSnapshotProvider _snapshotProvider;
|
||||
private readonly string _allAdaptersName;
|
||||
private readonly Dictionary<ulong, CounterSample> _previousSamples = new();
|
||||
private readonly Dictionary<ulong, List<float>> _chartValues = new();
|
||||
private readonly List<float> _allAdaptersChartValues = new();
|
||||
private readonly List<ulong> _missingInterfaceIds = new();
|
||||
private NetworkAdapterData[] _networkAdapters = [];
|
||||
private long? _lastTimestamp;
|
||||
private bool _snapshotReadFailureLogged;
|
||||
|
||||
public sealed class Data
|
||||
{
|
||||
public float Usage
|
||||
{
|
||||
get; set;
|
||||
get; init;
|
||||
}
|
||||
|
||||
public float Sent
|
||||
{
|
||||
get; set;
|
||||
get; init;
|
||||
}
|
||||
|
||||
public float Received
|
||||
{
|
||||
get; set;
|
||||
get; init;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record NetworkAdapterData(string Id, string Name, Data Usage, List<float> ChartValues);
|
||||
|
||||
private readonly record struct CounterSample(ulong ReceivedBytes, ulong SentBytes);
|
||||
|
||||
public NetworkStats()
|
||||
: this(new PhysicalNetworkInterfaceSnapshotProvider(), Resources.GetResource("All_Physical_Network_Adapters"))
|
||||
{
|
||||
InitNetworkPerfCounters();
|
||||
GetData();
|
||||
}
|
||||
|
||||
private void InitNetworkPerfCounters()
|
||||
internal NetworkStats(IPhysicalNetworkInterfaceSnapshotProvider snapshotProvider, string allAdaptersName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var perfCounterCategory = CreatePerformanceCounterCategory("Network Interface");
|
||||
if (perfCounterCategory is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var instanceNames = perfCounterCategory.GetInstanceNames();
|
||||
foreach (var instanceName in instanceNames)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytesSent = CreatePerformanceCounter("Network Interface", "Bytes Sent/sec", instanceName, logFailure: false);
|
||||
var bytesReceived = CreatePerformanceCounter("Network Interface", "Bytes Received/sec", instanceName, logFailure: false);
|
||||
var currentBandwidth = CreatePerformanceCounter("Network Interface", "Current Bandwidth", instanceName, logFailure: false);
|
||||
if (bytesSent is null || bytesReceived is null || currentBandwidth is null)
|
||||
{
|
||||
bytesSent?.Dispose();
|
||||
bytesReceived?.Dispose();
|
||||
currentBandwidth?.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
var instanceCounters = new List<PerformanceCounter> { bytesSent, bytesReceived, currentBandwidth };
|
||||
_networkCounters.Add(instanceName, instanceCounters);
|
||||
NetChartValues.Add(instanceName, new List<float>());
|
||||
NetworkUsages.Add(instanceName, new Data());
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Skip interfaces whose counters cannot be initialized.
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CoreLogger.LogError("Failed to initialize network performance counters.", ex);
|
||||
}
|
||||
_snapshotProvider = snapshotProvider;
|
||||
_allAdaptersName = allAdaptersName;
|
||||
_networkAdapters = [new(AllPhysicalAdaptersId, _allAdaptersName, new Data(), _allAdaptersChartValues)];
|
||||
}
|
||||
|
||||
public void GetData()
|
||||
{
|
||||
float maxUsage = 0;
|
||||
foreach (var networkCounterWithName in _networkCounters)
|
||||
try
|
||||
{
|
||||
try
|
||||
var snapshots = _snapshotProvider.GetSnapshots();
|
||||
var timestamp = Stopwatch.GetTimestamp();
|
||||
var elapsedSeconds = _lastTimestamp is long previousTimestamp
|
||||
? Stopwatch.GetElapsedTime(previousTimestamp, timestamp).TotalSeconds
|
||||
: 0;
|
||||
|
||||
ApplySnapshots(snapshots, elapsedSeconds);
|
||||
_lastTimestamp = timestamp;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!_snapshotReadFailureLogged)
|
||||
{
|
||||
var sent = networkCounterWithName.Value[0].NextValue();
|
||||
var received = networkCounterWithName.Value[1].NextValue();
|
||||
var bandWidth = networkCounterWithName.Value[2].NextValue();
|
||||
if (bandWidth == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var usage = 8 * (sent + received) / bandWidth;
|
||||
var name = networkCounterWithName.Key;
|
||||
NetworkUsages[name].Sent = sent;
|
||||
NetworkUsages[name].Received = received;
|
||||
NetworkUsages[name].Usage = usage;
|
||||
|
||||
var chartValues = NetChartValues[name];
|
||||
lock (chartValues)
|
||||
{
|
||||
ChartHelper.AddNextChartValue(usage * 100, chartValues);
|
||||
}
|
||||
|
||||
if (usage > maxUsage)
|
||||
{
|
||||
maxUsage = usage;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogFailureOnce(ref _networkCounterReadFailureLogged, "Failed while reading network performance counters.", ex);
|
||||
_snapshotReadFailureLogged = true;
|
||||
CoreLogger.LogError("Failed while reading physical network interface statistics.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void ApplySnapshots(IReadOnlyList<PhysicalNetworkInterfaceSnapshot> snapshots, double elapsedSeconds)
|
||||
{
|
||||
var currentInterfaceIds = new HashSet<ulong>(snapshots.Count);
|
||||
var adapterMeasurements = new List<(PhysicalNetworkInterfaceSnapshot Snapshot, Data Usage)>(snapshots.Count);
|
||||
double totalSent = 0;
|
||||
double totalReceived = 0;
|
||||
double totalBandwidth = 0;
|
||||
|
||||
foreach (var snapshot in snapshots)
|
||||
{
|
||||
currentInterfaceIds.Add(snapshot.InterfaceLuid);
|
||||
|
||||
var sent = 0d;
|
||||
var received = 0d;
|
||||
if (elapsedSeconds > 0 && _previousSamples.TryGetValue(snapshot.InterfaceLuid, out var previousSample))
|
||||
{
|
||||
sent = GetBytesPerSecond(previousSample.SentBytes, snapshot.SentBytes, elapsedSeconds);
|
||||
received = GetBytesPerSecond(previousSample.ReceivedBytes, snapshot.ReceivedBytes, elapsedSeconds);
|
||||
}
|
||||
|
||||
_previousSamples[snapshot.InterfaceLuid] = new(snapshot.ReceivedBytes, snapshot.SentBytes);
|
||||
|
||||
var usage = CreateUsage(sent, received, snapshot.LinkSpeed);
|
||||
adapterMeasurements.Add((snapshot, usage));
|
||||
totalSent += sent;
|
||||
totalReceived += received;
|
||||
totalBandwidth += snapshot.LinkSpeed;
|
||||
}
|
||||
|
||||
RemoveMissingInterfaces(currentInterfaceIds);
|
||||
|
||||
var aggregateUsage = CreateUsage(totalSent, totalReceived, totalBandwidth);
|
||||
AddChartValue(_allAdaptersChartValues, aggregateUsage.Usage);
|
||||
|
||||
var adapters = new NetworkAdapterData[adapterMeasurements.Count + 1];
|
||||
adapters[0] = new(AllPhysicalAdaptersId, _allAdaptersName, aggregateUsage, _allAdaptersChartValues);
|
||||
|
||||
for (var index = 0; index < adapterMeasurements.Count; index++)
|
||||
{
|
||||
var (snapshot, usage) = adapterMeasurements[index];
|
||||
if (!_chartValues.TryGetValue(snapshot.InterfaceLuid, out var chartValues))
|
||||
{
|
||||
chartValues = new List<float>();
|
||||
_chartValues.Add(snapshot.InterfaceLuid, chartValues);
|
||||
}
|
||||
|
||||
AddChartValue(chartValues, usage.Usage);
|
||||
adapters[index + 1] = new(GetPhysicalAdapterId(snapshot), snapshot.Name, usage, chartValues);
|
||||
}
|
||||
|
||||
Volatile.Write(ref _networkAdapters, adapters);
|
||||
}
|
||||
|
||||
public string CreateNetImageUrl(int netChartIndex)
|
||||
{
|
||||
return ChartHelper.CreateImageUrl(NetChartValues.ElementAt(netChartIndex).Value, ChartHelper.ChartType.Net);
|
||||
var adapters = Volatile.Read(ref _networkAdapters);
|
||||
var resolvedIndex = ResolveIndex(netChartIndex, adapters.Length);
|
||||
return ChartHelper.CreateImageUrl(adapters[resolvedIndex].ChartValues, ChartHelper.ChartType.Net);
|
||||
}
|
||||
|
||||
public string GetNetworkName(int networkIndex)
|
||||
{
|
||||
if (NetChartValues.Count <= networkIndex)
|
||||
var adapters = Volatile.Read(ref _networkAdapters);
|
||||
return adapters[ResolveIndex(networkIndex, adapters.Length)].Name;
|
||||
}
|
||||
|
||||
public string GetNetworkId(int networkIndex)
|
||||
{
|
||||
var adapters = Volatile.Read(ref _networkAdapters);
|
||||
return adapters[ResolveIndex(networkIndex, adapters.Length)].Id;
|
||||
}
|
||||
|
||||
public int GetNetworkIndex(string adapterId)
|
||||
{
|
||||
var adapters = Volatile.Read(ref _networkAdapters);
|
||||
for (var index = 0; index < adapters.Length; index++)
|
||||
{
|
||||
return string.Empty;
|
||||
if (string.Equals(adapters[index].Id, adapterId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return NetChartValues.ElementAt(networkIndex).Key;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public Data GetNetworkUsage(int networkIndex)
|
||||
{
|
||||
if (NetChartValues.Count <= networkIndex)
|
||||
{
|
||||
return new Data();
|
||||
}
|
||||
|
||||
var currNetworkName = NetChartValues.ElementAt(networkIndex).Key;
|
||||
if (!NetworkUsages.TryGetValue(currNetworkName, out var value))
|
||||
{
|
||||
return new Data();
|
||||
}
|
||||
|
||||
return value;
|
||||
var adapters = Volatile.Read(ref _networkAdapters);
|
||||
return adapters[ResolveIndex(networkIndex, adapters.Length)].Usage;
|
||||
}
|
||||
|
||||
public int GetPrevNetworkIndex(int networkIndex)
|
||||
{
|
||||
if (NetChartValues.Count == 0)
|
||||
var adapterCount = Volatile.Read(ref _networkAdapters).Length;
|
||||
if (adapterCount == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (networkIndex == 0)
|
||||
{
|
||||
return NetChartValues.Count - 1;
|
||||
}
|
||||
|
||||
return networkIndex - 1;
|
||||
return networkIndex <= 0 || networkIndex >= adapterCount ? adapterCount - 1 : networkIndex - 1;
|
||||
}
|
||||
|
||||
public int GetNextNetworkIndex(int networkIndex)
|
||||
{
|
||||
if (NetChartValues.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (networkIndex == NetChartValues.Count - 1)
|
||||
var adapterCount = Volatile.Read(ref _networkAdapters).Length;
|
||||
if (adapterCount == 0 || networkIndex < 0 || networkIndex >= adapterCount - 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -185,14 +197,57 @@ internal sealed partial class NetworkStats : PerformanceCounterSourceBase, IDisp
|
||||
return networkIndex + 1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
private static double GetBytesPerSecond(ulong previousValue, ulong currentValue, double elapsedSeconds)
|
||||
{
|
||||
foreach (var counterPair in _networkCounters)
|
||||
return currentValue >= previousValue ? (currentValue - previousValue) / elapsedSeconds : 0;
|
||||
}
|
||||
|
||||
private static int ResolveIndex(int requestedIndex, int adapterCount)
|
||||
{
|
||||
return (uint)requestedIndex < (uint)adapterCount ? requestedIndex : 0;
|
||||
}
|
||||
|
||||
private static string GetPhysicalAdapterId(PhysicalNetworkInterfaceSnapshot snapshot)
|
||||
{
|
||||
return snapshot.InterfaceGuid != Guid.Empty
|
||||
? PhysicalAdapterIdPrefix + snapshot.InterfaceGuid.ToString("D")
|
||||
: PhysicalAdapterIdPrefix + snapshot.InterfaceLuid.ToString("X16", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static Data CreateUsage(double sent, double received, double bandwidth)
|
||||
{
|
||||
var usage = bandwidth > 0 ? Math.Min(8 * (sent + received) / bandwidth, 1) : 0;
|
||||
return new Data
|
||||
{
|
||||
foreach (var counter in counterPair.Value)
|
||||
Sent = (float)sent,
|
||||
Received = (float)received,
|
||||
Usage = (float)usage,
|
||||
};
|
||||
}
|
||||
|
||||
private static void AddChartValue(List<float> chartValues, float usage)
|
||||
{
|
||||
lock (chartValues)
|
||||
{
|
||||
ChartHelper.AddNextChartValue(usage * 100, chartValues);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveMissingInterfaces(HashSet<ulong> currentInterfaceIds)
|
||||
{
|
||||
_missingInterfaceIds.Clear();
|
||||
foreach (var interfaceId in _previousSamples.Keys)
|
||||
{
|
||||
if (!currentInterfaceIds.Contains(interfaceId))
|
||||
{
|
||||
counter.Dispose();
|
||||
_missingInterfaceIds.Add(interfaceId);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var interfaceId in _missingInterfaceIds)
|
||||
{
|
||||
_previousSamples.Remove(interfaceId);
|
||||
_chartValues.Remove(interfaceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.NetworkManagement.IpHelper;
|
||||
|
||||
namespace CoreWidgetProvider.Helpers;
|
||||
|
||||
internal sealed class PhysicalNetworkInterfaceSnapshotProvider : IPhysicalNetworkInterfaceSnapshotProvider
|
||||
{
|
||||
public unsafe IReadOnlyList<PhysicalNetworkInterfaceSnapshot> GetSnapshots()
|
||||
{
|
||||
var result = PInvoke.GetIfTable2(out var table);
|
||||
if (result != WIN32_ERROR.NO_ERROR)
|
||||
{
|
||||
throw new Win32Exception(unchecked((int)result));
|
||||
}
|
||||
|
||||
if (table is null)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var snapshots = new List<PhysicalNetworkInterfaceSnapshot>(checked((int)table->NumEntries));
|
||||
foreach (ref readonly var row in table->Table.AsSpan(checked((int)table->NumEntries)))
|
||||
{
|
||||
var flags = row.InterfaceAndOperStatusFlags;
|
||||
if (!flags.HardwareInterface || flags.FilterInterface || flags.EndPointInterface)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = row.Description.ToString();
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = row.Alias.ToString();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
name = row.InterfaceGuid.ToString();
|
||||
}
|
||||
|
||||
snapshots.Add(new(
|
||||
row.InterfaceLuid.Value,
|
||||
row.InterfaceGuid,
|
||||
name,
|
||||
row.InOctets,
|
||||
row.OutOctets,
|
||||
GetKnownLinkSpeed(row.ReceiveLinkSpeed, row.TransmitLinkSpeed)));
|
||||
}
|
||||
|
||||
snapshots.Sort(static (left, right) =>
|
||||
{
|
||||
var nameComparison = StringComparer.OrdinalIgnoreCase.Compare(left.Name, right.Name);
|
||||
return nameComparison != 0 ? nameComparison : left.InterfaceLuid.CompareTo(right.InterfaceLuid);
|
||||
});
|
||||
|
||||
return snapshots;
|
||||
}
|
||||
finally
|
||||
{
|
||||
PInvoke.FreeMibTable(table);
|
||||
}
|
||||
}
|
||||
|
||||
internal static ulong GetKnownLinkSpeed(ulong receiveLinkSpeed, ulong transmitLinkSpeed)
|
||||
{
|
||||
var knownReceiveLinkSpeed = receiveLinkSpeed == ulong.MaxValue ? 0 : receiveLinkSpeed;
|
||||
var knownTransmitLinkSpeed = transmitLinkSpeed == ulong.MaxValue ? 0 : transmitLinkSpeed;
|
||||
return Math.Max(knownReceiveLinkSpeed, knownTransmitLinkSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly record struct PhysicalNetworkInterfaceSnapshot(
|
||||
ulong InterfaceLuid,
|
||||
Guid InterfaceGuid,
|
||||
string Name,
|
||||
ulong ReceivedBytes,
|
||||
ulong SentBytes,
|
||||
ulong LinkSpeed);
|
||||
|
||||
internal interface IPhysicalNetworkInterfaceSnapshotProvider
|
||||
{
|
||||
IReadOnlyList<PhysicalNetworkInterfaceSnapshot> GetSnapshots();
|
||||
}
|
||||
@@ -70,4 +70,6 @@ internal static class Icons
|
||||
internal static IconInfo NavigateBackwardIcon => new("\uE72B"); // Previous icon
|
||||
|
||||
internal static IconInfo NavigateForwardIcon => new("\uE72A"); // Next icon
|
||||
|
||||
internal static IconInfo SetDefaultIcon => new("\uE73E"); // Accept icon
|
||||
}
|
||||
|
||||
@@ -4,3 +4,5 @@ CreateDXGIFactory1
|
||||
IDXGIFactory1
|
||||
IDXGIAdapter1
|
||||
DXGI_ADAPTER_DESC1
|
||||
GetIfTable2
|
||||
FreeMibTable
|
||||
|
||||
@@ -971,6 +971,7 @@ internal sealed partial class SystemNetworkUsageWidgetPage : WidgetPage, IDispos
|
||||
private readonly DataManager _dataManager;
|
||||
private readonly SettingsManager _settingsManager;
|
||||
private int _networkIndex;
|
||||
private bool _defaultNetworkInitialized;
|
||||
|
||||
public SystemNetworkUsageWidgetPage(SettingsManager settingsManager)
|
||||
{
|
||||
@@ -979,6 +980,7 @@ internal sealed partial class SystemNetworkUsageWidgetPage : WidgetPage, IDispos
|
||||
Commands = [
|
||||
new CommandContextItem(new PrevNetworkCommand(this) { Name = Resources.GetResource("Previous_Network_Title") }),
|
||||
new CommandContextItem(new NextNetworkCommand(this) { Name = Resources.GetResource("Next_Network_Title") }),
|
||||
new CommandContextItem(new SetDefaultNetworkCommand(this) { Name = Resources.GetResource("Set_Default_Network_Title") }),
|
||||
new CommandContextItem(OpenTaskManagerCommand.Instance),
|
||||
];
|
||||
}
|
||||
@@ -994,6 +996,16 @@ internal sealed partial class SystemNetworkUsageWidgetPage : WidgetPage, IDispos
|
||||
|
||||
var currentData = _dataManager.GetNetworkStats();
|
||||
|
||||
if (!_defaultNetworkInitialized)
|
||||
{
|
||||
var defaultNetworkIndex = currentData.GetNetworkIndex(_settingsManager.DefaultNetworkAdapterId);
|
||||
if (defaultNetworkIndex >= 0)
|
||||
{
|
||||
_networkIndex = defaultNetworkIndex;
|
||||
_defaultNetworkInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
var dataDuration = timer.ElapsedMilliseconds;
|
||||
|
||||
var netName = currentData.GetNetworkName(_networkIndex);
|
||||
@@ -1096,16 +1108,32 @@ internal sealed partial class SystemNetworkUsageWidgetPage : WidgetPage, IDispos
|
||||
|
||||
private void HandlePrevNetwork()
|
||||
{
|
||||
_defaultNetworkInitialized = true;
|
||||
_networkIndex = _dataManager.GetNetworkStats().GetPrevNetworkIndex(_networkIndex);
|
||||
UpdateWidget();
|
||||
}
|
||||
|
||||
private void HandleNextNetwork()
|
||||
{
|
||||
_defaultNetworkInitialized = true;
|
||||
_networkIndex = _dataManager.GetNetworkStats().GetNextNetworkIndex(_networkIndex);
|
||||
UpdateWidget();
|
||||
}
|
||||
|
||||
private ICommandResult HandleSetDefaultNetwork()
|
||||
{
|
||||
_defaultNetworkInitialized = true;
|
||||
var networkStats = _dataManager.GetNetworkStats();
|
||||
var networkName = networkStats.GetNetworkName(_networkIndex);
|
||||
_settingsManager.SetDefaultNetworkAdapterId(networkStats.GetNetworkId(_networkIndex));
|
||||
|
||||
return CommandResult.ShowToast(new ToastArgs
|
||||
{
|
||||
Message = string.Format(CultureInfo.CurrentCulture, Resources.GetResource("Set_Default_Network_Success"), networkName),
|
||||
Result = CommandResult.KeepOpen(),
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_dataManager.Dispose();
|
||||
@@ -1150,6 +1178,25 @@ internal sealed partial class SystemNetworkUsageWidgetPage : WidgetPage, IDispos
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed partial class SetDefaultNetworkCommand : InvokableCommand
|
||||
{
|
||||
private readonly SystemNetworkUsageWidgetPage _page;
|
||||
|
||||
public SetDefaultNetworkCommand(SystemNetworkUsageWidgetPage page)
|
||||
{
|
||||
_page = page;
|
||||
}
|
||||
|
||||
public override string Id => "com.microsoft.cmdpal.network_widget.setDefault";
|
||||
|
||||
public override IconInfo Icon => Icons.SetDefaultIcon;
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
return _page.HandleSetDefaultNetwork();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class SystemGPUUsageWidgetPage : WidgetPage, IDisposable
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading;
|
||||
using CoreWidgetProvider.Helpers;
|
||||
using Microsoft.CmdPal.Common;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.PerformanceMonitor;
|
||||
@@ -12,6 +16,9 @@ namespace Microsoft.CmdPal.Ext.PerformanceMonitor;
|
||||
internal sealed class SettingsManager : JsonSettingsManager
|
||||
{
|
||||
private const string Namespace = "performanceMonitor";
|
||||
private static readonly JsonSerializerOptions _serializerOptions = new() { WriteIndented = true };
|
||||
private readonly Lock _fileLock = new();
|
||||
private string _defaultNetworkAdapterId = NetworkStats.AllPhysicalAdaptersId;
|
||||
|
||||
private static string Namespaced(string propertyName) => $"{Namespace}.{propertyName}";
|
||||
|
||||
@@ -30,6 +37,8 @@ internal sealed class SettingsManager : JsonSettingsManager
|
||||
? unit
|
||||
: SpeedUnit.BitsPerSecond;
|
||||
|
||||
public string DefaultNetworkAdapterId => _defaultNetworkAdapterId;
|
||||
|
||||
private readonly ChoiceSetSetting _diskSpeedUnit = new(
|
||||
Namespaced(nameof(DiskSpeedUnit)),
|
||||
Resources.GetResource("Disk_Speed_Unit_Setting_Title"),
|
||||
@@ -53,14 +62,82 @@ internal sealed class SettingsManager : JsonSettingsManager
|
||||
}
|
||||
|
||||
public SettingsManager()
|
||||
: this(SettingsJsonPath())
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
}
|
||||
|
||||
internal SettingsManager(string filePath)
|
||||
{
|
||||
FilePath = filePath;
|
||||
|
||||
Settings.Add(_networkSpeedUnit);
|
||||
Settings.Add(_diskSpeedUnit);
|
||||
|
||||
LoadSettings();
|
||||
LoadDefaultNetworkAdapterId();
|
||||
|
||||
Settings.SettingsChanged += (_, _) => SaveSettings();
|
||||
}
|
||||
|
||||
public void SetDefaultNetworkAdapterId(string adapterId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(adapterId) || string.Equals(_defaultNetworkAdapterId, adapterId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_defaultNetworkAdapterId = adapterId;
|
||||
SaveSettings();
|
||||
}
|
||||
|
||||
public override void SaveSettings()
|
||||
{
|
||||
lock (_fileLock)
|
||||
{
|
||||
base.SaveSettings();
|
||||
SaveDefaultNetworkAdapterId();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDefaultNetworkAdapterId()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(FilePath) && JsonNode.Parse(File.ReadAllText(FilePath)) is JsonObject savedSettings)
|
||||
{
|
||||
var value = savedSettings[Namespaced(nameof(DefaultNetworkAdapterId))]?.GetValue<string>();
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
_defaultNetworkAdapterId = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CoreLogger.LogError("Failed to load the default network adapter setting.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveDefaultNetworkAdapterId()
|
||||
{
|
||||
try
|
||||
{
|
||||
var savedSettings = File.Exists(FilePath)
|
||||
? JsonNode.Parse(File.ReadAllText(FilePath)) as JsonObject
|
||||
: new JsonObject();
|
||||
|
||||
if (savedSettings is null)
|
||||
{
|
||||
CoreLogger.LogError("Failed to parse the performance monitor settings file as a JSON object.");
|
||||
return;
|
||||
}
|
||||
|
||||
savedSettings[Namespaced(nameof(DefaultNetworkAdapterId))] = _defaultNetworkAdapterId;
|
||||
File.WriteAllText(FilePath, savedSettings.ToJsonString(_serializerOptions));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CoreLogger.LogError("Failed to save the default network adapter setting.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,16 @@
|
||||
<data name="Next_Network_Title" xml:space="preserve">
|
||||
<value>Next network</value>
|
||||
</data>
|
||||
<data name="Set_Default_Network_Title" xml:space="preserve">
|
||||
<value>Set as default</value>
|
||||
</data>
|
||||
<data name="Set_Default_Network_Success" xml:space="preserve">
|
||||
<value>Default network set to {0}</value>
|
||||
<comment>{0} is the network adapter name.</comment>
|
||||
</data>
|
||||
<data name="All_Physical_Network_Adapters" xml:space="preserve">
|
||||
<value>All physical network adapters</value>
|
||||
</data>
|
||||
<data name="NetworkUsage_Widget_Template.Ethernet_Heading" xml:space="preserve">
|
||||
<value>Ethernet</value>
|
||||
</data>
|
||||
@@ -463,4 +473,4 @@
|
||||
<data name="Disk_Speed_Unit_BinaryBytesPerSec" xml:space="preserve">
|
||||
<value>Binary bytes per second (KiB/s, MiB/s, GiB/s)</value>
|
||||
</data>
|
||||
</root>
|
||||
</root>
|
||||
|
||||
Reference in New Issue
Block a user