Logging and Systems Management in Microsoft .NET Framework Application Development Foundation

  • 11/12/2008

Lesson 2: Working with Performance Counters

For years, administrators have used performance counters to monitor the performance of computers, networks, and applications. Developers have also used performance counters to help identify bottlenecks in their application’s performance.

With the System.Diagnostics namespace in the .NET Framework, you can add custom performance counters and update the performance data from within your application. Then, you or an administrator can monitor any aspect of your application’s performance, which can be useful for performance tuning and troubleshooting.

This lesson describes how to monitor standard and custom performance counters and how to add and update custom performance counters.

Monitoring Performance Counters

Windows includes hundreds of performance counters that allow you to monitor the operating system’s activities in real time. You can view these counters using the Performance snap-in. In Windows Vista, you can access the Performance snap-in from within the Computer Management console by following these steps:

  1. Click Start, right-click Computer, and then click Manage. Respond to the UAC prompt if it appears.

  2. In the Computer Management console, expand System Tools, Reliability And Performance, and Monitoring Tools, and then select Performance Monitor.

  3. On the Performance Monitor toolbar, click the button marked with a green plus sign to add a counter.

    The Add Counters dialog box appears, as shown in Figure 10-2.

  4. In the Available Counters list, expand a category name and then click a counter. If required, select an instance and click Add.

    Figure 10-2

    Figure 10-2 Adding a performance counter

  5. Repeat step 4 to add more counters.

  6. Click OK to begin monitoring the counters in real time.

    The Performance snap-in displays the values for the counters you selected.

To monitor performance counters within a program, create an instance of PerformanceCounter by specifying the performance object, counter, and (if required) the instance. You can determine the names of these parameters, and whether an instance is required, by using the Performance snap-in. Then call the PerformanceCounter.NextValue method to reset the counter. Make a second call to PerformanceCounter.NextValue to retrieve the performance data. Depending on the counter, the performance data might be averaged over the time passed between calls to PerformanceCounter.NextValue.

The following code sample, which requires both the System.Diagnostics and System.Threading namespaces, displays the current processor utilization averaged over a period of 1 second:

'VB
' Create a PerformanceCounter object that measures processor time
Dim pc As New PerformanceCounter("Processor", "% Processor Time", "_Total")

' Reset the performance counter
pc.NextValue()

' Wait one second
Thread.Sleep(1000)

' Retrieve the processor usage over the past second
Console.WriteLine(pc.NextValue())
//C#
// Create a PerformanceCounter object that measures processor time
PerformanceCounter pc = new
    PerformanceCounter("Processor", "% Processor Time", "_Total");

// Reset the performance counter
pc.NextValue();

// Wait one second
Thread.Sleep(1000);

// Retrieve the processor usage over the past second
Console.WriteLine(pc.NextValue());

The first call to PerformanceCounter.NextValue always returns zero; therefore, it is always meaningless. Only subsequent calls contain useful data. The following code illustrates this by showing the datagrams sent per second:

'VB
Dim pc As New PerformanceCounter("IPv4", "Datagrams/sec")
For i As Integer = 0 To 9

    Console.WriteLine(pc.NextValue())
Next
//C#
PerformanceCounter pc = new PerformanceCounter("IPv4", "Datagrams/sec");

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(pc.NextValue());
}

The output resembles the following, showing that the network interface was receiving 100 to 220 datagrams per second:

0
136.4877
213.3919
210.881
106.4458
186.9752
208.2334
172.8078
127.5594
219.6767

Because the IPv4\Datagrams/sec counter is averaged over 1 second, you can query it repeatedly and always retrieve the previous second’s average. If you queried the Processor\% Processor Time\_Total counter repeatedly, the results would resemble the following because repeatedly querying the value results in the instantaneous utilization. In the case of a computer processor, in any given instant, the processor is either idle or fully utilized—values between 0 and 100 occur only when examining the utilization over a period of time:

0
100
100
100
100
100
0
0
100
100

Adding Custom Performance Counters

If you want to provide performance data generated by your application, you should create a custom performance counter category and then add the counters to that category. You can’t add performance counters to a built-in category.

