[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:
Jake Scott
2026-07-17 13:56:24 -04:00
committed by GitHub
parent dc330354a0
commit c2d505dd4d
19 changed files with 1015 additions and 128 deletions

View File

@@ -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" />

View File

@@ -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",

View File

@@ -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",

View File

@@ -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));
}
}

View File

@@ -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>

View File

@@ -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,
};

View File

@@ -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)

View File

@@ -37,4 +37,9 @@ public enum DataType
/// Battery related data.
/// </summary>
Battery,
/// <summary>
/// Disk related data.
/// </summary>
Disk,
}

View File

@@ -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();
}
}
}
}

View File

@@ -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;

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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>

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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")]

View File

@@ -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();

View File

@@ -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,

View File

@@ -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>