Skip to content

Getting Started

Christopher Lane Hinson (Web Performance) edited this page Jun 2, 2017 · 8 revisions

Introduction

In this tutorial we will:

  1. Set up a web server.
  2. Use Legion to make HTTP requests to the web server.
  3. Measure the time it takes for the server to respond.
  4. Talk about how to interpret the results.

Getting Started

We'll be using the legion-starter-pack, a "batteries included" package containing stable versions of all of the tools.

git clone https://github.com/lane-webperformance/legion-starter-pack.git

Having done this, you'll find examples in the examples directory, or you can code up this example from scratch by editing the skeleton testcase named testcase.js.

We'll start by requiring legion and legion-io-fetch:

const L = require('legion');
const fetch = require('legion-io-fetch');

Legion is the base library. With it, you can build and run testcases, and also add setup and shutdown hooks if you need them. Testcases built in this way contain a special main() method that makes it easy to launch your testcase from the command line. Legion-io-fetch is an instrumented wrapper based on node-fetch, which in turn is based on the new fetch standard.

Setting up the Test Server

const obstacle = require('legion-obstacle-course');

The Legion Obstacle Course isn't something we would use in serious testcase development. It's a trivial web server with a variety of apps that we can use as pedagogical examples. In this situation, our testcase is extremely simple: we'll just load the root index document.

The next lines set up the details for our test server. Some of this will look a lot like setting up an express.js server, because that's exactly what we're doing.

const port = 8500;
const host = 'http://localhost:' + port;
const secure_host = 'https://localhost:' + port;
let server = null; 

The next sequence of fluent calls will (1) create a testcase, (2) add a hook to start the test server before the test, and (3) add a hook to gracefully shut down the test server after the test.

L.create()

  .withBeforeTestAction(() => {
    server = obstacle.listen(port);
  })

  .withAfterTestAction(() => {
    server.close();
    server = null;
  })

Defining the Testcase

Now we're ready to describe the testcase itself. This testcase will make one request using HTTP and another using HTTPS. Since our test server (express) doesn't accept HTTPS requests out-of-the-box, this second request will fail.

  .testcase(fetch.text(host)
     .chain(fetch.text(secure_host)))

Let's break this down a bit. The call to testcase() sets the particular testcase that we want to run. The first call to fetch.text makes the HTTP request. Then we chain a second call to make the HTTPS request. New users may understand chain() in much the same way that we use then() with promises. In fact, legion relies heavily on promises for its implementation, the chain() method will accept arbitrary functions that return promises.

If we were writing the same code using fetch and promises, it would look kindof like this (not actually part of the example):

Promise.resolve()
  .then(fetch(host)).then(res => res.text())
  .then(fetch(secure_host)).then(res => res.text())

The difference is that our version is a little more compact, but, more importantly, our version gets a lot of free instrumentation to measure how long each call takes to complete.

Running the Testcase

There's one last line in our example, which looks like this:

  .main();

This is optional, and there other ways to do it, but the main() method acts like the main method in any executable program. It parses the command line arguments, executes your testcase, and prints the result to standard out. If there's a serious error, one which causes the test not to complete, it will also set the status code of the process. Because of the call to main, we can run our testcase like this:

node testcase.js

If you want to run multiple simultaneous users, try this, which will run the testcase 10 times in parallel:

node testcase.js -n 10

Output

After running the test, we get a large JSON object as output. I've pasted an important fragment of that output here to use as an example. The protocol:http(s) tag describes all response times associated with the HTTP protocol. Of particular interest are the sub-sections that break down our results by outcome:success and outcome:failure.

   "protocol": {
      "http(s)": {
        "value$max": 183,
        "value$min": 68,
        "time$max": 1465850681739,
        "time$min": 1465850681507,
        "total$sum": 2502,
        "count$sum": 20,
        "tags": {
          "everything": {
            "everything": {
              "value$max": 183,
              "value$min": 68,
              "time$max": 1465850681739,
              "time$min": 1465850681507,
              "total$sum": 2502,
              "count$sum": 20
            }
          },
          "testcase": {
            "run": {
              "value$max": 183,
              "value$min": 68,
              "time$max": 1465850681739,
              "time$min": 1465850681507,
              "total$sum": 2502,
              "count$sum": 20
            }
          },
          "legion-io-fetch-version": {
            "0.0.7": {
              "value$max": 183,
              "value$min": 68,
              "time$max": 1465850681739,
              "time$min": 1465850681507,
              "total$sum": 2502,
              "count$sum": 20
            }
          },
          "protocol": {
            "http(s)": {
              "value$max": 183,
              "value$min": 68,
              "time$max": 1465850681739,
              "time$min": 1465850681507,
              "total$sum": 2502,
              "count$sum": 20
            }
          },
          "outcome": {
            "success": {
              "value$max": 183,
              "value$min": 68,
              "time$max": 1465850681623,
              "time$min": 1465850681507,
              "total$sum": 1337,
              "count$sum": 10
            },
            "failure": {
              "value$max": 125,
              "value$min": 105,
              "time$max": 1465850681739,
              "time$min": 1465850681635,
              "total$sum": 1165,
              "count$sum": 10
            }
          }
        }
      }
    }

One important note:

If you look at the output outside of the protocol:http(s) block, you might see 5 successes and 10 failures. Why? Because there were 5 successful requests, 5 failed requests, and 5 testcase iterations that failed to to complete (due to the 5 failed requests).

Until we have a good UI to explain what each statistic means, it will be important to pay close attention to how each block is tagged. This is the only way you'll know what the block is measuring.

Clone this wiki locally