Home > .net 2.0, .net 3.0, .net 3.5, c#, Microsoft, Visual Studio 2008 > Using PerformanceCounters For ASP.NET Errors

Using PerformanceCounters For ASP.NET Errors

April 7, 2008

Project Download: .net v3.5

The flexibility of ASP.NET error reporting is fantastic—you can collect errors, send emails, read logs, write logs, and more.  Unfortunately, without a bit of dinking, there’s nothing to automagically (that I’ve found) record system specifications and diagnostic information.

For example, if the application pools are constantly resetting, that’s important to know; however, was the system out of memory?  Were the processors pegged at 100%?  This information is available through the System.Diagnostics.PerformanceCounter class.

I started out creating a basic PerformanceMonitor class.  The performance monitor has one public property and three public methods.  The property contains the collection of counters used by the monitor, populated using one of the methods, AddCounter. 

public class PerformanceMonitor

{

public List<Counter> Counters;

 

       public PerformanceMonitor()

       {

              Counters = new List<Counter>();

}

public void AddCounter(

              string name, string type,

string instance, string displaySuffix)

{

              Counters.Add(new Counter

             {

                    Name = name,

                    Type = type,

                    Instance = instance,

                    DisplaySuffix = displaySuffix

});

}

The remaining two methods populate the values into the counters, one looking at the local computer and a second allowing the computer name to be specified.

public void GetData(string computerName)

{

foreach (var counter in Counters)

       {

              try

             {

                    var perfCounter =

                    new PerformanceCounter(

counter.Name,

                                 counter.Type,

                                 counter.Instance,

                                 computerName);

 

// 10+ iterations needed then averaged for ‘true’

                    // results, but extreme latency–how to fix?

                    counter.AverageValue =

                        perfCounter.NextValue().ToString();

}

             catch (InvalidOperationException)

             {

                     // This is thrown when the counter

// doesn’t exist on the system.

counter.AverageValue = “N/A”;

}

catch (Exception ex)

{

                     throw new

                    Exception(“Exception occured in Monitor: “

+ ex.Message, ex);

}

}

}

If you notice from the code comments, there is a bit of a glitch right here.  NextValue() is a single millisecond (?) in time and if you’ve ever watched PerfMon, you see this go up and down VERY rapidly.  Unfortunately, with only a single look at the counter, you (more often than not) will get ‘0’ back.

A fix to this is to iterate through the counter, add them all up, then divide by the iterations to get an average.  Unfortunately, that REALLY increases the amount of time it takes to generate the page and pass the user on to the error page.  Could this be done Async?  Hmm.

The counters are self-contained in a simple nested class called Counter.

public class Counter

{

public string Name { get; set; }

       public string Type { get; set; }

       public string Instance { get; set; }

       public string AverageValue { get; set; }

       public string DisplaySuffix { get; set; }

}

To handle the errors, I’m using the Page_Error method (in a base class page called DefaultPage) to handle my error collection and redirection.

protected void Page_Error(object sender, EventArgs e)

{

Exception ex = Server.GetLastError();

 

var builder = new StringBuilder();

      

builder.AppendLine(

string.Format(“<h1>{0}</h1>”, Server.MachineName));

       builder.AppendLine(

string.Format(“<p>Exception: {0}</p>”, ex.Message));

 

       var monitor = new PerformanceMonitor();

Now, using the monitor object, start adding in counters.  Here are a few of the various counters you can add.  For a full list, go to Start > Run > “perfmon /wmi” and connect to your various servers—from there you can pick out of the list the available counters.

// Base counters (System counters).

       monitor.AddCounter(“Memory”,

“Available MBytes”, “”, “MB”);

       monitor.AddCounter(“Memory”,

             “Pages/sec”, “”, “”);

       monitor.AddCounter(“LogicalDisk”,

             “% Disk Time”, “_Total”, “%”);

       monitor.AddCounter(“Processor”,

             “% Processor Time”, “_Total”, “%”);

       monitor.AddCounter(“TCPv4”,

             “Connections Established”, “”, “”);

           

       // IIS specific errors.

       monitor.AddCounter(“Web Service”,

              “Current Connections”, “_Total”, ” Connection(s)”);

       monitor.AddCounter(“Web Service”,

             “Connection Attempts/sec”, “_Total”, ” Connection(s)”);

       monitor.AddCounter(“Web Service Cache”,

             “File Cache Hits %”, “”, “%”);

 

// ASP.NET specific errors.

       monitor.AddCounter(“ASP.NET v2.0.50727”,

              “Application Restarts”, “”, “”);

monitor.AddCounter(“ASP.NET v2.0.50727”,

              “Requests Current”, “”, “”);

 

// SQL Server specific errors.

       monitor.AddCounter(“SQLServer:General Statistics”,

              “Logical Connections”, “”, “”);

monitor.AddCounter(“SQLServer:General Statistics”,

              “User Connections”, “”, “”);

monitor.AddCounter(“SQLServer:SQL Errors”,

              “Errors/sec”, “_Total”, “”);

With that, call the GetData method and iterate through the Counters property. 

monitor.GetData(Server.MachineName);

foreach (var counter in monitor.Counters)

{

builder.AppendLine(

              string.Format(“<p><strong>{0} / {1}</strong> – {2}</p>”,

             counter.Name, counter.Type, counter.AverageValue));

}

Now, we’re ready to send our email and redirect the user.

var client = new SmtpClient(“mailServer”, 25);

var message = new MailMessage

{

       Subject =

“Error Report – “ +

Server.MachineName + “: {Application Name}”,

From =

new MailAddress(“weberrors@yourdomain.net”,

“WebServer Error Manager”),

Body = builder.ToString(),

       IsBodyHtml = true,

       To = { “youremail@yourdomain.net” }

};

 

client.Send(message);

message.Dispose();

 

Response.Redirect(“ErrorPage.aspx”, true);

Throwing a generic error on Default.aspx:

protected void Page_Load(object sender, EventArgs e)

{

throw new Exception(“Broke’d”);

}

Results in the following email:

WEBDEV1

Exception: Broke’d

Memory / Available MBytes – 8370

Memory / Pages/sec – 0

LogicalDisk / % Disk Time – 0

Processor / % Processor Time – 0

TCPv4 / Connections Established – 10

Web Service / Current Connections – 1

Web Service / Connection Attempts/sec – 0

Web Service Cache / File Cache Hits % – 61.90476

ASP.NET v2.0.50727 / Application Restarts – 1

ASP.NET v2.0.50727 / Requests Current – 1

SQLServer:General Statistics / Logical Connections – 1

SQLServer:General Statistics / User Connections – 1

SQLServer:SQL Errors / Errors/sec – 0

There are dozens of ways to do this—I’m simply passing my user to a generic error page and not bothering them with the error details.  I could return the error details to the user and send an email, or intercept and send additional information from the Server and Request object (such as username, etc).

This code is “barebones”—hence no error catching or such.  The PerformanceMonitor class could also be refactored into a larger ErrorMonitor class that handled sending the emails and such. 

%d bloggers like this: