When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips

Sections:
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
XML Info
Information:
Feedback
Author an Article
ASP ASP.NET ASP FAQs Message Board Feedback
Print this page.
Published: Friday, September 28, 2001

ASP.NET: Tips, Tutorials, and Code
Chapter 2: Common ASP.NET Code Techniques


10. Working with Server Performance Counters

As we examined in "Accessing the Windows Event Log," the .NET Framework contains a System.Diagnostics namespace that houses classes which can be used for diagnostic information. One such class is the EventLog class, which we just looked at in the previous section; another useful diagnostics class is the PerformanceCounter class. This class gives the developer direct read access to the existing Windows Performance counters and provides the capability to create new performance counters!

Because this functionality is encapsulated in the .NET Framework, these techniques can be used through an ASP.NET page. That means you can create an ASP.NET page that reports information on the Web server's various resources! Talk about a remote administrator's dream!

Performance counters, which are represented by the PerformanceCounter class, are defined by at least two required properties. The first two required properties are CategoryName and CounterName; these represent the category and counter one sees when selecting a performance monitor through the Administrative Performance tool (see Figure 2.31). For example, to view the percentage of the processor's power being used, you'd be interested in the "% Processor Time" counter of the "Processor" category.

For certain performance counters, a third property, InstanceName, is required. Specifically, the InstanceName is needed for counters that monitor a metric with multiple instances, such as Web server information. Because a single Web server can run many different Web sites, we must specify the Web site if we want to view specific Web server–related information (such as total number of requests). Figure 2.31, for example, shows three options for the Web Services Bytes Total/sec performance counter, a _Total instance and an instance for each of the Web sites configured on my server.

Figure 2.31
When choosing a performance counter through the administrative interface, a category and counter are selected.

When creating an instance of the PerformanceCounter, you can specify these two (or three) properties in the constructor. For example, to create an instance of the PerformanceCounter class that could be used to report the total number of threads, the following code could be used:

Dim objPerf as New PerformanceCounter("System", "Threads")

The previous snippet creates an instance of the PerformanceCounter class named objPerf. This instance represents the performance counter "Threads" found in the "System" category.

To read the performance counter's value, simply use the NextValue() method. NextValue() returns the value of the next performance counter's sample. Therefore, we could amend our previous code to display the total number of threads with a simple call to NextValue():

Dim objPerf as New PerformanceCounter("System", "Threads")
Response.Write("There are " & objPerf.NextValue() & " threads.")

Listing 2.10.1 illustrates a very simple ASP.NET page that could possibly serve as a handy tool for an off-site Web site administrator. The code for Listing 2.10.1, when viewed through a browser, will display the total available memory for the Web server, the number of processes currently running on the Web server, and the total number of anonymous page requests for the particular Web application in which the ASP.NET page resides. The output is shown in Figure 2.32.

Listing 2.10.1 Gather System Information about the Web Server via an ASP.NET Page

 1: <%@ Import Namespace="System.Diagnostics" %>
 2: <script language="VB" runat="server">
 3: Sub Page_Load(sender as Object, e as EventArgs)
 4:  Dim objMemPerf as New PerformanceCounter("Memory", "Available Bytes")
 5:  lblFreeMemory.Text = String.Format("{0:#,###}", 
 objMemPerf.NextValue()) & " bytes"
 6: 
 7:  Dim objProcPerf as New PerformanceCounter("System", "Processes")
 8:  lblProcesses.Text = objProcPerf.NextValue().ToString()
 9: 
10:  Dim strAppName as String = Request.ServerVariables("APPL_MD_PATH")
11:  strAppName = strAppName.Replace("/", "_")
12:  Dim objCompPerf as New PerformanceCounter("ASP.NET Applications", _
13:                     "Page Compilations", strAppName)
14:   
15:  lblPageReqs.Text = objCompPerf.NextValue().ToString()
16: End Sub
17: </script>
18: 
19: <html>
20: <body>
21:  <h1>Web Server Stats </h1>
22:  <i>These stats were taken as of <%=Now().ToLongDateString()%> at
23:  <%=Now().ToLongTimeString()%>...</i><p>
24: 
25:  <b>Available Memory:</b> <asp:label id="lblFreeMemory" 
 runat="server" /><br>
26:  <b>Processes:</b> <asp:label id="lblProcesses" runat="server" /><br>
27:  <b>ASP.NET Page Requests:</b>
28:   <asp:label id="lblPageReq" runat="server" /><br>
29: </body>
30: </html>

Figure 2.32
Performance counter values can be displayed through an ASP.NET page.

In Listing 2.10.1 we begin by importing the System.Diagnostics namespace (line 1). Next, in the Page_Load event handler, three PerformanceCounter class instances are created. The first one, objMemPerf, determines the amount of free memory in bytes available (line 4). On line 5, this value is output into the lblFreeMemory label control. The objProcPerf performance counter on line 7 determines the total number of currently running processes and displays this value in the lblProcesses label control (line 8).

The third performance counter displays the total number of anonymous requests (lines 12 and 13). For this metric, we must specify what Web application to use. To accomplish this, we can either hard-code a value for the InstanceName property, or we can dynamically determine it through the APPL_MD_PATH server variable. This server variable has values such as /LM/W3SVC/1/Root, for example. The PerformanceCounter class, however, prefers the InstanceName in the following format: _LM_W3SVC_1_Root, so, on line 11, all the forward slashes (/) are replaced with underscores (_). Finally, on line 15, the value of the page compilations performance counter is output.

Lines 19 through 30 define the HTML content of the page. Lines 22 and 23 output the current system time that the stats were monitored, whereas lines 25, 27, and 28 create the three performance counter label controls. These three label controls are populated with the values from their respective PerformanceCounter instances in the Page_Load event handler.


Caution

On line 12 of Listing 2.10.1 the performance counter category "ASP.NET Applications" is used to retrieve the Page Compilations counter. At the time of writing, this performance category and counter existed. However, since this is Beta software, these values may change. Refer to the Windows Performance Counter program to see a listing of existing performance categories and counters.


Although being able to view a particular performance counter through an ASP.NET page is an amazing feat in itself, it's only the tip of the iceberg! The .NET Framework provides an additional class, PerformanceCounterCategory, that can be used to iterate through the Web server's available performance categories and counters. This means that you can provide a remote administrator with an ASP.NET page that lists the entire contents set of performance counters for the administrator to choose from. After a performance counter has been selected, its current value can be displayed.

Listing 2.10.2 illustrates how to use the PerformanceCounterCategory class—in conjunction with the PerformanceCounter class—to list all the available performance categories and counters, permitting the user to select a particular category and counter and view its current value. The output of Listing 2.10.2, when viewed through a browser, can be seen in Figure 2.33.

Listing 2.10.2 Iterate Through the Available Performance Categories and Counters with the PerformanceCounterCategory Class

 1: <%@ Import Namespace="System.Diagnostics" %>
 2: <script language="c#" runat="server">
 3:  void Page_Load(Object sender, EventArgs e)
 4:  {
 5:   if (!Page.IsPostBack)
 6:   {
 7:    foreach (PerformanceCounterCategory objPerfCounterCat in 
 PerformanceCounterCategory.GetCategories())
 8:     lstCategories.Items.Add(new 
 ListItem(objPerfCounterCat.CategoryName));
 9: 
 10:    //Add the counters for the first category
 11:    PopulateCounters(PerformanceCounterCategory.GetCategories()[0]);
 12:   }
 13:  }
 14: 
 15: 
 16:  void PopulateCounters(PerformanceCounterCategory objPerfCat)
 17:  {
 18:   // Populates the lstCounters list box with the counters for the
 19:   // selected performance category (objPerfCat)...
 20: 
 21:   lstCounters.Items.Clear();
 22: 
 23:   PopulateInstances(objPerfCat);
 24: 
 25:   if (lstInstances.Items.Count > 0)
 26:   {
 27:    foreach (PerformanceCounter objCounter in 
 objPerfCat.GetCounters(lstInstances.Items[0].Value))
 28:     lstCounters.Items.Add(new ListItem(objCounter.CounterName));
 29: 
 30:    DisplayCounter(objPerfCat.GetCounters(
 lstInstances.Items[0].Value)[0]);
 31:   } else {
 32: 
 33:    foreach (PerformanceCounter objCounter in objPerfCat.GetCounters())
 34:     lstCounters.Items.Add(new ListItem(objCounter.CounterName));
 35: 
 36:    DisplayCounter(objPerfCat.GetCounters()[0]);
 37:   }
 38:  }
 39: 
 40: 
 41:  void PopulateInstances(PerformanceCounterCategory objPerfCat)
 42:  {
 43:   // Populates the lstInstances list box with the counters for the
 44:   // selected performance category (objPerfCat)...
 45: 
 46:   lstInstances.Items.Clear();
 47: 
 48:   foreach (String strInstanceName in objPerfCat.GetInstanceNames())
 49:    lstInstances.Items.Add(new ListItem(strInstanceName));
 50:  }
 51: 
 52: 
 53: 
 54:  void lstCategories_OnChange(Object sender, EventArgs e)
 55:  {
 56:   PerformanceCounterCategory objPerfCat;
 57:   objPerfCat = new PerformanceCounterCategory(
 lstCategories.SelectedItem.Value);
 58: 
 59:   PopulateCounters(objPerfCat);
 60:  }
 61: 
 62: 
 63:  void lstCounters_OnChange(Object sender, EventArgs e)
 64:  {
 65:   // Display counter information for the selected counter and category
 66:   if (lstCounters.SelectedItem != null)
 67:   {
 68:    PerformanceCounter objPerf = new PerformanceCounter();
 69:    objPerf.CategoryName = lstCategories.SelectedItem.Value;
 70:    objPerf.CounterName = lstCounters.SelectedItem.Value;
 71:    if (lstInstances.SelectedItem != null) objPerf.InstanceName = 
 lstInstances.SelectedItem.Value;
 72: 
 73:    DisplayCounter(objPerf);
 74:   }
 75:  }
 76: 
 77: 
 78:  void lstInstances_OnChange(Object sender, EventArgs e)
 79:  {
 80:   // Display counter information for the selected counter and category
 81:   if (lstCounters.SelectedItem != null)
 82:   {
 83:    PerformanceCounter objPerf = new PerformanceCounter();
 84:    objPerf.CategoryName = lstCategories.SelectedItem.Value;
 85:    objPerf.CounterName = lstCounters.SelectedItem.Value;
 86:    if (lstInstances.SelectedItem != null) objPerf.InstanceName = 
 lstInstances.SelectedItem.Value;
 87: 
 88:    DisplayCounter(objPerf);
 89:   }
 90:  }
 91: 
 92:  void DisplayCounter(PerformanceCounter objPerf)
 93:  {
 94:   try {
 95:    objPerf.NextValue();
 96:    lblLastValue.Text = objPerf.NextValue().ToString();
 97:   }
 98:   catch (Exception e)
 99:   {
100:    try {
101:     PerformanceCounterCategory tmpCat = new _PerformanceCounterCategory(lstCategories.SelectedItem.Value);
102:     objPerf.InstanceName = tmpCat.GetInstanceNames()[0];
103:     objPerf.NextValue();
104:     lblLastValue.Text = objPerf.NextValue().ToString();
105:    }
106:    catch (Exception e2)
107:    {
108:     lblLastValue.Text = "<font color=red>ERROR: " + 
 e.Message + "</font>";
109:    }
110:   }
111:  }
112: </script>
113: 
114: <html>
115: <body>
116:  <form runat="server">
117:   <h1>Performance Counter Display</h1>
118:   <asp:listbox runat="server" id="lstCategories" 
 Rows="1" AutoPostBack="True"
119:         OnSelectedIndexChanged="lstCategories_OnChange" />
120:   <asp:listbox runat="server" id="lstCounters" 
 Rows="1" AutoPostBack="True"
121:         OnSelectedIndexChanged="lstCounters_OnChange" />
122:   <asp:listbox runat="server" id="lstInstances" 
 Rows="1" AutoPostBack="True"
123:         OnSelectedIndexChanged="lstInstances_OnChange" />
124:   <p>
125:   <b>Current Value:</b>
126:   <asp:label id="lblLastValue" runat="server" />
127:  </form>
128: </body>
129: </html>

Figure 2.33
A list of available performance categories and counters are presented to the user.

Before examining the script block in Listing 2.10.2, let's first turn our attention to the HTML section of the page (lines 114 through 129). Keep in mind the intended functionality for the code: to list the available performance categories and counters on the Web server. To accomplish this, we'll use three list boxes: The first list box will list the available performance categories; the second will list the available performance counters for the selected performance category; the third will list the available instance values for the selected performance category.

These three list boxes are created on lines 118 through 123. The performance category list box, lstCategories, has the OnSelectedIndexChanged event wired up to the lstCategories_OnChange event handler (defined from lines 54 through 60). When this list box's currently selected item is changed, the form will be posted back and the lstCategories_OnChange event handler will fire. The list box for each counter, lstCounters, is defined on lines 120 and 121. This list box, too, has an event handler (lstCounters_OnChange) wired up to the OnSelectedIndexChanged event handler. Finally, lstInstances, the last list box, is defined on lines 122 and 123.

The final server control is a label control, defined on line 126. Its purpose is to display the current value of the selected performance counter. All these controls are encased by a postback form control (defined on line 116).

The Page_Load event handler, found spanning lines 3 through 13, only executes code the first time the page is visited. This is because of the conditional on line 5 that checks to determine whether the form has been posted back. If it is the first visit to the page (that is, the form has not been posted back), all the available performance categories are iterated through. The PerformanceCounterCategory class contains a static method GetCategories(), which will return an array of PerformanceCounterCategory instances that represent the available performance categories on the machine. A foreach loop is used to iterate through all these categories, adding a new item to the lstCategories list box at each iteration (line 8). On line 11, the PopulateCounters function is called, which populates the lstCounters list box with the applicable counters for the selected category.

The PopulateCounters function begins by clearing out all the items in the lstCounters list box (line 21). Next, the PopulateInstances function is called to load the instance values for the performance category into the lstInstance list box. Keep in mind that only certain performance counters have Instance values. Therefore, when control returns to the PopulateCounters function, an if statement is used on line 25 to check if any instances exist for the performance category. If the performance category contains any instances, a foreach loop is used to iterate through each of the counters in the performance category for the default instance (line 27). The GetCounters(InstanceName) method returns all the available counters for the performance category for the given instance InstanceName (see line 27). Each of these counter names are then added to the lstCounters list box (line 28). If the performance category does not contain any instances, the code following the else statement on line 31 is executed. The only difference in this code is that the GetCounters() method does not specify an InstanceName (line 33). Finally, before the PopulateCounters function terminates, the DisplayCounter function is called; this function displays the current value of the selected performance counter.

The DisplayCounter function (defined from lines 92 through 111) lists the current value of the passed-in PerformanceCounter, objPerf. A try ... catch block is used on line 94 to ensure that the performance counter can be successfully read. If an error occurs, we assume that it is because the instance name hasn't been specified. In such a case, the catch block starting on line 98 will begin executing, and another try ... catch block ensues (line 100). Here, the default instance name for the selected performance category is selected and used and the performance counter's value is output. The GetInstanceNames() method of the PerformanceCounterCategory class will return a String array of available instance names; on line 102 we simply choose the first available instance name. Again, if an error occurs, the catch block on line 106 will begin executing; at this point, we don't know how to handle the error, and the Message property of the exception is displayed (line 108). Because some performance counters require two consecutive reads to the NextValue() method to retrieve a meaningful value, two calls are made (lines 95 and 96, and lines 103 and 104).

Whenever the user changes the currently selected performance category in the lstCategories list box, the form is posted back and the lstCategories_OnChange event handler fires (see lines 54 through 60). This event handler creates a PerformanceCounterCategory instance representing the currently selected performance category (lines 56 and 57). Next, the PopulateCounters method is called (line 59), thereby populating the lstCounters list box with the appropriate counters for the selected performance category.

When the user selects a performance counter from the lstCounters list box, the form is posted back and the lstCounters_OnChange event handler is executed (see lines 63 through 75). This event handler begins by ensuring that a valid selection in the lstCounters list box has been made (line 65). Next, a PerformanceCounter instance is created (line 68), and its CategoryName, CounterName, and InstanceName properties are set based on the selected values in the corresponding list boxes. Finally, on line 73, the DisplayCounter method is called, displaying the current value for the selected performance counter. The lstInstances_OnChange event handler (lines 78 through 90), which fires when the user changes the current selection in the lstInstances list box, contains identical code to the lstCounters_OnChange list box.

To see Listing 2.10.2 in action, refer back to Figure 2.33. Using some enhancements we discussed earlier in this chapter (see the section "Generating Images Dynamically"), one could conceivably create an on-the-fly chart that would display the current (and past) status of one or more performance counters through an ASP.NET page, similar to the Performance tool in Windows NT/2000.

Summary

In this chapter we examined a number of common tasks that Web developers face and illustrated how to accomplish them through an ASP.NET page using the .NET Framework. Clearly the robust .NET Framework enables ASP.NET Web pages a degree of power that was only possible in classic ASP with clunky COM components.

If you have developed classic ASP Web pages in the past you will no doubt be impressed by ASP.NET's new capabilities. Just imagine, with ASP.NET you can dynamically create rich reports with graphs based on database data—no need for an expensive third-party component! With the System.Net classes you can seamlessly make HTTP requests through an ASP.NET Web page, access DNS information, and use with low-level socket programming techniques. Accepting and working with uploaded files from your Web visitors is now a breeze, no longer requiring a third-party component. With ASP.NET accomplishing common Web-related tasks is simple, easy, and built-in.

Despite this chapter's great length, it covered only a small handful of the capabilities of the .NET Framework. To get a full grasp of the capabilities, you should take a moment to poke through the .NET Framework Documentation (this documentation is included in the installation of this book's accompanying CD). The majority of these numerous classes can be used through an ASP.NET page just as they could through a VB.NET or C# standalone application.

This chapter provides a mere taste of the ASP.NET and .NET Framework's capabilities. As you work through the coming chapters you will soon appreciate the multitude of improvements and the ease with which you can create truly powerful Web applications!

Other Resources

Due to the extensive number of subjects this chapter covered, an in-depth examination of each subject was not possible. These supplemental readings, though, should provide a starting place for you to learn more about the topics in this chapter that piqued your interest.

  • Mastering Regular Expressions—This book is a must have for anyone who wishes to become truly proficient at regular expressions. While the book presents most of its examples using Perl, it still does a fantastic job of explaining regular expressions. Authored by: Friedl, Jeffrey E. F. Published By: O'Reilly Press, 1997

  • Form-Based File Upload in HTML—This RFC contains the gory technical details for uploading a file from a client's Web browser to the Web server. http://www.ietf.org/rfc/rfc1867.txt?number=1867

  • Using Cookies with ASP.NET—One common Web developer task we did not have time to examine in this chapter was using Cookies through ASP.NET. With VB.NET the syntax is nearly identical to Cookie usage in classic ASP, but if you're making the switch to C# there are some things you should be aware of. http://www.asp101.com/samples/cookie_aspx.asp

  • Fun ASP.NET Code Examples—This collection of useful ASP.NET code examples comes from Scott Guthrie, lead program manager and co-creator of ASP.NET! http://www.eraserver.net/scottgu/

  • Validating Emails Against a Mail Server—This article takes a more in-depth look at validating email address through a mail server. In this chapter we examined validating the domain—this article shows how to validate both the domain and mail account! http://www.aspnextgen.com/tutorials.aspx?tutorialid=64

  • Logging Unhandled Page Exceptions to the System Event Log—This article goes a bit beyond the "Writing to the Event Log" section in this chapter, showing how to record errors to a custom Windows Event Log category. http://www.aspnextgen.com/tutorials.aspx?tutorialid=42


  • Return to the Beginning...


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article