To add a custom performance counter category and a single counter, call the static PerformanceCounterCategory.Create method. Provide the category name, a description of the category, a name for the counter, and a description of the counter. The following code sample demonstrates this:

'VB
PerformanceCounterCategory.Create("CategoryName", "CounterHelp", _
    PerformanceCounterCategoryType.MultiInstance, "CounterName", _
    "CounterHelp")
//C#
PerformanceCounterCategory.Create("CategoryName", "CounterHelp",
    PerformanceCounterCategoryType.MultiInstance, "CounterName",
    "CounterHelp");

Note the third parameter: the PerformanceCounterCategoryType enumeration. You should specify SingleInstance if the counter definitely has only one instance. Specify MultiInstance if the counter might have multiple instances. For example, because computers might have two or more processors, counters that display processor status are always MultiInstance.

If you want to add multiple counters to a single category, create an instance of CounterCreationDataCollection and add multiple CounterCreationData objects to the collection. The following code sample demonstrates this:

'VB
Dim counters As CounterCreationDataCollection = New CounterCreationDataCollection
counters.Add(New CounterCreationData("Sales", _
    "Number of total sales", PerformanceCounterType.NumberOfItems64))
counters.Add(New CounterCreationData("Active Users", _
    "Number of active users", PerformanceCounterType.NumberOfItems64))
counters.Add(New CounterCreationData("Sales value", _
    "Total value of all sales", PerformanceCounterType.NumberOfItems64))
PerformanceCounterCategory.Create("MyApp Counters", _
    "Counters describing the performance of MyApp", _
    PerformanceCounterCategoryType.SingleInstance, counters)
//C#
CounterCreationDataCollection counters = new CounterCreationDataCollection();
counters.Add(new CounterCreationData("Sales",
    "Number of total sales", PerformanceCounterType.NumberOfItems64));
counters.Add(new CounterCreationData("Active Users",
    "Number of active users", PerformanceCounterType.NumberOfItems64));
counters.Add(new CounterCreationData ("Sales value",
    "Total value of all sales", PerformanceCounterType.NumberOfItems64));
PerformanceCounterCategory.Create("MyApp Counters",
    "Counters describing the performance of MyApp",
    PerformanceCounterCategoryType.SingleInstance, counters);

To check whether a category already exists, use the PerformanceCounterCategory.Exists method. To remove an existing category, call PerformanceCounterCategory.Delete.

You should add performance counters during an application’s setup process for two reasons. First, adding performance counters requires administrative privileges. Second, the operating system requires a few moments to refresh the list of performance counters. Therefore, they might not be accessible the moment you add the counters. However, the typical delay between installing an application and running the application is generally sufficient.

Providing Performance Counter Data

After you create a custom performance counter, you can update the data as needed. You don’t need to update it constantly—just when the value changes. Performance counter data is sampled only every 400 milliseconds, so if you update the value more frequently than that, it won’t improve the accuracy significantly.

To update a performance counter, create a PerformanceCounter object just as you would for reading a performance counter value. However, you must set the ReadOnly property to false. You can do this using the overloaded PerformanceCounter constructor that takes a boolean parameter, as shown here, or you can set the ReadOnly property after creating the object:

'VB
Dim pc As PerformanceCounter = New PerformanceCounter( _
    "MyApp Counters", "Sales", False)
//C#
PerformanceCounter pc = new PerformanceCounter(
    "MyApp Counters", "Sales", false);

After creating the PerformanceCounter object, you can set the value directly by defining the RawValue property. Alternatively, you can call the thread-safe Decrement, Increment, and IncrementBy methods to adjust the value relative to the current value. The following code sample demonstrates how to use each of these methods:

'VB
Dim pc As PerformanceCounter = New PerformanceCounter( _
    "MyApp Counters", "Sales", False)
pc.RawValue = 7
pc.Decrement
pc.Increment
pc.IncrementBy(3)
//C#
PerformanceCounter pc = new PerformanceCounter(
    "MyApp Counters", "Sales", false);
