A Quick Introduction to Performance Counters in Visual Studio 2012

Introduction

Quoted from MSDN : “Counters are used to provide information as to how well the operating system or an application, service, or driver is performing. The counter data can help determine system bottlenecks and fine-tune system and application performance. The operating system, network, and devices provide counter data that an application can consume to provide users with a graphical view of how well the system is performing.”

I couldn’t have said it better.

Our little project today will help you work with Performance Counters. Obviously there are many many more performance counters available on your system than what I’ll cover here, as this is only a quick and dirty introduction to performance counters.

Design

Design your VB.NET or C# Form as follows:

Memory Counters
Figure 1Memory Counters

Disk Counters
Figure 2Disk Counters

Process Counters
Figure 3Process Counters

Coding

There are literally millions of Counters available, as shown in Server Explorer / Performance Counters. I just chose randomly to be honest. We’ll concentrate on Memory, Processor and Physical disk counters.

Let us get started with the Namespaces:

VB.NET

Imports System.Diagnostics 'Contain types that enable you to interact with system processes, event logs, and performance counters

C#

using System.Diagnostics; //Contain types that enable you to interact with system processes, event logs, and performance counters

Let us create our Counter objects:

VB.NET

    Private pcProcess As PerformanceCounter 'Process
    Private pcMemory As PerformanceCounter 'Memory
    Private pcDisk As PerformanceCounter 'Disk

C#

        private PerformanceCounter pcProcess; //Process
        private PerformanceCounter pcMemory; //Memory
        private PerformanceCounter pcDisk; //Disk

Memory

Add the following to cover all Memory Counters in our program:

VB.NET

    Private Sub btnMem_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnMem.Click

        tmrMemory.Enabled = True 'Memory Counter

    End Sub

    Private Sub tmrMemory_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrMemory.Tick

        pcMemory = New PerformanceCounter()

        pcMemory.CategoryName = "Memory"

        'This counter gives a general idea of how many times information being requested is not where the application (and VMM) expects it to be
        pcMemory.CounterName = "Page Faults/sec"
        lblPageFaults.Text = pcMemory.NextValue.ToString

        'Use this counter in comparison with the Page Faults/sec counter to determine the percentage of the page faults that are hard page faults.
        pcMemory.CounterName = "Pages Input/sec"
        lblPageInputPerSec.Text = pcMemory.NextValue.ToString

        pcMemory.CounterName = "Pages Output/sec"
        lblPageOutputPerSec.Text = pcMemory.NextValue.ToString

        'The Pages/sec counter is a combination of Pages Input/sec and Pages Output/sec counters
        pcMemory.CounterName = "Pages/sec"
        lblPagesPerSec.Text = pcMemory.NextValue.ToString

        'This counter is probably the best indicator of a memory shortage because it indicates how often the system is reading from disk because of hard page faults.
        pcMemory.CounterName = "Page Reads/sec"
        lblPageReadsPerSec.Text = pcMemory.NextValue.ToString

        'This is the amount of memory that the process is using that cannot be moved out to the pagefile and thus will remain in physical RAM.
        pcMemory.CounterName = "Page Writes/sec"
        lblPageWritesPerSec.Text = pcMemory.NextValue.ToString

        pcMemory.CounterName = "Available MBytes"
        lblAvailableMemory.Text = pcMemory.NextValue.ToString

        'his counter provides an indication of how NT has divided up the physical memory resource
        pcMemory.CounterName = "Pool Nonpaged Bytes"
        Dim PNBytes As Decimal
        PNBytes = pcMemory.NextValue
        lblNonPageMemoryPoolBytes.Text = PNBytes.ToString

        'This is the amount of memory that the process is using in the pageable memory region
        pcMemory.CounterName = "Pool Paged Bytes"
        Dim PPBytes As Decimal
        PPBytes = pcMemory.NextValue
        lblPageMemPoolBytes.Text = PPBytes.ToString

        'This counter indicates the total amount of memory that has been committed for the exclusive use of any of the services or processes on Windows NT
        pcMemory.CounterName = "Committed Bytes"
        Dim decCommBytes As Decimal
        decCommBytes = pcMemory.NextValue

        lblCommittedBytes.Text = decCommBytes.ToString

    End Sub

