How to instrument a web application

The JavaScript Agent is the central component in UEM for web applications. It collects user experience data in the browser and sends it to a web or application server, where an AppMon agent forwards the data to an AppMon server.

The JavaScript Agent is a script that automatically injects into web pages (of mime type text/html) using either Java or Web Server Agents, or manually at design time if not otherwise possible.

Agent injection

Injecting Agents is what instruments a web site. This consists of:

  • Injecting an inline tag.
  • Injecting a tag with a src-attribute. The inline tag must be injected as the very first script and consists of essential instrumentation parts of the JavaScript Agent. For the best capturing experience, the second tag should be injected right after the inline tag, but before any other JavaScript.

Asynchronous injection

For AppMon 6.5 and later, the JavaScript Agent injects asynchronously by default using the defer script tag attribute. Refer to MDN Documentation for more information.

Asynchronously loaded scripts do not block page loads. However, since the Agent cannot redefine any library variables once initialized (like jQuery or AngularJS), the defer script tag attribute also used for any third party frameworks to be instrumented. Also, the JavaScript Agent must be available before those frameworks start initializing, otherwise it is not possible to capture actions triggered by scripts.

You can always disable asynchronous injection by clearing the Load the JavaScript Agent asynchronously check box in [System Profile] > User Experience > Web applications. See System profile - User Experience for more information.

Automatic injection

The following is a sample tag-combination automatically injected by the Agent as the first script tags in <head> at delivery of the web page ‐ with settings according to the configuration in [System Profile] > User Experience:

