Web App Diagnostics

Lightweight ASP.NET Automated Performance Analysis

CoverStory

LANGUAGES: VB.NET

ASP.NET VERSIONS: 1.x

 

Web App Diagnostics

Lightweight ASP.NET Automated Performance Analysis

 

By Dr. James McCaffrey

 

The ASP.NET environment has the ability to record performance metrics for a Web application simply by enabling tracing. When tracing is enabled you can manually retrieve the performance information by requesting the virtual trace.axd page. But what if you want to automate performance analysis using this mechanism? In this article, I ll show you a simple system to do just that.

 

As the saying goes, a picture is worth a thousand words, and the best way to demonstrate where I m headed is with a screen shot. Figure 1 shows that I am analyzing the performance of a dummy Web application that searches a data store of product information. Users can search by product ID or product description. My lightweight performance system automatically launches an instance of Internet Explorer, simulates searching for products with the character i in the description, then retrieves the performance trace information for the total time required to render the application page.

 


Figure 1: An example test run.

 

Obviously real Web applications are much more complex, but the techniques I ll show you can be used to automatically analyze the performance of any ASP.NET Web application hosted in Internet Explorer. In the sections that follow, I ll briefly describe the dummy Web application under analysis, present and explain in detail the performance analysis code that generated the image in Figure 1, and describe how you can modify and enhance the techniques presented in this article to meet your own needs. You should find the information here a useful addition to your developer, tester, and manager skill sets.

 

The Web Application under Analysis

Let s take a short look at the Web application we re analyzing so you can understand the goal of the performance automation. Figure 2 shows the state of the Web app after a user selected Product Description from the Search By dropdown control, then typed i into the textbox control, and then clicked the Search button. The search returns product ID, description, and price for two matching products in a listbox control. If you look at the IE address bar you can see I accepted the default WebForm1.aspx name for the dummy application. For simplicity, I also accepted default control names of DropDownList1, TextBox1, Button1, and ListBox1. I will need to know the names of my controls for my automation code.

 


Figure 2: The Web application under analysis.

 

One of the advantages of the performance analysis system I m presenting here is that it does not require access to the application s source code. I used C# to code the Web application, but I could have used VB.NET or any other .NET-compliant language. As I mentioned in the previous section, I do need to know the names of the controls to be able to write my performance analysis automation. In cases where you are analyzing your own Web application s performance, you ll obviously have this information. But even if you don t have direct access to the application s source code, you can easily get the control names by simply doing a View | Source in Internet Explorer.

 

One of the nice innovations that ASP.NET introduced to Web developers is the ability to enable the automatic capture and logging of performance data. If I go to the web.config file associated with my Web application, all I have to do is change the enabled attribute of the trace element from the default false to true:

 

 enabled="true"

 requestLimit="10"

 pageOutput="false"

 traceMode="SortByTime"

 localOnly="true"

/>

 

Now trace data is recorded for each response-request roundtrip. To view this data manually, I simply request the virtual page trace.axd and click on the View Details link for the request I want to see. Figure 3 shows the trace information for the first request (id=0).

 


Figure 3: Manually viewing trace data.

 

Trace.axd is not a physical file, it is an alias for an in-memory DataSet object on the Web server that holds all the trace information for a particular session. When you issue a request to http://machine/webroot/trace.axd, the request is intercepted, trace data is pulled from the Dataset, and the data is placed in a dynamically created page and returned to the client (as shown in Figure 3). This manual process of examining trace data is fine for many situations, but what if you want to automate the process? You ll need the ability to programmatically launch Internet Explorer, load the Web application under analysis, simulate manipulating the application, and retrieve the trace data.

 

The Automation Code

The performance analysis system consists of a single file. I decided to implement my harness as a VB.NET console application program, but I could have used C# or any other .NET-conformable language. The overall structure of the program is shown in Figure 4. After creating a new project using Visual Studio.NET, I add a project reference to the Microsoft Internet Controls component from the classic COM component list. This will allow me to create an InternetExplorer object. Next, I add a reference to the .NET component System.Net so I can create a WebClient object.

 

Imports System.Diagnostics

Imports System.Threading

Imports System.Net

Imports SHDocVw

Imports System.IO

Module Module1

 Dim docComplete As New AutoResetEvent(False)

 Friend WithEvents ie As SHDocVw.InternetExplorer

 Sub Main()

   Try

     ' Launch an IE process

     ' Connect an IE object to process

     ' Clear in-memory trace cache

     ' Simulate manipulating Web app

     docComplete.WaitOne(10000, True)

     ' Close IE

     ' Retrieve performance data

   Catch ex As Exception

     Console.WriteLine("Fatal error " & ex.Message)

   End Try

 End Sub

 Private Sub ie_docComplete(ByVal sender As System.Object, _

                            ByRef url As System.Object) _

                            Handles ie.DocumentComplete

   docComplete.Set()

 End Sub

