How To: Monitor the ASP.NET Thread Pool Using Custom Counters

 

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

patterns & practices Developer Center

Improving .NET Application Performance and Scalability

J.D. Meier, Srinath Vasireddy, Ashish Babbar, and Alex Mackman
Microsoft Corporation

May 2004

Related Links

Home Page for Improving .NET Application Performance and Scalability

Chapter 4, Architecture and Design Review of a .NET Application for Performance and Scalability

Chapter 6, Improving ASP.NET Performance

Checklist: ADO.NET Performance

Send feedback to Scale@microsoft.com

patterns & practices Library

Summary: This How To shows you how to monitor the ASP.NET thread pool by creating a set of custom performance counters. The How To also shows you how to modify the maximum number of threads available to an ASP.NET application.

Contents

Applies To
Overview
Create Custom Performance Counters
Create an ASP.NET Application to Refresh the Counters
View the Counters in Performance Monitor
Run a Test Page that Uses Threads
Additional Resources

Applies To

  • Microsoft® .NET Framework version 1.1

Overview

In this How To, you monitor the ASP.NET thread pool by creating a set of custom performance counters. You then instrument an ASP.NET application by using these counters. From the ASP.NET application, you set the performance counter values every half second.

This technique enables you to monitor threading activity in your ASP.NET application and to diagnose thread-related performance issues and bottlenecks.

**Note   **This solution is intended for development and testing purposes only. It is designed to help you learn about, and monitor, threading and threading behavior in an ASP.NET application.

Create Custom Performance Counters

In this section, you will create four custom performance counters as defined in Table 1. All counters will be added to the category named ASP.NET Thread Pool, and all counters will be of type PerformanceCounterType.NumberOfItems32.

Table 1: Custom Performance Counters

Counter name Description
Available Worker Threads The difference between the maximum number of thread-pool worker threads and the number currently active.
Available IO Threads The difference between the maximum number of thread-pool I/O threads and the number currently active.
Max Worker Threads The number of requests to the thread pool that can be active concurrently. All requests above that number remain queued until thread-pool worker threads become available.
Max IO Threads The number of requests to the thread pool that can be active concurrently. All requests above that number remain queued until thread-pool I/O threads become available.

Although it is possible to use the Microsoft Visual Studio® .NET Server Explorer to create performance counters manually, the code that follows shows how to create them from a console application.

Create a Console Application

Create an empty source file named CreateASPNETThreadCounters.cs and add the following code. This code creates a simple console application that in turn creates the relevant custom performance counters and applies the relevant settings to the Microsoft Windows® registry.

using System;
using System.Diagnostics;

class MyAspNetThreadCounters
{
  [STAThread]
  static void Main(string[] args)
  {
    CreateCounters();
    Console.WriteLine("MyAspNetThreadCounters performance counter category " +
                      "is created. [Press Enter]");
    Console.ReadLine();
  }

  public static void CreateCounters()
  {
    CounterCreationDataCollection col =  
      new CounterCreationDataCollection();

    // Create custom counter objects
    CounterCreationData counter1 = new CounterCreationData();
    counter1.CounterName = "Available Worker Threads";
    counter1.CounterHelp = "The difference between the maximum number " + 
                           "of thread pool worker threads and the " +
                           "number currently active.";
    counter1.CounterType = PerformanceCounterType.NumberOfItems32;

    CounterCreationData counter2 = new CounterCreationData();
    counter2.CounterName = "Available IO Threads";
    counter2.CounterHelp = "The difference between the maximum number of " + 
                           "thread pool IO threads and the number "+ 
                           "currently active.";
    counter2.CounterType = PerformanceCounterType.NumberOfItems32;

    CounterCreationData counter3 = new CounterCreationData();
    counter3.CounterName = "Max Worker Threads";
    counter3.CounterHelp = "The number of requests to the thread pool "+ 
                           "that can be active concurrently. All "+  
                           "requests above that number remain queued until " +
                           "thread pool worker threads become available.";
    counter3.CounterType = PerformanceCounterType.NumberOfItems32;

    CounterCreationData counter4 = new CounterCreationData();
    counter4.CounterName = "Max IO Threads";
    counter4.CounterHelp = "The number of requests to the thread pool " + 
                           "that can be active concurrently. All "+  
                           "requests above that number remain queued until " +
                           "thread pool IO threads become available.";
    counter4.CounterType = PerformanceCounterType.NumberOfItems32;

    // Add custom counter objects to CounterCreationDataCollection.
    col.Add(counter1);
    col.Add(counter2);
    col.Add(counter3);
    col.Add(counter4);
    // delete the category if it already exists
    if(PerformanceCounterCategory.Exists("MyAspNetThreadCounters"))
    {
      PerformanceCounterCategory.Delete("MyAspNetThreadCounters");
    }
    // bind the counters to the PerformanceCounterCategory
    PerformanceCounterCategory category = 
            PerformanceCounterCategory.Create("MyAspNetThreadCounters", 
                                              "", col);
  }
}

