Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / WPF

Monitoring Process Statistics in C# WPF

Rate me:
Please Sign up or sign in to vote.
4.85/5 (24 votes)
25 Jul 2009CPOL3 min read 127K   7.8K   79   15
In this article, I will explain the performance monitoring of any instance in the Form of statistics and graphs as well.

Introduction

In this article, I will explain the performance monitoring of any instance. In the sample application (its output is shown below), I will use the regedit process, however you can use any instance and monitor its statistics using different ways. On the basis of that, you can display the statistics graphs as well.

Process_Statistics2.jpg - Click to enlarge image

In Windows Task Manager, the memory related to a specific process which we are usually observing is the application working set memory. This is the memory used by all the virtual memory pages that have been loaded by a process and have not been swapped to disk for any reason. We can view this memory using "Working Set - Private" PerformanceCounter.

For physical memory usage, we can monitor this by using the "Working Set" PerformanceCounter. We can also get this value using the Process.WorkingSet64 property of System.Diagnostics namespace. But you must have to call Process.Refresh method before using property of Process class otherwise it may not give you the accurate value. The recommended way is to use performance counter.

Similarly to view the private memory size of a process, we have "Private Bytes" PerformanceCounter and the same thing is Process.PrivateMemorySize64 property value in System.Diagnostics namespace.

For processor time related to specific instance, we have different properties in Process class. We also have different performance counters for usage of processor time like "% Processor Time", "% User Time" and "% Privileged Time". In case of processor time’s performance counter, first time it will give you the value zero. To avoid this thing, call PerformanceCounter’s NextValue() then give some delay (like one second because usually the provider of counter value updates once in a second) and then call NextValue() again because it will calculate these two values. In this way, you will get the correct value of PerformanceCounter. I will demonstrate this thing as well in a sample application.

Using the Code

Here I wrote a sample application in C# WPF. This will show different property values of process class in System.Diagnostics namespace and PerformanceCounter values as well. It will show “regedit” process statistics related to memory and processor. It will update these statistics values in every second (you can change the instance instead of regedit and use this sample code for any process). There is a piece of code in MemoryandProcessCheck() function which displays all the possible counters of a process (regedit in this example code. I have written down all the counters of regedit process at the end of this article). Adjust the appearance of listView1 in Window1.xaml and sets its visibility property to visible if you want to see all the possible counters of regedit instance. (If you would like to watch all of these features with some other process instead of regedit, then just change the process name in the start of the file). Finally I am also displaying the graph to show the memory and processor usage for this specific instance. For graph/char display, I am using Microsoft.Research.DynamicDataDisplay namespace. Open regedit before running this sample application or run that specific instance for which you want to watch performance statistics. For complete source code, please download the attached MemoryPerformanceMonitoring.zip.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Threading;

namespace MemoryPerformanceMonitoring
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>

