Automated Gomez Performance Monitoring

Gomez provides an on-demand platform that you use to optimize the performance, availability and quality of your Web and mobile applications. It identifies business-impacting issues by testing and measuring Web applications from the “outside-in” — across your users, browsers, mobile devices and geographies — using a global network of 100,000+ locations, including the industry’s only true Last Mile.

F5 and Gomez have partnered to provide a users of both technologies an easy way to integrate the two together.  In this article I will focus on the Web Performance Management component of the Gomez Platform.  Key features of their Web Performance Management solution include

  • Rapidly detect and troubleshoot problems that directly impact your customers.
    • Identify the root cause of issues using real-user passive monitoring.
    • Ensure web application availability and performance for key user segments.
  • Optimize web application performance
    • Improve web experiences across mobile, streaming, and web applications.
    • Reduce downtime and response times with detailed object-level, page, connection, and host data across multiple browsers.
  • Make smart technology investments and manage service providers effectively
    • Quantify the benefits of technology investments such as Content Delivery Networks (CDNs), virtualization, and infrastructure changes.
    • Ensure service level agreement compliance

All of these features can be possible by simply inserting a small section of client side JavaScript code into your web page requests.  Now, for a small site with only a couple of pages, this is fairly simple to do, but for sites like DevCentral with many 1000’s of pages, it is very cumbersome and requires developer support for integration.  In addition to that, application level changes typically require a testing cycle before being deployed into production.

The Client-side Script

The Gomez script looks something like this:

   1: <SCRIPT LANGUAGE="JavaScript"><!--
   2: var gomez={gs: new Date().getTime(), acctId:'XXXXXX', pgId:'', grpId:''};
   3: //--></SCRIPT> 
   4: <script src="/js/axfTag.js" type="text/javascript"></script>

The key configuration components are the following variables

  • acctId – Your Gomez Application id to link client page request to your Gomez Account
  • pgId – An optional page identifier that allows you to give a page, or set of pages, a unique user friendly name in the reporting.
  • grpId – An optional group identifier that you can use to further identify your page views.  This is highly useful in multi-datacenter deployments when you would like to differentiate the servers serving up your applications.

Enter iRules

With our embedded scripting technology iRules on the BIG-IP, deploying this client script code across all of your application becomes very simple.  Since all of your application traffic is traveling through the BIG-IP, it makes perfect sense to “inject” this code at the network layer.  That is exactly what I’m going to do with this solution.

Prerequsites

Make sure your Virtual Server is configured as follows:

 

  • Ensure that your Virtual Server has the default (or custom) “stream” profile assigned in it’s configuration.
  • Your Virtual Server must also have an associated HTTP Profile with the “Response Chunking” property set to “Rechunk”.

Handling The Request

