npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

flowbench

v1.0.0

Published

Flow Bench

Downloads

836

Readme

flowbench

Build Status

HTTP traffic generator. Supports user flows with alternative paths. Stores stats on latency. Reports local event loop lag.

Install

$ npm install flowbench

Use

var flowbench = require('flowbench');

var experiment = flowbench('experiment name', {
  sessions: 100,
  maxConcurrentSessions: 50,
  requestDefaults: {
    baseUrl: 'http://localhost:3000',
    timeout: 10000,
    jar: false
  }
});

experiment
  .flow({probability: 0.6})
    locals(function() {
      return {
        counter: 0
      }
    })
    .get('/', {id: 1})
    .verify(verifyResponse1Function)
    .wait('0.5 seconds')
    .post('/abc', {
      id: 2,
      body: {
        a: "static value",
        b: "<%=fixtures.b.random()%>",
        c: "<%=++ locals.counter%>"
      },
      fixtures: {
        b: ['VALUE1', 'VALUE2', 'VALUE3']},
      timeout: 4000
    })
    .verify(
      flowbench.verify.response.status(200),
      flowbench.verify.response.body({a: '<%= req.body.b %>'}))
    .flow({probability: 0.5})
      .post('/abc/<%= res[2].prop2 %>',
            {body: {a: "<%= res[1].prop1 %>", "b": "<%= res[2].prop2} %>"}})
      .verify(...)
      .end()
    .flow({probability: 0.5})
      .get('/abc')
      .verify(...)
      .end()
    .end()
  .flow({probability: 0.4})
    .get('/')
    .verify(verifyResponse1Function);


experiment.begin(function(err, stats) {
  if (err) {
    throw err;
  }
  console.log('finished. stats:', JSON.stringify(stats, null, '  '));
});

API

flowbench([name, ] [options])

Options defaults:

{
  sessions: 1,
  maxConcurrentSessions: Infinity,
  requestDefaults: {
    pool: {
      maxSockets: Infinity
    },
    timeout: 10e3
  }
};

the requestDefaults object is the options for creating a scoped request.

Returns an Experiment

Experiment

experient.flow(options)

Adds an alternative flow to the experiment.

Options:

  • probability - when more than one sibiling flow is present, this represents the probability of this flow getting executed.

All flows within an experiment are alternative, and are given equal probability (unless otherwise specified.)

Returns an instance of a Flow.

experiment.begin(cb)

Begins an experiment. Callsback when there is an error or the experiment finishes.

The callback has the following signature:

function callback(err, stats) {}

The stats object is something like this:

{
  "requestsPerSecond": {
    "mean": 1651.547543071806,
    "count": 2000,
    "currentRate": 1651.4908801787194,
    "1MinuteRate": 0,
    "5MinuteRate": 0,
    "15MinuteRate": 0
  },
  "latencyNs": {
    "min": 397537333,
    "max": 489818898,
    "sum": 881597582934,
    "variance": 493325414798874.75,
    "mean": 440798791.467,
    "stddev": 22210930.07505257,
    "count": 2000,
    "median": 446440646.5,
    "p75": 454043121.5,
    "p95": 478719555.34999996,
    "p99": 488775828.4,
    "p999": 489641718.259
  },
  "requests": {
    "GET http://localhost:9000/abc": {
      "latencyNs": {
        "min": 429215073,
        "max": 489818898,
        "sum": 454618892085,
        "variance": 201579551941901.38,
        "mean": 454618892.085,
        "stddev": 14197871.387708137,
        "count": 1000,
        "median": 449254332.5,
        "p75": 463742870,
        "p95": 486903385.4,
        "p99": 488928787.48,
        "p999": 489818732.511
      },
      "statusCodes": {
        "200": {
          "count": 1000,
          "percentage": 1
        }
      }
    },
    "POST http://localhost:9000/def": {
      "latencyNs": {
        "min": 397537333,
        "max": 459961256,
        "sum": 426978690849,
        "variance": 403192361971691.8,
        "mean": 426978690.849,
        "stddev": 20079650.44445973,
        "count": 1000,
        "median": 419389668,
        "p75": 445073831.5,
        "p95": 459471652.6,
        "p99": 459851196.18,
        "p999": 459961244.691
      },
      "statusCodes": {
        "201": {
          "count": 1000,
          "percentage": 1
        }
      }
    }
  },
  "statusCodes": {
    "200": {
      "count": 1000,
      "percentage": 0.5
    },
    "201": {
      "count": 1000,
      "percentage": 0.5
    }
  }
}