public partial class Window1 : Window
{
//instance name for which you want to monitor Statistics
string sProcName = "regedit"; 
            
ObservableCollection<StatisticsData> _StatisticsCollection =
new ObservableCollection<StatisticsData>();

public ObservableCollection<StatisticsData> StatisticsCollection
{ 
    get { return _StatisticsCollection; } 
}
 
public Window1()
{
    InitializeComponent();
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    //create child thread for statistics display
    Thread MonitorThd = new Thread(this.StatisticsMonitoringThread);
    MonitorThd.Start();

    // MemoryandProcessCheck();
}

public void StatisticsMonitoringThread(object obj)
{
    MonitorMemoryandProcess();
}

//query whether the specified process running or not
private Process CurrentlyRunning(string sProcessName)
{
    //get a list of all running processes on current system
    Process[] Processes = Process.GetProcesses();

    //Iterate to every process to check if it is out required process
    foreach (Process SingleProcess in Processes)
    {

        if (SingleProcess.ProcessName.Contains(sProcessName))
        {
            //process found 
            return SingleProcess;
        }
    }

    //Process not found
    return null;
}

//main function for all processing
private bool MonitorMemoryandProcess()
{ 
    string ProcessStatus = null;
    string[] str = new string[10];

    Process ReqProcess;

    try
    { 
        GC.GetTotalMemory(true); // how much GC total use 
        ReqProcess = CurrentlyRunning(sProcName);

    do
    {
        if (ReqProcess != null)
        {
            // Refresh the current process property values.
            ReqProcess.Refresh(); 

            if (ReqProcess.Responding)
            {
                ProcessStatus = "Running";
            }
            else
            {
                ProcessStatus = "Not Responding";
            }

        PerformanceCounter totalProcessorTimeCounter = 
        	new PerformanceCounter("Process", 
        	"% Processor Time", ReqProcess.ProcessName);
        PerformanceCounter UserProcessorTimeCounter = 
        	new PerformanceCounter("Process", 
        	"% User Time", ReqProcess.ProcessName);
        PerformanceCounter PrivilegedProcessorTimeCounter = 
        	new PerformanceCounter("Process", 
        	"% Privileged Time", ReqProcess.ProcessName);
        PerformanceCounter WorkingSetMemoryCounter = 
        	new PerformanceCounter("Process", 
        	"Working Set", ReqProcess.ProcessName);
        PerformanceCounter WorkingSetPeakMemoryCounter = 
        	new PerformanceCounter("Process", 
        	"Working Set Peak", ReqProcess.ProcessName);

        PerformanceCounter ThreadCountCounter = 
        	new PerformanceCounter("Process", 
        	"Thread Count", ReqProcess.ProcessName);
        PerformanceCounter WorkingSetPrivateMemoryCounter = 
        	new PerformanceCounter("Process", 
        	"Working Set - Private", ReqProcess.ProcessName);

        PerformanceCounter HandleCountCounter = 
        	new PerformanceCounter("Process", 
        	"Handle Count", ReqProcess.ProcessName);
 
        totalProcessorTimeCounter.NextValue();
        UserProcessorTimeCounter.NextValue();
        PrivilegedProcessorTimeCounter.NextValue();

        System.Threading.Thread.Sleep(1000);// 1 second wait

        // Dispatcher.Invoke(new ClearListViewFromOutside(ClearListView));

        str[0] = ReqProcess.ProcessName;
        str[1] = ProcessStatus;
        str[2] = (WorkingSetMemoryCounter.NextValue() / 1024) + "K";
        str[3] = (WorkingSetPrivateMemoryCounter.NextValue() / 1024) + "K";
        str[4] = (WorkingSetPeakMemoryCounter.NextValue() / 1024) + "K";
        str[5] = (ThreadCountCounter.NextValue()).ToString();
        str[6] = (HandleCountCounter.NextValue()).ToString();
        str[7] = (totalProcessorTimeCounter.NextValue()).ToString();
        str[8] = (UserProcessorTimeCounter.NextValue()).ToString();
        str[9] = (PrivilegedProcessorTimeCounter.NextValue()).ToString(); 
        }
        else
        {
            str[0] = sProcName;
            str[1] = "Not Started";
            str[2] = "";
            str[3] = "";
            str[4] = "";
            str[5] = "";
            str[6] = "";
            str[7] = "";
            str[8] = "";
            str[9] = ""; 
        }

        Dispatcher.Invoke(new UpdateGUIOutsideFeedbackMessage
        	(UpdateGUIOutsideFeedbackMsg), new object[] { str });

    }while (true); //infinite loop

    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Performance Monitoring Statistics Exception ", 
        	MessageBoxButton.OK, MessageBoxImage.Error);
        return false;
    }

    return true;
}
public delegate void UpdateGUIOutsideFeedbackMessage(string[] msg);
public void UpdateGUIOutsideFeedbackMsg(string[] msg)
{ 
    Mutex firstMutex = new Mutex(false);
    firstMutex.WaitOne();

    //first clear the previous value and than add new one
    StatisticsCollection.Clear(); 
    _StatisticsCollection.Add(new StatisticsData
    {
        ProcessName = msg[0],
        ProcessRunningStatus = msg[1],
        WorkingSetMemory = msg[2],
        WorkingSetPrivateMemory = msg[3],
        WorkingSetPeak = msg[4],
        ThreadCount = msg[5],
        HandleCount = msg[6],
        TotalProcessorTime = msg[7],
        UserProcessorTime = msg[8],
        PrivilegedProcessorTime = msg[9]
    });

     firstMutex.Close();
}

