Add implementation

This commit is contained in:
2014-05-02 16:55:59 -04:00
parent 9c7809dc86
commit 35b8971f10
11 changed files with 521 additions and 64 deletions

View File

@@ -1,6 +1,24 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="ProcessCpuUsageStatusWindow.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
</sectionGroup>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<userSettings>
<ProcessCpuUsageStatusWindow.Properties.Settings>
<setting name="ProcessCount" serializeAs="String">
<value>3</value>
</setting>
<setting name="UpdateInterval" serializeAs="String">
<value>00:00:02</value>
</setting>
<setting name="WindowSettings" serializeAs="String">
<value />
</setting>
</ProcessCpuUsageStatusWindow.Properties.Settings>
</userSettings>
</configuration>

View File

@@ -1,17 +1,23 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows;
namespace ProcessCpuUsageStatusWindow
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
public partial class App
{
private WindowSource _windowSource;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
_windowSource = new WindowSource();
}
protected override void OnExit(ExitEventArgs e)
{
_windowSource.Dispose();
base.OnExit(e);
}
}
}

60
ProcessCpuUsage.cs Normal file
View File

@@ -0,0 +1,60 @@
using System;
using System.Diagnostics;
namespace ProcessCpuUsageStatusWindow
{
public class ProcessCpuUsage
{
#region Properties
public string ProcessName { get; private set; }
public float PercentUsage { get; internal set; }
public DateTime LastFound { get; set; }
public bool UsageValid { get; private set; }
internal CounterSample LastSample { get; set; }
#endregion
#region Constructor
internal ProcessCpuUsage(InstanceData instanceData)
: this(instanceData, DateTime.MinValue)
{ }
internal ProcessCpuUsage(InstanceData instanceData, DateTime timestamp)
{
// Store the process details
ProcessName = instanceData.InstanceName;
// Store the initial data
LastFound = timestamp;
LastSample = instanceData.Sample;
// We start out as not valid
UsageValid = false;
}
#endregion
#region Usage update
internal void UpdateCpuUsage(InstanceData instanceData, DateTime timestamp)
{
// Get the new sample
var newSample = instanceData.Sample;
// Calculate percent usage
PercentUsage = CounterSample.Calculate(LastSample, newSample) / Environment.ProcessorCount;
// Update the last sample and timestmap
LastSample = newSample;
LastFound = timestamp;
// Usage is now valid
UsageValid = true;
}
#endregion
}
}

View File

@@ -34,13 +34,13 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="FloatingStatusWindowLibrary">
<HintPath>..\FloatingStatusWindowLibrary\FloatingStatusWindowLibrary\bin\Debug\FloatingStatusWindowLibrary.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
@@ -57,8 +57,11 @@
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="WindowSource.cs" />
</ItemGroup>
<ItemGroup>
<Compile Include="ProcessCpuUsage.cs" />
<Compile Include="ProcessCpuUsageWatcher.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
@@ -85,6 +88,9 @@
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\ApplicationIcon.ico" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

157
ProcessCpuUsageWatcher.cs Normal file
View File

