
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
HTTP traffic generator. Supports user flows with alternative paths. Stores stats on latency. Reports local event loop lag.
$ npm install flowbench
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, ' '));
});
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
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.
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
}
}
}
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.One flow executes the requests added to it in sequence. You can add subflows to a flow (only after the requests have been specified).
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
};
});
You can alternativel define the locals as an object that gets cloned per session:
flow.locals({
counter: 0
});
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();
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.
Returns the parent flow (or experiment, if at root).
Add a request to a flow.
Options:
falseHelpers for flow.request().
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:
Error objectfalseError objectOtherwise, if verification passed, this function should return true.
You can use the following verifiers:
Example:
flow.verify(flowbench.verify.response.status(201));
Example:
flow.verify(flowbench.verify.response.body({a:1, b:2}));
In option you pass into the request (url, options), you can use strings as EJS templates. These templates can access these objects:
id.id.(see first example above of using ids and templates).
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) {}
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
});
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, ' '));
});
ISC
FAQs
Flow Bench
The npm package flowbench receives a total of 151 weekly downloads. As such, flowbench popularity was classified as not popular.
We found that flowbench demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.