public delegate void ClearListViewFromOutside();

public void ClearListView()
{
    StatisticsCollection.Clear();
}

private void CloseButton_Click(object sender, RoutedEventArgs e)
{
    //forcefully destroy the application
    Environment.Exit(0);
}

private bool MemoryandProcessCheck()
{
    Process ReqProcess;
   try{

    GC.GetTotalMemory(true); // how much GC total use
    ReqProcess = CurrentlyRunning(sProcName); 

    if (ReqProcess != null)
    {
        // calculate the CPU load
        System.TimeSpan CPULoad = (DateTime.Now - ReqProcess.StartTime); 
        listView1.Items.Add("CPU load: " + 
        	(ReqProcess.TotalProcessorTime.TotalMilliseconds / 
        	CPULoad.TotalMilliseconds) * 100);
        
        PerformanceCounter TotalProcessorTimeCounter = 
        	new PerformanceCounter("Process", 
        	"% Processor Time", ReqProcess.ProcessName);
        PerformanceCounter ProcessorUserTimeCounter = 
        	new PerformanceCounter("Process", 
        	"% User Time", ReqProcess.ProcessName);
        PerformanceCounter ProcessorPrivilegedTimeCounter = 
        	new PerformanceCounter("Process", 
        	"% Privileged Time", ReqProcess.ProcessName);

        PerformanceCounter ElapsedTimeCounter = 
        	new PerformanceCounter("Process", 
        	"Elapsed Time", ReqProcess.ProcessName);

        PerformanceCounter VirtualBytesPeakMemoryCounter = 
        	new PerformanceCounter("Process", 
        	"Virtual Bytes Peak", ReqProcess.ProcessName);
        PerformanceCounter VirtualBytesMemoryCounter = 
        	new PerformanceCounter("Process", 
        	"Virtual Bytes", ReqProcess.ProcessName);
        PerformanceCounter WorkingSetMemoryCounter = 
        	new PerformanceCounter("Process", 
        	"Working Set", ReqProcess.ProcessName);
        PerformanceCounter WorkingSetPeakMemoryCounter = 
        	new PerformanceCounter("Process", 
        	"Working Set Peak", ReqProcess.ProcessName);
        PerformanceCounter PrivateBytesMemoryCounter = 
        	new PerformanceCounter("Process", 
        	"Private Bytes", ReqProcess.ProcessName);
        PerformanceCounter WorkingSetPrivateMemoryCounter = 
        	new PerformanceCounter("Process", 
        	"Working Set - Private", ReqProcess.ProcessName);
        PerformanceCounter PageFileBytesPeakMemoryCounter = 
        	new PerformanceCounter("Process", 
        	"Page File Bytes Peak", ReqProcess.ProcessName);

        PerformanceCounter ThreadCountCounter = 
        	new PerformanceCounter("Process", 
        	"Thread Count", ReqProcess.ProcessName);
        PerformanceCounter HandleCountCounter = 
        	new PerformanceCounter("Process", 
        	"Handle Count", ReqProcess.ProcessName);

         TotalProcessorTimeCounter.NextValue();
        ProcessorUserTimeCounter.NextValue();
        ProcessorPrivilegedTimeCounter.NextValue();

        //if there is no wait for 1 second after the first call 
        //to NextValue() than processing time value will not be correct.
        System.Threading.Thread.Sleep(1000);// 1 second wait

        if (!ReqProcess.HasExited)
        {
            // Refresh the process property values.
            ReqProcess.Refresh();

            // Display process statistics related to memory.
            listView1.Items.Add(ReqProcess.ProcessName);
            listView1.Items.Add("******************************");
            listView1.Items.Add("Working Set: " + 
            	(WorkingSetMemoryCounter.NextValue() / 1024) + 
            	"K"); // more efficient. update quickly as compare to 
            		//ReqProcess.WorkingSet64 if Process's Refresh() did not call
            listView1.Items.Add("Physical memory usage(Working Set memory): 
            	" + ReqProcess.WorkingSet64 / 1024 + "K");
            listView1.Items.Add("Working Set - Private: " + 
            	(WorkingSetPrivateMemoryCounter.NextValue() / 1024) + "K");
            listView1.Items.Add("Private Memory Size: " + 
            	ReqProcess.PrivateMemorySize64 / 1024 + 
            	"K"); // usually same with PagedMemorySize64
            listView1.Items.Add("Private Bytes: " + 
            	(PrivateBytesMemoryCounter.NextValue() / 1024) + "K");
            listView1.Items.Add("Virtual memory paging file
            	(Process using RAM): " + ReqProcess.PagedMemorySize64 / 1024 + "K"); 
            listView1.Items.Add("Working Set Peak: " + 
            	(WorkingSetPeakMemoryCounter.NextValue() / 1024) + 
            	"K"); //same as peakWorkingSet
            listView1.Items.Add("Peak physical memory usage: " + 
            	ReqProcess.PeakWorkingSet64 / 1024 + "K");

             listView1.Items.Add("Thread Count: " + 
             	ThreadCountCounter.NextValue()); // how many threads are
            listView1.Items.Add("Handle Count: " + 
            	HandleCountCounter.NextValue()); // how many handles

             //The amount of system memory, in bytes, allocated for the 
             //associated process that can be written to the virtual memory paging file.
            listView1.Items.Add("Page System Memory Size: " + 
            	ReqProcess.PagedSystemMemorySize64 / 1024 + "K");

            //The amount of system memory, in bytes, allocated for the 
            //associated process that cannot be written to the virtual memory paging file.
            listView1.Items.Add("Nonpage System Memory Size: " + 
            	ReqProcess.NonpagedSystemMemorySize64 / 1024 + "K");
            listView1.Items.Add("Virtual Memory: " + 
            	ReqProcess.VirtualMemorySize64 / 1024 + "K");
            listView1.Items.Add("Virtual Bytes: " + 
            	(VirtualBytesMemoryCounter.NextValue() / 1024) + "K");
            listView1.Items.Add("Virtual Bytes Peak: " + 
            	(VirtualBytesPeakMemoryCounter.NextValue() / 1024) + "K");
            listView1.Items.Add("Peak Virtual Memory usage: " + 
            	ReqProcess.PeakVirtualMemorySize64 / 1024 + "K");

             listView1.Items.Add("Page File Bytes Peak: " + 
             	(PageFileBytesPeakMemoryCounter.NextValue() / 1024) + "K");
            listView1.Items.Add("Peak virtual memory paging file usage: " + 
            	ReqProcess.PeakPagedMemorySize64 / 1024 + "K");
 
            if (ReqProcess.Responding)
            {
                listView1.Items.Add("Status = Running");
            }
            else
            {
                listView1.Items.Add("Status = Not Responding");
            }

            //Display process statistics related to Processor
            listView1.Items.Add("%Processor Time: " + 
            		TotalProcessorTimeCounter.NextValue());
            listView1.Items.Add("%User Time: " + 
            		ProcessorUserTimeCounter.NextValue());
            listView1.Items.Add("%Privileged Time: " + 
            		ProcessorPrivilegedTimeCounter.NextValue());
            listView1.Items.Add("Elapsed Time: " + 
            		ElapsedTimeCounter.NextValue());
            listView1.Items.Add("Total processor time: " + 
            		ReqProcess.TotalProcessorTime);
            listView1.Items.Add("User processor time: " + 
            		ReqProcess.UserProcessorTime);
            listView1.Items.Add("Privileged processor time: " + 
            		ReqProcess.PrivilegedProcessorTime);

            //code to see all the possible Counter of a process. 
            PerformanceCounterCategory[] PCounterCtg = 
            		PerformanceCounterCategory.GetCategories();
            foreach (PerformanceCounterCategory category in PCounterCtg)
            {
                if (category.CategoryName != "Process")
                continue;

                listView1.Items.Add("");
                listView1.Items.Add("Category: " + category.CategoryName);
                string[] instances = category.GetInstanceNames();

                if (instances.Length == 0)
                {
                    foreach (PerformanceCounter PCounter in category.GetCounters())
                    listView1.Items.Add(" Counter: " + PCounter.CounterName);
                }
                else
                {
                    foreach (string instance in instances)
                    {
                        if (!(instance.Equals(sProcName)))
                        continue;
            
                        listView1.Items.Add(" Instance: " + instance);

                        if (category.InstanceExists(instance))

                        foreach 
			(PerformanceCounter Pctr in category.GetCounters(instance))
                        listView1.Items.Add(" Counter: " + Pctr.CounterName);
                    }
                }
            }
    //end test code to see all possible counter of specific process 
        }
    }
    else
    {
        listView1.Items.Add("");
        listView1.Items.Add("Process " + sProcName + " is not started. ");
    }

    }
    catch (Exception ex)
    {
        listView1.Items.Add("Process check exception: " + ex.Message);
        return false;
    }

    return true;
}

private void button1_Click(object sender, RoutedEventArgs e)
{
    Process viewProcess = null;
    viewProcess = CurrentlyRunning(sProcName);
    if (viewProcess != null)
    {
        Window2 w2 = new Window2("Process", "Working Set - 
        	Private", sProcName, "Memory (Private Working Set)");
        w2.Show();
    }
    else
    {
        MessageBox.Show(sProcName + " instance is not running.", 
        	"Memory Graph Exception", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}
private void button2_Click(object sender, RoutedEventArgs e)
{

    Process viewProcess = null;
    viewProcess = CurrentlyRunning(sProcName);

    if (viewProcess != null)
    {
        Window2 w2 = new Window2("Process", 
        	"% Processor Time", sProcName, "%Processor Time (Total)");
        w2.Show();
    }
    else
    {
        MessageBox.Show(sProcName + " instance is not running.", 
        	"Processor Graph Exception", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}
}

public class StatisticsData
{
    public string ProcessName { get; set; }
    public string ProcessRunningStatus { get; set; }
    public string WorkingSetMemory { get; set; }
    public string WorkingSetPrivateMemory { get; set; }
    public string WorkingSetPeak { get; set; }
    public string ThreadCount { get; set; }
    public string HandleCount { get; set; }
    public string TotalProcessorTime { get; set; }
    public string UserProcessorTime { get; set; }
    public string PrivilegedProcessorTime { get; set; }
}
}

You can see in the image below how much memory and Processor time it usually takes when you search something in the registry.

Process_Statistics1.jpg - Click to enlarge image

In process category, counters of ‘regedit’ instance are:

  • Instance: regedit
  • Counter: % Processor Time
  • Counter: % User Time
  • Counter: % Privileged Time
  • Counter: Virtual Bytes Peak
  • Counter: Virtual Bytes
  • Counter: Page Faults/sec
  • Counter: Working Set Peak
  • Counter: Working Set
  • Counter: Page File Bytes Peak
  • Counter: Page File Bytes
  • Counter: Private Bytes
  • Counter: Thread Count
  • Counter: Priority Base
  • Counter: Elapsed Time
  • Counter: ID Process
  • Counter: Creating Process ID
  • Counter: Pool Paged Bytes
  • Counter: Pool Nonpaged Bytes
  • Counter: Handle Count
  • Counter: IO Read Operations/sec
  • Counter: IO Write Operations/sec
  • Counter: IO Data Operations/sec
  • Counter: IO Other Operations/sec
  • Counter: IO Read Bytes/sec
  • Counter: IO Write Bytes/sec
  • Counter: IO Data Bytes/sec
  • Counter: IO Other Bytes/sec
  • Counter: Working Set - Private

History

  • 25th July, 2009: Initial post

License

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



Comments and Discussions

 
GeneralWorking set-Private exception error Pin
amakyjo12-Feb-10 4:37
amakyjo12-Feb-10 4:37 

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

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