1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
'use strict';
var fsm = (function() {
// args.data = 'this' object of the state functions
// args.db = database object
// args.collName = collection name
// args.cluster = connection strings for all cluster nodes (see fsm_libs/cluster.js for format)
// args.passConnectionCache = boolean, whether to pass a connection cache to the workload states
// args.startState = name of initial state function
// args.states = state functions of the form
// { stateName: function(db, collName) { ... } }
// args.transitions = transitions between state functions of the form
// { stateName: { nextState1: probability,
// nextState2: ... } }
// args.iterations = number of iterations to run the FSM for
function runFSM(args) {
var currentState = args.startState;
// We build a cache of connections that can be used in workload states. This cache
// allows state functions to access arbitrary cluster nodes for verification checks.
// See fsm_libs/cluster.js for the format of args.cluster.
var connCache;
if (args.passConnectionCache) {
connCache = {
mongos: [],
config: [],
shards: {}
};
connCache.mongos = args.cluster.mongos.map(connStr => new Mongo(connStr));
connCache.config = args.cluster.config.map(connStr => new Mongo(connStr));
var shardNames = Object.keys(args.cluster.shards);
shardNames.forEach(name =>
(connCache.shards[name] = args.cluster.shards[name].map(connStr =>
new Mongo(connStr))));
}
for (var i = 0; i < args.iterations; ++i) {
var fn = args.states[currentState];
assert.eq('function', typeof fn, 'states.' + currentState + ' is not a function');
fn.call(args.data, args.db, args.collName, connCache);
var nextState = getWeightedRandomChoice(args.transitions[currentState], Random.rand());
currentState = nextState;
}
// Null out the workload connection cache and perform garbage collection to clean up,
// i.e., close, the open connections.
if (args.passConnectionCache) {
connCache = null;
gc();
}
}
// doc = document of the form
// { nextState1: probability, nextState2: ... }
// randVal = a value on the interval [0, 1)
// returns a state, weighted by its probability,
// assuming randVal was chosen randomly by the caller
function getWeightedRandomChoice(doc, randVal) {
assert.gte(randVal, 0);
assert.lt(randVal, 1);
var states = Object.keys(doc);
assert.gt(states.length, 0, 'transition must have at least one state to transition to');
// weights = [ 0.25, 0.5, 0.25 ]
// => accumulated = [ 0.25, 0.75, 1 ]
var weights = states.map(function(k) { return doc[k]; });
var accumulated = [];
var sum = weights.reduce(function(a, b, i) {
accumulated[i] = a + b;
return accumulated[i];
}, 0);
// Scale the random value by the sum of the weights
randVal *= sum; // ~ U[0, sum)
// Find the state corresponding to randVal
for (var i = 0; i < accumulated.length; ++i) {
if (randVal < accumulated[i]) {
return states[i];
}
}
assert(false, 'not reached');
}
return {
run: runFSM,
_getWeightedRandomChoice: getWeightedRandomChoice
};
})();
|