From c2d505dd4de4a2c8e410de22ca6262c02e7e7d8c Mon Sep 17 00:00:00 2001 From: Jake Scott <69864859+JRScott812@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:56:24 -0400 Subject: [PATCH] [CmdPal][Performance Monitor] Add disk activity monitoring (#48844) ## Summary of the Pull Request ## PR Checklist - [X] Closes: #46724 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [X] **Tests:** Added/updated and all pass - (All Cmd Pal tests passed, but there were none specifically for Performance Monitor) - [X] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [X] **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 ## Detailed Description of the Pull Request / Additional comments Added the ability to see the Disk Stats in the Performance Monitor of Command Palette. It is also able to be pinned to the dock. It functions similarly to the Network Stats, in that you can cycle between different disks and can see the read & write speed. ## Validation Steps Performed Ran the dev version of Command Palette and cycled through my devices disk and compared them with Task Manager. Turned on the Dock and compared the values with Task Manager as well on all my disks. --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- PowerToys.slnx | 4 + .../cmdpal/CommandPalette - no UI tests.slnf | 4 +- src/modules/cmdpal/CommandPalette.slnf | 1 + .../FormatIncomingDataTests.cs | 69 +++ ...al.Ext.PerformanceMonitor.UnitTests.csproj | 23 + .../DevHome/Helpers/ChartHelper.cs | 10 + .../DevHome/Helpers/DataManager.cs | 24 + .../DevHome/Helpers/DataType.cs | 5 + .../DevHome/Helpers/DiskStats.cs | 197 +++++++ .../DevHome/Helpers/SystemData.cs | 3 + .../Templates/SystemDiskUsageTemplate.json | 106 ++++ .../Icons.cs | 4 + ...osoft.CmdPal.Ext.PerformanceMonitor.csproj | 3 + .../PerformanceMonitorCommandsProvider.cs | 6 + .../PerformanceWidgetsPage.cs | 526 ++++++++++++++---- .../Properties/AssemblyInfo.cs | 7 + .../SettingsManager.cs | 28 +- .../{NetworkSpeedUnit.cs => SpeedUnit.cs} | 4 +- .../Strings/en-US/Resources.resw | 119 +++- 19 files changed, 1015 insertions(+), 128 deletions(-) create mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests/FormatIncomingDataTests.cs create mode 100644 src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests.csproj create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DiskStats.cs create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Templates/SystemDiskUsageTemplate.json create mode 100644 src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Properties/AssemblyInfo.cs rename src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/{NetworkSpeedUnit.cs => SpeedUnit.cs} (86%) diff --git a/PowerToys.slnx b/PowerToys.slnx index dd79e518fb..c93236d9a1 100644 --- a/PowerToys.slnx +++ b/PowerToys.slnx @@ -333,6 +333,10 @@ + + + + diff --git a/src/modules/cmdpal/CommandPalette - no UI tests.slnf b/src/modules/cmdpal/CommandPalette - no UI tests.slnf index 74ad9d58a7..57caced6f4 100644 --- a/src/modules/cmdpal/CommandPalette - no UI tests.slnf +++ b/src/modules/cmdpal/CommandPalette - no UI tests.slnf @@ -19,7 +19,9 @@ "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Apps.UnitTests\\Microsoft.CmdPal.Ext.Apps.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Bookmarks.UnitTests\\Microsoft.CmdPal.Ext.Bookmarks.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Calc.UnitTests\\Microsoft.CmdPal.Ext.Calc.UnitTests.csproj", - "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests.csproj", + "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests.csproj", + "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Indexer.UnitTests\\Microsoft.CmdPal.Ext.Indexer.UnitTests.csproj", + "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests\\Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Registry.UnitTests\\Microsoft.CmdPal.Ext.Registry.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.WindowsSettings.UnitTests\\Microsoft.CmdPal.Ext.WindowsSettings.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests\\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests.csproj", diff --git a/src/modules/cmdpal/CommandPalette.slnf b/src/modules/cmdpal/CommandPalette.slnf index 2d2ef72ae9..fd543f357d 100644 --- a/src/modules/cmdpal/CommandPalette.slnf +++ b/src/modules/cmdpal/CommandPalette.slnf @@ -24,6 +24,7 @@ "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Calc.UnitTests\\Microsoft.CmdPal.Ext.Calc.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests\\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Indexer.UnitTests\\Microsoft.CmdPal.Ext.Indexer.UnitTests.csproj", + "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests\\Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Registry.UnitTests\\Microsoft.CmdPal.Ext.Registry.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests\\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests.csproj", "src\\modules\\cmdpal\\Tests\\Microsoft.CmdPal.Ext.Shell.UnitTests\\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj", diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests/FormatIncomingDataTests.cs b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests/FormatIncomingDataTests.cs new file mode 100644 index 0000000000..806be3407a --- /dev/null +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests/FormatIncomingDataTests.cs @@ -0,0 +1,69 @@ +// 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 Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests; + +[TestClass] +public class FormatIncomingDataTests +{ + [DataTestMethod] + [DataRow(0f, "0.0 Kbps")] + [DataRow(6400f, "50.0 Kbps")] + [DataRow(12793f, "99.9 Kbps")] + [DataRow(12800f, "100 Kbps")] + [DataRow(127872f, "999 Kbps")] + [DataRow(131072f, "1.0 Mbps")] + [DataRow(6553600f, "50.0 Mbps")] + [DataRow(13107200f, "100 Mbps")] + [DataRow(134217728f, "1.0 Gbps")] + [DataRow(6710886400f, "50.0 Gbps")] + [DataRow(13421772800f, "100 Gbps")] + public void AsBitsPerSecString_FormatsBoundaryValues(float bytesPerSecond, string expected) + { + Assert.AreEqual(expected, FormatIncomingData.AsBitsPerSecString(bytesPerSecond)); + } + + [DataTestMethod] + [DataRow(0f, "0.0 KB/s")] + [DataRow(50000f, "50.0 KB/s")] + [DataRow(100000f, "100 KB/s")] + [DataRow(999000f, "999 KB/s")] + [DataRow(1000000f, "1.0 MB/s")] + [DataRow(50000000f, "50.0 MB/s")] + [DataRow(100000000f, "100 MB/s")] + [DataRow(1000000000f, "1.0 GB/s")] + [DataRow(50000000000f, "50.0 GB/s")] + [DataRow(100000000000f, "100 GB/s")] + public void AsBytesPerSecString_FormatsBoundaryValues(float bytesPerSecond, string expected) + { + Assert.AreEqual(expected, FormatIncomingData.AsBytesPerSecString(bytesPerSecond)); + } + + [TestMethod] + public void AsBytesPerSecString_UsesDecimalScalingDistinctFromBinary() + { + const float bytesPerSecond = 51200f; + + Assert.AreEqual("51.2 KB/s", FormatIncomingData.AsBytesPerSecString(bytesPerSecond)); + Assert.AreEqual("50.0 KiB/s", FormatIncomingData.AsBinaryBytesPerSecString(bytesPerSecond)); + } + + [DataTestMethod] + [DataRow(0f, "0.0 KiB/s")] + [DataRow(51200f, "50.0 KiB/s")] + [DataRow(102400f, "100 KiB/s")] + [DataRow(1022976f, "999 KiB/s")] + [DataRow(1048576f, "1.0 MiB/s")] + [DataRow(52428800f, "50.0 MiB/s")] + [DataRow(104857600f, "100 MiB/s")] + [DataRow(1073741824f, "1.0 GiB/s")] + [DataRow(53687091200f, "50.0 GiB/s")] + [DataRow(107374182400f, "100 GiB/s")] + public void AsBinaryBytesPerSecString_FormatsBoundaryValues(float bytesPerSecond, string expected) + { + Assert.AreEqual(expected, FormatIncomingData.AsBinaryBytesPerSecString(bytesPerSecond)); + } +} diff --git a/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests.csproj b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests.csproj new file mode 100644 index 0000000000..979bfc6190 --- /dev/null +++ b/src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests.csproj @@ -0,0 +1,23 @@ + + + + + + false + true + Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests + $(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\ + false + false + true + + + + + + + + + + + diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/ChartHelper.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/ChartHelper.cs index bec3398c8b..4cc56866ef 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/ChartHelper.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/ChartHelper.cs @@ -17,6 +17,7 @@ internal sealed class ChartHelper GPU, Mem, Net, + Dis, } public const int ChartHeight = 86; @@ -28,6 +29,7 @@ internal sealed class ChartHelper private const string GPULineStyle = "fill:none;stroke:rgb(222,104,242);stroke-width:1"; private const string MemLineStyle = "fill:none;stroke:rgb(92,158,250);stroke-width:1"; private const string NetLineStyle = "fill:none;stroke:rgb(245,98,142);stroke-width:1"; + private const string DisLineStyle = "fill:none;stroke:rgb(103,153,24);stroke-width:1"; private const string FillStyle = "fill:url(#gradientId);stroke:transparent"; @@ -43,6 +45,9 @@ internal sealed class ChartHelper private const string NetBrushStop1Style = "stop-color:rgb(245,98,142);stop-opacity:0.4"; private const string NetBrushStop2Style = "stop-color:rgb(130,0,47);stop-opacity:0.25"; + private const string DisBrushStop1Style = "stop-color:rgb(103,153,24);stop-opacity:0.4"; + private const string DisBrushStop2Style = "stop-color:rgb(45,62,15);stop-opacity:0.25"; + private const string SvgElement = "svg"; private const string RectElement = "rect"; private const string PolylineElement = "polyline"; @@ -174,6 +179,10 @@ internal sealed class ChartHelper stop1Style = NetBrushStop1Style; stop2Style = NetBrushStop2Style; break; + case ChartType.Dis: + stop1Style = DisBrushStop1Style; + stop2Style = DisBrushStop2Style; + break; case ChartType.CPU: default: stop1Style = CPUBrushStop1Style; @@ -213,6 +222,7 @@ internal sealed class ChartHelper ChartType.GPU => GPULineStyle, ChartType.Mem => MemLineStyle, ChartType.Net => NetLineStyle, + ChartType.Dis => DisLineStyle, _ => CPULineStyle, }; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DataManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DataManager.cs index 8dc80db698..aefc001de9 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DataManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DataManager.cs @@ -45,6 +45,14 @@ internal sealed partial class DataManager : IDisposable } } + private void GetDiskData() + { + lock (_systemData.DiskStats) + { + _systemData.DiskStats.GetData(); + } + } + private void GetGPUData() { lock (_systemData.GPUStats) @@ -107,6 +115,13 @@ internal sealed partial class DataManager : IDisposable break; } + case DataType.Disk: + { + // disk + GetDiskData(); + break; + } + case DataType.Battery: { GetBatteryData(); @@ -146,6 +161,7 @@ internal sealed partial class DataManager : IDisposable DataType.GPU => "GPU.FirstUpdate", DataType.Memory => "Memory.FirstUpdate", DataType.Network => "Network.FirstUpdate", + DataType.Disk => "Disk.FirstUpdate", DataType.Battery => "Battery.FirstUpdate", _ => null, }; @@ -167,6 +183,14 @@ internal sealed partial class DataManager : IDisposable } } + internal DiskStats GetDiskStats() + { + lock (_systemData.DiskStats) + { + return _systemData.DiskStats; + } + } + internal GPUStats GetGPUStats() { lock (_systemData.GPUStats) diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DataType.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DataType.cs index e9af0f6986..700e9ccf4c 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DataType.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DataType.cs @@ -37,4 +37,9 @@ public enum DataType /// Battery related data. /// Battery, + + /// + /// Disk related data. + /// + Disk, } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DiskStats.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DiskStats.cs new file mode 100644 index 0000000000..8f5e629fa1 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DiskStats.cs @@ -0,0 +1,197 @@ +// 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.Diagnostics; +using System.Linq; +using Microsoft.CmdPal.Common; + +namespace CoreWidgetProvider.Helpers; + +internal sealed partial class DiskStats : PerformanceCounterSourceBase, IDisposable +{ + private readonly Dictionary> _diskCounters = new(); + private bool _diskCounterReadFailureLogged; + + private Dictionary DiskUsages { get; set; } = new(); + + private Dictionary> DiskChartValues { get; set; } = new(); + + public sealed class Data + { + public float Usage + { + get; set; + } + + public float Read + { + get; set; + } + + public float Written + { + get; set; + } + } + + public DiskStats() + { + InitDiskPerfCounters(); + } + + private void InitDiskPerfCounters() + { + try + { + var perfCounterCategory = CreatePerformanceCounterCategory("PhysicalDisk"); + if (perfCounterCategory is null) + { + return; + } + + var instanceNames = perfCounterCategory.GetInstanceNames(); + foreach (var instanceName in instanceNames) + { + if (string.Equals(instanceName, "_Total", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + try + { + var bytesRead = CreatePerformanceCounter("PhysicalDisk", "Disk Read Bytes/sec", instanceName, logFailure: false); + var bytesWritten = CreatePerformanceCounter("PhysicalDisk", "Disk Write Bytes/sec", instanceName, logFailure: false); + var diskTime = CreatePerformanceCounter("PhysicalDisk", "% Disk Time", instanceName, logFailure: false); + if (bytesRead is null || bytesWritten is null || diskTime is null) + { + bytesRead?.Dispose(); + bytesWritten?.Dispose(); + diskTime?.Dispose(); + continue; + } + + var instanceCounters = new List { bytesRead, bytesWritten, diskTime }; + _diskCounters.Add(instanceName, instanceCounters); + DiskChartValues.Add(instanceName, new List()); + DiskUsages.Add(instanceName, new Data()); + } + catch (Exception) + { + // Skip interfaces whose counters cannot be initialized. + } + } + } + catch (Exception ex) + { + CoreLogger.LogError("Failed to initialize disk performance counters.", ex); + } + } + + public void GetData() + { + foreach (var diskCounterWithName in _diskCounters) + { + try + { + var read = diskCounterWithName.Value[0].NextValue(); + var written = diskCounterWithName.Value[1].NextValue(); + + var diskTimePercent = Math.Clamp(diskCounterWithName.Value[2].NextValue(), 0f, 100f); + var name = diskCounterWithName.Key; + + DiskUsages[name].Read = read; + DiskUsages[name].Written = written; + DiskUsages[name].Usage = diskTimePercent / 100f; + + var chartValues = DiskChartValues[name]; + lock (chartValues) + { + ChartHelper.AddNextChartValue(diskTimePercent, chartValues); + } + } + catch (Exception ex) + { + LogFailureOnce(ref _diskCounterReadFailureLogged, "Failed while reading disk performance counters.", ex); + } + } + } + + public string CreateDiskImageUrl(int diskChartIndex) + { + var chartValues = DiskChartValues.ElementAt(diskChartIndex).Value; + lock (chartValues) + { + return ChartHelper.CreateImageUrl(chartValues, ChartHelper.ChartType.Dis); + } + } + + public string GetDiskName(int diskIndex) + { + if (DiskChartValues.Count <= diskIndex) + { + return string.Empty; + } + + return DiskChartValues.ElementAt(diskIndex).Key; + } + + public Data GetDiskUsage(int diskIndex) + { + if (DiskChartValues.Count <= diskIndex) + { + return new Data(); + } + + var currDiskName = DiskChartValues.ElementAt(diskIndex).Key; + if (!DiskUsages.TryGetValue(currDiskName, out var value)) + { + return new Data(); + } + + return value; + } + + public int GetPrevDiskIndex(int diskIndex) + { + if (DiskChartValues.Count == 0) + { + return 0; + } + + if (diskIndex == 0) + { + return DiskChartValues.Count - 1; + } + + return diskIndex - 1; + } + + public int GetNextDiskIndex(int diskIndex) + { + if (DiskChartValues.Count == 0) + { + return 0; + } + + if (diskIndex == DiskChartValues.Count - 1) + { + return 0; + } + + return diskIndex + 1; + } + + public void Dispose() + { + foreach (var counterPair in _diskCounters) + { + foreach (var counter in counterPair.Value) + { + counter.Dispose(); + } + } + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/SystemData.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/SystemData.cs index 55fdbc53db..1abab760d2 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/SystemData.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/SystemData.cs @@ -13,6 +13,7 @@ internal sealed partial class SystemData private readonly Lazy _memoryStats = new(() => CreateGuarded("Memory.Initialize", static () => new MemoryStats())); private readonly Lazy _networkStats = new(() => CreateGuarded("Network.Initialize", static () => new NetworkStats())); + private readonly Lazy _diskStats = new(() => CreateGuarded("Disk.Initialize", static () => new DiskStats())); private readonly Lazy _gpuStats = new(() => CreateGuarded("GPU.Initialize", static () => new GPUStats())); private readonly Lazy _cpuStats = new(() => CreateGuarded("CPU.Initialize", static () => new CPUStats())); private readonly Lazy _batteryStats = new(() => CreateGuarded("Battery.Initialize", static () => new BatteryStats())); @@ -21,6 +22,8 @@ internal sealed partial class SystemData public NetworkStats NetworkStats => _networkStats.Value; + public DiskStats DiskStats => _diskStats.Value; + public GPUStats GPUStats => _gpuStats.Value; public CPUStats CpuStats => _cpuStats.Value; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Templates/SystemDiskUsageTemplate.json b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Templates/SystemDiskUsageTemplate.json new file mode 100644 index 0000000000..851f689b0c --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Templates/SystemDiskUsageTemplate.json @@ -0,0 +1,106 @@ +{ + "type": "AdaptiveCard", + "body": [ + { + "type": "Container", + "$when": "${errorMessage != null}", + "items": [ + { + "type": "TextBlock", + "text": "${errorMessage}", + "wrap": true, + "size": "small" + } + ], + "style": "warning" + }, + { + "type": "Container", + "$when": "${errorMessage == null}", + "items": [ + { + "type": "Image", + "url": "${diskGraphUrl}", + "height": "${chartHeight}", + "width": "${chartWidth}", + "$when": "${$host.widgetSize != \"small\"}", + "horizontalAlignment": "center" + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "items": [ + { + "text": "%DiskUsage_Widget_Template/Disk_Usage%", + "type": "TextBlock", + "spacing": "none", + "size": "small", + "isSubtle": true + }, + { + "text": "${diskUsage}", + "type": "TextBlock", + "size": "large", + "weight": "bolder" + } + ] + }, + { + "type": "Column", + "items": [ + { + "text": "%DiskUsage_Widget_Template/Read%", + "type": "TextBlock", + "spacing": "none", + "size": "small", + "isSubtle": true + }, + { + "text": "${diskRead}", + "type": "TextBlock", + "size": "large", + "weight": "bolder" + } + ] + }, + { + "type": "Column", + "items": [ + { + "text": "%DiskUsage_Widget_Template/Write%", + "type": "TextBlock", + "spacing": "none", + "size": "small", + "isSubtle": true, + "horizontalAlignment": "right" + }, + { + "text": "${diskWrite}", + "type": "TextBlock", + "size": "large", + "weight": "bolder", + "horizontalAlignment": "right" + } + ] + } + ] + }, + { + "text": "%DiskUsage_Widget_Template/Disk_Name%", + "type": "TextBlock", + "size": "small", + "isSubtle": true + }, + { + "text": "${diskName}", + "type": "TextBlock", + "size": "medium" + } + ] + } + ], + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.5" +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Icons.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Icons.cs index 02b704d3d4..56fb01896c 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Icons.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Icons.cs @@ -19,6 +19,10 @@ internal static class Icons internal static IconInfo HardDriveIcon => new("\uEDA2"); // HardDrive icon + internal static IconInfo FileReadIcon => new("\uE890"); // FileRead icon + + internal static IconInfo FileWriteIcon => new("\uE70F"); // FileWrite icon + internal static IconInfo NetworkIcon => new("\uEC05"); // Network icon internal static IconInfo NetworkUpIcon => new("\uE74A"); // Up arrow icon diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Microsoft.CmdPal.Ext.PerformanceMonitor.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Microsoft.CmdPal.Ext.PerformanceMonitor.csproj index 9ec357605e..319296f540 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Microsoft.CmdPal.Ext.PerformanceMonitor.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Microsoft.CmdPal.Ext.PerformanceMonitor.csproj @@ -50,6 +50,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/PerformanceMonitorCommandsProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/PerformanceMonitorCommandsProvider.cs index aaf21373a1..ffdea05b1c 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/PerformanceMonitorCommandsProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/PerformanceMonitorCommandsProvider.cs @@ -39,6 +39,7 @@ public partial class PerformanceMonitorCommandsProvider : CommandProvider private PerformanceWidgetsPage? _bandPage; private PerformanceWidgetsPage? _cpuBandPage; private PerformanceWidgetsPage? _memoryBandPage; + private PerformanceWidgetsPage? _diskBandPage; private PerformanceWidgetsPage? _networkBandPage; private PerformanceWidgetsPage? _gpuBandPage; private PerformanceWidgetsPage? _batteryBandPage; @@ -198,6 +199,7 @@ public partial class PerformanceMonitorCommandsProvider : CommandProvider _cpuBandPage = new PerformanceWidgetsPage(_settingsManager, true, PerformanceMetricKind.Cpu); _memoryBandPage = new PerformanceWidgetsPage(_settingsManager, true, PerformanceMetricKind.Memory); _networkBandPage = new PerformanceWidgetsPage(_settingsManager, true, PerformanceMetricKind.Network); + _diskBandPage = new PerformanceWidgetsPage(_settingsManager, true, PerformanceMetricKind.Disk); _gpuBandPage = new PerformanceWidgetsPage(_settingsManager, true, PerformanceMetricKind.Gpu); _batteryBandPage = new PerformanceWidgetsPage(_settingsManager, true, PerformanceMetricKind.Battery); @@ -206,6 +208,7 @@ public partial class PerformanceMonitorCommandsProvider : CommandProvider new CommandItem(_cpuBandPage) { Title = Resources.GetResource("CPU_Usage_Title") }, new CommandItem(_memoryBandPage) { Title = Resources.GetResource("Memory_Usage_Title") }, new CommandItem(_networkBandPage) { Title = Resources.GetResource("Network_Usage_Title") }, + new CommandItem(_diskBandPage) { Title = Resources.GetResource("Disk_Usage_Title") }, new CommandItem(_gpuBandPage) { Title = Resources.GetResource("GPU_Usage_Title") } ]; var batteryStats = new BatteryStats(); @@ -242,6 +245,9 @@ public partial class PerformanceMonitorCommandsProvider : CommandProvider _memoryBandPage?.Dispose(); _memoryBandPage = null; + _diskBandPage?.Dispose(); + _diskBandPage = null; + _networkBandPage?.Dispose(); _networkBandPage = null; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/PerformanceWidgetsPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/PerformanceWidgetsPage.cs index 1459e5db28..90fd33d412 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/PerformanceWidgetsPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/PerformanceWidgetsPage.cs @@ -31,6 +31,7 @@ internal enum PerformanceMetricKind Cpu, Memory, Network, + Disk, Gpu, Battery, } @@ -59,6 +60,7 @@ internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDi PerformanceMetricKind.Cpu => Icons.CpuIcon, PerformanceMetricKind.Memory => Icons.MemoryIcon, PerformanceMetricKind.Network => Icons.NetworkIcon, + PerformanceMetricKind.Disk => Icons.HardDriveIcon, PerformanceMetricKind.Gpu => Icons.GpuIcon, PerformanceMetricKind.Battery => _batteryPage?.CurrentIcon ?? Icons.BatteryIcon, _ => Icons.PerformanceMonitorIcon, @@ -74,6 +76,9 @@ internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDi private readonly SystemMemoryUsageWidgetPage? _memoryPage; private readonly ListItem? _memoryItem; + private readonly SystemDiskUsageWidgetPage? _diskPage; + private readonly ListItem? _diskItem; + private readonly SystemNetworkUsageWidgetPage? _networkPage; private readonly ListItem? _networkItem; @@ -89,6 +94,12 @@ internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDi private string _networkUpSpeed = string.Empty; private string _networkDownSpeed = string.Empty; + // For bands, we want two bands, one for read and one for write + private ListItem? _diskReadItem; + private ListItem? _diskWriteItem; + private string _diskReadSpeed = string.Empty; + private string _diskWriteSpeed = string.Empty; + public PerformanceWidgetsPage(SettingsManager settingsManager, bool isBandPage = false, PerformanceMetricKind? singleMetric = null) { _isBandPage = isBandPage; @@ -163,6 +174,26 @@ internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDi }; } + if (IncludesMetric(PerformanceMetricKind.Disk)) + { + _diskPage = new SystemDiskUsageWidgetPage(settingsManager); + _diskItem = new ListItem(_diskPage) + { + Title = _diskPage.GetItemTitle(isBandPage), + MoreCommands = _diskPage.Commands, + }; + + _diskPage.Updated += (s, e) => + { + _diskItem.Title = _diskPage.GetItemTitle(isBandPage); + _diskReadSpeed = _diskPage.GetReadSpeed(); + _diskWriteSpeed = _diskPage.GetWriteSpeed(); + _diskReadItem?.Title = $"{_diskReadSpeed}"; + _diskWriteItem?.Title = $"{_diskWriteSpeed}"; + RaiseItemsChanged(); + }; + } + if (IncludesMetric(PerformanceMetricKind.Gpu)) { _gpuPage = new SystemGPUUsageWidgetPage(); @@ -225,6 +256,12 @@ internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDi _networkItem.Subtitle = Resources.GetResource("Network_Usage_Subtitle"); } + if (_diskItem is not null) + { + _diskItem.Subtitle = Resources.GetResource("Disk_Active_Time_Subtitle"); + _diskItem.Icon = Icons.HardDriveIcon; + } + if (_gpuItem is not null) { _gpuItem.Subtitle = Resources.GetResource("GPU_Usage_Subtitle"); @@ -242,6 +279,7 @@ internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDi _cpuPage?.PushActivate(); _memoryPage?.PushActivate(); _networkPage?.PushActivate(); + _diskPage?.PushActivate(); _gpuPage?.PushActivate(); _batteryPage?.PushActivate(); } @@ -251,6 +289,7 @@ internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDi _cpuPage?.PopActivate(); _memoryPage?.PopActivate(); _networkPage?.PopActivate(); + _diskPage?.PopActivate(); _gpuPage?.PopActivate(); _batteryPage?.PopActivate(); } @@ -260,11 +299,17 @@ internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDi // Per-metric pages just return the single matching item. if (_singleMetric is PerformanceMetricKind metric) { + if (metric == PerformanceMetricKind.Disk && _isBandPage) + { + return CreateDiskBandItems(); + } + return metric switch { PerformanceMetricKind.Cpu => new IListItem[] { _cpuItem! }, PerformanceMetricKind.Memory => new IListItem[] { _memoryItem! }, PerformanceMetricKind.Network => new IListItem[] { _networkItem! }, + PerformanceMetricKind.Disk => new IListItem[] { _diskItem! }, PerformanceMetricKind.Gpu => new IListItem[] { _gpuItem! }, PerformanceMetricKind.Battery => new IListItem[] { _batteryItem! }, _ => Array.Empty(), @@ -275,22 +320,71 @@ internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDi { // TODO add details return _batteryItem is not null - ? new[] { _cpuItem!, _memoryItem!, _networkItem!, _gpuItem!, _batteryItem! } - : new[] { _cpuItem!, _memoryItem!, _networkItem!, _gpuItem! }; + ? new[] { _cpuItem!, _memoryItem!, _networkItem!, _diskItem!, _gpuItem!, _batteryItem! } + : new[] { _cpuItem!, _memoryItem!, _networkItem!, _diskItem!, _gpuItem! }; } else { + if (_networkUpItem is null) + { + _networkUpItem = new ListItem(_networkPage!) + { + Title = $"{_networkUpSpeed}", + Subtitle = Resources.GetResource("Network_Send_Subtitle"), + Icon = Icons.NetworkUpIcon, + MoreCommands = _networkPage!.Commands, + }; + } + + _networkUpItem.Title = _networkUpSpeed; + + if (_networkDownItem is null) + { + _networkDownItem = new ListItem(_networkPage!) + { + Title = $"{_networkDownSpeed}", + Subtitle = Resources.GetResource("Network_Receive_Subtitle"), + Icon = Icons.NetworkDownIcon, + MoreCommands = _networkPage!.Commands, + }; + } + + _networkDownItem.Title = _networkDownSpeed; + + CreateDiskBandItems(); return _batteryItem is not null - ? new[] { _cpuItem!, _memoryItem!, _networkUpItem!, _networkDownItem!, _gpuItem!, _batteryItem! } - : new[] { _cpuItem!, _memoryItem!, _networkUpItem!, _networkDownItem!, _gpuItem! }; + ? new[] { _cpuItem!, _memoryItem!, _networkUpItem!, _networkDownItem!, _diskReadItem!, _diskWriteItem!, _diskItem!, _gpuItem!, _batteryItem! } + : new[] { _cpuItem!, _memoryItem!, _networkUpItem!, _networkDownItem!, _diskReadItem!, _diskWriteItem!, _diskItem!, _gpuItem! }; } } + private IListItem[] CreateDiskBandItems() + { + _diskReadItem ??= new ListItem(_diskPage!) + { + Subtitle = Resources.GetResource("Disk_Read_Subtitle"), + Icon = Icons.FileReadIcon, + MoreCommands = _diskPage!.Commands, + }; + _diskReadItem.Title = _diskReadSpeed; + + _diskWriteItem ??= new ListItem(_diskPage!) + { + Subtitle = Resources.GetResource("Disk_Write_Subtitle"), + Icon = Icons.FileWriteIcon, + MoreCommands = _diskPage!.Commands, + }; + _diskWriteItem.Title = _diskWriteSpeed; + + return [_diskReadItem, _diskWriteItem, _diskItem!]; + } + public void Dispose() { _cpuPage?.Dispose(); _memoryPage?.Dispose(); _networkPage?.Dispose(); + _diskPage?.Dispose(); _gpuPage?.Dispose(); _batteryPage?.Dispose(); } @@ -312,6 +406,7 @@ internal sealed partial class PerformanceWidgetsPage : OnLoadStaticListPage, IDi PerformanceMetricKind.Cpu => "cpu", PerformanceMetricKind.Memory => "memory", PerformanceMetricKind.Network => "network", + PerformanceMetricKind.Disk => "disk", PerformanceMetricKind.Gpu => "gpu", PerformanceMetricKind.Battery => "battery", _ => "unknown", @@ -674,6 +769,198 @@ internal sealed partial class SystemMemoryUsageWidgetPage : WidgetPage, IDisposa } } +internal sealed partial class SystemDiskUsageWidgetPage : WidgetPage, IDisposable +{ + public override string Id => "com.microsoft.cmdpal.disk_widget"; + + public override string Title => Resources.GetResource("Disk_Usage_Title"); + + public override IconInfo Icon => Icons.HardDriveIcon; + + private readonly DataManager _dataManager; + private readonly SettingsManager _settingsManager; + private int _diskIndex; + + public SystemDiskUsageWidgetPage(SettingsManager settingsManager) + { + _settingsManager = settingsManager; + _dataManager = new(DataType.Disk, () => UpdateWidget()); + Commands = [ + new CommandContextItem(new PrevDiskCommand(this) { Name = Resources.GetResource("Previous_Disk_Title") }), + new CommandContextItem(new NextDiskCommand(this) { Name = Resources.GetResource("Next_Disk_Title") }), + new CommandContextItem(OpenTaskManagerCommand.Instance), + ]; + } + + protected override void LoadContentData() + { + // CoreLogger.LogDebug("Getting Disk stats"); + try + { + ContentData.Clear(); + + var timer = Stopwatch.StartNew(); + + var currentData = _dataManager.GetDiskStats(); + + var dataDuration = timer.ElapsedMilliseconds; + + var diskName = currentData.GetDiskName(_diskIndex); + var diskStats = currentData.GetDiskUsage(_diskIndex); + + ContentData["diskUsage"] = FloatToPercentString(diskStats.Usage); + ContentData["diskRead"] = SpeedToString(diskStats.Read); + ContentData["diskWrite"] = SpeedToString(diskStats.Written); + ContentData["diskName"] = diskName; + ContentData["diskGraphUrl"] = currentData.CreateDiskImageUrl(_diskIndex); + ContentData["chartHeight"] = ChartHelper.ChartHeight + "px"; + ContentData["chartWidth"] = ChartHelper.ChartWidth + "px"; + + var contentDuration = timer.ElapsedMilliseconds - dataDuration; + + // CoreLogger.LogDebug($"Disk stats retrieved in {dataDuration} ms, content prepared in {contentDuration} ms. (Total {timer.ElapsedMilliseconds} ms)"); + } + catch (Exception e) + { + ContentData.Clear(); + ContentData["errorMessage"] = e.Message; + return; + } + } + + protected override string GetTemplatePath(WidgetPageState page) + { + return page switch + { + WidgetPageState.Content => @"DevHome\Templates\SystemDiskUsageTemplate.json", + WidgetPageState.Loading => @"DevHome\Templates\SystemDiskUsageTemplate.json", + _ => throw new NotImplementedException(), + }; + } + + public string GetItemTitle(bool isBandPage) + { + if (ContentData.TryGetValue("diskName", out var name) && ContentData.TryGetValue("diskUsage", out var usage)) + { + return isBandPage ? usage : string.Format(CultureInfo.CurrentCulture, Resources.GetResource("Disk_Usage_Label"), name, usage); + } + else + { + return isBandPage ? Resources.GetResource("Disk_Usage_Unknown") : Resources.GetResource("Disk_Usage_Unknown_Label"); + } + } + + // read/write speed is always used for bands + public string GetReadSpeed() + { + if (ContentData.TryGetValue("diskRead", out var readSpeed)) + { + return readSpeed; + } + else + { + return "???"; + } + } + + public string GetWriteSpeed() + { + if (ContentData.TryGetValue("diskWrite", out var writeSpeed)) + { + return writeSpeed; + } + else + { + return "???"; + } + } + + private string SpeedToString(float bytesPerSec) + { + return _settingsManager.DiskSpeedUnit switch + { + SpeedUnit.BytesPerSecond => FormatIncomingData.AsBytesPerSecString(bytesPerSec), + SpeedUnit.BinaryBytesPerSecond => FormatIncomingData.AsBinaryBytesPerSecString(bytesPerSec), + _ => FormatIncomingData.AsBitsPerSecString(bytesPerSec), + }; + } + + internal override void PushActivate() + { + base.PushActivate(); + if (IsActive) + { + _dataManager.Start(); + } + } + + internal override void PopActivate() + { + base.PopActivate(); + if (!IsActive) + { + _dataManager.Stop(); + } + } + + private void HandlePrevDisk() + { + _diskIndex = _dataManager.GetDiskStats().GetPrevDiskIndex(_diskIndex); + UpdateWidget(); + } + + private void HandleNextDisk() + { + _diskIndex = _dataManager.GetDiskStats().GetNextDiskIndex(_diskIndex); + UpdateWidget(); + } + + public void Dispose() + { + _dataManager.Dispose(); + } + + private sealed partial class PrevDiskCommand : InvokableCommand + { + private readonly SystemDiskUsageWidgetPage _page; + + public PrevDiskCommand(SystemDiskUsageWidgetPage page) + { + _page = page; + } + + public override string Id => "com.microsoft.cmdpal.disk_widget.prev"; + + public override IconInfo Icon => Icons.NavigateBackwardIcon; + + public override ICommandResult Invoke() + { + _page.HandlePrevDisk(); + return CommandResult.KeepOpen(); + } + } + + private sealed partial class NextDiskCommand : InvokableCommand + { + private readonly SystemDiskUsageWidgetPage _page; + + public NextDiskCommand(SystemDiskUsageWidgetPage page) + { + _page = page; + } + + public override string Id => "com.microsoft.cmdpal.disk_widget.next"; + + public override IconInfo Icon => Icons.NavigateForwardIcon; + + public override ICommandResult Invoke() + { + _page.HandleNextDisk(); + return CommandResult.KeepOpen(); + } + } +} + internal sealed partial class SystemNetworkUsageWidgetPage : WidgetPage, IDisposable { public override string Id => "com.microsoft.cmdpal.network_widget"; @@ -784,123 +1071,12 @@ internal sealed partial class SystemNetworkUsageWidgetPage : WidgetPage, IDispos { return _settingsManager.NetworkSpeedUnit switch { - NetworkSpeedUnit.BytesPerSecond => FormatAsBytesPerSecString(bytesPerSec), - NetworkSpeedUnit.BinaryBytesPerSecond => FormatAsBinaryBytesPerSecString(bytesPerSec), - _ => FormatAsBitsPerSecString(bytesPerSec), + SpeedUnit.BytesPerSecond => FormatIncomingData.AsBytesPerSecString(bytesPerSec), + SpeedUnit.BinaryBytesPerSecond => FormatIncomingData.AsBinaryBytesPerSecString(bytesPerSec), + _ => FormatIncomingData.AsBitsPerSecString(bytesPerSec), }; } - private static string FormatAsBitsPerSecString(float value) - { - // Bytes to bits - value *= 8; - - // bits to Kbits - value /= 1024; - if (value < 1024) - { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Kbps", value); - } - - return string.Format(CultureInfo.InvariantCulture, "{0:0} Kbps", value); - } - - // Kbits to Mbits - value /= 1024; - if (value < 1024) - { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Mbps", value); - } - - return string.Format(CultureInfo.InvariantCulture, "{0:0} Mbps", value); - } - - // Mbits to Gbits - value /= 1024; - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Gbps", value); - } - - return string.Format(CultureInfo.InvariantCulture, "{0:0} Gbps", value); - } - - private static string FormatAsBytesPerSecString(float value) - { - // Bytes to KB - value /= 1024; - if (value < 1024) - { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} KB/s", value); - } - - return string.Format(CultureInfo.InvariantCulture, "{0:0} KB/s", value); - } - - // KB to MB - value /= 1024; - if (value < 1024) - { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} MB/s", value); - } - - return string.Format(CultureInfo.InvariantCulture, "{0:0} MB/s", value); - } - - // MB to GB - value /= 1024; - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} GB/s", value); - } - - return string.Format(CultureInfo.InvariantCulture, "{0:0} GB/s", value); - } - - private static string FormatAsBinaryBytesPerSecString(float value) - { - // Bytes to KiB - value /= 1024; - if (value < 1024) - { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} KiB/s", value); - } - - return string.Format(CultureInfo.InvariantCulture, "{0:0} KiB/s", value); - } - - // KiB to MiB - value /= 1024; - if (value < 1024) - { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} MiB/s", value); - } - - return string.Format(CultureInfo.InvariantCulture, "{0:0} MiB/s", value); - } - - // MiB to GiB - value /= 1024; - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} GiB/s", value); - } - - return string.Format(CultureInfo.InvariantCulture, "{0:0} GiB/s", value); - } - internal override void PushActivate() { base.PushActivate(); @@ -1327,3 +1503,117 @@ internal sealed partial class OpenTaskManagerCommand : InvokableCommand return CommandResult.Hide(); } } + +internal static class FormatIncomingData +{ + public static string AsBitsPerSecString(float value) + { + // Bytes to bits + value *= 8; + + // bits to Kbits + value /= 1024; + if (value < 1024) + { + if (value < 100) + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Kbps", value); + } + + return string.Format(CultureInfo.InvariantCulture, "{0:0} Kbps", value); + } + + // Kbits to Mbits + value /= 1024; + if (value < 1024) + { + if (value < 100) + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Mbps", value); + } + + return string.Format(CultureInfo.InvariantCulture, "{0:0} Mbps", value); + } + + // Mbits to Gbits + value /= 1024; + if (value < 100) + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Gbps", value); + } + + return string.Format(CultureInfo.InvariantCulture, "{0:0} Gbps", value); + } + + public static string AsBytesPerSecString(float value) + { + // Bytes to KB (SI decimal, 1000-based) + value /= 1000; + if (value < 1000) + { + if (value < 100) + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.0} KB/s", value); + } + + return string.Format(CultureInfo.InvariantCulture, "{0:0} KB/s", value); + } + + // KB to MB + value /= 1000; + if (value < 1000) + { + if (value < 100) + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.0} MB/s", value); + } + + return string.Format(CultureInfo.InvariantCulture, "{0:0} MB/s", value); + } + + // MB to GB + value /= 1000; + if (value < 100) + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.0} GB/s", value); + } + + return string.Format(CultureInfo.InvariantCulture, "{0:0} GB/s", value); + } + + public static string AsBinaryBytesPerSecString(float value) + { + // Bytes to KiB + value /= 1024; + if (value < 1024) + { + if (value < 100) + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.0} KiB/s", value); + } + + return string.Format(CultureInfo.InvariantCulture, "{0:0} KiB/s", value); + } + + // KiB to MiB + value /= 1024; + if (value < 1024) + { + if (value < 100) + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.0} MiB/s", value); + } + + return string.Format(CultureInfo.InvariantCulture, "{0:0} MiB/s", value); + } + + // MiB to GiB + value /= 1024; + if (value < 100) + { + return string.Format(CultureInfo.InvariantCulture, "{0:0.0} GiB/s", value); + } + + return string.Format(CultureInfo.InvariantCulture, "{0:0} GiB/s", value); + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Properties/AssemblyInfo.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c9f8493e56 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests")] diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/SettingsManager.cs index fd975a65e7..8adf7120c4 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/SettingsManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/SettingsManager.cs @@ -20,15 +20,30 @@ internal sealed class SettingsManager : JsonSettingsManager Resources.GetResource("Network_Speed_Unit_Setting_Title"), Resources.GetResource("Network_Speed_Unit_Setting_Description"), [ - new ChoiceSetSetting.Choice(Resources.GetResource("Network_Speed_Unit_BitsPerSec"), NetworkSpeedUnit.BitsPerSecond.ToString("G")), - new ChoiceSetSetting.Choice(Resources.GetResource("Network_Speed_Unit_BytesPerSec"), NetworkSpeedUnit.BytesPerSecond.ToString("G")), - new ChoiceSetSetting.Choice(Resources.GetResource("Network_Speed_Unit_BinaryBytesPerSec"), NetworkSpeedUnit.BinaryBytesPerSecond.ToString("G")), + new ChoiceSetSetting.Choice(Resources.GetResource("Network_Speed_Unit_BitsPerSec"), SpeedUnit.BitsPerSecond.ToString("G")), + new ChoiceSetSetting.Choice(Resources.GetResource("Network_Speed_Unit_BytesPerSec"), SpeedUnit.BytesPerSecond.ToString("G")), + new ChoiceSetSetting.Choice(Resources.GetResource("Network_Speed_Unit_BinaryBytesPerSec"), SpeedUnit.BinaryBytesPerSecond.ToString("G")), ]); - public NetworkSpeedUnit NetworkSpeedUnit => - Enum.TryParse(_networkSpeedUnit.Value, out var unit) + public SpeedUnit NetworkSpeedUnit => + Enum.TryParse(_networkSpeedUnit.Value, out var unit) ? unit - : NetworkSpeedUnit.BitsPerSecond; + : SpeedUnit.BitsPerSecond; + + private readonly ChoiceSetSetting _diskSpeedUnit = new( + Namespaced(nameof(DiskSpeedUnit)), + Resources.GetResource("Disk_Speed_Unit_Setting_Title"), + Resources.GetResource("Disk_Speed_Unit_Setting_Description"), + [ + new ChoiceSetSetting.Choice(Resources.GetResource("Disk_Speed_Unit_BitsPerSec"), SpeedUnit.BitsPerSecond.ToString("G")), + new ChoiceSetSetting.Choice(Resources.GetResource("Disk_Speed_Unit_BytesPerSec"), SpeedUnit.BytesPerSecond.ToString("G")), + new ChoiceSetSetting.Choice(Resources.GetResource("Disk_Speed_Unit_BinaryBytesPerSec"), SpeedUnit.BinaryBytesPerSecond.ToString("G")), + ]); + + public SpeedUnit DiskSpeedUnit => + Enum.TryParse(_diskSpeedUnit.Value, out var unit) + ? unit + : SpeedUnit.BytesPerSecond; private static string SettingsJsonPath() { @@ -42,6 +57,7 @@ internal sealed class SettingsManager : JsonSettingsManager FilePath = SettingsJsonPath(); Settings.Add(_networkSpeedUnit); + Settings.Add(_diskSpeedUnit); LoadSettings(); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/NetworkSpeedUnit.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/SpeedUnit.cs similarity index 86% rename from src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/NetworkSpeedUnit.cs rename to src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/SpeedUnit.cs index 0578bb1d88..2bfaa994d4 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/NetworkSpeedUnit.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/SpeedUnit.cs @@ -5,9 +5,9 @@ namespace Microsoft.CmdPal.Ext.PerformanceMonitor; /// -/// Controls the unit used to display network transmission speed. +/// Controls the unit used to display network or disk transmission speed. /// -internal enum NetworkSpeedUnit +internal enum SpeedUnit { /// Bits per second (Kbps, Mbps, Gbps) — SI decimal prefixes. BitsPerSecond, diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Strings/en-US/Resources.resw b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Strings/en-US/Resources.resw index 5d88b51a9a..0ed930d5e4 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Strings/en-US/Resources.resw +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/Strings/en-US/Resources.resw @@ -1,5 +1,64 @@  + @@ -135,6 +194,24 @@ Ethernet + + Active time + + + Read + + + Write + + + Name + + + Previous disk + + + Next disk + Utilization @@ -183,6 +260,9 @@ Network + + Disk + GPU @@ -257,6 +337,19 @@ Network Usage: ??? + + Disk Usage + + + Disk ({0}): {1} + {0} is the disk interface name, {1} is the usage percentage + + + ??? + + + Disk Usage: ??? + GPU Usage @@ -331,6 +424,15 @@ Receive + + Read + + + Write + + + Active time + Network speed unit @@ -346,4 +448,19 @@ Binary bytes per second (KiB/s, MiB/s, GiB/s) - + + Disk speed unit + + + Choose the unit used to display disk transfer speed + + + Bits per second (Kbps, Mbps, Gbps) + + + Bytes per second (KB/s, MB/s, GB/s) + + + Binary bytes per second (KiB/s, MiB/s, GiB/s) + + \ No newline at end of file