C#

        private void btnMem_Click(System.Object sender, System.EventArgs e)
        {

            tmrMemory.Enabled = true; //Memory COunter

        }

        private void tmrMemory_Tick(System.Object sender, System.EventArgs e)
        {
            pcMemory = new PerformanceCounter();

            pcMemory.CategoryName = "Memory";

            //This counter gives a general idea of how many times information being requested is not where the application (and VMM) expects it to be
            pcMemory.CounterName = "Page Faults/sec";
            lblPageFaults.Text = pcMemory.NextValue().ToString();

            //Use this counter in comparison with the Page Faults/sec counter to determine the percentage of the page faults that are hard page faults.
            pcMemory.CounterName = "Pages Input/sec";
            lblPageInputPerSec.Text = pcMemory.NextValue().ToString();

            pcMemory.CounterName = "Pages Output/sec";
            lblPageOutputPerSec.Text = pcMemory.NextValue().ToString();

            //The Pages/sec counter is a combination of Pages Input/sec and Pages Output/sec counters
            pcMemory.CounterName = "Pages/sec";
            lblPagesPerSec.Text = pcMemory.NextValue().ToString();

            //This counter is probably the best indicator of a memory shortage because it indicates how often the system is reading from disk because of hard page faults.
            pcMemory.CounterName = "Page Reads/sec";
            lblPageReadsPerSec.Text = pcMemory.NextValue().ToString();

            // indicates how many times the disk was written to in an effort to clear unused items out of memory
            pcMemory.CounterName = "Page Writes/sec";
            lblPageWritesPerSec.Text = pcMemory.NextValue().ToString();

            pcMemory.CounterName = "Available MBytes";
            lblAvailableMemory.Text = pcMemory.NextValue().ToString();

            //This is the amount of memory that the process is using that cannot be moved out to the pagefile and thus will remain in physical RAM.
            pcMemory.CounterName = "Pool Nonpaged Bytes";
            float PNBytes;
            PNBytes = pcMemory.NextValue();
            lblNonPageMemoryPoolBytes.Text = PNBytes.ToString();

            //This is the amount of memory that the process is using in the pageable memory region
            pcMemory.CounterName = "Pool Paged Bytes";
            float PPBytes;
            PPBytes = pcMemory.NextValue();
            lblPageMemPoolBytes.Text = PPBytes.ToString();

            //This counter indicates the total amount of memory that has been committed for the exclusive use of any of the services or processes on Windows NT
            pcMemory.CounterName = "Committed Bytes";
            float decCommBytes;
            decCommBytes = pcMemory.NextValue();

            lblCommittedBytes.Text = decCommBytes.ToString();

        }

Processes

Add the Process Counters:

