1 | # flowbench
|
2 |
|
3 | [![Build Status](https://travis-ci.org/pgte/flowbench.svg?branch=master)](https://travis-ci.org/pgte/flowbench)
|
4 |
|
5 | HTTP traffic generator. Supports user flows with alternative paths.
|
6 | Stores stats on latency.
|
7 |
|
8 | # Install
|
9 |
|
10 | ```
|
11 | $ npm install flowbench
|
12 | ```
|
13 |
|
14 |
|
15 | # Use
|
16 |
|
17 | ```js
|
18 | var flowbench = require('flowbench');
|
19 |
|
20 | var experiment = flowbench({
|
21 | base: 'http://localhost:3000',
|
22 | population: 100,
|
23 | maxConcurrentFlows: 50,
|
24 | requestDefaults: {
|
25 | timeout: 10000,
|
26 | jar: false
|
27 | }
|
28 | });
|
29 |
|
30 | experiment
|
31 | .flow({probability: 0.6})
|
32 | .get('/', {id: 1})
|
33 | .verify(verifyResponse1Function)
|
34 | .wait(500)
|
35 | .post('/abc', {
|
36 | id: 2,
|
37 | body: {a: "static value", b: "<%=fixtures.b.random()%>"},
|
38 | fixtures: {
|
39 | b: ['VALUE1', 'VALUE2', 'VALUE3']},
|
40 | timeout: 4000
|
41 | })
|
42 | .verify(
|
43 | flowbench.verify.response.status(200),
|
44 | flowbench.verify.response.body({a: '#{req.body.b}'}))
|
45 | .flow({probability: 0.5})
|
46 | .post('/abc/<%= res[2].prop2 %>',
|
47 | {body: {a: "<%= res[1].prop1 %>", "b": "<%= res[2].prop2} %>"}})
|
48 | .verify(...)
|
49 | .end()
|
50 | .flow({probability: 0.5})
|
51 | .get('/abc')
|
52 | .verify(...)
|
53 | .end()
|
54 | .end()
|
55 | .flow({probability: 0.4})
|
56 | .get('/')
|
57 | .verify(verifyResponse1Function);
|
58 |
|
59 |
|
60 | experiment.begin(function(err, stats) {
|
61 | if (err) {
|
62 | throw err;
|
63 | }
|
64 | console.log('finished. stats:', JSON.stringify(stats, null, ' '));
|
65 | });
|
66 | ```
|
67 |
|
68 | # API
|
69 |
|
70 | ## flowbench(options)
|
71 |
|
72 | Options defaults:
|
73 |
|
74 | ```js
|
75 | {
|
76 | population: 1,
|
77 | maxConcurrentFlows: Infinity,
|
78 | requestDefaults: {
|
79 | pool: {
|
80 | maxSockets: Infinity
|
81 | },
|
82 | timeout: 10e3
|
83 | }
|
84 | };
|
85 | ```
|
86 |
|
87 | the `requestDefaults` object is the options for creating a [scoped request](https://github.com/request/request#requestdefaultsoptions).
|
88 |
|
89 | Returns an Experiment
|
90 |
|
91 | ## Experiment
|
92 |
|
93 | ### experient.flow(options)
|
94 |
|
95 | Adds an alternative flow to the experiment.
|
96 |
|
97 | Options:
|
98 |
|
99 | * `probability` - when more than one sibiling flow is present, this represents the probability of this flow getting executed.
|
100 |
|
101 | All flows within an experiment are alternative, and are given equal probability (unless otherwise specified.)
|
102 |
|
103 | Returns an instance of a Flow.
|
104 |
|
105 | ### experiment.begin(cb)
|
106 |
|
107 | Begins an experiment. Callsback when there is an error or the experiment finishes.
|
108 |
|
109 | The callback has the following signature:
|
110 |
|
111 | ```
|
112 | function callback(err, stats) {}
|
113 | ```
|
114 |
|
115 | The `stats` object is something like this:
|
116 |
|
117 | ```js
|
118 | {
|
119 | "requestsPerSecond": {
|
120 | "mean": 1651.547543071806,
|
121 | "count": 2000,
|
122 | "currentRate": 1651.4908801787194,
|
123 | "1MinuteRate": 0,
|
124 | "5MinuteRate": 0,
|
125 | "15MinuteRate": 0
|
126 | },
|
127 | "latencyNs": {
|
128 | "min": 397537333,
|
129 | "max": 489818898,
|
130 | "sum": 881597582934,
|
131 | "variance": 493325414798874.75,
|
132 | "mean": 440798791.467,
|
133 | "stddev": 22210930.07505257,
|
134 | "count": 2000,
|
135 | "median": 446440646.5,
|
136 | "p75": 454043121.5,
|
137 | "p95": 478719555.34999996,
|
138 | "p99": 488775828.4,
|
139 | "p999": 489641718.259
|
140 | },
|
141 | "requests": {
|
142 | "GET http://localhost:9000/abc": {
|
143 | "latencyNs": {
|
144 | "min": 429215073,
|
145 | "max": 489818898,
|
146 | "sum": 454618892085,
|
147 | "variance": 201579551941901.38,
|
148 | "mean": 454618892.085,
|
149 | "stddev": 14197871.387708137,
|
150 | "count": 1000,
|
151 | "median": 449254332.5,
|
152 | "p75": 463742870,
|
153 | "p95": 486903385.4,
|
154 | "p99": 488928787.48,
|
155 | "p999": 489818732.511
|
156 | },
|
157 | "statusCodes": {
|
158 | "200": {
|
159 | "count": 1000,
|
160 | "percentage": 1
|
161 | }
|
162 | }
|
163 | },
|
164 | "POST http://localhost:9000/def": {
|
165 | "latencyNs": {
|
166 | "min": 397537333,
|
167 | "max": 459961256,
|
168 | "sum": 426978690849,
|
169 | "variance": 403192361971691.8,
|
170 | "mean": 426978690.849,
|
171 | "stddev": 20079650.44445973,
|
172 | "count": 1000,
|
173 | "median": 419389668,
|
174 | "p75": 445073831.5,
|
175 | "p95": 459471652.6,
|
176 | "p99": 459851196.18,
|
177 | "p999": 459961244.691
|
178 | },
|
179 | "statusCodes": {
|
180 | "201": {
|
181 | "count": 1000,
|
182 | "percentage": 1
|
183 | }
|
184 | }
|
185 | }
|
186 | },
|
187 | "statusCodes": {
|
188 | "200": {
|
189 | "count": 1000,
|
190 | "percentage": 0.5
|
191 | },
|
192 | "201": {
|
193 | "count": 1000,
|
194 | "percentage": 0.5
|
195 | }
|
196 | }
|
197 | }
|
198 | ```
|
199 |
|
200 | ### Emitted events
|
201 |
|
202 | An Experience instance emits the following events:
|
203 |
|
204 | * `error (error)` — when an unrecoverrable error occurs.
|
205 | * `request (request)` - when a request is made.
|
206 | * `end ()` — once the experiment ends.
|
207 | * `request-error (req, err)` — when a request errors.
|
208 | * `verify-error (err, req, res)` — when a verification error occurs.
|
209 |
|
210 |
|
211 | ## Flow
|
212 |
|
213 | One flow executes the requests added to it in sequence. You can add subflows to a flow (only after the requests have been specified).
|
214 |
|
215 | ### flow.flow(options)
|
216 |
|
217 | Creates a child flow.
|
218 |
|
219 | Options:
|
220 |
|
221 | * `probability` - when more than one sibiling flow is present, this represents the probability of this flow getting executed.
|
222 |
|
223 | Returns a flow.
|
224 |
|
225 |
|
226 | ### flow.end()
|
227 |
|
228 | Returns the parent flow (or experiment, if at root).
|
229 |
|
230 | ### flow.request(method, url[, options])
|
231 |
|
232 | Add a request to a flow.
|
233 |
|
234 | Options:
|
235 |
|
236 | * id: a string identifying the request. Can access it later inside templates.
|
237 | * fixtures: See the [Fixtures](#fixtures) section below.
|
238 | * body: object or string, representing the request body
|
239 | * headers: object with headers
|
240 | * qs: an object with the query string names and values
|
241 | * form: sets the body to a querystring representation
|
242 | * jar: cookie jar or `false`
|
243 | * ... all other options supported by [request](https://github.com/request/request).
|
244 |
|
245 | ### flow.get(url[, options]), flow.post, flow.put, flow.delete, flow.head
|
246 |
|
247 | Helpers for `flow.request()`.
|
248 |
|
249 |
|
250 | ### flow.verify(fn)
|
251 |
|
252 | Pass in a verification function. This function has the following signature:
|
253 |
|
254 | ```js
|
255 | function(req, res) {}
|
256 | ```
|
257 |
|
258 | This function will then be responsible for verifying the latest request and response.
|
259 |
|
260 | If the verification fails, this function can either:
|
261 |
|
262 | * return an `Error` object
|
263 | * return `false`
|
264 | * throw an `Error` object
|
265 |
|
266 | Otherwise, if verification passed, this function should return `true`.
|
267 |
|
268 | ### flow builtin verifiers
|
269 |
|
270 | You can use the following verifiers:
|
271 |
|
272 | #### flowbench.verify.response.status
|
273 |
|
274 | Example:
|
275 |
|
276 | ```
|
277 | flow.verify(flowbench.verify.response.status(201));
|
278 | ```
|
279 |
|
280 | #### flowbench.verify.response.body
|
281 |
|
282 | Example:
|
283 |
|
284 | ```js
|
285 | flow.verify(flowbench.verify.response.body({a:1, b:2}));
|
286 | ```
|
287 |
|
288 |
|
289 | ## About string interpolation and templating
|
290 |
|
291 | In option you pass into the request (url, options), you can use strings as EJS templates. These templates can access these objects:
|
292 |
|
293 | * req: an object with all requests performed, addressed by `id`.
|
294 | * res: an object with all the responses received, addressed by `id`.
|
295 |
|
296 | (see first example above of using ids and templates).
|
297 |
|
298 |
|
299 | ### Functions instead of values
|
300 |
|
301 | 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:
|
302 |
|
303 | ```js
|
304 | function (req, res, fixtures) {}
|
305 | ```
|
306 |
|
307 | ## Fixtures
|
308 |
|
309 | You can define fixtures for any given request, and you can use these fixtures in your request options.
|
310 |
|
311 | For instance, you can have a given set of airports as fixtures that you can use randomly throughout the request like this:
|
312 |
|
313 | ```js
|
314 | experiment
|
315 | .flow.get('/search', {
|
316 | qs: {
|
317 | 'airportcode': '<%= fixtures.airports.random() %>'
|
318 | },
|
319 | fixtures: {
|
320 | airports: require('./airport-codes.json')
|
321 | }
|
322 | });
|
323 | ```
|
324 |
|
325 | If you wish, you can then verify the response by looking at the request:
|
326 |
|
327 | ```js
|
328 | experiment
|
329 | .flow.get('/search', {
|
330 | qs: {
|
331 | 'airportcode': '<%= fixtures.airports.random() %>'
|
332 | },
|
333 | fixtures: {
|
334 | airports: require('./airport-codes.json')
|
335 | }
|
336 | })
|
337 | .verify(function(req, res) {
|
338 | return res.body.airportcode == req.qs.airportcode
|
339 | });
|
340 | ```
|
341 |
|
342 | ## flowbench.humanize (experimental)
|
343 |
|
344 | Once you get the stats, you can get a more humanized version of it by passing it through `flowbench.humanize` like this:
|
345 |
|
346 | ```js
|
347 | experiment.begin(function(err, stats) {
|
348 | if (err) {
|
349 | throw err;
|
350 | }
|
351 | stats = flowbench.humanize(stats);
|
352 | console.log(JSON.stringify(stats, null, ' '));
|
353 | });
|
354 | ```
|
355 |
|
356 | # License
|
357 |
|
358 | ISC |
\ | No newline at end of file |