Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

ASP.NET Health Monitoring

0.00/5 (No votes)
13 Jul 2012 1  
ASP.NET Health Monitoring

ASP.NET Health Monitoring is one of the framework gems that is often forgotten by ASP.NET developers or web server administrators. It provides great monitoring features, often allowing you to rapidly diagnose failing applications or systems. Have you ever wondered why ASP.NET errors appear in the system event log? It’s thanks to the ASP.NET Health Monitoring and its configuration in the master web.config file (the one stored in the framework installation directory):

<healthMonitoring>
    <bufferModes>...</bufferModes>
    <providers>...</providers>
    <eventMappings>...</eventMappings>
    <profiles>...</profiles>
    <rules>
        <add name="All Errors Default" 
            eventName="All Errors" provider="EventLogProvider"
            profile="Default" minInstances="1" 
            maxLimit="Infinite" minInterval="00:01:00"
            custom="" />
        <add name="Failure Audits Default" eventName="Failure Audits"
            provider="EventLogProvider" profile="Default" minInstances="1"
            maxLimit="Infinite" minInterval="00:01:00" custom="" />
    </rules>
</healthMonitoring>

Let’s analyze quickly the above snippet. A rule defines which ASP.NET health events will be logged (eventname) by the chosen provider and at what frequency. The ASP.NET health events hierarchy can be found here. eventname used in the rule definition is a name of the mapping that is defined earlier in the web.config file. Example event mapping looks as follows:

<eventMappings>
    <add name="All Errors" type="System.Web.Management.WebBaseErrorEvent,
     System.Web,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"
        startEventCode="0" endEventCode="2147483647" />
</eventMappings>

If you recall the events hierarchy diagram, you would notice that WebBaseErrorEvent is a parent for WebErrorEvent and WebRequestErrorEvent. So by using the All Errors mapping in the rule’s eventname attribute’s value, we inform ASP.NET framework that we want to log all ASP.NET error events. This is a really nice feature and you may use it to create your own event groupings. Other events that are performance related include:

<add name="Heartbeats" type="System.Web.Management.WebHeartbeatEvent,
System.Web,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"
        startEventCode="0" endEventCode="2147483647" />
<add name="Application Lifetime Events" 
type="System.Web.Management.WebApplicationLifetimeEvent,System.Web,Version=4.0.0.0,
Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"
    startEventCode="0" endEventCode="2147483647" />
<add name="Request Processing Events" type="System.Web.Management.WebRequestEvent,
System.Web,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a"
    startEventCode="0" endEventCode="2147483647" />

If you are also interested in security auditing, check the WebAuditEvent specification. Let’s see how this works with a real ASP.NET code. We will start with a simple Default.aspx page:

<%@ Page Language="C#" AutoEventWireup="true" %>

<script runat="server">

  public void Page_Load() {
      Response.Write("OK");
  }
</script>

and a following web.config file:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.0">
    </compilation>

    <healthMonitoring enabled="true" heartbeatInterval="120">
      <rules>
        <add name="Heartbeats Default" eventName="Heartbeats" provider="EventLogProvider"
              profile="Default" />
        <add name="Application Lifetime Events Default" 
              eventName="Application Lifetime Events" 
              provider="EventLogProvider"
              profile="Default" />
        <add name="Request Processing Events Default" 
              eventName="Request Processing Events" 
              provider="EventLogProvider"
              profile="Default" />
      </rules>
    </healthMonitoring>
  </system.web>
</configuration>

If you deploy this code to a server and call the Default.aspx page, new events should appear in the event log. Events with codes 1001 and 1002 provide information about application startup and shutdown. Events with code 1005 issued every 120s (heartbeatInterval="120") reveal the application state:

Look how many information is exposed by this simple event. For instance, we can easily say whether our application is consuming too much memory or whether there is any latency in serving user requests (Requests queued). If we find any issue, we can further profile the application on a machine where the problem appeared (Machine name, Process ID). For instance, if your application shares an application pool with some other applications, you may need to check whether the memory problem is not caused by one of them.

