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 bec3398c8b76..4cc56866ef96 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 @@ public enum ChartType GPU, Mem, Net, + Dis, } public const int ChartHeight = 86; @@ -28,6 +29,7 @@ public enum ChartType 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 @@ public enum ChartType 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 @@ private static XElement CreateGradientDefinition(ChartType type) stop1Style = NetBrushStop1Style; stop2Style = NetBrushStop2Style; break; + case ChartType.Dis: + stop1Style = DisBrushStop1Style; + stop2Style = DisBrushStop2Style; + break; case ChartType.CPU: default: stop1Style = CPUBrushStop1Style; @@ -213,6 +222,7 @@ private static string GetLineStyle(ChartType type) 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 8dc80db69826..aefc001de975 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 @@ private void GetNetworkData() } } + private void GetDiskData() + { + lock (_systemData.DiskStats) + { + _systemData.DiskStats.GetData(); + } + } + private void GetGPUData() { lock (_systemData.GPUStats) @@ -107,6 +115,13 @@ private void UpdateTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs break; } + case DataType.Disk: + { + // disk + GetDiskData(); + break; + } + case DataType.Battery: { GetBatteryData(); @@ -146,6 +161,7 @@ private void UpdateTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs 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 NetworkStats GetNetworkStats() } } + 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 e9af0f6986ad..700e9ccf4c72 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 000000000000..fafd40977e51 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Helpers/DiskStats.cs @@ -0,0 +1,192 @@ +// 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 = diskCounterWithName.Value[2].NextValue(); + 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) + { + return ChartHelper.CreateImageUrl(DiskChartValues.ElementAt(diskChartIndex).Value, 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 55fdbc53dbc5..1abab760d247 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 000000000000..80194b3af0af --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DevHome/Templates/SystemDiskUsageTemplate.json @@ -0,0 +1,88 @@ +{ + "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/Sent%", + "type": "TextBlock", + "spacing": "none", + "size": "small", + "isSubtle": true + }, + { + "text": "${diskRead}", + "type": "TextBlock", + "size": "large", + "weight": "bolder" + } + ] + }, + { + "type": "Column", + "items": [ + { + "text": "%DiskUsage_Widget_Template/Received%", + "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/DiskSpeedUnit.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DiskSpeedUnit.cs new file mode 100644 index 000000000000..7fea63be5b6d --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/DiskSpeedUnit.cs @@ -0,0 +1,20 @@ +// 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. + +namespace Microsoft.CmdPal.Ext.PerformanceMonitor; + +/// +/// Controls the unit used to display disk read/write speed. +/// +internal enum DiskSpeedUnit +{ + /// Bits per second (Kbps, Mbps, Gbps) — SI decimal prefixes. + BitsPerSecond, + + /// Bytes per second (KB/s, MB/s, GB/s) — SI decimal prefixes. + BytesPerSecond, + + /// Bytes per second (KiB/s, MiB/s, GiB/s) — IEC binary prefixes. + BinaryBytesPerSecond, +} 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 02b704d3d434..56fb01896c0f 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 9ec357605eaf..319296f5406e 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 18864c1f8798..a3973043b0e0 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/PerformanceMonitorCommandsProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/PerformanceMonitorCommandsProvider.cs @@ -29,6 +29,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; @@ -138,6 +139,7 @@ private void SetEnabledState() _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); @@ -146,6 +148,7 @@ private void SetEnabledState() 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(); @@ -182,6 +185,9 @@ private void DisposeActivePages() _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 e1394a3e38c2..6e8a76d9ec48 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 @@ public PerformanceWidgetsPage(SettingsManager settingsManager, bool isBandPage = }; } + 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(); @@ -217,6 +248,11 @@ public PerformanceWidgetsPage(SettingsManager settingsManager, bool isBandPage = _networkItem.Subtitle = Resources.GetResource("Network_Usage_Subtitle"); } + if (_diskItem is not null) + { + _diskItem.Subtitle = Resources.GetResource("Disk_Usage_Subtitle"); + } + if (_gpuItem is not null) { _gpuItem.Subtitle = Resources.GetResource("GPU_Usage_Subtitle"); @@ -234,6 +270,7 @@ protected override void Loaded() _cpuPage?.PushActivate(); _memoryPage?.PushActivate(); _networkPage?.PushActivate(); + _diskPage?.PushActivate(); _gpuPage?.PushActivate(); _batteryPage?.PushActivate(); } @@ -243,6 +280,7 @@ protected override void Unloaded() _cpuPage?.PopActivate(); _memoryPage?.PopActivate(); _networkPage?.PopActivate(); + _diskPage?.PopActivate(); _gpuPage?.PopActivate(); _batteryPage?.PopActivate(); } @@ -257,6 +295,7 @@ public override IListItem[] GetItems() 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(), @@ -267,14 +306,46 @@ public override IListItem[] GetItems() { // 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 { + _networkUpItem = new ListItem(_networkPage!) + { + Title = $"{_networkUpSpeed}", + Subtitle = Resources.GetResource("Network_Send_Subtitle"), + Icon = Icons.NetworkUpIcon, + MoreCommands = _networkPage!.Commands, + }; + + _networkDownItem = new ListItem(_networkPage!) + { + Title = $"{_networkDownSpeed}", + Subtitle = Resources.GetResource("Network_Receive_Subtitle"), + Icon = Icons.NetworkDownIcon, + MoreCommands = _networkPage!.Commands, + }; + + _diskReadItem = new ListItem(_diskPage!) + { + Title = $"{_diskReadSpeed}", + Subtitle = Resources.GetResource("Disk_Read_Subtitle"), + Icon = Icons.FileReadIcon, + MoreCommands = _diskPage!.Commands, + }; + + _diskWriteItem = new ListItem(_diskPage!) + { + Title = $"{_diskWriteSpeed}", + Subtitle = Resources.GetResource("Disk_Write_Subtitle"), + Icon = Icons.FileWriteIcon, + MoreCommands = _diskPage!.Commands, + }; + 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!, _gpuItem!, _batteryItem! } + : new[] { _cpuItem!, _memoryItem!, _networkUpItem!, _networkDownItem!, _diskReadItem!, _diskWriteItem!, _gpuItem! }; } } @@ -283,6 +354,7 @@ public void Dispose() _cpuPage?.Dispose(); _memoryPage?.Dispose(); _networkPage?.Dispose(); + _diskPage?.Dispose(); _gpuPage?.Dispose(); _batteryPage?.Dispose(); } @@ -299,6 +371,7 @@ private static string GetMetricSuffix(PerformanceMetricKind metric) PerformanceMetricKind.Cpu => "cpu", PerformanceMetricKind.Memory => "memory", PerformanceMetricKind.Network => "network", + PerformanceMetricKind.Disk => "disk", PerformanceMetricKind.Gpu => "gpu", PerformanceMetricKind.Battery => "battery", _ => "unknown", @@ -661,56 +734,56 @@ public void Dispose() } } -internal sealed partial class SystemNetworkUsageWidgetPage : WidgetPage, IDisposable +internal sealed partial class SystemDiskUsageWidgetPage : WidgetPage, IDisposable { - public override string Id => "com.microsoft.cmdpal.network_widget"; + public override string Id => "com.microsoft.cmdpal.disk_widget"; - public override string Title => Resources.GetResource("Network_Usage_Title"); + public override string Title => Resources.GetResource("Disk_Usage_Title"); - public override IconInfo Icon => Icons.NetworkIcon; + public override IconInfo Icon => Icons.HardDriveIcon; private readonly DataManager _dataManager; private readonly SettingsManager _settingsManager; - private int _networkIndex; + private int _diskIndex; - public SystemNetworkUsageWidgetPage(SettingsManager settingsManager) + public SystemDiskUsageWidgetPage(SettingsManager settingsManager) { _settingsManager = settingsManager; - _dataManager = new(DataType.Network, () => UpdateWidget()); + _dataManager = new(DataType.Disk, () => UpdateWidget()); 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 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 Network stats"); + // CoreLogger.LogDebug("Getting Disk stats"); try { ContentData.Clear(); var timer = Stopwatch.StartNew(); - var currentData = _dataManager.GetNetworkStats(); + var currentData = _dataManager.GetDiskStats(); var dataDuration = timer.ElapsedMilliseconds; - var netName = currentData.GetNetworkName(_networkIndex); - var networkStats = currentData.GetNetworkUsage(_networkIndex); + var diskName = currentData.GetDiskName(_diskIndex); + var diskStats = currentData.GetDiskUsage(_diskIndex); - ContentData["networkUsage"] = FloatToPercentString(networkStats.Usage); - ContentData["netSent"] = SpeedToString(networkStats.Sent); - ContentData["netReceived"] = SpeedToString(networkStats.Received); - ContentData["networkName"] = netName; - ContentData["netGraphUrl"] = currentData.CreateNetImageUrl(_networkIndex); + 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($"Network stats retrieved in {dataDuration} ms, content prepared in {contentDuration} ms. (Total {timer.ElapsedMilliseconds} ms)"); + // CoreLogger.LogDebug($"Disk stats retrieved in {dataDuration} ms, content prepared in {contentDuration} ms. (Total {timer.ElapsedMilliseconds} ms)"); } catch (Exception e) { @@ -724,30 +797,30 @@ protected override string GetTemplatePath(WidgetPageState page) { return page switch { - WidgetPageState.Content => @"DevHome\Templates\SystemNetworkUsageTemplate.json", - WidgetPageState.Loading => @"DevHome\Templates\SystemNetworkUsageTemplate.json", + WidgetPageState.Content => @"DevHome\Templates\SystemDiskUsageTemplate.json", + WidgetPageState.Loading => @"DevHome\Templates\SystemDiskUsageTemplate.json", _ => throw new NotImplementedException(), }; } public string GetItemTitle(bool isBandPage) { - if (ContentData.TryGetValue("networkName", out var name) && ContentData.TryGetValue("networkUsage", out var usage)) + if (ContentData.TryGetValue("diskName", out var name) && ContentData.TryGetValue("diskUsage", out var usage)) { - return isBandPage ? usage : string.Format(CultureInfo.CurrentCulture, Resources.GetResource("Network_Usage_Label"), name, usage); + return isBandPage ? usage : string.Format(CultureInfo.CurrentCulture, Resources.GetResource("Disk_Usage_Label"), name, usage); } else { - return isBandPage ? Resources.GetResource("Network_Usage_Unknown") : Resources.GetResource("Network_Usage_Unknown_Label"); + return isBandPage ? Resources.GetResource("Disk_Usage_Unknown") : Resources.GetResource("Disk_Usage_Unknown_Label"); } } - // up/down speed is always used for bands - public string GetUpSpeed() + // read/write speed is always used for bands + public string GetReadSpeed() { - if (ContentData.TryGetValue("netSent", out var upSpeed)) + if (ContentData.TryGetValue("diskRead", out var readSpeed)) { - return upSpeed; + return readSpeed; } else { @@ -755,11 +828,11 @@ public string GetUpSpeed() } } - public string GetDownSpeed() + public string GetWriteSpeed() { - if (ContentData.TryGetValue("netReceived", out var downSpeed)) + if (ContentData.TryGetValue("diskWrite", out var writeSpeed)) { - return downSpeed; + return writeSpeed; } else { @@ -769,123 +842,204 @@ public string GetDownSpeed() private string SpeedToString(float bytesPerSec) { - return _settingsManager.NetworkSpeedUnit switch + return _settingsManager.DiskSpeedUnit switch { - NetworkSpeedUnit.BytesPerSecond => FormatAsBytesPerSecString(bytesPerSec), - NetworkSpeedUnit.BinaryBytesPerSecond => FormatAsBinaryBytesPerSecString(bytesPerSec), - _ => FormatAsBitsPerSecString(bytesPerSec), + DiskSpeedUnit.BytesPerSecond => FormatIncomingData.AsBytesPerSecString(bytesPerSec), + DiskSpeedUnit.BinaryBytesPerSecond => FormatIncomingData.AsBinaryBytesPerSecString(bytesPerSec), + _ => FormatIncomingData.AsBitsPerSecString(bytesPerSec), }; } - private static string FormatAsBitsPerSecString(float value) + internal override void PushActivate() { - // Bytes to bits - value *= 8; + base.PushActivate(); + if (IsActive) + { + _dataManager.Start(); + } + } - // bits to Kbits - value /= 1024; - if (value < 1024) + internal override void PopActivate() + { + base.PopActivate(); + if (!IsActive) { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Kbps", value); - } + _dataManager.Stop(); + } + } - return string.Format(CultureInfo.InvariantCulture, "{0:0} Kbps", value); + 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; } - // Kbits to Mbits - value /= 1024; - if (value < 1024) + public override string Id => "com.microsoft.cmdpal.disk_widget.prev"; + + public override IconInfo Icon => Icons.NavigateBackwardIcon; + + public override ICommandResult Invoke() { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Mbps", value); - } + _page.HandlePrevDisk(); + return CommandResult.KeepOpen(); + } + } - return string.Format(CultureInfo.InvariantCulture, "{0:0} Mbps", value); + private sealed partial class NextDiskCommand : InvokableCommand + { + private readonly SystemDiskUsageWidgetPage _page; + + public NextDiskCommand(SystemDiskUsageWidgetPage page) + { + _page = page; } - // Mbits to Gbits - value /= 1024; - if (value < 100) + public override string Id => "com.microsoft.cmdpal.disk_widget.next"; + + public override IconInfo Icon => Icons.NavigateForwardIcon; + + public override ICommandResult Invoke() { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} Gbps", value); + _page.HandleNextDisk(); + return CommandResult.KeepOpen(); } + } +} - return string.Format(CultureInfo.InvariantCulture, "{0:0} Gbps", value); +internal sealed partial class SystemNetworkUsageWidgetPage : WidgetPage, IDisposable +{ + public override string Id => "com.microsoft.cmdpal.network_widget"; + + public override string Title => Resources.GetResource("Network_Usage_Title"); + + public override IconInfo Icon => Icons.NetworkIcon; + + private readonly DataManager _dataManager; + private readonly SettingsManager _settingsManager; + private int _networkIndex; + + public SystemNetworkUsageWidgetPage(SettingsManager settingsManager) + { + _settingsManager = settingsManager; + _dataManager = new(DataType.Network, () => UpdateWidget()); + 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(OpenTaskManagerCommand.Instance), + ]; } - private static string FormatAsBytesPerSecString(float value) + protected override void LoadContentData() { - // Bytes to KB - value /= 1024; - if (value < 1024) + // CoreLogger.LogDebug("Getting Network stats"); + try { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} KB/s", value); - } + ContentData.Clear(); - return string.Format(CultureInfo.InvariantCulture, "{0:0} KB/s", value); - } + var timer = Stopwatch.StartNew(); - // KB to MB - value /= 1024; - if (value < 1024) - { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} MB/s", value); - } + var currentData = _dataManager.GetNetworkStats(); - return string.Format(CultureInfo.InvariantCulture, "{0:0} MB/s", value); - } + var dataDuration = timer.ElapsedMilliseconds; - // MB to GB - value /= 1024; - if (value < 100) + var netName = currentData.GetNetworkName(_networkIndex); + var networkStats = currentData.GetNetworkUsage(_networkIndex); + + ContentData["networkUsage"] = FloatToPercentString(networkStats.Usage); + ContentData["netSent"] = SpeedToString(networkStats.Sent); + ContentData["netReceived"] = SpeedToString(networkStats.Received); + ContentData["networkName"] = netName; + ContentData["netGraphUrl"] = currentData.CreateNetImageUrl(_networkIndex); + ContentData["chartHeight"] = ChartHelper.ChartHeight + "px"; + ContentData["chartWidth"] = ChartHelper.ChartWidth + "px"; + + var contentDuration = timer.ElapsedMilliseconds - dataDuration; + + // CoreLogger.LogDebug($"Network stats retrieved in {dataDuration} ms, content prepared in {contentDuration} ms. (Total {timer.ElapsedMilliseconds} ms)"); + } + catch (Exception e) { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} GB/s", value); + ContentData.Clear(); + ContentData["errorMessage"] = e.Message; + return; } - - return string.Format(CultureInfo.InvariantCulture, "{0:0} GB/s", value); } - private static string FormatAsBinaryBytesPerSecString(float value) + protected override string GetTemplatePath(WidgetPageState page) { - // Bytes to KiB - value /= 1024; - if (value < 1024) + return page switch { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} KiB/s", value); - } + WidgetPageState.Content => @"DevHome\Templates\SystemNetworkUsageTemplate.json", + WidgetPageState.Loading => @"DevHome\Templates\SystemNetworkUsageTemplate.json", + _ => throw new NotImplementedException(), + }; + } - return string.Format(CultureInfo.InvariantCulture, "{0:0} KiB/s", value); + public string GetItemTitle(bool isBandPage) + { + if (ContentData.TryGetValue("networkName", out var name) && ContentData.TryGetValue("networkUsage", out var usage)) + { + return isBandPage ? usage : string.Format(CultureInfo.CurrentCulture, Resources.GetResource("Network_Usage_Label"), name, usage); } - - // KiB to MiB - value /= 1024; - if (value < 1024) + else { - if (value < 100) - { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} MiB/s", value); - } + return isBandPage ? Resources.GetResource("Network_Usage_Unknown") : Resources.GetResource("Network_Usage_Unknown_Label"); + } + } - return string.Format(CultureInfo.InvariantCulture, "{0:0} MiB/s", value); + // up/down speed is always used for bands + public string GetUpSpeed() + { + if (ContentData.TryGetValue("netSent", out var upSpeed)) + { + return upSpeed; } + else + { + return "???"; + } + } - // MiB to GiB - value /= 1024; - if (value < 100) + public string GetDownSpeed() + { + if (ContentData.TryGetValue("netReceived", out var downSpeed)) { - return string.Format(CultureInfo.InvariantCulture, "{0:0.0} GiB/s", value); + return downSpeed; + } + else + { + return "???"; } + } - return string.Format(CultureInfo.InvariantCulture, "{0:0} GiB/s", value); + private string SpeedToString(float bytesPerSec) + { + return _settingsManager.NetworkSpeedUnit switch + { + NetworkSpeedUnit.BytesPerSecond => FormatIncomingData.AsBytesPerSecString(bytesPerSec), + NetworkSpeedUnit.BinaryBytesPerSecond => FormatIncomingData.AsBinaryBytesPerSecString(bytesPerSec), + _ => FormatIncomingData.AsBitsPerSecString(bytesPerSec), + }; } internal override void PushActivate() @@ -1304,3 +1458,117 @@ public override ICommandResult Invoke() 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 + 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); + } + + 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/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/SettingsManager.cs index fd975a65e7d0..2d6eedf7fdf1 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/SettingsManager.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PerformanceMonitor/SettingsManager.cs @@ -30,6 +30,21 @@ internal sealed class SettingsManager : JsonSettingsManager ? unit : NetworkSpeedUnit.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"), DiskSpeedUnit.BitsPerSecond.ToString("G")), + new ChoiceSetSetting.Choice(Resources.GetResource("Disk_Speed_Unit_BytesPerSec"), DiskSpeedUnit.BytesPerSecond.ToString("G")), + new ChoiceSetSetting.Choice(Resources.GetResource("Disk_Speed_Unit_BinaryBytesPerSec"), DiskSpeedUnit.BinaryBytesPerSecond.ToString("G")), + ]); + + public DiskSpeedUnit DiskSpeedUnit => + Enum.TryParse(_diskSpeedUnit.Value, out var unit) + ? unit + : DiskSpeedUnit.BytesPerSecond; + private static string SettingsJsonPath() { var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal"); @@ -42,6 +57,7 @@ public SettingsManager() FilePath = SettingsJsonPath(); Settings.Add(_networkSpeedUnit); + Settings.Add(_diskSpeedUnit); LoadSettings(); 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 e6fa2862a5df..d8c3a7ab77c5 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 + + Utilization + + + 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,12 @@ Receive + + Read + + + Write + Network speed unit @@ -346,4 +445,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