mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
[CmdPal][Performance Monitor] Add disk activity monitoring (#48844)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [X] Closes: #46724 <!-- - [ ] 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 - [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 <!-- 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 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. <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## 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>
This commit is contained in:
@@ -333,6 +333,10 @@
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests/Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/cmdpal/Tests/Microsoft.CmdPal.Ext.Registry.UnitTests/Microsoft.CmdPal.Ext.Registry.UnitTests.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="$(RepoRoot)src\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.PerformanceMonitor.UnitTests</RootNamespace>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.PerformanceMonitor\Microsoft.CmdPal.Ext.PerformanceMonitor.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -37,4 +37,9 @@ public enum DataType
|
||||
/// Battery related data.
|
||||
/// </summary>
|
||||
Battery,
|
||||
|
||||
/// <summary>
|
||||
/// Disk related data.
|
||||
/// </summary>
|
||||
Disk,
|
||||
}
|
||||
|
||||
@@ -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<string, List<PerformanceCounter>> _diskCounters = new();
|
||||
private bool _diskCounterReadFailureLogged;
|
||||
|
||||
private Dictionary<string, Data> DiskUsages { get; set; } = new();
|
||||
|
||||
private Dictionary<string, List<float>> 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<PerformanceCounter> { bytesRead, bytesWritten, diskTime };
|
||||
_diskCounters.Add(instanceName, instanceCounters);
|
||||
DiskChartValues.Add(instanceName, new List<float>());
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ internal sealed partial class SystemData
|
||||
|
||||
private readonly Lazy<MemoryStats> _memoryStats = new(() => CreateGuarded("Memory.Initialize", static () => new MemoryStats()));
|
||||
private readonly Lazy<NetworkStats> _networkStats = new(() => CreateGuarded("Network.Initialize", static () => new NetworkStats()));
|
||||
private readonly Lazy<DiskStats> _diskStats = new(() => CreateGuarded("Disk.Initialize", static () => new DiskStats()));
|
||||
private readonly Lazy<GPUStats> _gpuStats = new(() => CreateGuarded("GPU.Initialize", static () => new GPUStats()));
|
||||
private readonly Lazy<CPUStats> _cpuStats = new(() => CreateGuarded("CPU.Initialize", static () => new CPUStats()));
|
||||
private readonly Lazy<BatteryStats> _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;
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
<None Update="DevHome\Templates\SystemMemoryTemplate.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DevHome\Templates\SystemDiskUsageTemplate.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DevHome\Templates\SystemNetworkUsageTemplate.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<IListItem>(),
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
@@ -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>(_networkSpeedUnit.Value, out var unit)
|
||||
public SpeedUnit NetworkSpeedUnit =>
|
||||
Enum.TryParse<SpeedUnit>(_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<SpeedUnit>(_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();
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
namespace Microsoft.CmdPal.Ext.PerformanceMonitor;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the unit used to display network transmission speed.
|
||||
/// Controls the unit used to display network or disk transmission speed.
|
||||
/// </summary>
|
||||
internal enum NetworkSpeedUnit
|
||||
internal enum SpeedUnit
|
||||
{
|
||||
/// <summary>Bits per second (Kbps, Mbps, Gbps) — SI decimal prefixes.</summary>
|
||||
BitsPerSecond,
|
||||
@@ -1,5 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
@@ -135,6 +194,24 @@
|
||||
<data name="NetworkUsage_Widget_Template.Ethernet_Heading" xml:space="preserve">
|
||||
<value>Ethernet</value>
|
||||
</data>
|
||||
<data name="DiskUsage_Widget_Template.Disk_Usage" xml:space="preserve">
|
||||
<value>Active time</value>
|
||||
</data>
|
||||
<data name="DiskUsage_Widget_Template.Read" xml:space="preserve">
|
||||
<value>Read</value>
|
||||
</data>
|
||||
<data name="DiskUsage_Widget_Template.Write" xml:space="preserve">
|
||||
<value>Write</value>
|
||||
</data>
|
||||
<data name="DiskUsage_Widget_Template.Disk_Name" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="Previous_Disk_Title" xml:space="preserve">
|
||||
<value>Previous disk</value>
|
||||
</data>
|
||||
<data name="Next_Disk_Title" xml:space="preserve">
|
||||
<value>Next disk</value>
|
||||
</data>
|
||||
<data name="GPUUsage_Widget_Template.GPU_Usage" xml:space="preserve">
|
||||
<value>Utilization</value>
|
||||
</data>
|
||||
@@ -183,6 +260,9 @@
|
||||
<data name="Network_Usage_Subtitle" xml:space="preserve">
|
||||
<value>Network</value>
|
||||
</data>
|
||||
<data name="Disk_Usage_Subtitle" xml:space="preserve">
|
||||
<value>Disk</value>
|
||||
</data>
|
||||
<data name="GPU_Usage_Subtitle" xml:space="preserve">
|
||||
<value>GPU</value>
|
||||
</data>
|
||||
@@ -257,6 +337,19 @@
|
||||
<data name="Network_Usage_Unknown_Label" xml:space="preserve">
|
||||
<value>Network Usage: ???</value>
|
||||
</data>
|
||||
<data name="Disk_Usage_Title" xml:space="preserve">
|
||||
<value>Disk Usage</value>
|
||||
</data>
|
||||
<data name="Disk_Usage_Label" xml:space="preserve">
|
||||
<value>Disk ({0}): {1}</value>
|
||||
<comment>{0} is the disk interface name, {1} is the usage percentage</comment>
|
||||
</data>
|
||||
<data name="Disk_Usage_Unknown" xml:space="preserve">
|
||||
<value>???</value>
|
||||
</data>
|
||||
<data name="Disk_Usage_Unknown_Label" xml:space="preserve">
|
||||
<value>Disk Usage: ???</value>
|
||||
</data>
|
||||
<data name="GPU_Usage_Title" xml:space="preserve">
|
||||
<value>GPU Usage</value>
|
||||
</data>
|
||||
@@ -331,6 +424,15 @@
|
||||
<data name="Network_Receive_Subtitle" xml:space="preserve">
|
||||
<value>Receive</value>
|
||||
</data>
|
||||
<data name="Disk_Read_Subtitle" xml:space="preserve">
|
||||
<value>Read</value>
|
||||
</data>
|
||||
<data name="Disk_Write_Subtitle" xml:space="preserve">
|
||||
<value>Write</value>
|
||||
</data>
|
||||
<data name="Disk_Active_Time_Subtitle" xml:space="preserve">
|
||||
<value>Active time</value>
|
||||
</data>
|
||||
<data name="Network_Speed_Unit_Setting_Title" xml:space="preserve">
|
||||
<value>Network speed unit</value>
|
||||
</data>
|
||||
@@ -346,4 +448,19 @@
|
||||
<data name="Network_Speed_Unit_BinaryBytesPerSec" xml:space="preserve">
|
||||
<value>Binary bytes per second (KiB/s, MiB/s, GiB/s)</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="Disk_Speed_Unit_Setting_Title" xml:space="preserve">
|
||||
<value>Disk speed unit</value>
|
||||
</data>
|
||||
<data name="Disk_Speed_Unit_Setting_Description" xml:space="preserve">
|
||||
<value>Choose the unit used to display disk transfer speed</value>
|
||||
</data>
|
||||
<data name="Disk_Speed_Unit_BitsPerSec" xml:space="preserve">
|
||||
<value>Bits per second (Kbps, Mbps, Gbps)</value>
|
||||
</data>
|
||||
<data name="Disk_Speed_Unit_BytesPerSec" xml:space="preserve">
|
||||
<value>Bytes per second (KB/s, MB/s, GB/s)</value>
|
||||
</data>
|
||||
<data name="Disk_Speed_Unit_BinaryBytesPerSec" xml:space="preserve">
|
||||
<value>Binary bytes per second (KiB/s, MiB/s, GiB/s)</value>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user