Using Performance Counters in C#

The application we are building in this tutorial will make use of the Performance Counters made available by Windows. They allow us to retrieve CPU, memory and network usage among other counters.

The performance counters offered by the Windows Operating system can be accessed through Control Panel -> Administrative Tools -> Performance. But what’s so great is that these performance counters can be easily queried from inside .NET Framework.

First of all, I want to make clear that the application we are building in this tutorial does not work with many of the performance counters that it lists, simply because there’s too much code to write to make it adapt to different counter type, and we want to keep things simple. Fortunately it works with the most popular performance characters such as the processor usage and disk usage. Furthermore, since the graph that you see in the picture below is for fixed values of 0 to 100 (typical percentage values) it will not display correctly those performance counters that return values with a different range.

Let’s start by creating the form:

Three drop-down lists: lstCats, lstCounters, lstInstances are the ones that we populate with counter categories, counter names and counter instances. A start button btnStart is being used to enable the timer that reads the values. And speaking of that, don’t forget to add the timer tmrCounter and set its Interval property to 1000 (1 second). This means the graph will update every second. I also added three labels lblValue, lblLow and lblHigh to display further information coming from the counter. The graph is drawn inside a PictureBox named picGraph that has a background image showing a scale. One more thing left: the actual PerformanceCounter object perfMain; you can find this on the Toolbox under Components.

Getting a list of available Performance Counters In order to make this tutorial more useful we’re not going to use a fixed, predefined list of counters, instead we’re going to retrieve them dinamically from the user’s operating system. This is particularly useful because the number of performance counters differ from system to system, depending on the hardware and software installed. Counters are divided into categories, thus first we need to retrieve these categories. We’ll do this in the Load event of the form. But first let’s place some using statements:

using System.Drawing.Drawing2D;

using System.Diagnostics;

And then initialize a variable we’ll use to set the position of the graph line:

// Used to move the graph line

int graphIncrement = 0;

And now the Form1_Load event:

private void Form1_Load(object sender, EventArgs e)

{

    // Get a list of available performance counter categories

    PerformanceCounterCategory[] perfCounters = PerformanceCounterCategory.GetCategories();

    for (int i = 0; i < perfCounters.Length; i++)

    {

        // Add the category to the drop-down list

        lstCats.Items.Add(perfCounters[i].CategoryName);

    }

}

Now that we have the categories in the lstCats dropdown list, we should retrieve the counters depending on the selected category from the dropdown list. This will be done in the SelectedIndexChanged event of lstCats list, which fires each time a selection is made from the dropdown list:

private void lstCats_SelectedIndexChanged(object sender, EventArgs e)

{

    string[] instanceNames;

    System.Collections.ArrayList counters = new System.Collections.ArrayList();

    if (lstCats.SelectedIndex != -1)

    {

        System.Diagnostics.PerformanceCounterCategory mycat = new System.Diagnostics.PerformanceCounterCategory(this.lstCats.SelectedItem.ToString());

        // Remove the current contents of the list.

        this.lstCounters.Items.Clear();

        // Retrieve the counters.

        instanceNames = mycat.GetInstanceNames();

        if (instanceNames.Length == 0)

        {

            counters.AddRange(mycat.GetCounters());

        }

        else

        {

            for (int i = 0; i < instanceNames.Length; i++)

            {

                counters.AddRange(mycat.GetCounters(instanceNames[i]));

            }

        }

 

        // Add the retrieved counters to the list.

        foreach (System.Diagnostics.PerformanceCounter counter in counters)

        {

            this.lstCounters.Items.Add(counter.CounterName);

        }

    }

}

For the code above to work, don’t forget to bind lstCats_SelectedIndexChanged to the SelectedIndexChanged event. The easiest way to do this in Visual Studio is to select the dropdown list, switch the Properties window to Events and double click the SelectedIndexChanged field.

We’re not done yet with retrieving counters. Each counter has its own instances, which could reflect different values to be retrieved by that counter, some in percentages and others in various ranges. Some may not have any instances at all. _Total is a popular instance name which most of the time is used to retrieve the most popularly demanded type of value from that counter.
Each time a counter is selected we want to retrieve the list of possible instances:

private void lstCounters_SelectedIndexChanged(object sender, EventArgs e)

{

    // Clear the existing instance list

    lstInstances.Items.Clear();

    PerformanceCounterCategory perfCat = new PerformanceCounterCategory(lstCats.SelectedItem.ToString());

    string[] catInstances;

    catInstances = perfCat.GetInstanceNames();

    lstInstances.Items.Clear();

    foreach (string catInstance in catInstances)

    {

        lstInstances.Items.Add(catInstance);

    }

}

And now that the code for populating the instance list is in, we can write the code that starts the monitoring, and this is located in the Click even of btnStart:

private void btnStart_Click(object sender, EventArgs e)

{

    if (lstCats.SelectedIndex != -1 && lstCounters.SelectedIndex != -1 && lstInstances.SelectedIndex != -1)

    {

        // Clear the graph

        picGraph.Invalidate();

        graphIncrement = 0;

        perfMain.CategoryName = lstCats.SelectedItem.ToString();

        perfMain.CounterName = lstCounters.SelectedItem.ToString();

        perfMain.InstanceName = lstInstances.SelectedItem.ToString();

        tmrCounter.Start();

    }

    else

    {

        MessageBox.Show("Please select a category, counter and instance first.", "Selection needed", MessageBoxButtons.OK, MessageBoxIcon.Error);

    }

}

Aside from setting the properties for the counter object, we’re starting the tmrCounter Timer object – it will be doing all the work from now on, so let’s look at what’s inside its Tick event:

private void tmrCounter_Tick(object sender, EventArgs e)