In this paragraph, I will pick few events and show you which part of the ASP.NET framework fires them. I found this out by simply placing a breakpoint on a System.Web.Management.EventLogWebEventProvider.ProcessEvent method. Once the breakpoint was hit, I collected a managed call stack from the debugger. For instance, a call stack for WebApplicationLifetimeEvent shows that it’s the HttpApplicationFactory who pulled the event trigger when application was awakening to serve the first request:

System.Web.dll!System.Web.Management.EventLogWebEventProvider.ProcessEvent
(System.Web.Management.WebBaseEvent eventRaised = 
       {System.Web.Management.WebApplicationLifetimeEvent}) + 0x13 bytes	
System.Web.dll!System.Web.Management.WebBaseEvent.RaiseInternal(System.Web.Management.WebBaseEvent 
       eventRaised = {System.Web.Management.WebApplicationLifetimeEvent}, System.Collections.ArrayList 
       firingRuleInfos = Count = Cannot evaluate expression because the code 
                                 of the current method is optimized., 
       int index0, int index1) + 0x265 bytes	
System.Web.dll!System.Web.Management.WebBaseEvent.RaiseSystemEventInternal
      (string message, object source, 
      int eventCode, int eventDetailCode, System.Exception exception, string nameToAuthenticate) + 
      0xef bytes	
System.Web.dll!System.Web.HttpApplicationFactory.EnsureAppStartCalled
(System.Web.HttpContext context = {System.Web.HttpContext}) + 0xaf bytes	
System.Web.dll!System.Web.HttpApplicationFactory.GetApplicationInstance
(System.Web.HttpContext context = {System.Web.HttpContext}) + 0x5d bytes	
System.Web.dll!System.Web.HttpRuntime.ProcessRequestInternal
(System.Web.HttpWorkerRequest wr = {Microsoft.VisualStudio.WebHost.Request}) + 0x20e bytes	
System.Web.dll!System.Web.HttpRuntime.ProcessRequestNoDemand
(System.Web.HttpWorkerRequest wr) + 0x6e bytes	
System.Web.dll!System.Web.HttpRuntime.ProcessRequest(System.Web.HttpWorkerRequest wr) + 0x47 bytes

From WebHeartbeatEvent call stack, we can read that it’s fired by a timer (on a dedicated system thread):

System.Web.dll!System.Web.Management.EventLogWebEventProvider.ProcessEvent
(System.Web.Management.WebBaseEvent eventRaised = 
         {System.Web.Management.WebHeartbeatEvent}) + 0x13 bytes
System.Web.dll!System.Web.Management.WebBaseEvent.RaiseInternal
(System.Web.Management.WebBaseEvent eventRaised = 
         {System.Web.Management.WebHeartbeatEvent}, 
         System.Collections.ArrayList firingRuleInfos = Count = 
         Cannot evaluate expression because the code of the current method is optimized., 
         int index0, int index1) + 0x265 bytes
System.Web.dll!System.Web.Management.WebBaseEvent.RaiseSystemEventInternal(string message, 
          object source, int eventCode, int eventDetailCode, 
          System.Exception exception, string nameToAuthenticate) + 0xef bytes
System.Web.dll!System.Web.Management.HealthMonitoringManager.HeartbeatCallback(object state) + 
0x27 bytes
mscorlib.dll!System.Threading.TimerQueueTimer.CallCallbackInContext(object state) + 0x2c bytes
mscorlib.dll!System.Threading.ExecutionContext.RunInternal
(System.Threading.ExecutionContext executionContext, 
          System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0xa7 bytes
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, 
          System.Threading.ContextCallback callback, object state, bool preserveSyncCtx) + 0x16 bytes
mscorlib.dll!System.Threading.TimerQueueTimer.CallCallback() + 0x71 bytes
mscorlib.dll!System.Threading.TimerQueueTimer.Fire() + 0xc0 bytes
mscorlib.dll!System.Threading.TimerQueue.FireNextTimers() + 0x131 bytes
mscorlib.dll!System.Threading.TimerQueue.AppDomainTimerCallback() + 0x1c bytes

Finally WebRequestErrorEvent is fired by the HttpRuntime after the request processing was finished:

System.Web.dll!System.Web.Management.EventLogWebEventProvider.ProcessEvent
(System.Web.Management.WebBaseEvent eventRaised = 
         {System.Web.Management.WebRequestErrorEvent}) + 0x13 bytes	
System.Web.dll!System.Web.Management.WebBaseEvent.RaiseInternal(System.Web.Management.WebBaseEvent 
          eventRaised = {System.Web.Management.WebRequestErrorEvent}, 
          System.Collections.ArrayList firingRuleInfos 
          = Count = Cannot evaluate expression because the code of the current method is optimized., 
          int index0, int index1) + 0x265 bytes	
System.Web.dll!System.Web.Management.WebBaseEvent.RaiseSystemEventInternal
          (string message, object source, 
          int eventCode, int eventDetailCode, System.Exception exception, 
          string nameToAuthenticate) + 0xef bytes	
System.Web.dll!System.Web.Management.WebBaseEvent.RaiseRuntimeError
(System.Exception e, object source) + 0xa3 bytes	
System.Web.dll!System.Web.HttpResponse.ReportRuntimeError
(System.Exception e = {Cannot evaluate expression because 
          the code of the current method is optimized.}, 
          bool canThrow = true, bool localExecute = false) + 0x58 bytes	
System.Web.dll!System.Web.HttpRuntime.FinishRequest
(System.Web.HttpWorkerRequest wr = {Microsoft.VisualStudio.WebHost.Request}, 
          System.Web.HttpContext context = {System.Web.HttpContext}, System.Exception e) + 0x16e bytes	
System.Web.dll!System.Web.HttpRuntime.OnHandlerCompletion(System.IAsyncResult ar) + 0x9c bytes	
System.Web.dll!System.Web.HttpAsyncResult.Complete(bool synchronous, object result, 
          System.Exception error, System.Web.RequestNotificationStatus status) + 0x3a bytes	
System.Web.dll!System.Web.HttpApplication.ApplicationStepManager.ResumeSteps
(System.Exception error) + 0x288 bytes	
System.Web.dll!System.Web.HttpApplication.System.Web.IHttpAsyncHandler.BeginProcessRequest(
          System.Web.HttpContext context, System.AsyncCallback cb, object extraData) + 0xf8 bytes	
System.Web.dll!System.Web.HttpRuntime.ProcessRequestInternal(System.Web.HttpWorkerRequest wr = 
         {Microsoft.VisualStudio.WebHost.Request}) + 0x284 bytes	
System.Web.dll!System.Web.HttpRuntime.ProcessRequestNoDemand(System.Web.HttpWorkerRequest wr) + 
0x6e bytes	
System.Web.dll!System.Web.HttpRuntime.ProcessRequest(System.Web.HttpWorkerRequest wr) + 0x47 bytes

You may now ask yourself how such monitoring impacts your application performance. Unfortunately, I can’t give you the exact numbers but I assure you that Microsoft did its best to make it as invisible as possible Smile. I think that the most important performance related decision is the choice of the provider which will collect health events. There are few providers available out of the box, including EventLogWebEventProvider, IisTraceWebEventProvider, TraceWebEventProvider or SqlWebEventProvider. For instance, I would recommend using SqlWebEventProvider for storing infrequent events, such as audit, error or application lifetime events – having them saved in a SQL Server table might help you generate some performance-related reports in the future. For more frequent events (such as heart beats or web request events), you should look for providers that can consume large amount of data with insignificant delays – I suppose that TraceWebEventProvider, IisTraceWebEventProvider or even EventLogWebEventProvider will lead the field here.

To conclude, health monitoring events provide great insight into an application lifetime. By processing them correctly, you may easily monitor your server web farms and spot any anomalies before they become a major issue. If you would like to play with this technology, go and grab my sample app and adapt it to your needs.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here