pc.RawValue = 7;
pc.Decrement();
pc.Increment();
pc.IncrementBy(3);

PerformanceCounter.Increment and PerformanceCounter.Decrement are thread-safe, but they’re much slower than simply updating PerformanceCounter.RawValue. Therefore, you should use PerformanceCounter.Increment and PerformanceCounter.Decrement only when multiple threads might update the performance counter simultaneously.

Lab: Providing Performance Data

In this lab, you will create an application that provides performance data that systems administrators can use to monitor the application’s performance.

Exercise 1: Create and Update Performance Counters

In this exercise, you will create a solution that includes three projects: a WPF application, a class derived from Installer, and a Setup project. You must create a Setup project to add the custom performance counter during the setup process because the user typically has administrative credentials only during setup. The application that you create will record the number of times the user has clicked a button in a custom performance counter.

  1. Use Visual Studio to create a new WPF Application project named PerfApp in either Visual Basic .NET or C#.

  2. Add a single Label control named counterLabel and a single Button control named counterButton to the form. Double-click counterButton to edit the Click event handler.

  3. In the code file, add the System.Diagnostics namespace. Then write code in the counterButton.Click event handler to increment the PerfApp\Clicks counter and display the current value in counterLabel. The following code demonstrates how to do this:

    ' VB
    Dim pc As New PerformanceCounter("PerfApp", "Clicks", False)
    pc.Increment()
    counterLabel.Content = pc.NextValue().ToString()
    // C#
    PerformanceCounter pc = new PerformanceCounter("PerfApp", "Clicks", false);
    pc.Increment();
    counterLabel.Content = pc.NextValue().ToString();
  4. Add a new project to the solution using the Class Library template. Name the project PerfAppInstaller.

  5. In the PerfAppInstaller namespace, derive a custom class named InstallCounter from the Installer class. As described in Lesson 3 of Chapter 9, implement the Install, Rollback, and Uninstall methods to add and remove a performance category named PerfApp and a counter named Clicks. You need to add a reference to the System.Configuration.Install DLL. The following code sample demonstrates how to write the code:

    ' VB
    Imports System.Configuration.Install
    Imports System.ComponentModel
    
    <RunInstaller(True)> _
           Public Class InstallCounter
       Inherits Installer
       Public Sub New()
           MyBase.New()
       End Sub
       Public Overloads Overrides Sub Commit( _
           ByVal mySavedState As IDictionary)
           MyBase.Commit(mySavedState)
       End Sub
    
       Public Overloads Overrides Sub Install( _
           ByVal stateSaver As IDictionary)
           MyBase.Install(stateSaver)
           If Not PerformanceCounterCategory.Exists("PerfApp") Then
               PerformanceCounterCategory.Create("PerfApp", _
                   "Counters for PerfApp", _
                   PerformanceCounterCategoryType.SingleInstance, _
                   "Clicks", "Times the user has clicked the button.")
           End If
       End Sub
    
       Public Overloads Overrides Sub Uninstall( _
           ByVal savedState As IDictionary)
           MyBase.Uninstall(savedState)
           If PerformanceCounterCategory.Exists("PerfApp") Then
               PerformanceCounterCategory.Delete("PerfApp")
           End If
       End Sub
    
       Public Overloads Overrides Sub Rollback( _
           ByVal savedState As IDictionary)
           MyBase.Rollback(savedState)
           If PerformanceCounterCategory.Exists("PerfApp") Then
               PerformanceCounterCategory.Delete("PerfApp")
           End If
       End Sub
    End Class
    // C#
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using System.Diagnostics;
    using System.Configuration.Install;
    using System.ComponentModel;
    using System.Collections;
    
    namespace PerfAppInstaller
    {
       [RunInstaller(true)]
       public class InstallCounter : Installer
       {
           public InstallCounter()
               : base()
           {
           }
    
           public override void Commit(IDictionary mySavedState)
           {
               base.Commit(mySavedState);
           }
    
           public override void Install(IDictionary stateSaver)
           {
               base.Install(stateSaver);
               if (!PerformanceCounterCategory.Exists("PerfApp"))
                   PerformanceCounterCategory.Create("PerfApp",
                       "Counters for PerfApp",
                       PerformanceCounterCategoryType.SingleInstance,
                       "Clicks",
                       "Times the user has clicked the button.");
           }
    
           public override void Uninstall(IDictionary savedState)
           {
               base.Uninstall(savedState);
               if (PerformanceCounterCategory.Exists("PerfApp"))
                   PerformanceCounterCategory.Delete("PerfApp");
           }
    
           public override void Rollback(IDictionary savedState)
           {
               base.Rollback(savedState);
               if (PerformanceCounterCategory.Exists("PerfApp"))
                   PerformanceCounterCategory.Delete("PerfApp");
           }
       }
    }
  6. Add a Setup project named PerfApp Setup to your solution.

  7. Right-click Application Folder in the left pane, click Add, and then click Project Output. In the Add Project Output Group dialog box, click Primary Output for the PerfApp project and click OK.

  8. Right-click Primary Output From PerfApp and then click Create Shortcut To Primary Output From PerfApp. Name the shortcut PerfApp and then drag it to the User’s Programs Menu folder.

  9. Add custom actions to the Setup project to call the appropriate InstallLog methods. Right-click PerfApp Setup in Solution Explorer, click View, and then click Custom Actions.

  10. Right-click Install and then click Add Custom Action. In the Select Item In Project dialog box, double-click Application Folder and then click Add Output. In the Add Project Output Group dialog box, click the Project drop-down list and then click PerfAppInstaller. Select Primary Output, click OK and then click OK again. Accept the default name, and notice that the InstallerClass property for the primary output is set to True.

  11. Right-click Rollback and then click Add Custom Action. In the Select Item In Project dialog box, double-click Application Folder. Click Primary Output From PerfAppInstaller and then click OK.

  12. Repeat step 11 to add the PerfAppInstaller DLL for the Uninstall custom action.

  13. Build your solution. Right-click PerfApp Setup in Solution Explorer and then click Build.

  14. Open the PerfApp Setup build destination folder and double-click PerfApp Setup.msi to start the installer. Accept the default settings to install the application. If you are running Windows Vista, respond appropriately when UAC prompts you for administrative credentials.

  15. Open the Performance snap-in and add the PerfApp\Clicks counter to monitor it in real time.

  16. Leave the Performance snap-in running. Click Start, click All Programs, and then click PerfApp to start the program. Click the button several times to increment the counter. Notice that counterLabel displays the number of clicks and the Performance snap-in shows the value in real time.

  17. Uninstall PerfApp using the Programs And Features tool in Control Panel.

  18. Close and reopen the Performance snap-in. Notice that the PerfApp counter category has been removed.

