Return to HomePage



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

Source: http://msdn.microsoft.com/library/en-us/dnpag/html/scalenethowto02.asp
J.D. Meier, Srinath Vasireddy, Ashish Babbar, and Alex Mackman

Related Links

* Improving .NET Application Performance and Scalability home page
* Chapter 4, "Architecture and Design Review of a .NET Application for Performance and Scalability"
* Chapter 6, "Improving ASP.NET Performance"
* "Checklist: ASP.NET Performance" in the "Checklists" section of this guide

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.

Applies To

* Microsoft® .NET Framework version 1.1

Contents

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

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.

HKEYLOCALMACHINE\SYSTEM\CurrentControlSet\Services

MyAspNetThreadCounters is the name of the performance counter category and the counter names include Available Worker Threads, Available IO Threads, Max Worker 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.

http://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 New Counter Set 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 http://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:
* For a printable checklist, see "Checklist: ASP.NET Performance" in the "Checklists" section of this guide.
* Chapter 6, "Improving ASP.NET Performance."
* For more information about the thread pool class, see "ThreadPool Class" in .NET Framework Class Library on MDSN® at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemthreadingthreadpoolclasstopic.asp.
* For more information about the <processModel> element, see "<processModel> Element" in .NET Framework General Reference on MSDN at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/gngrfprocessmodelsection.asp.



Return to HomePage
Microsoft Communities