Click here to Skip to main content
Click here to Skip to main content

CPUAlert: Save your CPU from Burning Hot and Battery Running Out Quickly

, 11 Jun 2011
Rate this:
Please Sign up or sign in to vote.
CPUAlert monitors CPU and Memory consumption of processes and alerts you when they are taking too much consistently and gives you an option to recycle or terminate
CPU Alert popup

Introduction

If your computer is running hot or battery is running out quickly, then it is most likely due to some application or process consuming high CPU or memory. If you keep running applications for a long time, for example, Outlook, then it continues to grow in memory consumption and does not free up memory efficiently. As a result, your computer runs out of physical memory and other applications run slower. Sometimes Outlook, browser, image editing applications or some other applications start taking full CPU as they get into some infinite loop and make your CPU hot and you get sluggish performance from your application.

CPUAlert is an application that monitors CPU and memory consumption of applications and alerts you if some application is consistently taking high CPU or high memory. It will not only save your CPU and battery’s lifetime but also make your computer run smoothly and let your active applications run as fast as possible.

How to Use the Application

While it is running, if some process is consuming more than 200 MB memory, it will show you an alert:

image

Here you can see that my Outlook is taking 244 MB of physical RAM.

You can either postpone the alert for 5 mins (just press ESC), or ignore the process permanently so that you no longer receive alerts for the process anymore, or you can close it and reclaim memory.

The handy feature is “Restart” which closes the application and starts again. This generally frees up memory that clogs up the process.

The same alert will come up if some process is consuming more than 30% CPU for over 5 mins.

You can configure all these settings like what’s the tolerable limit for CPU and memory, how frequently to show the alert, how long to wait before closing an application, etc. by right clicking on the Task bar icon and choosing Settings.

image

image

The application registers itself to start at Windows startup. If you want to remove it from Windows Startup, just delete the application shortcut from Start Menu->All Programs->Startup menu.

Using the Code

CPU Alert is a small project. The important files are:

  • MonitorCPUForm.cs: This is the main window which is loaded when the app starts. It hides itself in the system tray and keeps the app running in background. It also hosts a "Settings" view which lets you change the CPU and Memory threshold, alert interval, etc. It also orchestrates the operations and shows the necessary alerts.
  • KillProcessForm.cs: It has just the UI for the alert that you see in the above popup. No intelligence.
  • Monitor.cs: This is the important class that does the monitoring, measurement of CPU and Memory usage and raises the alert. If you want to have this kind of feature in your application, you can reuse this class.

Let's look at the Monitor.cs class first which has the brainpower to monitor CPU and memory. It uses WMI to monitor the processes. I tried using Process class first and tried to access the TotalProcessorTime but it gave me "Access Denied" for processes like mysqld. For some processes, I did not get the WorkingSet and so on. Then I tried using PerformanceCounter to get the CPU and Memory usage of process. It was too expensive to query Performance Counter for each process. So, I had to rely on WMI to give me the information.