{

    float currVal;

    // Move to and get the latest value in the performance counter

    currVal = perfMain.NextValue();

    // Update the label with the value

    lblValue.Text = Math.Round(Convert.ToDouble(currVal.ToString()), 2) + "%";

    Graphics gfx = picGraph.CreateGraphics();

    Pen pn = new Pen(Color.Red, 1);

    gfx.DrawLine(pn, graphIncrement, 200, graphIncrement, 199 - (currVal * 2));

 

    graphIncrement += 2;

    if (graphIncrement > 500)

    {

        picGraph.Invalidate();

        graphIncrement = 0;

    }

}

Below is the application in action. There are many issues to address, including the PictureBox clearing when it gets invalidated and not all performance counters being properly scaled on the graph, but this is only a proof of concept application. One issue that you might experience though and which can easily be addressed is the “Access to the registry key ‘Global’ is denied.” exception. This is caused on certain operating systems such as Windows Vista Ultimate when the account doesn’t have administrative privileges and it’s not allowed access to all the performance counters. After you compile the code, right click the application’s executable, select Properties and in the Compatibility tab check the “Run this program as an administrator” checkbox.

The full source code for the application is below:

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Text;

using System.Windows.Forms;

using System.Drawing.Drawing2D;

using System.Diagnostics;

 

namespace PerformanceCounters

{

    public partial class Form1 : Form

    {

        // Used to move the graph line

        int graphIncrement = 0;

 

        public Form1()

        {

            InitializeComponent();

        }

 

        private void Form1_Load(object sender, EventArgs e)

        {

            // Get a list of available performance counter categories

            PerformanceCounterCategory[] perfCounters = PerformanceCounterCategory.GetCategories();

            for (int i = 0; i < perfCounters.Length; i++)

            {

                // Add the category to the drop-down list

                lstCats.Items.Add(perfCounters[i].CategoryName);

            }

        }

 

        private void tmrCounter_Tick(object sender, EventArgs e)

        {

            float currVal;

            // Move to and get the latest value in the performance counter

            currVal = perfMain.NextValue();

            // Update the label with the value

            lblValue.Text = Math.Round(Convert.ToDouble(currVal.ToString()), 2) + "%";

            Graphics gfx = picGraph.CreateGraphics();

            Pen pn = new Pen(Color.Red, 1);

            gfx.DrawLine(pn, graphIncrement, 200, graphIncrement, 199 - (currVal * 2));

 

            graphIncrement += 2;

            if (graphIncrement > 500)

            {

                picGraph.Invalidate();

                graphIncrement = 0;

            }

        }

 

        private void lstCats_SelectedIndexChanged(object sender, EventArgs e)

        {

            string[] instanceNames;

            System.Collections.ArrayList counters = new System.Collections.ArrayList();

            if (lstCats.SelectedIndex != -1)

            {

                System.Diagnostics.PerformanceCounterCategory mycat = new System.Diagnostics.PerformanceCounterCategory(this.lstCats.SelectedItem.ToString());

                // Remove the current contents of the list.

                this.lstCounters.Items.Clear();

                // Retrieve the counters.

                instanceNames = mycat.GetInstanceNames();

                if (instanceNames.Length == 0)

                {

                    counters.AddRange(mycat.GetCounters());

                }

                else

                {

                    for (int i = 0; i < instanceNames.Length; i++)

                    {

                        counters.AddRange(mycat.GetCounters(instanceNames[i]));

                    }

                }

 

                // Add the retrieved counters to the list.

                foreach (System.Diagnostics.PerformanceCounter counter in counters)

                {

                    this.lstCounters.Items.Add(counter.CounterName);

                }

            }

        }

 

        private void btnStart_Click(object sender, EventArgs e)

        {

            if (lstCats.SelectedIndex != -1 && lstCounters.SelectedIndex != -1 && lstInstances.SelectedIndex != -1)

            {

                // Clear the graph

                picGraph.Invalidate();

                graphIncrement = 0;

                perfMain.CategoryName = lstCats.SelectedItem.ToString();

                perfMain.CounterName = lstCounters.SelectedItem.ToString();

                perfMain.InstanceName = lstInstances.SelectedItem.ToString();

                tmrCounter.Start();

            }

            else

            {

                MessageBox.Show("Please select a category, counter and instance first.", "Selection needed", MessageBoxButtons.OK, MessageBoxIcon.Error);

            }

        }

 

        private void lstCounters_SelectedIndexChanged(object sender, EventArgs e)

        {

            // Clear the existing instance list

            lstInstances.Items.Clear();

            PerformanceCounterCategory perfCat = new PerformanceCounterCategory(lstCats.SelectedItem.ToString());

            string[] catInstances;

            catInstances = perfCat.GetInstanceNames();

            lstInstances.Items.Clear();

            foreach (string catInstance in catInstances)

            {

                lstInstances.Items.Add(catInstance);

            }

        }

    }

}
Nathan Pakovskie is an esteemed senior developer and educator in the tech community, best known for his contributions to Geekpedia.com. With a passion for coding and a knack for simplifying complex tech concepts, Nathan has authored several popular tutorials on C# programming, ranging from basic operations to advanced coding techniques. His articles, often characterized by clarity and precision, serve as invaluable resources for both novice and experienced programmers. Beyond his technical expertise, Nathan is an advocate for continuous learning and enjoys exploring emerging technologies in AI and software development. When he’s not coding or writing, Nathan engages in mentoring upcoming developers, emphasizing the importance of both technical skills and creative problem-solving in the ever-evolving world of technology. Specialties: C# Programming, Technical Writing, Software Development, AI Technologies, Educational Outreach

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top