UNPKG

happn-random-activity-generator

Version:
476 lines (330 loc) 13.9 kB
var async = require("async"); var shortid = require("shortid"); function RandomActivityGenerator(happnClient, opts) { if (!opts) opts = {}; if (!opts.interval) opts.interval = 20;//20 milliseconds if (!opts.percentageGets) opts.percentageGets = [0,20]; if (!opts.percentageSets) opts.percentageSets = [20,80]; if (!opts.percentageRemoves) opts.percentageRemoves = [80,90]; if (!opts.percentageOns) opts.percentageOns = [90,100]; if (!opts.initialDataRemoveCount) opts.initialDataRemoveCount = 40;//40 items that can be .remove if (!opts.initialDataOnCount) opts.initialDataOnCount = 20;//20 items that can be .on if (!opts.initialDataGetCount) opts.initialDataGetCount = 50;//100 items that can be .get if (!opts.randomDataSize) opts.randomDataSize = 3;//multiplies 32 length string, so 96 characters if (!opts.onTimeout) opts.onTimeout = 100;//100 milliseconds this.opts = opts; this.__client = happnClient; this.__operationLog = {}; this.__operationInitialData = {}; this.__operationLogAggregated = {}; this.__state = {}; this.__updateLog = function(key, operationLogItem, operationResponse, operationError){ var mode = this.__operationLogAggregated[key].mode; if (mode != "daemon"){ //we push into a teh replay and verify log operationLogItem.response = operationResponse; operationLogItem.error = operationError; this.__operationLog[key].push(operationLogItem); } this.__operationLogAggregated[key][operationLogItem.opType]++; if (operationError) this.__operationLogAggregated[key].errors++; return operationLogItem; } this.generateActivity = function(timespan, callback, key){ this.generateActivityStart(key); setTimeout(timespan, this.generateActivityEnd.call(this, key)); } this.__getRandomOperationType = function(){ var random = Math.floor(Math.random() * 100); if (random >= opts.percentageGets[0] && random <= opts.percentageGets[1]) return 'get'; if (random >= opts.percentageSets[0] && random <= opts.percentageSets[1]) return 'set'; if (random >= opts.percentageRemoves[0] && random <= opts.percentageRemoves[1]) return 'remove'; if (random >= opts.percentageOns[0] && random <= opts.percentageOns[1]) return 'on'; throw new Error('random value ' + random + ' does not fall into operation ranges'); } this.__getRandomPathFromInitial = function(key, operationType){ var list = this.__operationInitialData[key][operationType]; return this.__selectRandomArrayItem(list).path; } this.__generateRandomData = function(key, operationType){ var _this = this; var bigDataStr = "hsgatwyhrnshefd6yhrmesdatehfndbf"; var bigData = ""; for (var i = 0;i < opts.randomDataSize;i++) bigData += bigDataStr; return {key:key, data:shortid.generate(), bigData:bigData}; } this.__selectRandomArrayItem = function(array){ return array[Math.floor(Math.random() * array.length)]; } this.__generateRandomPath = function(key, operationType){ var _this = this; if (["remove","get","on"].indexOf(operationType) > -1){ return _this.__getRandomPathFromInitial(key, operationType); } var path = "/random_activity_generator/" + key + "/" + shortid.generate(); if (_this.opts.pathPrefix && _this.opts.pathPrefix.length > 0) path = _this.__selectRandomArrayItem(_this.opts.pathPrefix) + path; return path; } this.__generateInitialGroup = function(key, opType, opCount, callback){ var _this = this; async.times(opCount, function(n, next){ var path = _this.__generateRandomPath(key) + "/initial_data/" + opType + "/" + n; var data = _this.__generateRandomData(key); _this.__client.set(path, data, function(e, response){ if (e) return next(e); var operationLogItem = { opType:"set", path:path, data:data, key:key }; _this.__operationInitialData[key][opType].push(operationLogItem); _this.__operationLogAggregated[key].initial[opType]++; next(); }); },callback); } this.__generateInitialData = function(key, callback){ var _this = this; var millisecondsStart = new Date().getTime(); _this.__generateInitialGroup(key, "remove", opts.initialDataRemoveCount, function(e){ if (e) return callback(e); _this.__generateInitialGroup(key, "on", opts.initialDataOnCount, function(e){ if (e) return callback(e); _this.__generateInitialGroup(key, "get", opts.initialDataGetCount, function(e){ if (e) return callback(e); var millisecondsEnd = new Date().getTime(); _this.__operationLogAggregated[key].initializationTimespan = millisecondsEnd - millisecondsStart; return callback(null, _this.__operationLogAggregated[key]); }); }); }); } this.__initializeActivity = function(key, mode){ var _this = this; if (!key)key = 'default'; if (!mode)mode = 'default'; _this.__operationLog[key] = []; _this.__operationLogAggregated[key] = {get:0, set:0, remove:0, on:0, initial:{on:0,get:0,remove:0}, mode:mode}; _this.__operationInitialData[key] = {on:[], remove:[], get:[]}; return _this.__operationLogAggregated[key]; } this.__doOperation = function(key, operationLogItem, callback, initial){ var _this = this; if (_this.opts.verbose) console.log('doing operation:', key, operationLogItem); var operationDone = function(key, operationLogItem, response, e){ if (!initial){ _this.__updateLog(key, operationLogItem, response, e); }else{ //replay - always sets for these _this.__operationInitialData[key][initial].push(operationLogItem); _this.__operationLogAggregated[key].initial[initial]++; } if (callback) callback(key, operationLogItem, response, e); } if (operationLogItem.opType == "get"){ return _this.__client.get(operationLogItem.path, function(e, response){ operationDone(key, operationLogItem, response, e); }); } if (operationLogItem.opType == "set"){ if (operationLogItem.data == null){//could be a replay var operationData = _this.__getRandomOperationData(key); operationLogItem.data = operationData; } return _this.__client.set(operationLogItem.path, operationLogItem.data, function(e, response){ operationDone(key, operationLogItem, response, e); }); } if (operationLogItem.opType == "remove"){ return _this.__client.remove(operationLogItem.path, function(e, response){ operationDone(key, operationLogItem, response, e); }); } if (operationLogItem.opType == "on"){//crikey - thats gonna be hard _this.__client.on(operationLogItem.path, function(data){ if (this.onHandler)//because sometimes .on gets doubled registered - this doesnt exist this.onHandler(data);//gets overriden on verify }.bind(operationLogItem), function(e, response){ operationDone(key, operationLogItem, response, e); }); } } this.generateActivityStart = function(key, callback, mode){ var _this = this; if (!key)key = 'default'; if (!mode)mode = 'default'; _this.__initializeActivity(key, mode); _this.__generateInitialData(key, function(e){ if (e) return callback(e); _this.__operationLogAggregated[key].started = Date.now(); _this.__state[key] = setInterval(function(){ var operationType = _this.__getRandomOperationType(key); //if the type is get/remove/on, we take an existing path from the sets in the log var operationPath = _this.__generateRandomPath(key, operationType); var operationData = _this.__generateRandomData(key, operationType); var operationLogItem = { opType:operationType, path:operationPath, data:operationData, key:key }; _this.__doOperation(key, operationLogItem); }, opts.interval); callback(null, _this.__operationLogAggregated[key]); }); } this.generateActivityEnd = function(key, callback){ var _this = this; if (!key)key = 'default'; setTimeout(function(){ clearInterval(_this.__state[key]); _this.__operationLogAggregated[key].completed = Date.now(); callback(_this.__operationLogAggregated[key]); }, _this.__operationLogAggregated[key].initializationTimespan);//we take this into account - so if you do a 2 second run, the initialization time doesnt impact it } this.verifyOn = function(activityItem, callback){ var _this = this; activityItem.cb = callback; activityItem.onHandler = function(data){ this.handled = true; if (this.cb){//sometimes the .on is doubled up this.cb(null); delete this.cb; } }.bind(activityItem); _this.__client.set(activityItem.path, activityItem.data, function(e){ if (e) return callback(e); setTimeout(function(){ if (!activityItem.handled) return callback(new Error('on timed out')); }.bind(activityItem), opts.onTimeout);//configurable }); } this.verifySet = function(activityItem, callback){ var _this = this; _this.__client.get(activityItem.path, function(e, item){ if (e) return callback(e); if (item.data != activityItem.data.data) return callback(new Error('set data does not match what is in the log')); if (item.bigData != activityItem.data.bigData) return callback(new Error('set bigData does not match what is in the log')); return callback(null); }); } this.verifyRemove = function(activityItem, callback){ var _this = this; _this.__client.get(activityItem.path, function(e, data){ if (e) return callback(e); if (data) return callback(new Error('deleted item still exists')); callback(null); }); } this.verifyItem = function(activityItem, callback, key){ var _this = this; this.__verifyLog[activityItem.key][activityItem.opType]++; if (activityItem.error)//takes care of get return callback(activityItem.error); if (activityItem.opType == "set"){ return _this.verifySet(activityItem, callback); } if (activityItem.opType == "remove"){ return _this.verifyRemove(activityItem, callback); } if (activityItem.opType == "on"){//crikey - thats gonna be hard return _this.verifyOn(activityItem, callback); } callback(null); } this.__verifyLog = {}; this.verify = function(callback, key){ var _this = this; if (!key)key = 'default'; this.__verifyLog[key] = {"on":0, "get":0, "remove":0, "set":0}; var operationActivities = _this.__operationLog[key]; async.eachSeries(operationActivities, _this.verifyItem.bind(_this), function(e){ callback(e, _this.__verifyLog[key]); }); } //replays a previous run this.replay = function(generator, key, callback){ var _this = this; if (typeof key == 'function'){ callback = key; key = null; } if (!key)key = 'default'; if (!generator.__operationLogAggregated[key]) return callback(new Error("invalid key: " + key)); if (generator.__operationLogAggregated[key].mode == "daemon") return callback(new Error("invalid mode daemon was used, you need to record for replay in default mode")); _this.__initializeActivity(key); //_this.__operationInitialData[key][opType] async.eachSeries(generator.__operationInitialData[key]["get"], function(logItem, next){ _this.__doOperation(key, logItem, function(key, operationLogItem, response, e){ next(e); }, "get"); }, function(e){ if (e) return callback(e); async.eachSeries(generator.__operationInitialData[key]["remove"], function(logItem, next){ _this.__doOperation(key, logItem, function(key, operationLogItem, response, e){ next(e); }, "remove"); }, function(e){ if (e) return callback(e); async.eachSeries(generator.__operationInitialData[key]["on"], function(logItem, next){ _this.__doOperation(key, logItem, function(key, operationLogItem, response, e){ next(e); }, "on"); }, function(e){ if (e) return callback(e); _this.__operationInitialData[key] = generator.__operationInitialData[key]; async.eachSeries(generator.__operationLog[key], function(logItem, next){ _this.__doOperation(key, logItem, function(key, operationLogItem, response, e){ next(e); }); }, function(e){ if (e) return callback(e); if (_this.__operationLogAggregated[key].get != generator.__operationLogAggregated[key].get) return callback(new Error('invalid replay: gets dont match')); if (_this.__operationLogAggregated[key].set != generator.__operationLogAggregated[key].set) return callback(new Error('invalid replay: set dont match')); if (_this.__operationLogAggregated[key].remove != generator.__operationLogAggregated[key].remove) return callback(new Error('invalid replay: remove dont match')); if (_this.__operationLogAggregated[key].on != generator.__operationLogAggregated[key].on) return callback(new Error('invalid replay: on dont match')); callback(null, _this.__operationLogAggregated[key]); }); }); }); }); } this.getOperationLog = function(){ return _this.__operationLog; } }; module.exports = RandomActivityGenerator;