@@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Timers;
using System.Windows.Threading;
namespace ProcessCpuUsageStatusWindow
{
public class ProcessCpuUsageWatcher
{
#region Update delegate
public delegate void ProcessListUpdatedDelegate(Dictionary<string, ProcessCpuUsage> currentProcessList);
#endregion
#region Member variables
private Dispatcher _dispatcher;
private Timer _processUpdateTimer;
private ProcessListUpdatedDelegate _processListUpdatedCallback;
private PerformanceCounterCategory _processCategory;
#endregion
#region Properties
public Dictionary<string, ProcessCpuUsage> CurrentProcessList;
#endregion
#region Initialize and terminate
public void Initialize(TimeSpan updateInterval, ProcessListUpdatedDelegate callback)
{
_dispatcher = Dispatcher.CurrentDispatcher;
// Create a new dictionary for the process list
CurrentProcessList = new Dictionary<string, ProcessCpuUsage>();
// Get the category for process performance info
_processCategory = PerformanceCounterCategory.GetCategories().FirstOrDefault(category => category.CategoryName == "Process");
if (_processCategory == null)
return;
// Read the entire category
InstanceDataCollectionCollection processCategoryData = _processCategory.ReadCategory();
// Get the processor time data
InstanceDataCollection processorTimeData = processCategoryData["% processor time"];
if (processorTimeData == null || processorTimeData.Values == null)
return;
// Loop over each instance and add it to the list
foreach (InstanceData instanceData in processorTimeData.Values)
{
// Create a new process usage object
var processCpuUsage = new ProcessCpuUsage(instanceData);
// Add to the list
CurrentProcessList.Add(processCpuUsage.ProcessName, processCpuUsage);
}
// Save the update callback
_processListUpdatedCallback = callback;
// Create a timer to update the process list
_processUpdateTimer = new Timer(updateInterval.TotalMilliseconds) { AutoReset = false };
_processUpdateTimer.Elapsed += HandleProcessUpdateTimerElapsed;
_processUpdateTimer.Start();
}
public void Terminate()
{
// Get rid of the timer
_processUpdateTimer.Stop();
_processUpdateTimer.Dispose();
// Clear the callback
_processListUpdatedCallback = null;
// Clear the process list
CurrentProcessList = null;
}
#endregion
#region Timer handling
private void HandleProcessUpdateTimerElapsed(object sender, ElapsedEventArgs e)
{
// Update the current process list
UpdateCurrentProcessList();
// Restart the timer
_processUpdateTimer.Start();
}
#endregion
#region Process list management
private void UpdateCurrentProcessList()
{
// Get a timestamp for the current time that we can use to see if a process was found this check
DateTime checkStart = DateTime.Now;
// Read the entire category
InstanceDataCollectionCollection processCategoryData = _processCategory.ReadCategory();
// Get the processor time data
InstanceDataCollection processorTimeData = processCategoryData["% processor time"];
if (processorTimeData == null || processorTimeData.Values == null)
return;
// Loop over each instance and add it to the list
foreach (InstanceData instanceData in processorTimeData.Values)
{
// See if we already know about this process
if (CurrentProcessList.ContainsKey(instanceData.InstanceName))
{
// Get the previous process usage object
ProcessCpuUsage processCpuUsage = CurrentProcessList[instanceData.InstanceName];
// Update the CPU usage with new data
processCpuUsage.UpdateCpuUsage(instanceData, checkStart);
}
else
{
// Create a new CPU usage object
var processCpuUsage = new ProcessCpuUsage(instanceData, checkStart);
// Add it to the list
CurrentProcessList.Add(processCpuUsage.ProcessName, processCpuUsage);
}
}
// Build a list of cached processes we haven't found this check
var oldProcessList = (from processCpuUsage in CurrentProcessList
where processCpuUsage.Value.LastFound != checkStart
select processCpuUsage.Key).ToList();
// Loop over the list and remove the old process
foreach (var key in oldProcessList)
CurrentProcessList.Remove(key);
// Invoke the callback with the new current process list
_dispatcher.InvokeAsync(() => _processListUpdatedCallback.Invoke(CurrentProcessList));
}
#endregion
}
}

View File

@@ -8,10 +8,10 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace ProcessCpuUsageStatusWindow.Properties
{
namespace ProcessCpuUsageStatusWindow.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@@ -22,50 +22,88 @@ namespace ProcessCpuUsageStatusWindow.Properties
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources
{
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources()
{
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if ((resourceMan == null))
{
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ProcessCpuUsageStatusWindow.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set
{
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
/// </summary>
internal static System.Drawing.Icon ApplicationIcon {
get {
object obj = ResourceManager.GetObject("ApplicationIcon", resourceCulture);
return ((System.Drawing.Icon)(obj));
}
}
/// <summary>
/// Looks up a localized string similar to CPU: {0,4:f1}% - Total.
/// </summary>
internal static string FooterLine {
get {
return ResourceManager.GetString("FooterLine", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
internal static string HeaderLine {
get {
return ResourceManager.GetString("HeaderLine", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Loading....
/// </summary>
internal static string Loading {
get {
return ResourceManager.GetString("Loading", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to CPU: {1,4:f1}% - {0}.
/// </summary>
internal static string ProcessLine {
get {
return ResourceManager.GetString("ProcessLine", resourceCulture);
}
}
}
}

View File

@@ -46,7 +46,7 @@
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
@@ -60,6 +60,7 @@
: 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">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
@@ -68,9 +69,10 @@
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
@@ -85,9 +87,10 @@
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
@@ -109,9 +112,25 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="ApplicationIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\ApplicationIcon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="FooterLine" xml:space="preserve">
<value>CPU: {0,4:f1}% - Total</value>
</data>
<data name="HeaderLine" xml:space="preserve">
<value />
</data>
<data name="Loading" xml:space="preserve">
<value>Loading...</value>
</data>
<data name="ProcessLine" xml:space="preserve">
<value>CPU: {1,4:f1}% - {0}</value>
</data>
</root>

View File

@@ -8,23 +8,55 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace ProcessCpuUsageStatusWindow.Properties
{
namespace ProcessCpuUsageStatusWindow.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
private static Settings defaultInstance = ((Settings) (global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("3")]
public int ProcessCount {
get {
return ((int)(this["ProcessCount"]));
}
set {
this["ProcessCount"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("00:00:02")]
public global::System.TimeSpan UpdateInterval {
get {
return ((global::System.TimeSpan)(this["UpdateInterval"]));
}
set {
this["UpdateInterval"] = value;
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("")]
public string WindowSettings {
get {
return ((string)(this["WindowSettings"]));
}
set {
this["WindowSettings"] = value;
}
}
}
}

View File

@@ -1,7 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="ProcessCpuUsageStatusWindow.Properties" GeneratedClassName="Settings">
<Profiles />
<Settings>
<Setting Name="ProcessCount" Type="System.Int32" Scope="User">
<Value Profile="(Default)">3</Value>
</Setting>
<Setting Name="UpdateInterval" Type="System.TimeSpan" Scope="User">
<Value Profile="(Default)">00:00:02</Value>
</Setting>
<Setting Name="WindowSettings" Type="System.String" Scope="User">
<Value Profile="(Default)" />
</Setting>
</Settings>
</SettingsFile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

113
WindowSource.cs Normal file
View File

@@ -0,0 +1,113 @@
using FloatingStatusWindowLibrary;
using ProcessCpuUsageStatusWindow.Properties;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ProcessCpuUsageStatusWindow
{
public class WindowSource : IWindowSource, IDisposable
{
private readonly FloatingStatusWindow _floatingStatusWindow;
private readonly ProcessCpuUsageWatcher _processCpuUsageWatcher;
internal WindowSource()
{
_floatingStatusWindow = new FloatingStatusWindow(this);
_floatingStatusWindow.SetText(Resources.Loading);
_processCpuUsageWatcher = new ProcessCpuUsageWatcher();
_processCpuUsageWatcher.Initialize(Settings.Default.UpdateInterval, UpdateDisplay);
}
public void Dispose()
{
_processCpuUsageWatcher.Terminate();
_floatingStatusWindow.Save();
_floatingStatusWindow.Dispose();
}
public string Name
{
get { return "Process CPU Usage"; }
}
public System.Drawing.Icon Icon
{
get { return Resources.ApplicationIcon; }
}
public string WindowSettings
{
get
{
return Settings.Default.WindowSettings;
}
set
{
Settings.Default.WindowSettings = value;
Settings.Default.Save();
}
}
#region Display updating
private static class PredefinedProcessName
{
public const string Total = "_Total";
public const string Idle = "Idle";
}
private void UpdateDisplay(Dictionary<string, ProcessCpuUsage> currentProcessList)
{
// Filter the process list to valid ones and exclude the idle and total values
var validProcessList = (currentProcessList.Values.Where(
process =>
process.UsageValid && process.ProcessName != PredefinedProcessName.Total &&
process.ProcessName != PredefinedProcessName.Idle)).ToList();
// Calculate the total usage by adding up all the processes we know about
var totalUsage = validProcessList.Sum(process => process.PercentUsage);
// Sort the process list by usage and take only the top few
var sortedProcessList = (validProcessList.OrderByDescending(process => process.PercentUsage)).Take(Settings.Default.ProcessCount);
// Create a new string builder
var stringBuilder = new StringBuilder();
// Add the header line (if any)
if (Resources.HeaderLine.Length > 0)
{
stringBuilder.AppendFormat(Resources.HeaderLine, totalUsage);
stringBuilder.AppendLine();
stringBuilder.AppendLine();
}
// Loop over all processes in the sorted list
foreach (ProcessCpuUsage processCpuUsage in sortedProcessList)
{
// Move to the next line if it isn't the first line
if (stringBuilder.Length != 0)
stringBuilder.AppendLine();
// Format the process information into a string to display
stringBuilder.AppendFormat(Resources.ProcessLine, processCpuUsage.ProcessName, processCpuUsage.PercentUsage);
}
// Add the footer line (if any)
if (Resources.FooterLine.Length > 0)
{
stringBuilder.AppendLine();
stringBuilder.AppendLine();
stringBuilder.AppendFormat(Resources.FooterLine, totalUsage);
}
// Update the window with the text
_floatingStatusWindow.SetText(stringBuilder.ToString());
}
#endregion
}
}