private ManagementObjectSearcher _Searcher = 
            new ManagementObjectSearcher("root\\CIMV2",
            "SELECT IDProcess, Name, PercentProcessorTime, Description, 
	   WorkingSet FROM Win32_PerfFormattedData_PerfProc_Process");
.
.
.
private List<ProcessInfo> GetUsage()
{
    var processes = new List<ProcessInfo>();
    foreach (ManagementObject queryObj in _Searcher.Get())
    {
        var process = new ProcessInfo
        {
            Id = Convert.ToInt32(queryObj["IDProcess"]),
            Name = Convert.ToString(queryObj["Name"]),
            CpuUsage = Convert.ToInt32(queryObj["PercentProcessorTime"]),
            Description = Convert.ToString(queryObj["Description"]),
            WorkingSet = Convert.ToInt64(queryObj["WorkingSet"]),
        };

        if (string.IsNullOrEmpty(process.Description))
            process.Title = process.Name;
        else
            process.Title = process.Description;

        if (process.Id > 0)
            processes.Add(process);
    }

    return processes;
}  

Pretty straight forward. The only cool thing to learn here is I declared the ManageObjectSearcher as a private variable, not a local variable to this function because it seems creating and disposing it every time the GetUsage function is called is very expensive. CPU was getting 20% for about 30 seconds on WMIpserv.exe whenever I tried to create and dispose the searcher.

Closing a process turned out to be a R&D challenge. You can kill a process by calling Process.Kill, but that terminates the process abnormally. If you are killing Outlook this way, it causes data corruption. So, you have to gracefully close the application. However, if some app is stuck and taking 100% CPU, trying to close it gracefully does not work. You have to take drastic measures. In that case, killing it is only the solution.

So, first I try to close the process gently:

/// <summary>
/// Closes the process by sending Shutdown message to the main window. 
// Then it starts a timer to check if the process is really closed. 
/// </summary>
/// <param name="processToKill">The process to kill.</param>
private void CloseProcess(ProcessInfo processToKill)
{
    try
    {
        Process process = Process.GetProcessById(processToKill.Id);
        processToKill.Path = process.MainModule.FileName;
        process.CloseMainWindow();
    }
    catch (Exception closeException)
    {
        if (MessageBox.Show(this, "Unable to close process: " 
            + (processToKill.Title) + Environment.NewLine
            + closeException.Message + Environment.NewLine
            + "Do you want to kill it?", "Unable to close process", 
            MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)
        {
            KillProcess(processToKill);
        }
    }

    // Check back after a while if the process is still running. 
    //If it's running kill it
    CheckAfterAWhileIfItsStillRunning(processToKill);
} 

Here it calls the Process.CloseMainWindow function to send a shutdown message to the process. If the process has a visual window, it will receive the message and hopefully it will close itself soon. If there's no visual window, then it will not receive the message and it will not shutdown. So, we will have to kill it.

Even if some process receives the shutdown message, it may not terminate properly if it's stuck in some infinite loop. In that case, we need to check back after a while if it's still running and then kill it.

private void KillProcess(ProcessInfo processToKill)
{
try
{
	foreach (Process process in Process.GetProcessesByName(processToKill.Name))
	{
		// Check if the process is still running
		if (process.Id == processToKill.Id)
		{
			if (!process.HasExited)
			{
				process.Kill();
				process.Close();
			}
		}
	}

	// If user has requested the process to be restarted, then restart it.
	if (processToKill.CanRestart)
		Process.Start(processToKill.Path);

}
catch (Exception killException)
{
	MessageBox.Show(this, "Unable to kill process: " 
	+ (processToKill.Title)
	+ Environment.NewLine + "You should restart your computer", 
	"Unable to kill process",
	MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}

This function is fired from a timer. We wait for a while and then go for the kill. First check, if the process is still lying around. If it is, then kill it.

Another interesting topic is memory usage. As you know, Windows Forms apps when started, take up about 10 MB to 15 MB memory. It's too much for a utility which is running in the background and trying to save you some memory. It's because when .NET WinForms libraries load, they load a lot of stuff hoping you would need it and it would save additional load time. So, I had to force .NET to flush out any unused stuff. I have a MemoryHelper which does this:

static class MemoryHelper
{
	[DllImport("psapi.dll")]
	static extern int EmptyWorkingSet(IntPtr hwProc);

	public static void ClearMemory()
	{
		try
		{
			GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
		   
			EmptyWorkingSet(Process.GetCurrentProcess().Handle);
		}
		catch
		{
		}
	}
} 

First it forces a Garbage Collection, then it calls the EmptyWorkingSet function on psapi.dll to flush out the working set, which is the physical memory allocation. I don't know whether or not it works, but at least Taskbar shows that the app now takes only 5 MB memory. Super!

Conclusion

Hope CPUAlert will save you from slowing down your PC or burning out laptop batteries quickly. If you find it useful, please spread the love. Start by voting for this article.

History

  • 2nd March, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author


Comments and Discussions

 
GeneralMy vote of 5 PinmemberAbinash Bishoyi21-Feb-13 20:59 
GeneralMy vote of 5 PinmemberPhillip46511-Jun-11 8:09 
GeneralMy vote of 5 PinmemberSChristmas17-Mar-11 0:32 
Question"Warning! Process taking high CPU" Message PinmemberSpasticus8-Mar-10 22:50 
AnswerRe: "Warning! Process taking high CPU" Message PinmvpOmar Al Zabir8-Mar-10 22:53 
AnswerRe: "Warning! Process taking high CPU" Message PinmemberSpasticus10-Mar-10 19:47 
GeneralRe: "Warning! Process taking high CPU" Message PinmvpOmar Al Zabir10-Mar-10 21:18 
AnswerRe: "Warning! Process taking high CPU" Message PinmemberSpasticus11-Mar-10 20:35 
GeneralRe: "Warning! Process taking high CPU" Message PinmvpOmar Al Zabir12-Mar-10 0:02 
GeneralProperty Names PinmemberTrendyTim8-Mar-10 19:19 
GeneralRe: Property Names PinmvpOmar Al Zabir8-Mar-10 22:56 
GeneralRe: Property Names PinmemberTrendyTim9-Mar-10 6:01 
Ahh sorry, didn't realize it was a auto generated settings class.
 
This is a quick and dirty way i would get around MSs short sightedness it (probably needs a bit more work for general use, but gets the job done), using a proxy class that implements ICustomTypeDescriptor (if VB bothers you, you can convert it with http://www.developerfusion.com/tools/convert/vb-to-csharp/), a much easier way though more maintenance if you add properties regularly would probably be to just create a wrapper class and add new properties to it at the same time as adding new settings.
 
Its pretty self explanatory, but if you would like any clarification on any of it, just let me know.
 

You could even have it so you could prefix the property name with something like USRVIS_ to signify the user can see the setting, or set up advanced config options that are usually hidden from some users, and only add it to the proxy if that existed (and strip it from the display name), or filter the properties based on the existence of the System.Configuration.ApplicationScopedSettingAttribute or System.Configuration.UserScopedSettingAttribute to show user/app wide config settings separately (or place them in difference categories).
 

Is it overkill just to replace some _ with a space, maybe, but then i have been known to be fussy about some things. Anyway, hope it helps someone.
 
Public Class PropProxy
    Implements System.ComponentModel.ICustomTypeDescriptor
    Private _proxyFor As Object
    Sub New(ByVal proxyFor As Object)
        _proxyFor = proxyFor
    End Sub
 
    Public Function GetAttributes() As System.ComponentModel.AttributeCollection Implements System.ComponentModel.ICustomTypeDescriptor.GetAttributes
 
    End Function
 
    Public Function GetClassName() As String Implements System.ComponentModel.ICustomTypeDescriptor.GetClassName
 
    End Function
 
    Public Function GetComponentName() As String Implements System.ComponentModel.ICustomTypeDescriptor.GetComponentName
 
    End Function
 
    Public Function GetConverter() As System.ComponentModel.TypeConverter Implements System.ComponentModel.ICustomTypeDescriptor.GetConverter
 
    End Function
 
    Public Function GetDefaultEvent() As System.ComponentModel.EventDescriptor Implements System.ComponentModel.ICustomTypeDescriptor.GetDefaultEvent
 
    End Function
 
    Public Function GetDefaultProperty() As System.ComponentModel.PropertyDescriptor Implements System.ComponentModel.ICustomTypeDescriptor.GetDefaultProperty
 
    End Function
 
    Public Function GetEditor(ByVal editorBaseType As System.Type) As Object Implements System.ComponentModel.ICustomTypeDescriptor.GetEditor
 
    End Function
 
    Public Function GetEvents() As System.ComponentModel.EventDescriptorCollection Implements System.ComponentModel.ICustomTypeDescriptor.GetEvents
 
    End Function
 
    Public Function GetEvents(ByVal attributes() As System.Attribute) As System.ComponentModel.EventDescriptorCollection Implements System.ComponentModel.ICustomTypeDescriptor.GetEvents
 
    End Function
 
    Public Function GetProperties() As System.ComponentModel.PropertyDescriptorCollection Implements System.ComponentModel.ICustomTypeDescriptor.GetProperties
        Return GetProperties(Nothing)
    End Function
 
    Public Function GetProperties(ByVal attributes() As System.Attribute) As System.ComponentModel.PropertyDescriptorCollection Implements System.ComponentModel.ICustomTypeDescriptor.GetProperties
        Dim pdc As New System.ComponentModel.PropertyDescriptorCollection(Nothing)
        Dim propInfos() As System.Reflection.PropertyInfo = _proxyFor.GetType.GetProperties(Reflection.BindingFlags.Public Or Reflection.BindingFlags.Instance)
 
        For Each pi As System.Reflection.PropertyInfo In propInfos
            If pi.GetIndexParameters.Length = 0 Then 'doesnt like getValue on indexed params like item
                If attributes IsNot Nothing AndAlso IsBrowsable(pi) Then
                    Dim pd As New PropertyWrapperDescriptor(pi, _proxyFor, pi.Name.Replace("_", " "))
                    pdc.Add(pd)
                End If
            End If
        Next
 
        Return pdc
    End Function
    Private Function IsBrowsable(ByVal pi As Reflection.PropertyInfo) As Boolean
        Dim atts() As Object = pi.GetCustomAttributes(GetType(System.ComponentModel.BrowsableAttribute), True)
        For Each o As Object In atts
            Dim browsAtt As System.ComponentModel.BrowsableAttribute = TryCast(o, System.ComponentModel.BrowsableAttribute)
            If browsAtt IsNot Nothing AndAlso Not browsAtt.Browsable Then
                Return False
            End If
        Next
 
        Return True
    End Function
 
    Public Function GetPropertyOwner(ByVal pd As System.ComponentModel.PropertyDescriptor) As Object Implements System.ComponentModel.ICustomTypeDescriptor.GetPropertyOwner
        Return _proxyFor
    End Function
 
    Public Class PropertyWrapperDescriptor
        Inherits System.ComponentModel.PropertyDescriptor
 
        Private _owner As Object
        Private _wrappedProperty As System.Reflection.PropertyInfo
        Sub New(ByVal wrappedProperty As Reflection.PropertyInfo, ByVal owner As Object, ByVal displayName As String)
            MyBase.New(wrappedProperty.Name, New System.Attribute() {New System.ComponentModel.DisplayNameAttribute(displayName)})
            _owner = owner
            _wrappedProperty = wrappedProperty
        End Sub
 
        Public Overrides Function CanResetValue(ByVal component As Object) As Boolean
            Return False
        End Function
 
        Public Overrides ReadOnly Property ComponentType() As System.Type
            Get
                Return _owner.GetType
            End Get
        End Property
 
        Public Overrides Function GetValue(ByVal component As Object) As Object
            Return _wrappedProperty.GetValue(component, Nothing)
        End Function
 
        Public Overrides ReadOnly Property IsReadOnly() As Boolean
            Get
                Return Not _wrappedProperty.CanWrite
            End Get
        End Property
 
        Public Overrides ReadOnly Property PropertyType() As System.Type
            Get
                Return _wrappedProperty.PropertyType
            End Get
        End Property
 
        Public Overrides Sub ResetValue(ByVal component As Object)
 
        End Sub
 
        Public Overrides Sub SetValue(ByVal component As Object, ByVal value As Object)
            _wrappedProperty.SetValue(component, value, Nothing)
        End Sub
 
        Public Overrides Function ShouldSerializeValue(ByVal component As Object) As Boolean
            Return False
        End Function
    End Class
End Class

GeneralMy vote of 2 PinmemberEd Brey8-Mar-10 15:36 
GeneralNice post PinmvpMd. Marufuzzaman2-Mar-10 18:44 
GeneralI like your work Omar PinmvpSacha Barber2-Mar-10 3:57 
GeneralThanks Pinmemberguyinfun2-Mar-10 3:26 
GeneralSource project PinmemberLOUIS Christian2-Mar-10 2:12 
GeneralRe: Source project PinmvpOmar Al Zabir2-Mar-10 2:19 
GeneralRe: Source project PinmemberLOUIS Christian2-Mar-10 2:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 11 Jun 2011
Article Copyright 2010 by Omar Al Zabir
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid