Navigation

>> Home
 
ASP.NET (13)
Security (5)
"Classic" ASP (2)
C# (1)
VB.NET (1)
ASP and Flash (1)
 
About Us
 
Chinese Content German Content

 

Performance Monitoring the .NET Way

Written by: Christoph Wille
Translated by: Bernhard Spuida
First published: 8/9/2000

In the article Introduction to Performance Monitoring I have explained the basic workings of performance monitoring. In the follow up article Monitoring of ASP reading the performance counter in an ASP page was the topic - using a component.

In today's article I will show you how much easier it is in ASP.NET using the System.Diagnostics Namespace of the .NET framework. No more external components, more functionality instead. You can even reconstruct parts of the NT performance monitor without spending too much time on it.

The use of the source code in this article requires the Microsoft .NET Framework SDK installed on a Webserver. I also presume that the reader is familiar to some degree with the C# programming language.

Security notice: this article assumes that you are running the ASP.NET worker process under the SYSTEM account, and not the locked-down ASPNET user account (which is used by default). Please change processModel in machine.config accordingly, or enforce impersonation on the files in question.

What is the goal of this article?

Take a look at the following screenshot. It shows the Add Counters dialog box of the performance monitor:

In this article I want to show you how to recreate this dialog box (almost) identically in ASP.NET without having to delve into the depths of WIN32 programming. The only prominent omission is the selection of the computer as this quickly leads to security issues - as the reading of performance counters requires more permissions than the anonymous web site user has.

First steps

Let us begin with a minimal example. This just checks whether a given counter exists in the ASP.NET category on the computer on which it runs. (basictest.aspx).

<% @Page Language="C#" %>
<% @Import Namespace="System.Diagnostics" %>
<%
if ( !PerformanceCounterCategory.CounterExists("State Server Sessions Active", 
     "ASP.NET", ".") ) 
{
  Response.Write("No state server counters on this machine");
}
else
{
  Response.Write("State Server counter is available.");
}
%>

I want to call your attention especially to the @Import Namespace statement: I need this to correctly bind the System.Diagnostics namespace with my ASP.NET page. Otherwise I won't be able to use the PerformanceCounter object (here it is the class with a static method).

Querying categories

Now we proceed to a bigger and more functional example - listing all performance counter categories in a drop down list (listcounters.aspx).

<% @Page Language="C#" %>
<% @Import Namespace="System.Diagnostics" %>
<% @Import Namespace="System.Collections" %>
<script language="C#" runat="server">
void Page_Load(Object Src, EventArgs E) 
{
  if (!Page.IsPostBack)
  {
    ArrayList arrItems = new ArrayList();
    PerformanceCounterCategory[] arrCategories = 
	          PerformanceCounterCategory.GetCategories();
    for (int i=0; i < arrCategories.Length;i++)
      arrItems.Add(arrCategories[i].CategoryName);
    Category.DataSource = arrItems;
    Category.DataBind();
  }
}
</script>

<html>
<body>

<h3>Dump performance counter categories</h3>

<form runat="server" method="post">
Category:  
<asp:dropdownlist id="Category" runat="server"/>
</form>

</body>
</html>

Now let us tackle this program from the end up, starting with the web form which only contains one single drop down list. This has got the name Category and is empty. Where do the entires in this list come from?

This drop down is being filled in the Page_Load event. This is run every time the page is executed, even when it already is a post back with results. As the view state manages the entries for us, we can omit filling in the list again.

The interesting part of the code querying the categories is the following:

PerformanceCounterCategory[] arrCategories = 
       PerformanceCounterCategory.GetCategories();

With this, I have an array of PerformanceCounterCategory objects at hand, which saves the categroy name in the CategoryName property. And how do I now get this array into the drop down list? A brute force approach is shown in the following code:

ArrayList arrItems = new ArrayList();
for (int i=0; i < arrCategories.Length;i++)
      arrItems.Add(arrCategories[i].CategoryName);
Category.DataSource = arrItems;
Category.DataBind();

