Comparing version 0.0.5 to 0.4.0
@@ -18,6 +18,6 @@ #!/usr/bin/env node | ||
, o = require('o-core') | ||
, runner = require('../lib/runner') | ||
, log = logger.getLogger('run') | ||
, macro = path.join( process.cwd(), args.macro ) | ||
, starttime | ||
, runner | ||
; | ||
@@ -43,3 +43,3 @@ | ||
starttime = Date.now(); | ||
runner(macro, function(e, macro) { | ||
runner = require('../lib/runner')(macro, function(e, macro) { | ||
//TODO final stats | ||
@@ -49,2 +49,5 @@ log[ e ? "error" : "info" ]("Complete in [%ss], with ", (Date.now() - starttime ) / 1000, e || "SUCCESS"); | ||
if (macro.stats.errored) log.warn("Counted total of [%s] errors", macro.stats.errored); | ||
}) | ||
}); | ||
process.on('SIGINT' , runner.stop ); | ||
process.on('SIGTERM', runner.stop ); |
@@ -19,3 +19,3 @@ var fs = require('fs') | ||
csv = fs.createWriteStream(filename, {'flags': 'a'}); | ||
csv = fs.createWriteStream(filename, { flags: 'a' }); | ||
if (!exists) | ||
@@ -37,12 +37,12 @@ csv.write( fields.map(function(c) { return options.columns[c] }).join(",") + "\n" ); | ||
csv.write(","); | ||
}; | ||
} | ||
csv.write(String(rslt[ fields[i] ] || "")); | ||
csv.write("\n",cb); | ||
csv.write("\n",cb) | ||
} | ||
, end: | ||
function csv_end(m,f) { | ||
function csv_end(msg,cb) { | ||
log.info("[%s] - closing" , filename); | ||
csv.end(m,f) | ||
csv.end(msg,cb) | ||
} | ||
} | ||
} |
@@ -0,0 +0,0 @@ var log = require('log4js').getLogger('logiload') |
@@ -26,11 +26,7 @@ var async = require('async') | ||
, sid = 0 //sid = step id | ||
, lastRPS = 0 //Request Per Second | ||
, csvStats | ||
, csvReq | ||
, secIntrv | ||
, statsIntrv | ||
; | ||
ctx.agents = []; | ||
ctx.byUrl = {}; | ||
//prepare stats bag per step | ||
@@ -52,3 +48,3 @@ ctx.scenario.forEach(function(step) { | ||
macro.samples = []; | ||
macro.samples = 1; | ||
nextSample(); | ||
@@ -63,3 +59,3 @@ | ||
macro.sid = 0; | ||
macro.starttime = new Date(); | ||
macro.starttime = Date.now(); | ||
@@ -132,6 +128,5 @@ //init CSV output | ||
} | ||
} | ||
secIntrv = setInterval( | ||
statsIntrv = setInterval( | ||
function() { | ||
@@ -144,8 +139,14 @@ dumpStatsRow(); | ||
macro.terminate = 0; | ||
async.whilst( | ||
function() { | ||
if (total) | ||
if (macro.terminate) return false; | ||
if (total >= 0) { | ||
log.debug("%s more agent(s) to go", total); | ||
return total--; | ||
return total-- | ||
} | ||
log.debug("agent #%s done", - --total); | ||
return true; | ||
} | ||
@@ -159,22 +160,39 @@ , agent_start | ||
); | ||
return { | ||
stop: function() { | ||
switch(++macro.terminate) { | ||
case 1: | ||
log.warn("Term signal: No more agents will be created. Expecting %s agents to finish. Last agent: ", macro.stats.expected, agentId - 1); | ||
return; | ||
case 2: | ||
log.warn("2nd Term signal: No more requests will be fired") | ||
return; | ||
case 3: | ||
log.fatal("3rd Term signal: Hard abort"); | ||
cleanup(done) | ||
case 4: | ||
log.fatal("4th Term signal: force-exitting"); | ||
process.exit(); | ||
} | ||
} | ||
} | ||
//called when whilst signals true | ||
function agent_start(next) { | ||
var agent = run_user(ctx | ||
, function agent_finished(e) { | ||
//TODO - check if to restart the user | ||
//if (testDur && ) { | ||
//} | ||
if (--macro.stats.expected) | ||
return log.info("[a#%s] - finished. expecting %s more agents to finish", agent.id ); | ||
cleanup(done); | ||
} | ||
); | ||
var agentId = | ||
run_user(ctx | ||
, function agent_finished(e) { | ||
//TODO - implement mode of test-duration, as opposed to total users | ||
//if (testDur && ) {} | ||
macro.stats.dur = Date.now() - macro.starttime; | ||
if (--macro.stats.expected) | ||
return log.info("[a#%s] - finished. Current stats:", agentId, macro.stats ); | ||
cleanup(done); | ||
} | ||
).id; | ||
setTimeout(next, arrivIntr) | ||
} | ||
//called upon interval | ||
@@ -212,3 +230,3 @@ function dumpStatsRow(f) { | ||
//clear interval | ||
clearInterval(secIntrv); | ||
clearInterval(statsIntrv); | ||
@@ -241,6 +259,3 @@ //dump & close csvs | ||
//TODO - skip keeping samples for very big tests | ||
macro.samples.push(macro.sample); | ||
macro.sample.sid = macro.samples.length; | ||
macro.sample.sid = macro.samples++; | ||
} | ||
@@ -263,7 +278,8 @@ } | ||
log.debug("[%s] - constructed", agent.id);5/7/2015 | ||
log.debug("[%s] - constructed", agent.id); | ||
async.eachSeries( macro.scenario | ||
, function(step, next) { | ||
step.starttime = Date.now(); | ||
, function(step, next) { | ||
if (macro.terminate >= 2) return next({ message: "ABORTED" }); | ||
stepTypes[ step.type ]( | ||
@@ -299,5 +315,6 @@ { agent: agent | ||
//TODO: count the brutally errored users | ||
log[e?"error":"info"]( "[a#%s] - finished with %s, stats: ", agent.id, e ? e.message : "SUCCESS", stats); | ||
log[e && e.message != "ABORTED" ?"error":"info"]( "[a#%s] - finished with %s, stats: ", agent.id, e ? e.message : "SUCCESS", stats); | ||
if (e && "ABORTED" == e.message) e = null; | ||
fFinished(e); | ||
@@ -304,0 +321,0 @@ } |
@@ -8,7 +8,7 @@ var format = require('util').format | ||
function Stats() { | ||
this.min = 999999; | ||
this.max = 0; | ||
this.sum = 0; | ||
this.count= 0; | ||
this.avg = null | ||
this.min = 999999; | ||
this.max = 0; | ||
this.sum = 0; | ||
this.count = 0; | ||
this.avg = null | ||
} | ||
@@ -15,0 +15,0 @@ |
@@ -25,3 +25,2 @@ var LOG = require('log4js').getLogger('lib/steps/req') | ||
, function(reqInfo, next) { | ||
//TODO: let the user provide req object, ready to fire with request | ||
//TODO: add event hooks to measures | ||
@@ -33,5 +32,5 @@ // - connect time - until connected | ||
var reqStats = reqInfo.stats | ||
, req = reqOf(reqInfo, agent) | ||
, rid = reqInfo.id | ||
, url = parameterize(agent.params, reqInfo.get).replace(/,/g,"%2C") | ||
, rslt = | ||
, rslt = | ||
{ aid : aid | ||
@@ -41,3 +40,4 @@ , step : step.name | ||
, starttime : Date.now() | ||
, url : url | ||
, req : req | ||
, url : req.url | ||
} | ||
@@ -47,12 +47,6 @@ , sec | ||
log.info("[a#%s/r#%s] - firing: ", aid, rid, url ); | ||
log.info("[a#%s/r#%s] - firing %s: ", aid, rid, req.method || "GET", req.url ); | ||
request( | ||
{ url : url | ||
, jar : agent.jar | ||
, headers: | ||
//TODO - let user-code pass headers | ||
{ "user-agent" : "logiload stress tester" | ||
} | ||
} | ||
req.jar = agent.jar; | ||
request( req | ||
, function(e,r) { | ||
@@ -107,3 +101,3 @@ var dur = | ||
if (e) { | ||
e.reqUrl = url; | ||
e.req = req; | ||
e.responseBody = (r || e).body; | ||
@@ -126,2 +120,3 @@ e.httpCode = (r || e).statusCode; | ||
); | ||
delete req.jar; | ||
@@ -137,6 +132,29 @@ macro.onRequest() | ||
function parameterize(params, url) { | ||
return url.replace(/\%\%_([^%]+)\%\%/g, function(_,p) { | ||
return params[p]; | ||
}) | ||
function reqOf(reqInfo, agent) { | ||
if ('string' == typeof reqInfo) reqInfo = { req: { url: reqInfo } }; | ||
var req = reqInfo.req /* backward compatibility: */|| { url: reqInfo.get }; | ||
if (!req.headers) req.headers = {}; | ||
if (!req.headers["user-agent"]) req.headers["user-agent"] = "logiload stress tester"; | ||
req = parameterize( req, agent.params ); | ||
if (!req.timeout) req.timeout = agent.params.reqTimeout; | ||
return req; | ||
} | ||
function parameterize(v, params) { | ||
var k, o; | ||
switch(typeof v) { | ||
case 'string' : | ||
return v.replace(/\%\%_([^%]+)\%\%/g, function(_,p) { | ||
return params[p]; | ||
}); | ||
case 'object' : | ||
o = {}; | ||
for (k in v) o[k] = parameterize(v[k], params); | ||
return o | ||
default: | ||
return v | ||
} | ||
} |
{ | ||
"name": "logiload", | ||
"version": "0.0.5", | ||
"version": "0.4.0", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "lib", |
@@ -6,17 +6,60 @@ logiload | ||
Features overview | ||
================= | ||
*) url parameterization | ||
*) any setting can be overriden usign CLI switches | ||
*) patterned file-names | ||
*) output request stat info into req.csv | ||
*) output overview stats into stats.csv | ||
- describe your test as a data-structure simply exported by a node-module | ||
- use request parameterization to express your logic | ||
- override any setting given in the scenario usign CLI switches | ||
- writes it's output to patterned file-names | ||
- output request stat info into req.csv | ||
- output overview stats into stats.csv | ||
- run modes: | ||
- to a given number of times to run the scenario (by providing -n <num-of-times>) | ||
- until SIGTERM/SIGINT (by prviding -n-1) | ||
- Command types: | ||
- req - to fire one or more requests in parallel | ||
- wait - thinktime between simulated page-views | ||
- uses a bell curve to spread think times for more humanlike behavior | ||
url parameterization | ||
Request parameterization | ||
--- | ||
TBD | ||
Parameterizing of parts in request descriptors are done using the tsung placeholders style, | ||
i.e using placeholders wrapped with `%%_` and `%%`, example: `%%_param1%%` will replace this expression with the value in `agent.params.param1`. | ||
`agent.params` collection starts as a clone of the `macro.options.params` provided by the user. | ||
User may manipulate the `agent.params` using `onResponse(err, response)` hook, | ||
which is called on a context with reference to `agent`. | ||
Note in the following example how the 2nd request adds to the cart the product-id returned by the 1st request. | ||
``` | ||
module.exports = | ||
{ options: | ||
{ params: | ||
{ env: "stage" | ||
} | ||
, scenario: | ||
[ { type: "req" | ||
req : | ||
[ "http://%%_env%%.mydomain.com/promo-data" | ||
] | ||
, onResponse: function(e, r) { | ||
if (e) throw e; | ||
this.agent.params.product = JSON.parse(r.body).product | ||
} | ||
} | ||
, { type: "wait", wait: 1500 } | ||
, { type: "req" | ||
req: | ||
[ { method: "POST" | ||
, url: "http://%%_env%%.mydomain.com/addToChart" | ||
, body: { | ||
pid: "%%_product%%" | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
} | ||
``` | ||
any setting can be overriden usign CLI switches | ||
@@ -23,0 +66,0 @@ --- |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
27693
648
73
14