Lesson Summary

  • To monitor performance counters programmatically, create an instance of PerformanceCounter. Then call the PerformanceCounter.NextValue method to reset the counter. Make subsequent calls to PerformanceCounter.NextValue to retrieve the performance data.

  • To add custom performance counters, call the static PerformanceCounterCategory.Create method. Provide the category name, a description of the category, a name for the counter, and a description of the counter.

  • To provide performance counter data, create a PerformanceCounter object and set the ReadOnly property to false. You can then set the value directly by defining the RawValue property or by calling the thread-safe Decrement, Increment, and IncrementBy methods.

Lesson Review

You can use the following questions to test your knowledge of the information in Lesson 2, “Working with Performance Counters.” The questions are also available on the companion CD if you prefer to review them in electronic form.

  1. You are creating a multithreaded application. You create an instance of PerformanceCounter named pc that might be referenced from multiple threads simultaneously. Which of the following calls is thread-safe? (Choose all that apply.)

    1. pc.RawValue = pc.RawValue + 32

    2. pc.Increment()

    3. pc.Decrement()

    4. pc.Increment(12)

  2. You want to add a performance counter category with multiple counters programmatically. Which class should you use to specify the counters?

    1. PerformanceCounterCategory

    2. CounterSample

    3. CounterCreationDataCollection

    4. CounterCreationData