VB.NET

    Private Sub btnProc_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnProc.Click

        tmrProcess.Enabled = True 'Start Process Counter

    End Sub

    Private Sub tmrProcess_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrProcess.Tick

        pcProcess = New PerformanceCounter() 'New PerformanceCounter Object

        pcProcess.CategoryName = "Processor" 'Specify Process Counter

        'provides a measure of how much time the processor actually spends working on productive threads and how often it was busy servicing requests
        pcProcess.CounterName = "% Processor Time"
        pcProcess.InstanceName = "_Total"
        lblProcessorTime.Text = pcProcess.NextValue.ToString 'Display

        'The numbers of interrupts the processor was asked to respond to
        pcProcess.CounterName = "Interrupts/sec"
        lblInterruptsPerSec.Text = pcProcess.NextValue

        'This is the percentage of time that the processor is spending on handling Interrupts
        pcProcess.CounterName = "% Interrupt Time"
        lblInterruptTime.Text = pcProcess.NextValue

        'The value of this counter helps to determine the kind of processing that is affecting the system
        pcProcess.CounterName = "% User Time"
        lblUserTime.Text = pcProcess.NextValue

        'This is the amount of time the processor was busy with Kernel mode operations
        pcProcess.CounterName = "% Privileged Time"
        lblPrivilegeTime.Text = pcProcess.NextValue

        'shows the amount of time that the processor spends servicing DPC requests
        pcProcess.CounterName = "% DPC Time"
        lblDPCTime.Text = pcProcess.NextValue

        pcProcess.CategoryName = "System" 'Change to Sysytem Category
        pcProcess.InstanceName = ""

        'provides a measure of the instantaneous size of the queue for all processors at the moment that the measurement was taken
        pcProcess.CounterName = "Processor Queue Length"
        lblProcessorQueueLength.Text = pcProcess.NextValue

        'This counter is a measure of the number of calls made to the system components, Kernel mode services.
        pcProcess.CounterName = "System Calls/sec"
        lblProcessorQueueLength.Text = pcProcess.NextValue

        pcProcess.CategoryName = "Thread"
        pcProcess.InstanceName = "_Total"

        pcProcess.CounterName = "ID Thread"
        lblIDThread.Text = pcProcess.NextValue

        'The thread gets a base priority from the Process that created it.
        pcProcess.CounterName = "Priority Base"
        lblPriorityBase.Text = pcProcess.NextValue

        pcProcess.CounterName = "ID Process"
        lblProcessID.Text = pcProcess.NextValue

    End Sub

C#

        private void btnProc_Click(System.Object sender, System.EventArgs e)
        {

            tmrProcess.Enabled = true; //Start Process Counter

        }

        private void tmrProcess_Tick(System.Object sender, System.EventArgs e)
        {

            pcProcess = new PerformanceCounter(); //New Performance Counter Object

            pcProcess.CategoryName = "Processor"; //Specify Process Counter

            //provides a measure of how much time the processor actually spends working on productive threads and how often it was busy servicing requests
            pcProcess.CounterName = "% Processor Time";
            pcProcess.InstanceName = "_Total";
            lblProcessorTime.Text = pcProcess.NextValue().ToString(); //Display

            //The numbers of interrupts the processor was asked to respond to
            pcProcess.CounterName = "Interrupts/sec";
            lblInterruptsPerSec.Text = pcProcess.NextValue().ToString();

            //This is the percentage of time that the processor is spending on handling Interrupts
            pcProcess.CounterName = "% Interrupt Time";
            lblInterruptTime.Text = pcProcess.NextValue().ToString();

            //The value of this counter helps to determine the kind of processing that is affecting the system
            pcProcess.CounterName = "% User Time";
            lblUserTime.Text = pcProcess.NextValue().ToString();

            //This is the amount of time the processor was busy with Kernel mode operations
            pcProcess.CounterName = "% Privileged Time";
            lblPrivilegeTime.Text = pcProcess.NextValue().ToString();

            //shows the amount of time that the processor spends servicing DPC requests
            pcProcess.CounterName = "% DPC Time";
            lblDPCTime.Text = pcProcess.NextValue().ToString();

            pcProcess.CategoryName = "System"; //Change to System Category
            pcProcess.InstanceName = "";

            //provides a measure of the instantaneous size of the queue for all processors at the moment that the measurement was taken
            pcProcess.CounterName = "Processor Queue Length";
            lblProcessorQueueLength.Text = pcProcess.NextValue().ToString();

            //This counter is a measure of the number of calls made to the system components, Kernel mode services.
            pcProcess.CounterName = "System Calls/sec";
            lblProcessorQueueLength.Text = pcProcess.NextValue().ToString();

            pcProcess.CategoryName = "Thread";
            pcProcess.InstanceName = "_Total";

            pcProcess.CounterName = "ID Thread";
            lblIDThread.Text = pcProcess.NextValue().ToString();

            //The thread gets a base priority from the Process that created it.
            pcProcess.CounterName = "Priority Base";
            lblPriorityBase.Text = pcProcess.NextValue().ToString();


            pcProcess.CounterName = "ID Process";
            lblProcessID.Text = pcProcess.NextValue().ToString();

        }