End Module

Figure 4: The performance analysis program structure.

 

Next, I add several Imports statements so I don t have to fully qualify classes contained in their namespaces. I add an import to System.Diagnostics so I can access its Process class, and add an import to System.Threading so I can call its Thread.Sleep method and create an AutoResetEvent to signal when my Web app is fully loaded into IE. I also add an import to SHDocVw, which is the namespace name associated with the Microsoft Internet Controls component. Finally, I add an import to System.IO so I can create a Stream object to capture the response to the request for performance data on the Web server.

 

One of the trickiest parts of automating Web applications is determining exactly when a Web page is fully loaded in IE. To do this, I declare a module-scope AutoResetEvent object named docComplete that I ll use to notify a waiting thread that a document (Web application) is fully loaded:

 

Dim docComplete As New AutoResetEvent(False)

 

I ll explain this idea in more detail in a moment. Next, I declare an InternetExplorer object and name it ie :

 

Friend WithEvents ie As SHDocVw.InternetExplorer

 

Notice that I use the WithEvents modifier so I can access the InternetExplorer object s DocumentComplete event. I begin my performance analysis by printing a start message to the command shell and declaring two objects:

 

Console.WriteLine(vbCrLf & "Start peformance

                 analysis" & vbCrLf)

Dim p As New System.Diagnostics.Process

Dim browsers As New SHDocVw.ShellWindows

 

The Process object p is my actual Internet Explorer process and the browsers object is a collection of all running browsers, which includes Windows Explorer browsers and Internet Explorer browsers. Next, I launch an instance of Internet Explorer:

 

Console.WriteLine("Launching IE process")

p = Process.Start("iexplore.exe", "about:blank")

If p Is Nothing Then

 Throw New Exception("Failed to launch IE")

End If

Console.WriteLine("IE process handle = " _

                 & p.MainWindowHandle.ToString)

Console.WriteLine("Number open browsers = " _

                 & browsers.Count & " browsers")

 

Notice that I launch IE and load the null about:blank page. I capture the IE process information into my Process object p ; I ll use this later to determine which active browser is my newly launched IE object:

 

Console.WriteLine("Connecting IE object to IE process")

Dim i As Integer

Dim someIE As SHDocVw.InternetExplorer

While i < browsers.Count And ie Is Nothing

 someIE = browsers.Item(i)

 If someIE.HWND.ToString() = _

  p.MainWindowHandle.ToString() Then

   ie = someIE

 End If

 i = i + 1

End While

 

I iterate through all the active browser objects looking for a handle match. Because the InternetExplorer HWND (handle to window) property is an integer, but the process handle is a System.IntPtr data type, I convert both to strings so I can easily compare them for equality. My loop terminates when I run out of browser objects to examine or when I find a match. As a general rule, it s important to add error-checking to almost every step in any automation:

 

If ie Is Nothing Or p.MainWindowHandle.ToString() _

 <> ie.HWND.ToString() Then

 Throw New Exception("Failed to attach to process correctly")

Else

 Console.WriteLine("Successfully attached to process")

End If

 

I then make sure that my IE object attached to a browser object and that it attached my newly created Internet Explorer instance. My next step isn t completely necessary, but it s a nice touch:

 

Console.WriteLine(vbCrLf & "Setting IE to 350 x 460")

ie.Height = 350

ie.Width = 460

 

I adjust the InternetExplorer object s Height and Width properties to a fixed size. Because I m measuring performance and the total time for my Web app to load and render, I want to make sure the IE client area is the same size for each analysis. Next I clear the trace cache:

 

Console.WriteLine(vbCrLf & "Clearing trace cache")

Dim zilch As New Object

ie.Navigate("http://localhost/WebPerfApp/trace.axd?clear=1", _

           zilch, zilch, zilch, zilch)

docComplete.WaitOne(10000, True)

To manually clear the trace cache, navigate to the virtual trace.axd page and click on a clear current trace link. That link simply sends a clear=1 query string to the Web server. So, to automate clearing the trace cache I can send the same query string. The Navigate method of the InternetExplorer object has a lot of parameters that I m not interested in so I just pass empty objects as arguments to those parameters. I will explain the WaitOne method call shortly. Next comes the key code to simulate a user manipulating the Web app:

 

Console.WriteLine("Simulating 'Product Description = i'")

ie.Navigate("http://localhost/WebPerfApp/WebForm1.aspx? _

           __VIEWSTATE=dDwzOTI1MjUyNzQ7Oz7jkzW4GxDFbmU/ _

           fHE0xnFD2fJxAA==&TextBox1=i&DropDownList1= _

           Product%20Description&Button1=clicked", _

           zilch, zilch, zilch, zilch)

docComplete.WaitOne(10000, True)

 

I append a query string with a ViewState value, and values for the TextBox1, DropDownList1, and Button1 control values. Let me explain each. The __VIEWSTATE (note: two underscores) is a Base64-encoded string that represents the state of the Web application when the server last processed it. The value I m using here is the value for the application when it is first loaded. It is possible to get the ViewState value programmatically, but in this example I found it simply by launching the application and then immediately doing a View | Source in IE. If you try this code on your system you ll probably have to change the ViewState value. The TextBox1 and DropDownList1 parts of the query string are fairly self-explanatory. Notice I use %20 to represent a blank space in Product Description . The Button1 value is a bit tricky. It turns out the Web server will interpret any value for Button1 as meaning clicked , so I could have written TextBox1=foo or anything else, but clicked is more readable.

 

Now let me explain how I pause my test automation until the Web application document is completely loaded into IE. Recall that I declared an AutoResetEvent named docComplete at the top of my Module. I also wrote a subroutine that handles the InternetExplorer object s DocumentComplete event:

 

Private Sub ie_docComplete(ByVal sender As System.Object, _

                          ByRef url As System.Object) _

                          Handles ie.DocumentComplete

 docComplete.Set()

End Sub

 

When this subroutine is called it invokes the docComplete object s Set method. In short, when I call the WaitOne method, my automation will halt until the Web application is fully loaded, which fires the InternetExplorer Document Complete event, which in turn will invoke the docComplete.Set method, which will signal the automation to restart. The 10000 argument to WaitOne says to timeout after 10,000 milliseconds, or 10 seconds, so I don t wait forever. After I ve exercised the Web application I can close it:

 

Console.WriteLine("Closing IE in 2 seconds")

Thread.Sleep(2000)

ie.Quit()

 

This is very easy because the InternetExplorer object has a Quit method. I put in a Thread.Sleep(2000) call to arbitrarily pause for two seconds. Because this pauses my automation and not IE, I don t affect my performance metrics. Now that I ve exercised the Web application under analysis, I can programmatically retrieve the trace performance data. First I set up my variables and objects:

 

Dim webClient As New WebClient

Dim s As Stream = _

 webClient.OpenRead("http://localhost/WebPerfApp/ _

                   trace.axd?id=0")

Dim sr As New StreamReader(s)

Dim response As String

response = sr.ReadToEnd()

 

My strategy is to send a Web request to the virtual trace.axd page and grab the HTTP response. I create a new WebClient object, and a Stream object and a StreamReader object capture the HTTP response. I request trace data, then grab the entire response stream into a single string variable I named response . The next step is to parse out the information I need from the response string:

 

Dim target, pattern, time As String

pattern = "End Render"

target = response.Substring(response.IndexOf(pattern),_

                           pattern.Length + 8)

time = target.Substring(pattern.Length, 8)

 

The trace result has a lot of information that you can examine. In this case, I m looking for the cumulative time from the beginning of application initialization to the end of page rendering. By examining the complete response stream, I know that the time to end render is contained in the HTTP response:

 

End Renderx.xxxxxx

 

I use the String.Subtring method to fetch a substring beginning with End Render and then use Substring again to extract only the eight characters that represent the total time value. Depending on what you re looking for, you ll have to edit this part of the program. All that s left to do is to tidy up and print my result:

 

sr.Close()

s.Close()

Console.WriteLine(vbCrLf & "Total time to

                 End Render = " & time)

Console.WriteLine(vbCrLf & "Done")

 

I close my open stream resources and print my total elapsed time to the shell. You may want to write results to a text file, SQL database, or other data store.

 

Adapting and Extending the Automation

You can extend and adapt the lightweight Web application performance analysis automation technique I ve presented here in several ways. Because the performance analysis system is an .exe file, you can easily schedule it to run without manual interaction, using the Windows Task Scheduler for example, or you can integrate the system into an automated build process.

 

For simplicity I used only minimal error checking. In a production system you ll want to insert additional exception handling try-catch-finally blocks and traditional error-checking statements. Also, the performance analysis system I ve presented has all arguments hard-coded for clarity. Depending on your environment, you may want to parameterize the program to make it more flexible.

 

You must be careful how you use performance analysis tools and interpret their results. Because the absolute performance of a Web application will depend on many user factors that are outside your control, you cannot use performance data to conclude exactly how users will experience your application. The best use of performance data is relative meaning, for example, that you can measure performance characteristics of your Web application and see how changes in your design affect performance.

 

With ASP.NET systems growing in complexity, analyzing the performance of your applications is more important than ever before. The lightweight automated performance analysis technique I ve described here can play an important role in your product s development effort.

 

The sample code referenced in this article is available for download.

 

Dr. James McCaffrey works for Volt Information Sciences, Inc., where he manages technical training for software engineers working at Microsoft s Redmond, WA campus. He has worked on several Microsoft products, including Internet Explorer and MSN Search. James can be reached at mailto:[email protected] or mailto:[email protected].

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish