Previous Page
Next Page

5.2. Measuring Improvements

Measuring the time it takes to complete a task is one of the most useful metrics when looking at the success of an AJAX implementation. The actual measurement process is broken down into three simple steps:

1.
Identifying a task's starting and ending points

2.
Adding instrumentation to measure the starting and ending times

3.
Combining multiple data points into useful information

Deciding which tasks to measure is generally a simple process; you need only find the areas in the application about which users always complain. If a process is slow and clunky, it's a good target for AJAX and therefore a good target for measurement. After choosing the task you need to measure, identify its starting and ending points. It's important that you measure the entire process. You don't want to focus on page loads or the technical pieces, but actual steps taken by the user. If the process is complex, you may find it useful to watch users in action to see how things are actually done.

Once you've identified the start and end points, you need to add instrumentation. In most cases, you can do this by making some simple AJAX requests to a recording script. One call marks the beginning of the process, and a second records the ending. In our example case, we'll be recording the time it takes to select a user to edit, as shown in Figure 5-5. This example is extremely artificial, but useful, because the goal is to show how to instrument a process, not how to build an AJAX user editor.

Figure 5-5. Selecting a user


The breakdown of this task is as follows: Load the page, search for a user, and then select the user from the results. The start of this task could be considered the loading of the page or the clicking of the Search for User button. In our example, we use the clicking of the Search for User button because it will help reduce the amount of variance in our measurement. The end of the process occurs when the selectUser JavaScript function runs; in an actual implementation, this would either redirect us to a user editor or populate an editing form below the selection section. A unique ID will also need to be created so that the start and end times can be matched together, but as long as the client is making only one request at a time, this ID can be created and stored in our data-storing script.

To implement the instrumentation, we will be using our basic HttpClient XMLHttpRequest wrapper. We will be making an AJAX call to process.php at the beginning and the end of the process. process.php will store this data in the session, and then process.php's endProcess function will match it with a second end request that happens when the process is complete. A simple report (see Figure 5-6) can then be run to see how long each attempt to select a user took. The data storage is basic in this implementation and would need to be replaced with a database if you wanted to collect data from multiple machines.

The reusable parts of the measurement process are the process.php storage script shown in Listing 5-1 and the Monitor JavaScript class shown in Listing 5-2.

Listing 5-1. process.php

1  <?php
2  session_start();
3
4  if (!isset($_SESSION['data'])) {
5     $_SESSION['data'] = array();
6  }
7  if (!isset($_SESSION['id'])) {
8    $_SESSION['id'] = false;
9  }
10
11
12  function startProcess() {
13    if (!$_SESSION['id']) {
14      $now = time();
15      $id = uniqid('m');
16          $_SESSION['id'] = $id;
17      $_SESSION['data'][$id]['start'] = $now;
18      $_SESSION['data'][$id]['name'] = $_GET['process'];
19    }
20  }
21
22  function endProcess() {
23    $now = time();
24    $_SESSION['data'][$_SESSION['id']]['end'] = $now;
25    $_SESSION['id'] = false;
26  }
27
28  function printStats() {
29    echo "<table border=1><tr><th>Name</th><th>Start Time</th>
30      <th>Run   time (seconds)</th></tr>";
31    foreach($_SESSION['data'] as $process) {
32      echo "<tr><td>$process[name]</td><td>".
33        date('Y-m-d H:i:s',$process['start']) .
34        '</td><td>';
35      if (isset($process['end'])) {
36        echo ($process['end'] - $process['start']);
37      }
38      echo '</td></tr>';
39    }
40    echo "</table>";
41  }
42
43  switch($_GET['action']) {
44    case 'start':
45      startProcess();
46      break;
47    case 'end':
48      endProcess();
49      break;
50    case 'data':
51      printStats();
52      break;
53  }
54  ?>

Listing 5-1 uses a PHP session to store its data, so the script starts out by setting this up. We start the session on line 2 and then set some default values on lines 49. Lines 1241 define three functions, one for each of the actions the script can take. The startProcess function (lines 1220) first checks if we have a current ID stored in the session; this check allows us to ignore multiple start requests for the same process. If there isn't a stored ID, startProcess stores the current time, creates a new random ID, and then puts this data, along with the process name, into the session. The endProcess function (lines 2226) stores the current time as the end time and then clears out the ID to allow another process to start. These two functions provide the basic data-gathering capabilities of the script.

The third function, printStats (lines 2841), creates a table that provides basic reporting. It loops over the data stored in the session and creates an HTML table. While doing this, it uses the start and end times to calculate how long each process took. The output from this function is shown in Figure 5-6. Lines 4353 control which of the functions is called. The function to call is selected by the GET variable action. On the HTML side, JavaScript monitoring code makes AJAX requests to process.php to store the usage data.

Figure 5-6. A simple report of how long attempts to select a user took


Listing 5-2. Monitor.js

1  // Class for monitoring time users spend performing actions
2
3  function Monitor() {
4     this.httpclient = new HttpClient();
5     this.httpclient.isAsync = true;
6     this.httpclient.callback = function() {};
7  }
8  Monitor.prototype = {
9     startProcess: function(name) {
10          this.httpclient.makeRequest(
11          'process.php?action=start&process='+name);
12    },
13    endProcess: function(name) {
14          this.httpclient.makeRequest(
15          'process.php?action=end&process='+name);
16    }
17 }