Simply run through the array of PerformanceCounterCategory objects, copy the CategoryNames out and put them in an ArrayList. This can then be assigned as the DataSource for the drop down, and with DataBind the list is finally filled.

It is obvious that this copying around is less than elegant and not exactly enhances performance.In the next examples I show how it can be done faster and better and how simple ASP.NET makes this task for us.

Displaying help texts for categories and counters

Let us take the example a step further. The performance monitor can display a help text for each category or counter. Wouldn't it be great, if our application could do the same? The following code shows how to implement that for the categories (categoryhelp.aspx).

<% @Page Language="C#" %>
<% @Import Namespace="System.Diagnostics" %>
<% @Import Namespace="System.Collections" %>
<script language="C#" runat="server">
void Page_Load(Object Src, EventArgs E) 
{
  if (!Page.IsPostBack)
  {
    PerformanceCounterCategory[] arrCategories = PerformanceCounterCategory.GetCategories();
    Category.DataSource = arrCategories;
    Category.DataBind();
  }
}

void OnDisplayCategory(Object sender, EventArgs e)
{
  string strCategory = Category.SelectedItem.Value;
  PerformanceCounterCategory pcInfo = new PerformanceCounterCategory(strCategory);
  Message.Text = pcInfo.CategoryHelp;
}
</script>

<html>
<body>

<h3>Performance Counter Help</h3>

<form runat="server" method="post">
Category:  
<asp:dropdownlist id="Category" AutoPostBack="True" 
    DataTextField="CategoryName" DataValueField="CategoryName"
      runat=server OnSelectedIndexChanged="OnDisplayCategory"/>

<p><b>Help text:</b>&nbsp;
<asp:Label id="Message" runat="server" />
</p>
</form>

</body>
</html>

OK, let us begin at the end again. The form now looks much bigger than before. The most important changes concern the drop down list:

  • The AutoPostBack attribute indicates that the form is posted to the server whenever the selection changes.
  • When this happens, the method OnDisplayCategory is called on the server which is defined in the attribute OnSelectedIndexChanged
  • Last but not least we have the attributes DataTextField and DataValueField - they define which properties out of the items of the DataSource are to be called to get the Values and display values.

These last two attributes are our ticket for simplifying the Page_Load code: fare well, extra collection, ASP.NET now handles all this for us and even without re-copying!

PerformanceCounterCategory[] arrCategories = 
       PerformanceCounterCategory.GetCategories();
Category.DataSource = arrCategories;
Category.DataBind();

The event OnDisplayCategory is basically kept quite simple. The selected category is read, with this a PerformanceCounterCategory object is created and from this, the CategoryHelp property is read. To be able to present it to the user, this string is being written into a label control (represented as a SPAN tag in the browser).

With this, we only have half of what we need - the perfomance counter categories. A further step is the display of the counters of the respective category and of the help texts for the counters. As the source code for this is a bit 'longish', the listing can be looked at here (fullhelp.aspx).

The most important innovation is a Listbox for the counters:

<asp:ListBox id="Counters" Width="200px" runat="server" 
  DataTextField="CounterName" DataValueField="CounterName"
  AutoPostBack="True" 
  runat=server OnSelectedIndexChanged="OnHelpCounter"/>

This Listbox also triggers an AutoPostBack and I again use DataTextField and DataValueField for writing the values to them. To get this to work, I would have to adapt the code in OnDisplayCategory so that the Counters Listbox is being filled with data as well, not only the Category help:

PerformanceCounter[] arrCntrs = pcInfo.GetCounters();
Counters.DataSource = arrCntrs;
Counters.DataBind();

Finally, we also have to display the help text for the counters and this happens in OnHelpCounter:

void OnHelpCounter(Object sender, EventArgs e)
{
 string strCategory = Category.SelectedItem.Value;
 string strCounter = Counters.SelectedItem.Value;
 PerformanceCounter pcInfo = 
        new PerformanceCounter(strCategory, strCounter);
 CounterHelp.Text = pcInfo.CounterHelp;
}

As you can see, not exactly 'magic', but very effective. There is a large number of roundtrips to the server, but theoretically it might be made more network efficient using hidden SPANs and client side code.

Querying Performance Counter Values

We approach our goal with giant steps - what is still missing? Most prominent are the instances of the counters. Instances of counters are defined for an entire category, however not every counter of the category has instances. An example would be the Processor category, where every processor can be addressed via an instance (or all at the same time).

Also unchecked on the To-Do list is the querying of the values of the performance counters. I added this to the final example, too, which presents itself to the user as follows:

For reasons of clarity, I removed the source code for the help texts so that we can focus on the essentials. Considering number of lines, the listing still is long enough! (querycounter.aspx)

Out of old habit let us begin 'yet again' with the form code:

<form runat="server" method="post">
<table width="400">
<tr><td>Category:</td><td>Counter:</td><td>Instances</td></tr>
<tr><td valign="top">
<asp:dropdownlist id="Category"  AutoPostBack="True" 
	DataTextField="CategoryName" DataValueField="CategoryName"
	runat=server OnSelectedIndexChanged="OnDisplayCategory" />
</td><td valign="top">
<asp:ListBox id="Counters" Width="200px" runat="server" 
	DataTextField="CounterName" DataValueField="CounterName"
  	AutoPostBack="True" 
	OnSelectedIndexChanged="OnCounterInfo"/>
</td><td valign="top">
<asp:ListBox id="CounterInstances" Width="100px" runat="server" />
</td></tr>
</table>
<asp:button OnClick="SubmitBtn_Click" text="Lookup" runat="server"/>
</form>

As you can see, another listbox has been added for the instances of a counter. Otherwise, nothing has changed. Ah yes, a button called 'Lookup' has been added which triggers a postback which then triggers the querying of the performance counter.

Which leads us to the OnCounterInfo event - this had to suffer quite some changes:

void OnCounterInfo(Object sender, EventArgs e)
{
  string strCategory = Category.SelectedItem.Value;
  CounterInstances.Items.Clear();
  
  PerformanceCounterCategory pcCat = new PerformanceCounterCategory(strCategory);
  string[] arrInstanceNames = pcCat.GetInstanceNames();
  if (arrInstanceNames.GetLength(0) > 1)
  {
    CounterInstances.DataSource = arrInstanceNames;
    CounterInstances.DataBind();
  }
}

As I already have explained, the instances are defined per category but not every counter of the category must make the instances available. Therefore, we fill the CounterInstances listbox only if necessary. From this point in time it is possible to obtain the current value by clicking on the lookup button:

void SubmitBtn_Click(Object sender, EventArgs e) 
{
  string strCat = Category.SelectedItem.Value;
  string strCounter = Counters.SelectedItem.Value;
  string strInstance = "";
  
  if (CounterInstances.Items.Count > 0)
    strInstanceName = CounterInstances.SelectedItem.Value;
    
  PerformanceCounter pc = 
        new PerformanceCounter(strCat, strCounter, strInstance);

  float dResult = pc.NextValue();
  Message.Text = "<" + strCat + "> [" + strCounter + 
	               "] {" + strInstance + "} = " + dResult.ToString();
}

For clarity I yanked the Exception Handling Code (intended for security violations). A single call of NextValue returns the current value which is then presented to the user. With this, we have done today's task.

Who now plays around with the performance counters in this example will find that some values are not what is expected. I of course did not implement the full functionality of all counter types (percent, total, etc.). Who wants to implement this will find the information necessary for correct calculations with performance counters in the .NET Framework SDK help under the following entry:

Conclusion

In today's article we have dealt with Performance Monitoring the .NET way and I hope to have fairly well demonstrated its power in these quite simple examples. Take the time to read about the features of Perfomance Monitoring in the documentation!

Downloading the Code

Click here to start the download.

©2000-2004 AspHeute.com
All rights reserved. The content of these pages is copyrighted.
All content and images on this site are copyright. Reuse (even parts) needs our written consent.