You’ll notice I utilized the ability to serve up content directly from the iRule to eliminate the need to deploy the Gomez bootstrap code to a separate server.  Also, by implementing it this way instead of injecting the code directly into the page response, allows the browser to cache that data and reduce the size of your page request.

   1: when HTTP_REQUEST {
   2:  
   3:   set GOMEZ_APP_ID "XXXXXX";
   4:   set GOMEZ_DEBUG 0;
   5:   set gws_luri [string tolower [HTTP::uri]]
   6:   
   7:   if { $gws_luri eq "/js/axftag.js" } {
   8:     
   9:     # -----------------------------------------------------------------------
  10:     # Serve up Gomez bootstrap javascript
  11:     # -----------------------------------------------------------------------
  12:     HTTP::respond 200 content [string map {LBR \{ RBR \}} {/*Gomez ...}] "Content-Type" "application/x-javascript";
  13:  
  14:   } else {
  15:     
  16:     # ---------------------------------------------------------------------
  17:     # Don't allow response data to be chunked
  18:     # ---------------------------------------------------------------------
  19:     if { [HTTP::version] eq "1.1" } {
  20:       # -------------------------------------------------------------------
  21:       # Force downgrade to HTTP 1.0, but still allow keep-alive connections.
  22:       # Since HTTP 1.1 is keep-alive by default, and 1.0 isn't,
  23:       # we need make sure the headers reflect the keep-alive status.
  24:       # -------------------------------------------------------------------
  25:       if { [HTTP::header is_keepalive] } {
  26:         HTTP::header replace "Connection" "Keep-Alive";
  27:       }
  28:     }
  29:   }
  30: }

So, for request for the Gomez Bootstrap code (“/js/axftag.js”), the iRule will serve it up.  Otherwise, it will allow the request to pass through to the backend server.

Also, you will need to update your GOMEZ_APP_ID variable with your personal Application Id supplied to you from your Gomez account.

 

Injecting The Client-Side JavaScript

Now that the request has gone through to the backend application, we are ready to handle the response from the servers.  We are going to utilize the Stream profile to do the actual content replacement.  It’s fast and very easy to configure. 

Since we only want to inject the client code in valid web page responses, I’ve added a check for Content-Type “text/html” as well as a success HTTP Response code (200).  Then we’ll create a variable for the Gomez client code with the embedded Application Id and set the Stream expression for the Stream Filter to insert it right before the end of the Head element (or Body element if the Head element doesn’t exist in the response).

 

   1: when HTTP_RESPONSE {
   2:   
   3:  # -------------------------------------------------------------------------
   4:  # Stream filter is disabled by default
   5:  # -------------------------------------------------------------------------
   6:  STREAM::disable;
   7:   
   8:  # -------------------------------------------------------------------------
   9:  # Only process stream replacement for a valid response and content type is html.
  10:  # -------------------------------------------------------------------------
  11:  if { ([HTTP::header Content-Type] starts_with "text/html") && ([HTTP::status] == 200) } {
  12:    
  13:    set gomez_client [subst {<SCRIPT LANGUAGE="JavaScript"><!--
  14: ar gomez={gs: new Date().getTime(), acctId:'$::GOMEZ_APP_ID', pgId:'', grpId:''};
  15: /--></SCRIPT> 
  16: script src="/js/axfTag.js" type="text/javascript"></script>
  17:  ];
  18:  
  19:    if { $::GOMEZ_DEBUG > 1 } { log local0. "Adding Gomez JavaScript"; }
  20:    if { $::GOMEZ_DEBUG > 2 } { log local0. "$gomez_client"; }
  21:    
  22:    # -----------------------------------------------------------------------
  23:    # Set the stream replacement expression
  24:    # -----------------------------------------------------------------------
  25:    set stream_expression "@</\[Hh]\[Ee]\[Aa]\[Dd]>@$gomez_client</head>@@<\[Bb]\[Oo]\[Dd]\[Yy]>@$gomez_client<body>@";
  26:    STREAM::expression $stream_expression;
  27:     
  28:    if { $::GOMEZ_DEBUG > 2 } { log local0. "Current Stream Expression: $stream_expression"; }
  29:     
  30:    # -----------------------------------------------------------------------
  31:    # Enable the stream filter for this request.
  32:    # -----------------------------------------------------------------------
  33:    STREAM::enable;
  34:    
  35:  } else {
  36:    if { $::GOMEZ_DEBUG > 1 } { log local0. "Ignoring type [HTTP::header Content-Type], status=[HTTP::status]" }
  37:  }
  38:    

I’ve included the STREAM_MATCHED event in the case that the Stream profile finds multiple matches.  After the first match, the Stream profile is disabled so that only one replacement occurs.

   1: when STREAM_MATCHED {
   2:   if { $::GOMEZ_DEBUG > 0 } { log local0. "URI: $gws_luri matched [STREAM::match]"; }
   3:  
   4:   # -------------------------------------------------------------------------
   5:   # We've found a match, so disable the stream profile for all subsequent
   6:   # matches in the response.
   7:   # -------------------------------------------------------------------------
   8:   STREAM::disable;
   9: }

Conclusion

With this simple iRule, you can now inject the Gomez client side JavaScript code in all of your applications without effecting your application teams.  In a future article, I will discuss how you can further customize this iRule by adding support for their Page and Group identifiers so if you want a little more granularity in the reporting, stay tuned.

Download

You can download the full script in the iRules CodeShare under GomezInjection

Published May 13, 2010
Version 1.0