Comparing version 0.0.2 to 0.0.3
@@ -9,25 +9,31 @@ var async = require('async') | ||
, Stats = require('./stats') | ||
, agentId = 1 | ||
, csvFcty = require('./csv-output') | ||
, agentId = 1 | ||
; | ||
module.exports = fire; | ||
module.exports = runner_run; | ||
function fire(ctx, done) { | ||
function runner_run(ctx, done) { | ||
//TODO: validate ctx!!! | ||
var macro = ctx | ||
, total = ctx.total | ||
, interval = ctx.interval | ||
, expected = 0 | ||
, rid = 0 | ||
, lastSec = 0 | ||
, lastRPS = 0 | ||
, csv | ||
, secIntr | ||
var macro = ctx | ||
, total = ctx.options.num | ||
, arrivIntr = ctx.options.arriveInterval | ||
, samplIntr = ctx.options.sampleInterval | ||
, bail = !!ctx.options.bail | ||
, rid = 0 //rid = request id | ||
, sid = 0 //sid = step id | ||
, lastRPS = 0 //Request Per Second | ||
, csvStats | ||
, csvReq | ||
, secIntrv | ||
; | ||
ctx.agents = []; | ||
ctx.byUrl = {}; | ||
csv = initCsvOutput(ctx); | ||
//prepare stats bag per step | ||
ctx.scenario.forEach(function(step) { | ||
step.id = ++sid; | ||
if (step.type != 'req') return; | ||
@@ -37,38 +43,103 @@ | ||
//stats bag & rid per URL | ||
//stats bag & rid per request-info | ||
step.results = {}; | ||
step.req.forEach(function(req) { | ||
req.id = ++rid; | ||
req.stats = new Stats() | ||
step.req.forEach(function(reqInfo) { | ||
reqInfo.id = ++rid; | ||
reqInfo.stats = new Stats(); | ||
if (!('bail' in reqInfo)) reqInfo.bail = bail; | ||
}) | ||
}); | ||
macro.fired = 0; | ||
macro.sec = lastSec = 0; | ||
macro.starttime = Date.now(); | ||
macro.samples = []; | ||
nextSample(); | ||
secIntr = setInterval( | ||
function() { | ||
log.info( "RPS:" , lastRPS = macro.fired ); | ||
csv.write( format("%s,%s\n", lastRPS, macro.sec) ); | ||
macro.fired = 0; | ||
lastSec = macro.sec++ | ||
} | ||
, 1000 | ||
); | ||
macro.stats = newMacroStats(); | ||
macro.stats.started = 0; | ||
macro.stats.expected = 0; | ||
macro.stats.finished = 0; | ||
macro.stats.returned = 0; | ||
macro.sid = 0; | ||
macro.starttime = new Date(); | ||
//init CSV output | ||
csvReq = | ||
csvFcty( | ||
{ path : ctx.options.reqLog | ||
, columns : | ||
{ sid : "s-id" | ||
, aid : "agent-id" | ||
, step : "step-name" | ||
, rid : "req-id-in-step" | ||
, dur : "duration" | ||
, statusCode: "status-code" | ||
, error : "error" | ||
, url : "url" | ||
, starttime : "start-time" | ||
, endtime : "end-time" | ||
} | ||
} | ||
); | ||
csvStats = | ||
csvFcty( | ||
{ path: ctx.options.statsLog | ||
, columns: | ||
{ sid : "sample-id" //macro.sample.sid | ||
, reqPS : "Req/sec in sample" //macro.sample.fired / ( samplIntr / 1000 ) | ||
, retPS : "Ret/sec in sample" //macro.sample.returned / ( samplIntr / 1000 ) | ||
, sTime : "Req dur in sample" //macro.sample.avg | ||
, errored : "Err in sample" //macro.sample.errored | ||
, resTime : "Req dur" //macro.sample.avg | ||
, users_conc : "conc. users" //macro.stats.expected ==>> macro.stats.started - macro.stats.finished | ||
, reqcount : "conc. requests" //macro.stats.fired - macro.stats.returned | ||
, smplstarted : "sent in sample" //macro.sample.started | ||
, smpldone : "exitted in sample" //macro.sample.finished | ||
, userstarted : "sent users" //macro.stats.started | ||
, usersdone : "exitted users" //macro.stats.finished | ||
, fired : "fired in sample" //macro.sample.fired | ||
, returned : "returned in sample" //macro.sample.returned | ||
, errorcount : "total req. err" //macro.stats.errored | ||
, firecount : "total req. fired" //macro.stats.fired | ||
, returncount : "total req. returned" //macro.stats.returned | ||
} | ||
} | ||
); | ||
macro.onRequest = function() { | ||
macro.fired++; | ||
macro.sample.fired++; | ||
macro.stats.fired++; | ||
} | ||
macro.onResult = function(rslt) { | ||
csv.write( | ||
format(",%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n" | ||
, macro.sec, rslt.aid, rslt.step, rslt.rid, rslt.dur, rslt.statusCode, rslt.url, rslt.error, rslt.starttime, rslt.endtime | ||
) | ||
) | ||
macro.sample.returned++; | ||
macro.stats.returned++; | ||
macro.sample.gather(rslt.dur); | ||
macro.stats.gather(rslt.dur); | ||
rslt.sid = macro.sample.sid; | ||
rslt.starttime /= 1000; | ||
rslt.endtime /= 1000; | ||
if (rslt.error) { | ||
log.error("error", rslt); | ||
//TODO - bail on unexpected errors | ||
//OR - just count | ||
macro.sample.errored++; | ||
macro.stats.errored++; | ||
} | ||
csvReq.write( rslt ); | ||
} | ||
secIntrv = setInterval( | ||
function() { | ||
dumpStatsRow(); | ||
nextSample(); | ||
} | ||
, samplIntr | ||
); | ||
async.whilst( | ||
async.whilst( | ||
function() { | ||
@@ -80,20 +151,3 @@ if (total) | ||
} | ||
, function(next) { | ||
var agent = | ||
run_user(ctx | ||
, function() { | ||
if (--expected) | ||
return log.info("[a#%s] - finished. expecting %s more agents to finish", agent.id ); | ||
clearInterval(secIntr); | ||
csv.end(); | ||
done() | ||
} | ||
) | ||
; | ||
ctx.agents.push( agent ); | ||
++expected; | ||
setTimeout(next, interval) | ||
} | ||
, agent_start | ||
, function(e) { | ||
@@ -104,3 +158,88 @@ e | ||
} | ||
) | ||
); | ||
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); | ||
} | ||
); | ||
setTimeout(next, arrivIntr) | ||
} | ||
//called upon interval | ||
function dumpStatsRow(f) { | ||
var s = | ||
{ sid : macro.sample.sid || 0 | ||
, reqPS : macro.sample.fired / (samplIntr / 1000) || 0 | ||
, retPS : macro.sample.returned / (samplIntr / 1000) || 0 | ||
, sTime : macro.sample.avg || 0 | ||
, errored : macro.sample.errored || 0 | ||
, resTime : macro.stats.avg || 0 | ||
, users_conc : macro.stats.expected || 0 | ||
, reqcount : macro.stats.fired - macro.stats.returned || 0 | ||
, smplstarted : macro.sample.started || 0 | ||
, smpldone : macro.sample.finished || 0 | ||
, userstarted : macro.stats.started || 0 | ||
, usersdone : macro.stats.finished || 0 | ||
, fired : macro.sample.fired || 0 | ||
, returned : macro.sample.returned || 0 | ||
, errorcount : macro.stats.errored || 0 | ||
, firecount : macro.stats.fired || 0 | ||
, returncount : macro.stats.returned || 0 | ||
} | ||
; | ||
log.info("ReqPS: [%s], RetPS: [%s], avg Req. dur: [%s]", s.reqPS, s.retPS, s.sTime); | ||
csvStats.write(s , f); | ||
} | ||
//called after last agent has finished scenario or bailed | ||
function cleanup(done) { | ||
log.info("cleanup..."); | ||
//clear interval | ||
clearInterval(secIntrv); | ||
//dump & close csvs | ||
async.waterfall( | ||
[ dumpStatsRow | ||
, csvStats.end | ||
, csvReq.end | ||
] | ||
, function(e) { | ||
done(e, macro) | ||
} | ||
) | ||
} | ||
//creates a bag for the samples, and for the overal stats | ||
function newMacroStats() { | ||
return extend( new Stats(), { | ||
fired : 0 | ||
, returned : 0 | ||
, started : 0 | ||
, finished : 0 | ||
, errored : 0 | ||
}) | ||
} | ||
function nextSample() { | ||
macro.sample = newMacroStats(); | ||
//TODO - skip keeping samples for very big tests | ||
macro.samples.push(macro.sample); | ||
macro.sample.sid = macro.samples.length; | ||
} | ||
} | ||
@@ -113,8 +252,13 @@ | ||
, stats : new Stats() | ||
, params : extend({}, macro.params) | ||
, results: [] | ||
, params : extend({}, macro.options.params) | ||
} | ||
; | ||
macro.agents.push(agent); | ||
//on agent-start | ||
++macro.stats.expected; | ||
++macro.stats.started; | ||
++macro.sample.started; | ||
log.debug("[%s] - constructed", agent.id);5/7/2015 | ||
async.eachSeries( macro.scenario | ||
@@ -149,2 +293,7 @@ , function(step, next) { | ||
//on agent-finish | ||
++macro.stats.finished; | ||
++macro.sample.finished; | ||
//TODO: count the brutally errored users | ||
log[e?"error":"info"]( "[a#%s] - finished with %s, stats: ", agent.id, e ? e.message : "SUCCESS", stats); | ||
@@ -157,17 +306,2 @@ | ||
return agent; | ||
} | ||
function initCsvOutput(ctx) { | ||
var exists = fs.existsSync( ctx.csv ) | ||
, csv | ||
; | ||
csv = fs.createWriteStream(ctx.csv, {'flags': 'a'}); | ||
if (!exists) | ||
csv.write("sec-id,RPS,agent-id,step-name,req-id-in-step,dur,status-code,url,hard-error,start-time,end-time\n"); | ||
else | ||
log.warn("appending to an existing file: ", ctx.csv); | ||
return csv | ||
} |
@@ -19,7 +19,14 @@ var LOG = require('log4js').getLogger('lib/steps/req') | ||
log.debug("[a#%s] - entering step: " , aid, step.name); | ||
log.info("[a#%s] - entering step: " , aid, step.name); | ||
async.each( step.req | ||
, function(reqInfo, next) { | ||
var url = parameterize(agent.params, reqInfo.get) | ||
//TODO: let the user provide req object, ready to fire with request | ||
//TODO: add event hooks to measures | ||
// - connect time - until connected | ||
// - think time - from connected to accepting response headers | ||
// - response time - from response headers to response end | ||
var reqStats = reqInfo.stats | ||
, url = parameterize(agent.params, reqInfo.get).replace(/,/g,"%2C") | ||
, rslt = | ||
@@ -34,8 +41,5 @@ { aid : aid | ||
; | ||
agent.results.push( rslt ); | ||
log.info("[a#%s] - firing: ", aid, url ); | ||
request( | ||
@@ -50,4 +54,4 @@ { url : url | ||
rslt.endtime = Date.now(); | ||
var reqStats = reqInfo.stats | ||
, dur = rslt.dur = rslt.endtime - rslt.starttime | ||
var dur = rslt.dur = rslt.endtime - rslt.starttime | ||
, rej | ||
; | ||
@@ -59,3 +63,4 @@ | ||
if (e) { | ||
rslt.error = e.name; | ||
log.warn("request errored", e.message, reqInfo); | ||
rslt.error = e.message; | ||
rslt.statusCode = null; | ||
@@ -68,14 +73,37 @@ }else{ | ||
log.info("[a#%s] - response [r#%s] arrived in [%sms] - ", aid, reqInfo.id, rslt.dur, e || "ok"); | ||
if ( reqInfo.expect && reqInfo.expect.code | ||
&& r.statusCode != reqInfo.expectCode | ||
) { | ||
e = | ||
{ message: "Wrong Status Code" | ||
, expect : reqInfo.expect.code | ||
, found : r.statusCode | ||
} | ||
} | ||
log.info("[a#%s] - response [r#%s] arrived in [%sms] - ", aid, reqInfo.id, rslt.dur, e || r.statusCode); | ||
log.info("req stats [r#%s]: %s", reqInfo.id, reqStats); | ||
if ('function' == typeof reqInfo.onResponse) { | ||
log.info("[a#%s] - runnig onResponse hook", aid); | ||
log.info("[a#%s] - runnig onResponse hook:", aid, e ? e.message : "successfull request"); | ||
//TODO - exception safety + gather error when caught exception | ||
reqInfo.onResponse.apply(ctx, [e, r]); | ||
//TODO - move to event emittion | ||
log.debug("[a#%s] - with", r ? r.body || "no body" : e); | ||
try { | ||
reqInfo.onResponse.apply(ctx, [e, r, rslt]); | ||
} catch (ex) { | ||
e = ex; | ||
e.reqInfo = reqInfo; | ||
e.reqUrl = url; | ||
e.responseBody = (r || e).body; | ||
e.httpCode = (r || e).statusCode; | ||
log.error("req.onResponse",e); | ||
} | ||
} | ||
//TODO - move to event emittion? | ||
macro.onResult( rslt ); | ||
next(e) | ||
next(reqInfo.bail ? e : null); | ||
} | ||
@@ -82,0 +110,0 @@ ); |
@@ -12,2 +12,3 @@ var LOG = require('log4js').getLogger('lib/steps/wait') | ||
, aid = agent.id | ||
, sleep = (step.wait || step.sleep) * rnd() | ||
; | ||
@@ -19,3 +20,5 @@ | ||
done() | ||
}, step.sleep * 1000 ); | ||
} | ||
}, sleep * 1000 ); | ||
} | ||
function rnd() { return (Math.random() + Math.random() + Math.random()) / 1.5 } |
{ | ||
"name": "logiload", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"description": "", | ||
"main": "lib", | ||
"bin" : { | ||
"run" : "run.js" | ||
"bin": { | ||
"run": "bin/run" | ||
}, | ||
"dependencies": { | ||
"async": "~0.9.0", | ||
"yargs": "~3.8.0", | ||
"log4js": "~0.6.24", | ||
"o-core": "0.0.5", | ||
"request": "~2.55.0", | ||
"log4js": "~0.6.24" | ||
"yargs": "~3.8.0" | ||
}, | ||
@@ -15,0 +16,0 @@ "devDependencies": { |
logiload | ||
========== | ||
programmatic tool for load-testing in node-js for scenarios with heavy logic | ||
programmatic tool for load-testing in node-js for scenarios with heavy logic. | ||
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 | ||
url parameterization | ||
--- | ||
TBD | ||
any setting can be overriden usign CLI switches | ||
--- | ||
TBD | ||
Patterned file names | ||
--- | ||
- %a - num of users (corresponds to CLI param -n,--num) | ||
- %r - arrival interval (corresponds to CLI param -a,--arrive-interval) | ||
- %p - sample interval (corresponds to CLI param -p,--sample-interval) | ||
- %t - timestamp or timestamp+tag when tag is provided (tag corresponds to CLI param -t,--tag |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 1 instance in 1 package
23795
15
601
30
5
4
+ Addedo-core@0.0.5
+ Addedo-core@0.0.5(transitive)