Disk

Finally, add the PhysicalDisk Counters:

VB.NET

    Private Sub btnDisk_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDisk.Click

        tmrDisk.Enabled = True 'Enable Disk Counter

    End Sub

    Private Sub tmrDisk_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles tmrDisk.Tick

        pcDisk = New PerformanceCounter()

        pcDisk.CategoryName = "PhysicalDisk"
        pcDisk.InstanceName = "_Total"

        'This counter provides a primary measure of disk congestion. Just as the processor queue was an indication of waiting threads, the disk queue is an indication of the number of transactions that are waiting to be processed.
        pcDisk.CounterName = "Current Disk Queue Length"
        lblCurrDiskQueueLength.Text = pcDisk.NextValue.ToString

        'this counter is a general mark of how busy the disk is.
        pcDisk.CounterName = "% Disk Time"
        lblDiskTime.Text = pcDisk.NextValue.ToString

        'This counter is actually strongly related to the %Disk Time counter. This counter converts the %Disk Time to a decimal value and displays it.
        pcDisk.CounterName = "Avg. Disk Queue Length"
        lblAvgDiskQueueLength.Text = pcDisk.NextValue.ToString

        'This counter is used to compare to the Memory: Page Inputs/sec counter.
        pcDisk.CounterName = "Disk Reads/sec"
        lblDiskReadPerSec.Text = pcDisk.NextValue.ToString

        pcDisk.CounterName = "Disk Read Bytes/sec"
        lblDiskReadBytesPerSec.Text = pcDisk.NextValue.ToString

        pcDisk.CounterName = "Avg. Disk Bytes/Read"
        lblAvgDiskBytesRead.Text = pcDisk.NextValue.ToString

        pcDisk.CounterName = "Avg. Disk sec/Read"
        lblAvgDiskSecRead.Text = pcDisk.NextValue.ToString

    End Sub

C#

        private void btnDisk_Click(System.Object sender, System.EventArgs e)
        {

            tmrDisk.Enabled = true; //Enable Disk Counter

        }

        private void tmrDisk_Tick(System.Object sender, System.EventArgs e)
        {

            pcDisk = new PerformanceCounter();

            pcDisk.CategoryName = "PhysicalDisk";
            pcDisk.InstanceName = "_Total";

            //This counter provides a primary measure of disk congestion. Just as the processor queue was an indication of waiting threads, the disk queue is an indication of the number of transactions that are waiting to be processed.
            pcDisk.CounterName = "Current Disk Queue Length";
            lblCurrDiskQueueLength.Text = pcDisk.NextValue().ToString();

            //this counter is a general mark of how busy the disk is.
            pcDisk.CounterName = "% Disk Time";
            lblDiskTime.Text = pcDisk.NextValue().ToString();

            //This counter is actually strongly related to the %Disk Time counter. This counter converts the %Disk Time to a decimal value and displays it.
            pcDisk.CounterName = "Avg. Disk Queue Length";
            lblAvgDiskQueueLength.Text = pcDisk.NextValue().ToString();

            //This counter is used to compare to the Memory: Page Inputs/sec counter.
            pcDisk.CounterName = "Disk Reads/sec";
            lblDiskReadPerSec.Text = pcDisk.NextValue().ToString();

            pcDisk.CounterName = "Disk Read Bytes/sec";
            lblDiskReadBytesPerSec.Text = pcDisk.NextValue().ToString();

            pcDisk.CounterName = "Avg. Disk Bytes/Read";
            lblAvgDiskBytesRead.Text = pcDisk.NextValue().ToString();

            pcDisk.CounterName = "Avg. Disk sec/Read";
            lblAvgDiskSecRead.Text = pcDisk.NextValue().ToString();

        }

Conclusion

As you can see, working with Performance Counters is quite easy. Explore all of them and then you’ll see how quick these counters can spot bottlenecks in your programs. I hope you have benefited from this article. Until next time, this is me signing off for a while…

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read