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:
Figure 1 – Memory Counters
Figure 2 – Disk Counters
Figure 3 – Process 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…