<script type="text/javascript">(function(){var a=window;a.dT_?a.console&&a.console.log("Duplicate agent injection detected, turning off redundant initConfig."):window.dT_||(window.dT_={cfg:"tp=0,50,10|lab=1|reportUrl=dynaTraceMonitor|agentUri=/ajax/dtagent_pqtx_0000000.js  
|auto=1|domain=#DOMAIN#|rid=RID_1640928385|rpid=1646377341|app=#APP#"})})();...</script>
<script type="text/javascript" src="/dtagent_bdpx_0000000.js"></script>

The first tag (without src-attribute) is the agent's initialization code. It must be delivered inline and executes synchronously. It performs early-wrapping of native browser functions and any available third party libraries to ensure reliable capturing.

The second tag requests the part of the JavaScript Agent that can be loaded asynchronously and therefore won't block a page from being loaded. The filename defines it's version and which parts of the Agent should be delivered:

  • dtagent is the JavaScript Agent Name set in [System Profile] > User Experience > Global Settings.
  • 700000000GABuildNotYetKnown is the AppMon Agent version (major, minor, revision, buildnumber).

Each part of the version string consists of four characters, trimming leading zeroes, such that it is read like this: 7 0000 0000 GABuildNotYetKnown, which translates to 7.0.0.GABuildNotYetKnown.

  • bdpx is the feature hash, and shows information captured by the Agent, such as bandwidth, dojo, perceived render time and basic XHR detection.

Manual injection

If agents cannot be injected automatically, you can insert the initialization tag manually at design time as the first script tag in <head>. Since the initialization tag and it's contained code varies depending on your application settings, it can easily be retrieved by using the Server REST Interfaces API using https://<AppMon server>:8021/api/v1/profiles/<systemprofile>/applications/<application>/javascriptagent/initcode.

The initialization code performs operations to load the JavaScript Agent (either with or without the defer attribute) and updates it's configuration if required. It is not necessary to update the initialization tag every time you change the UEM configuration, though adjustments are applied faster for new visitors if you do.

Agent Configuration

Injected Agents use configuration settings in the System Profile - User Experience vertical tab, generically:

<script type="text/javascript" src="/dtagent_<featurehash>_<version>.js"></script>

  • <featurehash>: A set of characters that identifies which Agent modules to include and activate. When only the basic Agent is activated, the feature hash is empty resulting in two adjacent underscores in dtagent__<version>.js. The following modules are delivered with AppMon:

  • Ajax (XHR) detection modules:

    • ActiveX XHR Detection (v)1
    • Angular JS (g)
    • Basic XHR Detection (x)
    • Dojo (d)
    • ExtJS (e)2
    • ICEfaces (i)
    • jQuery (j)
    • MooTools (m)
    • Prototype (o)
  • Additional packs:

    • JavaScript Errors (q)
    • Perceived render time (p)
    • Speed Index (S)
    • Streaming (s)
    • Timed Action Support (t)
    • User Timings (T)
    • Visually Complete (V)
  • Community packs (debug mode only):

    • Gomez page and group IDs (z)
    • JS agent async core module
    • JS agent initconfig module
    • Windchill (1)

1 ActiveXObject support (required by IE 6, supported by IE 7 and later) is split from the basic XMLHttpRequest module to reduce file size and performance overhead. The ActiveXObject support module is only needed if the instrumented application utilizes ActiveXObjects to send requests from the JavaScript side that have to be captured. Every AppMon supported browser supports XMLHttpRequests, so most applications only need basic XMLHttpRequest detection. If uncertain whether you need ActiveXObjects support, both modules can be enabled.
2 Some ExtJs-Functions use deferred callbacks when they send XHRs. AppMon does not capture these requests because it can't create a link to a user action. Activate the Timed Action support To avoid this, activate the Timed Action support. It extends user actions to track deferred functions. Ext.FormPanel is a known ExtJs function that must have Timed Action (t) support.
3 The resource timings modules takes advantage of the W3C Resource Timing API. However, not all browsers currently support it, thus the JavaScript Agent performs timing checks based on instrumented html tags and gathers information about load times of external resources. This approach can cause some overhead, so be sure to set resource timing limits within your application configuration.

The following shows a configuration example with activated Dojo and jQuery JavaScript packs:

<script type="text/javascript" src="/dtagent_dj_700000000 GABuildNotYetKnown.js"></script>

Automatic injection Agent tag placement rules

The following set of rules specifies the criteria that determine a suitable location within an HTML document to inject the JavaScript Agent:

  • (<?xml?> rule) If an <?xml ...?> specification is encountered:

    • Ignore it and continue to scan the document.
    • Overrules (non-<meta> tag rule), (non-<head> tag rule) and (initial tag rule).
  • (initial tag rule) If a tag appears before <html> that is not <!DOCTYPE ...>, <html>, <link>, <meta>, <script>, or <style>:

    • Abort without injecting.
  • (<html> tag rule) When <html> is encountered:

    • If a potential injection point is found earlier, inject there, and do not scan further (if there are multiples, the earliest injection point is used).
    • Otherwise continue to scan the document.
  • (<!DOCTYPE> rule) If <!DOCTYPE ...> doesn't specify an HTML (is not <!DOCTYPE HTML ...>):

    • Abort and do not inject.
  • (comment rule) If a <!- comment -> is encountered:

    • Ignore it and continue to scan the document.
    • Overrules (non-<meta> tag rule) and (non-<head> tag rule).
  • (<title> tag rule) If <title> is encountered:

    • Ignore everything until </title>, then continue to scan the document.
    • Overrules (<body> tag rule).
  • (<body> tag rule) If <body> is encountered:

    • Document scan stops.
    • Any potential injection point found earlier is used (in case there were multiple, the earliest injection point is used).
    • If no rule provided an injection point earlier, inject after <body>.
  • (<script src> tag rule) If a <script ...> tag is found within <head> that has a src="" attribute:

    • If a (potential) injection point is found earlier, inject there, and do not scan further (if there are multiples, the earliest injection point is used).
    • Otherwise, inject before this <script> and do not scan further.
    • Overrules (non-<meta> tag rule), (<base> tag rule).
  • (<link> tag rule) If a <link ...> tag is found within <head> that is not inline (doesn't have an href="data:" attribute):

    • If a (potential) injection point is found earlier, inject there, and do not scan further (if there are multiples the earliest injection point is used).
    • Otherwise, inject before this <link> and do not scan further.
    • Overrules (non-<meta> tag rule), (<base> tag rule).
  • (flush rule) (Java only) when flush is called on the injecting stream/writer outside of <head>...</head> and a conditional injection awaits confirmation:

    • Discard the conditional injection point, propagate the flush and continue to scan the document for a new injection point.
    • Overrules any conditional injection point found before by the (non-<head> tag rule).
  • (flush in <head> rule) (Java only) when flush is called on the injecting stream/writer within <head>...</head> and conditional injection awaits confirmation:

    • Keep the injection point and continue to scan the document.
    • Disregard the flush (don't propagate it).
  • (<base> tag rule) If <base ...> is encountered:

    • Inject after the <base ...> tag.
    • Overrules any conditional injection point found before by the (non-<meta> tag rule), (unclosed <meta> rule), (non-<head> tag rule).
  • (non-<meta> tag rule) If a tag is found within <head> that is neither <meta> nor <title>:

    • Inject before it (conditional injection).
    • Continue to scan the document, in case this injection choice gets overruled.
  • (non-<head> tag rule) If a tag is found after <html> but before <head> that is neither <head> nor <body>

    • Inject before it (conditional injection).
    • Continue to scan the document, in case this injection choice gets overruled.
  • (unclosed <meta> rule) If a </head> arrives after a <meta> that doesn't get closed (either by a closing </meta> tag or by the XML-style <meta ... /> tag):

    • Add </meta> followed by the injection, both before the </head> (conditional injection).
    • Continue to scan the document, in case this injection choice gets overruled.
  • (</head> tag rule) on </head>:

    • If a (potential) injection point is found earlier, inject there, and do not scan further (if there are multiples the earliest injection point is used).
    • Otherwise, continue to scan the document.
  • (end of file rule) When end of file is reached (and scanning did not terminate before that):

    • Do not perform an injection.
    • Overrules any conditional injection point found before by the (non-<meta> tag rule), (unclosed <meta> rule), (non-<head> tag rule).
  • (parse error rule) When the document's contents doesn't appear to exhibit the basic structure expected from HTML tags and attributes, it makes further parsing futile:

    • Document scan stops.
    • Any potential injection point found earlier is used (in case there were multiple, the earliest injection point is used).
    • If no rule provided an injection point earlier, do not perform an injection.

Manual injection details

If the JavaScript Agent can't be auto-injected by the Java or Web Server Agent at page delivery, you can still insert the Agent initialization script tag manually at design time.

  • If the server providing the Agent and the one getting the monitoring data are different (domains), you must set the Monitor Request Path and select the Send the Dynatrace monitor request to a foreign domain (CORS) check box. For Cordova / PhoneGap make sure this reference resolves through the domain whitelist and CORS is disabled. See PhoneGap Whitelist Guide for more information.
  • You must also switch the Injection Point to manual in the respective <System Profile> > Agent Group / tier > Sensor Configuration > User Experience sensor > Properties.

Dynamically served Agent

As previously mentioned, use the Server REST Interfaces API to inject the JavaScript Agent initialization tag from this URL:

https://<AppMon server>:8021/api/v1/profiles/<systemprofile>/applications/<application>/javascriptagent/initcode

The initialization tag performs operations to load the correct JavaScript Agent and update the application's UEM configuration as it changes.

Statically served Agent

For less traffic to the instrumented server, you can manually download the JavaScript Agent file and locate it anywhere on your server. As the agent file differs depending on your configuration, the correct file for your application can be retrieved using the Server REST Interfaces API:

https://<AppMon server>:8021/api/v1/profiles/<systemprofile>/applications/<application>/javascriptagent

Be sure to configure the agent location accordingly, so the initialization tag is capable of building the correct agent tag.

For statically served Agents you just need to inline the initialization tag. Upon configuration changes, just switch the agent file.

How does it work?

After UEM configuration changes, the initialization code is created containing all necessary information about the current configuration for each application. It can then be retrieved using the REST API.

If a page is injected with the JavaScript Agent initialization code, it checks if the browser already stored some application information and creates the JavaScript Agent script tag. It is then injected directly after the initialization tag and starts working immediately.

Performance notes

  • Overhead: Since the initialization tag is inlined into a page, there's no additional request that has to be made by the browser. The configuration updates are retrieved as soon as an action is sent.
  • Caching: Since the JavaScript Agent is cached for about a week, there is a chance that the initialization tag loads it with outdated settings. In this case the first page load is tracked using the old settings. A beacon is sent and in response the app / web server Agent provides new configurations which are then stored in the browser's local storage. Upon the next page load these settings are loaded and the initialization code is capable of creating an updated JavaScript Agent tag.
  • Filesize: Depending on the application configuration the obfuscated initialization code's size is about 6.65kb.
  • Browser support: Internet Explorer 6 and 7 have no implementation of Local or Session Storage and therefore do not automatically update upon configuration changes. However, as soon as the cached page ages, the initialization code updates to the latest configuration revision.

Scenarios

There are various ways the initialization code is executed, depending on the visitor.

  • New visitor: First, the initialization code is loaded, which loads the JavaScript Agent depending on the current configuration.
  • Recurring visitor: The full page is loaded from cache. The initialization code checks the browser's local storage for configuration entries. If there are none, it takes the last known configuration and loads the JavaScript Agent from cache. Any new settings sent with the beacon get stored in the local storage.
  • Recurring visitor with changed settings: The full page is loaded from cache. The initialization code checks the browser's local storage for configuration entries. If there are any, it checks if they are newer than the ones contained in the initialization code and takes the latest settings to create the JavaScript Agent tag. If feature hash and version are the same, the JavaScript Agent is loaded from cache.
  • Recurring visitor which shouldn't be tracked: If a visit should not be tracked due to visit percentage settings, the JavaScript Agent still initially injects using the initialization code and sends a beacon containing a load action. The application / web server Agents receive this beacon and decide if the visit should be tracked or not. If not, the beacon is ignored and the response contains a flag. Should the JavaScript Agent receive such a flag, every communication shuts down and no more beacons are sent until the browser session ends.

Monitor signal behavior

The JavaScript Agent sends the gathered information in the form of POST requests. This behavior is recommended for most servers and requires that POST requests are allowed on dynaTraceMonitor*.

In most cases signals are sent as soon as actions are finished. This action starts with a user input that triggers a request and ends as soon as all of the request's callbacks are finished. The load-action occurs on each iframe and full page load and is sent on every page, if user inputs exist or not.

These actions trigger signals to be sent immediately after the actions are finished, in contrast to medium priority signals. These are triggered by timeless and lesser important actions. These medium priority actions are bandwidth, streaming, and resource information, as well as reported values like errors or custom strings. If a signal is queued, this information is appended and doesn't create a separate request.

If multiple actions happen in a small time frame, such as a user action during page load, all of those actions are sent in a single signal.

For longer actions, a preview signal is sent as medium priority signal. Actions are sent only if all of their children and siblings are finished. If, for example, an action is opened and a second one is opened (which automatically is a child of the first one), the first one won't be sent until the second one is finished.

Support for cross-origin resource sharing (CORS)

Reporting from a non-instrumented host

AppMon enables injection of the JavaScript Agent on web servers that are not instrumented. If a website is hosted on a web server where UEM is not active, the JavaScript Agent has to report to a different domain, which can cause cross domain problems. If you use a CDN and don't want to send the monitor signal back through the CDN, you can send the monitor signal using CORS (Cross Origin Resource Sharing).

To enable support for CORS, start the AppMon Client and select the Send AppMon monitoring request to foreign domain in [System Profile] > User Experience vertical tab > [application] horizontal tab > Advanced configuration section.

Be sure to set the Monitor request path configuration to a location (use an absolute path that starts with http) that is UEM-enabled (for example by a Java Agent).

Monitoring CORS requests

Web requests can only be linked to user actions if cookies are available. In case of CORS requests, cookies aren't sent per default. Therefore correlation between user actions and CORS web requests can only be achieved by using the withCredentials property of the XMLHttpRequest object. This applies for an environment like this:

Restrictions

Due to access-control-restrictions it is not possible to capture OPTIONS requests like those sent as CORS preflight requests. Capturing those requests would require sending a AppMon cookie to the receiver of the request, which is not allowed. This could result in User Action PurePaths that have the correct timing values, but are missing the time the OPTIONS request took before the real CORS request fires. See MDN for more information about CORS preflight requests.