Compile the Console Application

At a command prompt, use the following command line to compile your code.

csc.exe /out:CreateAspNetThreadCounters.exe /t:exe /r:system.dll 
CreateASPNETThreadCounters.cs

Run AspNetThreadCounters.exe

To run the console application, run the following.

CreateAspNetThreadCounters.exe

Results

When you run CreateAspNetThreadCounters.exe, the following output is produced.

MyAspNetThreadCounters performance counter category is created. [Press Enter]

Use Regedt32.exe to validate that your performance counter category and your custom performance counter are created beneath the following registry location.

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services

MyAspNetThreadCounters is the name of the performance counter category and the counter names include Available Worker Threads, Available IO Threads, MaxWorker Threads, and Max IO Threads.

Create an ASP.NET Application to Refresh the Counters

To refresh the counters, you must retrieve information about the ASP.NET thread pool, and for that, you must run code from within an ASP.NET application.

Create an ASP.NET Application

Follow these steps to create the application.

To create the ASP.NET application

  1. Create a new folder named C:\InetPub\wwwroot\AspNetThreadPoolMonitor\.

  2. Use Internet Services Manager to mark this folder as an application and to create a virtual directory.

  3. Create the following three files in the folder: Global.asax, Sleep.aspx, and StartWebApp.aspx.

    Global.asax

    <%@ Application Language=C# %>
    <%@ import namespace="System.Threading" %>
    <%@ import namespace="System.Diagnostics" %>
    
    <script runat=server>
    
    public bool MonitorThreadPoolEnabled = true;
    
    protected void Application_Start(Object sender, EventArgs e)
    {
      Thread t = new Thread(new ThreadStart(RefreshCounters));
      t.Start();
    }
    
    public void RefreshCounters()
    {
      while (MonitorThreadPoolEnabled)
      {
        ASPNETThreadInfo t = GetThreadInfo();
        ShowPerfCounters(t);
        System.Threading.Thread.Sleep(500);
      }
    }
    
    protected void Application_End(Object sender, EventArgs e)
    {
      MonitorThreadPoolEnabled = false;
    }
    
    public struct ASPNETThreadInfo
    {
      public int MaxWorkerThreads;
      public int MaxIOThreads;
      public int MinFreeThreads;
      public int MinLocalRequestFreeThreads;
      public int AvailableWorkerThreads;
      public int AvailableIOThreads;
    
      public bool Equals(ASPNETThreadInfo other)
      {
        return (
          MaxWorkerThreads == other.MaxWorkerThreads &&
          MaxIOThreads == other.MaxIOThreads &&
          MinFreeThreads == other.MinFreeThreads &&
          MinLocalRequestFreeThreads == other.MinLocalRequestFreeThreads &&
          AvailableWorkerThreads == other.AvailableWorkerThreads &&
          AvailableIOThreads == other.AvailableIOThreads
        );
      }
    }
    
    public ASPNETThreadInfo GetThreadInfo()
    {
      // use ThreadPool to get the current status
      int availableWorker, availableIO;
      int maxWorker, maxIO;
    
      ThreadPool.GetAvailableThreads( out availableWorker, out availableIO);
      ThreadPool.GetMaxThreads(out maxWorker, out maxIO);
    
      ASPNETThreadInfo threadInfo;
      threadInfo.AvailableWorkerThreads = (Int16)availableWorker;
      threadInfo.AvailableIOThreads = (Int16)availableIO;
      threadInfo.MaxWorkerThreads = (Int16)maxWorker;
      threadInfo.MaxIOThreads = (Int16)maxIO;
     // hard code for now; could get this from  machine.config
      threadInfo.MinFreeThreads = 8;
      threadInfo.MinLocalRequestFreeThreads = 4;
      return threadInfo;
    }
    
    public void ShowPerfCounters(ASPNETThreadInfo t)
    {
    
      // get an instance of our Available Worker Threads counter
      PerformanceCounter counter1 = new PerformanceCounter();
      counter1.CategoryName = "MyAspNetThreadCounters";
      counter1.CounterName = "Available Worker Threads";
      counter1.ReadOnly = false;
    
      // set the value of the counter
      counter1.RawValue = t.AvailableWorkerThreads;
      counter1.Close();
    
      // repeat for other counters
    
      PerformanceCounter counter2 = new PerformanceCounter();
      counter2.CategoryName = "MyAspNetThreadCounters";
      counter2.CounterName = "Available IO Threads";
      counter2.ReadOnly = false;
      counter2.RawValue = t.AvailableIOThreads;
      counter2.Close();
    
      PerformanceCounter counter3 = new PerformanceCounter();
      counter3.CategoryName = "MyAspNetThreadCounters";
      counter3.CounterName = "Max Worker Threads";
      counter3.ReadOnly = false;
      counter3.RawValue = t.MaxWorkerThreads;
      counter3.Close();
    
      PerformanceCounter counter4 = new PerformanceCounter();
      counter4.CategoryName = "MyAspNetThreadCounters";
      counter4.CounterName = "Max IO Threads";
      counter4.ReadOnly = false;
      counter4.RawValue = t.MaxIOThreads;
      counter4.Close();
    }
    </script>
    

    Sleep.aspx

    <%@ Page language="C#" %>
    <script runat=server>
      void Page_Load(Object sender, EventArgs e)
      {
        Response.Write("Sleep");
        System.Threading.Thread.Sleep(30000);
      }
    </script>
    

    StartWebApp.aspx

    <%@ Page language="C#" %>
    <script runat=server>
      void Page_Load(Object sender, EventArgs e)
      {
        Response.Write("This ASP.NET application has started.<br>");
        Response.Write("You can now close this page.");
      }
    </script>
    

Start the ASP.NET Application

Start your ASP.NET application by opening Microsoft Internet Explorer and browsing to the following page.

https://localhost/AspNetThreadPoolMonitor/StartWebApp.aspx

View the Counters in Performance Monitor

Use the Performance Monitor tool to view the counters.

To view the counters in Performance Monitor

  1. At a command prompt, type perfmon.exe, and then press Enter.
  2. On the toolbar, click New Counter Set. (If the NewCounterSet button is disabled, you already have a new counter set.)
  3. On the toolbar, click Add.
  4. In the Add Counters dialog box, for Performance object, click MyASPNetThreadCounters.
  5. In the Select counters from this list box, click Available IO Threads, and then click Add.
  6. In the Select counters from this list box, click Available Worker Threads, and then click Add.
  7. In the Select counters from this list box, click Max IO Threads, and then click Add.
  8. In the Select counters from this list box, click Max Worker Threads, and then click Add.
  9. Click Close.
  10. On the toolbar, click Properties.
  11. In the System Monitor Properties dialog box, click the Graph tab.
  12. On the Graph tab, set Maximum for the Vertical scale to 20.
  13. Click OK.

**Note   **If the counters show zero values, the ASP.NET application is not running.

Run a Test Page that Uses Threads

The Sleep.aspx test page can be used to keep an ASP.NET I/O thread busy. Open multiple instances of your browser and, in each instance, open the Sleep.aspx page. In Performance Monitor, you can see the number of available worker or I/O threads changing, depending on your scenario. For example, if you do not have the hotfix mentioned in Microsoft Knowledge Base article 816829, "FIX: When I/O Thread Processes a Slow Request, Completions on Named Pipes Between Inetinfo.exe and Aspnet_wp.exe Are Blocked," at https://support.microsoft.com/default.aspx?scid=kb;EN-US;816829, then only I/O threads change and not the worker threads.

Additional Resources

For more information, see the following resources:

patterns & practices Developer Center

Retired Content

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

© Microsoft Corporation. All rights reserved.