Emitted events

An Experience instance emits the following events:

  • error (error) — when an unrecoverrable error occurs.
  • request (request) - when a request is made.
  • end () — once the experiment ends.
  • request-error (req, err) — when a request errors.
  • verify-error (err, req, res) — when a verification error occurs.

Flow

One flow executes the requests added to it in sequence. You can add subflows to a flow (only after the requests have been specified).

flow.locals(fn)

You can define some session-specific locals (accessible in the template as the var locals) by defining a constructor function like this:

flow.locals(function() {
  return {
    counter: 0
  };
});

flow.locals(object)

You can alternativel define the locals as an object that gets cloned per session:

flow.locals({
  counter: 0
});

flow.repeat(count)

Create a subflow and repeat it count times.

To get back to the parent flow you must end it. Example:

flow
  .locals({
    count: 0
  })
  .repeat(2)
    .get('/', {body: '<%= ++locals.count %>'})
    .end()
  .end();

flow.flow(options)

Creates a child flow.

Options:

  • probability - when more than one sibiling flow is present, this represents the probability of this flow getting executed.

Returns a flow.

flow.end()

Returns the parent flow (or experiment, if at root).

flow.request(method, url[, options])

Add a request to a flow.

Options:

  • id: a string identifying the request. Can access it later inside templates.
  • fixtures: See the Fixtures section below.
  • body: object, a string or a function returning the body, representing the request body
  • headers: object with headers
  • qs: an object with the query string names and values
  • form: sets the body to a querystring representation
  • jar: cookie jar or false
  • ... all other options supported by request.

flow.get(url[, options]), flow.post, flow.put, flow.delete, flow.head

Helpers for flow.request().

flow.verify(fn)

Pass in a verification function. This function has the following signature:

function(req, res) {}

This function will then be responsible for verifying the latest request and response.

If the verification fails, this function can either:

  • return an Error object
  • return false
  • throw an Error object

Otherwise, if verification passed, this function should return true.

flow builtin verifiers

You can use the following verifiers:

flowbench.verify.response.status

Example:

flow.verify(flowbench.verify.response.status(201));

flowbench.verify.response.body

Example:

flow.verify(flowbench.verify.response.body({a:1, b:2}));

About string interpolation and templating

In option you pass into the request (url, options), you can use strings as EJS templates. These templates can access these objects:

  • req: an object with all requests performed, addressed by id.
  • res: an object with all the responses received, addressed by id.

(see first example above of using ids and templates).

Functions instead of values

In any of the url or options for a request, you can pass in a function with the followig signature to be evaluated at run time:

function (req, res, fixtures) {}

Fixtures

You can define fixtures for any given request, and you can use these fixtures in your request options.

For instance, you can have a given set of airports as fixtures that you can use randomly throughout the request like this:

experiment
  .flow.get('/search', {
    qs: {
      'airportcode': '<%= fixtures.airports.random() %>'
    },
    fixtures: {
      airports: require('./airport-codes.json')
    }
  });

If you wish, you can then verify the response by looking at the request:

experiment
  .flow.get('/search', {
    qs: {
      'airportcode': '<%= fixtures.airports.random() %>'
    },
    fixtures: {
      airports: require('./airport-codes.json')
    }
  })
  .verify(function(req, res) {
    return res.body.airportcode == req.qs.airportcode
  });

flowbench.humanize (experimental)

Once you get the stats, you can get a more humanized version of it by passing it through flowbench.humanize like this:

experiment.begin(function(err, stats) {
  if (err) {
    throw err;
  }
  stats = flowbench.humanize(stats);
  console.log(JSON.stringify(stats, null, '  '));
});

License

ISC