The monitor class is quite simple; it sets up an instance of HttpClient for asynchronous operation in its constructor (lines 37) and then defines two functions. The first startProcess sends a request to process.php, which triggers its startProcess function. The second function, endProcess (lines 1316), sends a similar request to process.php, but this time, the AJAX request triggers the matching endProcess PHP function. The main purpose of this class is to make it easier to instrument application pages, so it takes care of the boilerplate code for you. It is also a good place to add other methods if you find yourself needing to collect other data, such as which actions a user performs.

Now that we have a basic instrumentation framework set up to collect process times, we need to add it to a script. The process-time data can be useful in AJAX-driven pages to collect data that is generated by changes you are making. It is also useful in non-AJAX pages to help measure slow processes and to find ones that should be updated. Data collection like this can also be useful in making decisions on what data to prefetch, but normally, more data than simple timings is necessary because you need to identify which actions the user is most likely to perform. Listing 5-3 shows a simple user-selection script that uses the Monitor JavaScript class from Listing 5-2 to record how long each selection takes.

Listing 5-3. selectUser.class.php

1  <?php
2  /**
3   * This is an example class, which searches for a user from an array
4   * In most cases this class would actually query a database
5   */
6  class selectUser {
7
8     var $users = array(
9           1 => 'Joshua Eichorn',
10          2 => 'Travis Swicegood',
11          3 => 'Random Person 1',
12          4 => 'Random Person 2',
13          );
14
15    function search($input) {
16          $ret = array();
17
18          foreach($this->users as $key => $name) {
19                if (stristr($name,$input)) {
20                      $ret[$key] = $name;
21                }
22          }
23          return $ret;
24    }
25 }
26 ?>

The actual searching takes place in a related class, selectUser. This class does all its searching against an array to keep the example as simple as possible, but in most cases, this process would be database-driven. The class has a single method search (lines 1524), which takes an input. The method then case-insensitively checks this input to see if it exists in the array of any of the users stored in the class. Last, the method builds an array from the matching results and then returns this array. The HTML user interface is built in Listing 5-4, which uses the selectUser class to handle POST requests to the page.

Listing 5-4. selectUser.php

1  <html>
2  <head>
3     <title>Select User Non AJAX</title>
4
5
6  <script type="text/javascript" src="HttpClient.js"></script>
7  <script type="text/javascript" src="Monitor.js"></script>
8  <script type="text/javascript">
9  var monitor = new Monitor();
10  function selectUser(el) {
11     alert('Selected User with an id of: '+el.value);
12     monitor.endProcess('Select User');
13  }
14  </script>
15  </head>
16  <body>
17
18 <div id="HttpClientStatus"></div>
19
20 <h1>Select a User</h1>
21
22 <form action="selectUser.php" method='post'>
23    <input name="name" onclick="monitor.startProcess('Select User')">
24    <input type="submit" value="Search for User">
25 </form>
26
27 <?php
28 require_once 'selectUser.class.php';
29
30  if (isset($_POST['name']) && !empty($_POST['name'])) {
31    $users = new selectUser();
32    $results = $users->search($_POST['name']);
33
34    foreach($results as $key => $val) {
35      echo "<input type='radio' name='user' value='$key'".
36               "id='user_$key' onclick='selectUser(this)'>".
37        "<label for='user_$key'>$val</label><br>\n";
38    }
39  }
40?>

The script starts with some basic setup; then, line 6 includes the XMLHttpRequest wrapper, and line 7 includes the JavaScript Monitor class. Line 9 creates an instance of the Monitor class so that we can easily call startProcess and endProcess throughout the page. Lines 1013 define the JavaScript function that is called at the end of the user-selection process; this function just outputs a selected message and then runs endProcess. Lines 2025 provide the basic HTML UI of the page; this is a form that POSTs its results to the current page. The search input box runs a start process when you click it to start entering your search term.

Lines 2740 perform the search after the form is POSTed to the page. An instance of the selectUser class does the actual searching. The results from this search are then looped over, creating a radio button for each result. An onclick action is added to each radio button, which calls the selectUser function defined in lines 1013.

The basic workflow of this page is shown in Figure 5-7.

  1. The user clicks the Search input box, sending a startProcess request.

  2. The user clicks the Search for User button, POSTing the form.

  3. The script uses the name sent in the POST request to build a list of radio buttons from which the user can select a specific user.

  4. The user clicks a radio button, sending an endProcess request.

Figure 5-7. Workflow measuring how long it takes to select a user


Data collection is an important first step to making good decisions about how and when to implement AJAX. If the user-selection process is already fast, it doesn't make sense to spend time adding AJAX to it; instead, you'll want to find a different process to upgrade. If the process is slow, you can build an AJAX version that adds instrumentation to it and then obtain real numbers on how effective the process is. Ultimately, you may find that an AJAX version of the form won't give you the increased speed that you need, because searching by name doesn't scale to the number of users in your system; instead, your best results might be accomplished by limiting the search results using other criteria, such as department or job title.


Previous Page
Next Page