tchannel 3.6.19 → 3.6.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/benchmarks/Makefile +5 -1
- package/benchmarks/index.js +1 -0
- package/benchmarks/multi_bench.js +15 -1
- package/package.json +2 -2
- package/peer.js +57 -0
- package/peer_heap.js +52 -36
- package/sub_peers.js +79 -9
- package/test/index.js +1 -0
- package/test/lib/alloc-cluster.js +4 -0
- package/test/lib/batch-client.js +17 -2
- package/test/peer-to-peer-load-balance.js +459 -0
package/benchmarks/Makefile
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
torch-client-bench:
|
|
2
2
|
node index.js --relay --torch client --torchFile ./flame.raw --torchTime 10 \
|
|
3
|
-
-- --relay --skipPing -s 4096,16384 -p 1000
|
|
3
|
+
-- --relay --skipPing -s 4096,16384 -p 1000 -m 3
|
|
4
|
+
|
|
5
|
+
torch-client-only-bench:
|
|
6
|
+
node index.js --torch client --torchFile ./flame.raw --torchTime 10 \
|
|
7
|
+
-- --skipPing -s 4096,16384 -p 1000 -m 3
|
|
4
8
|
|
|
5
9
|
torch-server-bench:
|
|
6
10
|
node index.js --relay --torch server --torchFile ./flame.raw --torchTime 10 \
|
package/benchmarks/index.js
CHANGED
|
@@ -265,6 +265,7 @@ function startClient(clientPort) {
|
|
|
265
265
|
'--benchPort', String(self.ports.serverPort),
|
|
266
266
|
'--relayServerPort', String(self.ports.relayServerPort),
|
|
267
267
|
'--clientPort', String(clientPort),
|
|
268
|
+
'--instances', String(self.instanceCount),
|
|
268
269
|
'--instanceNumber', String(self.benchCounter)
|
|
269
270
|
]);
|
|
270
271
|
var benchProc = self.run(bench, args);
|
|
@@ -63,13 +63,17 @@ argv.pipeline = parseIntList(argv.pipeline);
|
|
|
63
63
|
argv.sizes = parseIntList(argv.sizes);
|
|
64
64
|
|
|
65
65
|
var DESTINATION_SERVER;
|
|
66
|
+
var DESTINATION_PORT;
|
|
66
67
|
var TRACE_SERVER;
|
|
67
68
|
var CLIENT_PORT = argv.clientPort;
|
|
69
|
+
var INSTANCES = 0;
|
|
68
70
|
|
|
69
71
|
if (argv.relay) {
|
|
70
72
|
DESTINATION_SERVER = '127.0.0.1:' + argv.relayServerPort;
|
|
71
73
|
} else {
|
|
72
74
|
DESTINATION_SERVER = '127.0.0.1:' + argv.benchPort;
|
|
75
|
+
DESTINATION_PORT = argv.benchPort;
|
|
76
|
+
INSTANCES = parseInt(argv.instances, 10);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
if (argv.trace) {
|
|
@@ -171,9 +175,19 @@ Test.prototype.newClient = function newClient(id, callback) {
|
|
|
171
175
|
};
|
|
172
176
|
}
|
|
173
177
|
|
|
178
|
+
var peers = [DESTINATION_SERVER];
|
|
179
|
+
if (INSTANCES > 0) {
|
|
180
|
+
peers = [];
|
|
181
|
+
var basePort = DESTINATION_PORT;
|
|
182
|
+
for (var i = 0; i < INSTANCES; i++) {
|
|
183
|
+
peers.push('127.0.0.1:' + (basePort + i));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
var client = clientChan.makeSubChannel({
|
|
175
188
|
serviceName: 'benchmark',
|
|
176
|
-
peers:
|
|
189
|
+
peers: peers,
|
|
190
|
+
minConnections: INSTANCES > 0 ? 10 : 1
|
|
177
191
|
});
|
|
178
192
|
client.createTime = Date.now();
|
|
179
193
|
clientChan.listen(port, '127.0.0.1', function listened(err) {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "tchannel",
|
|
3
3
|
"description": "network multiplexing and framing protocol for RPC or parser drag racing",
|
|
4
4
|
"author": "mranney@uber.com",
|
|
5
|
-
"version": "3.6.
|
|
5
|
+
"version": "3.6.20",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "eslint $(git ls-files | grep '.js$')",
|
|
8
8
|
"travis": "npm run test",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"benchmark": "echo '!!! DEPRECATED: Better to just run `node benchmarks` directly' >&2; node benchmarks",
|
|
11
11
|
"hyperbahn-link-test": "./test/link_hyperbahn.sh",
|
|
12
12
|
"tcurl-link-test": "./test/link_tcurl.sh",
|
|
13
|
-
"check-benchmark": "node benchmarks -- -r 10000 -p
|
|
13
|
+
"check-benchmark": "node benchmarks -- -r 10000 -p 750",
|
|
14
14
|
"take-benchmark": "make -C benchmarks take",
|
|
15
15
|
"take-relay-benchmark": "make -C benchmarks take_relay",
|
|
16
16
|
"take-trace-benchmark": "amke -C benchmarks take_trace",
|
package/peer.js
CHANGED
|
@@ -39,6 +39,8 @@ var PeerDrain = require('./drain.js').PeerDrain;
|
|
|
39
39
|
var ObjectPool = require('./lib/object_pool');
|
|
40
40
|
|
|
41
41
|
var DEFAULT_REPORT_INTERVAL = 1000;
|
|
42
|
+
var INITIAL_CONN_ATTEMPT_DELAY = 5000;
|
|
43
|
+
var CONN_ATTEMPT_DELAY_MULTIPLER = 2;
|
|
42
44
|
|
|
43
45
|
/*eslint max-statements: [2, 40]*/
|
|
44
46
|
function TChannelPeer(channel, hostPort, options) {
|
|
@@ -50,6 +52,8 @@ function TChannelPeer(channel, hostPort, options) {
|
|
|
50
52
|
this.stateChangedEvent = this.defineEvent('stateChanged');
|
|
51
53
|
this.allocConnectionEvent = this.defineEvent('allocConnection');
|
|
52
54
|
this.removeConnectionEvent = this.defineEvent('removeConnection');
|
|
55
|
+
this.deltaOutConnectionEvent = this.defineEvent('deltaOutConnection');
|
|
56
|
+
|
|
53
57
|
this.channel = channel;
|
|
54
58
|
this.logger = this.channel.logger;
|
|
55
59
|
this.timers = this.channel.timers;
|
|
@@ -66,6 +70,11 @@ function TChannelPeer(channel, hostPort, options) {
|
|
|
66
70
|
this.boundOnPendingChange = onPendingChange;
|
|
67
71
|
this.scoreRange = null;
|
|
68
72
|
|
|
73
|
+
// Timestamp when next conn attempt is allowed
|
|
74
|
+
this.nextConnAttemptTime = 0;
|
|
75
|
+
// How long to delay conn attempt by on failure (ms)
|
|
76
|
+
this.nextConnAttemptDelay = 0;
|
|
77
|
+
|
|
69
78
|
this.waitForIdentifiedListeners = [];
|
|
70
79
|
|
|
71
80
|
this.reportInterval = options.reportInterval || DEFAULT_REPORT_INTERVAL;
|
|
@@ -408,6 +417,47 @@ TChannelPeer.prototype.connectTo = function connectTo() {
|
|
|
408
417
|
return self.connect(true);
|
|
409
418
|
};
|
|
410
419
|
|
|
420
|
+
TChannelPeer.prototype.tryConnect = function tryConnect() {
|
|
421
|
+
var self = this;
|
|
422
|
+
|
|
423
|
+
var connectTime = Date.now();
|
|
424
|
+
if (connectTime < self.nextConnAttemptTime) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
var conn = this.getOutConnection();
|
|
429
|
+
if (!conn || conn.direction !== 'out') {
|
|
430
|
+
conn = this.connectTo();
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
this.waitForIdentified(conn, onIdentified);
|
|
434
|
+
|
|
435
|
+
function onIdentified(err) {
|
|
436
|
+
if (!err) {
|
|
437
|
+
self.nextConnAttemptDelay = 0;
|
|
438
|
+
self.nextConnAttemptTime = 0;
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (self.nextConnAttemptDelay === 0) {
|
|
443
|
+
self.nextConnAttemptDelay = INITIAL_CONN_ATTEMPT_DELAY;
|
|
444
|
+
} else {
|
|
445
|
+
self.nextConnAttemptDelay *= CONN_ATTEMPT_DELAY_MULTIPLER;
|
|
446
|
+
// Add some amount of fuzz, +-100ms
|
|
447
|
+
self.nextConnAttemptDelay += Math.floor(100 * (Math.random() - 0.5));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
var afterConnect = Date.now();
|
|
451
|
+
if (self.nextConnAttemptTime < afterConnect) {
|
|
452
|
+
// When in the past set next attempt to now + delay
|
|
453
|
+
self.nextConnAttemptTime = afterConnect + self.nextConnAttemptDelay;
|
|
454
|
+
} else {
|
|
455
|
+
// When in the future; go further in the future
|
|
456
|
+
self.nextConnAttemptTime += self.nextConnAttemptDelay;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
|
|
411
461
|
TChannelPeer.prototype.waitForIdentified =
|
|
412
462
|
function waitForIdentified(conn, callback) {
|
|
413
463
|
var self = this;
|
|
@@ -522,9 +572,11 @@ TChannelPeer.prototype.addConnection = function addConnection(conn) {
|
|
|
522
572
|
// TODO: second approx support pruning
|
|
523
573
|
if (conn.direction === 'out') {
|
|
524
574
|
self.connections.push(conn);
|
|
575
|
+
self.deltaOutConnectionEvent.emit(self, 1);
|
|
525
576
|
} else {
|
|
526
577
|
self.connections.unshift(conn);
|
|
527
578
|
}
|
|
579
|
+
|
|
528
580
|
conn.errorEvent.on(self.boundOnConnectionError);
|
|
529
581
|
conn.closeEvent.on(self.boundOnConnectionClose);
|
|
530
582
|
conn.ops.pendingChangeEvent.on(self.boundOnPendingChange);
|
|
@@ -606,15 +658,20 @@ TChannelPeer.prototype.removeConnection = function removeConnection(conn) {
|
|
|
606
658
|
var self = this;
|
|
607
659
|
|
|
608
660
|
var ret = null;
|
|
661
|
+
var isRemoved = false;
|
|
609
662
|
|
|
610
663
|
var index = self.connections ? self.connections.indexOf(conn) : -1;
|
|
611
664
|
if (index !== -1) {
|
|
612
665
|
ret = self.connections.splice(index, 1)[0];
|
|
666
|
+
isRemoved = conn.direction === 'out';
|
|
613
667
|
}
|
|
614
668
|
|
|
615
669
|
self._maybeInvalidateScore('removeConnection');
|
|
616
670
|
|
|
617
671
|
self.removeConnectionEvent.emit(self, conn);
|
|
672
|
+
if (isRemoved) {
|
|
673
|
+
self.deltaOutConnectionEvent.emit(self, -1);
|
|
674
|
+
}
|
|
618
675
|
return ret;
|
|
619
676
|
};
|
|
620
677
|
|
package/peer_heap.js
CHANGED
|
@@ -30,8 +30,10 @@ module.exports = PeerHeap;
|
|
|
30
30
|
// peer list.
|
|
31
31
|
var dfsStack = [0, 1, 2];
|
|
32
32
|
|
|
33
|
-
function PeerHeap(random) {
|
|
33
|
+
function PeerHeap(peers, random) {
|
|
34
34
|
this.array = [];
|
|
35
|
+
this.peers = peers || null;
|
|
36
|
+
this.hasMinConnections = peers ? peers.hasMinConnections : false;
|
|
35
37
|
|
|
36
38
|
this.random = random || Math.random;
|
|
37
39
|
assert(typeof this.random === 'function', 'PeerHeap expected random fn');
|
|
@@ -44,25 +46,6 @@ function PeerHeap(random) {
|
|
|
44
46
|
this.maxRangeStart = 0;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
PeerHeap.prototype.choose1 = function choose1(threshold, filter) {
|
|
48
|
-
var self = this;
|
|
49
|
-
if (self.array[0].range.lo >= threshold && (!filter || filter(self.array[0].peer))) {
|
|
50
|
-
return self.array[0].peer;
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
PeerHeap.prototype.choose2 = function choose2(threshold, filter) {
|
|
55
|
-
var self = this;
|
|
56
|
-
var prob1 = self.array[0].peer.getScore();
|
|
57
|
-
var prob2 = self.array[1].peer.getScore();
|
|
58
|
-
|
|
59
|
-
if (prob1 > threshold && prob1 >= prob2 && (!filter || filter(self.array[0].peer))) {
|
|
60
|
-
return self.array[0].peer;
|
|
61
|
-
} else if (prob2 > threshold && (!filter || filter(self.array[1].peer))) {
|
|
62
|
-
return self.array[1].peer;
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
49
|
/*eslint-disable complexity */
|
|
67
50
|
/*eslint-disable max-statements */
|
|
68
51
|
PeerHeap.prototype.choose = function choose(threshold, filter) {
|
|
@@ -72,26 +55,36 @@ PeerHeap.prototype.choose = function choose(threshold, filter) {
|
|
|
72
55
|
return null;
|
|
73
56
|
}
|
|
74
57
|
|
|
75
|
-
|
|
76
|
-
return self.choose1(threshold, filter);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (self.array.length === 2) {
|
|
80
|
-
return self.choose2(threshold, filter);
|
|
81
|
-
}
|
|
82
|
-
|
|
58
|
+
var isSecondary = false;
|
|
83
59
|
var chosenPeer = null;
|
|
60
|
+
var secondaryPeer = null;
|
|
84
61
|
var highestProbability = 0;
|
|
62
|
+
var secondaryProbability = 0;
|
|
85
63
|
var firstScore = self.array[0].peer.getScore();
|
|
86
64
|
|
|
65
|
+
var notEnoughPeers = false;
|
|
66
|
+
if (self.hasMinConnections) {
|
|
67
|
+
notEnoughPeers = self.peers.currentConnectedPeers < self.peers.minConnections;
|
|
68
|
+
}
|
|
69
|
+
|
|
87
70
|
// Pointers into dfsStack
|
|
88
71
|
var stackBegin = 0;
|
|
89
72
|
var stackEnd = 0;
|
|
90
73
|
|
|
91
74
|
if (firstScore > threshold && (!filter || filter(self.array[0].peer))) {
|
|
92
75
|
// Don't check first peer if it looks good, check its children though
|
|
93
|
-
|
|
94
|
-
|
|
76
|
+
|
|
77
|
+
var firstPeer = self.array[0].peer;
|
|
78
|
+
isSecondary = notEnoughPeers && firstPeer.isConnected('out');
|
|
79
|
+
|
|
80
|
+
if (isSecondary) {
|
|
81
|
+
secondaryPeer = firstPeer;
|
|
82
|
+
secondaryProbability = firstScore;
|
|
83
|
+
} else {
|
|
84
|
+
chosenPeer = firstPeer;
|
|
85
|
+
highestProbability = firstScore;
|
|
86
|
+
}
|
|
87
|
+
|
|
95
88
|
// The array is seeded with 0, 1, 2 so we just have to advance the
|
|
96
89
|
// stack pointers
|
|
97
90
|
stackBegin = 1;
|
|
@@ -104,13 +97,20 @@ PeerHeap.prototype.choose = function choose(threshold, filter) {
|
|
|
104
97
|
|
|
105
98
|
var el = self.array[i];
|
|
106
99
|
|
|
107
|
-
if (
|
|
100
|
+
if (!el || (
|
|
101
|
+
self.rangehis[i] <= self.maxRangeStart &&
|
|
102
|
+
(!filter && !notEnoughPeers)
|
|
103
|
+
)) {
|
|
108
104
|
// This range ends before the range with the largest start begins,
|
|
109
105
|
// so it can't possibly be chosen over any of the ranges we've
|
|
110
106
|
// seen. All ranges below this one have a smaller end, so this
|
|
111
107
|
// range and any below it can't be chosen.
|
|
112
108
|
continue;
|
|
113
|
-
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!filter || filter(el.peer)) {
|
|
112
|
+
isSecondary = notEnoughPeers && el.peer.isConnected('out');
|
|
113
|
+
|
|
114
114
|
// INLINE of TChannelPeer#getScore
|
|
115
115
|
var lo = self.rangelos[i];
|
|
116
116
|
var hi = self.rangehis[i];
|
|
@@ -120,9 +120,18 @@ PeerHeap.prototype.choose = function choose(threshold, filter) {
|
|
|
120
120
|
}
|
|
121
121
|
var probability = lo + ((hi - lo) * rand);
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
highestProbability
|
|
125
|
-
|
|
123
|
+
var isBestChoice = !isSecondary ?
|
|
124
|
+
(probability > highestProbability) :
|
|
125
|
+
(probability > secondaryProbability);
|
|
126
|
+
|
|
127
|
+
if ((probability > threshold) && isBestChoice) {
|
|
128
|
+
if (isSecondary) {
|
|
129
|
+
secondaryProbability = probability;
|
|
130
|
+
secondaryPeer = el.peer;
|
|
131
|
+
} else {
|
|
132
|
+
highestProbability = probability;
|
|
133
|
+
chosenPeer = el.peer;
|
|
134
|
+
}
|
|
126
135
|
}
|
|
127
136
|
}
|
|
128
137
|
|
|
@@ -138,11 +147,18 @@ PeerHeap.prototype.choose = function choose(threshold, filter) {
|
|
|
138
147
|
}
|
|
139
148
|
}
|
|
140
149
|
|
|
141
|
-
|
|
150
|
+
if (secondaryProbability > highestProbability && chosenPeer) {
|
|
151
|
+
chosenPeer.tryConnect(noop);
|
|
152
|
+
return secondaryPeer;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return chosenPeer || secondaryPeer;
|
|
142
156
|
};
|
|
143
157
|
/*eslint-enable complexity */
|
|
144
158
|
/*eslint-enable max-statements */
|
|
145
159
|
|
|
160
|
+
function noop() {}
|
|
161
|
+
|
|
146
162
|
PeerHeap.prototype.clear = function clear() {
|
|
147
163
|
var self = this;
|
|
148
164
|
|
package/sub_peers.js
CHANGED
|
@@ -27,13 +27,37 @@ var PeerHeap = require('./peer_heap.js');
|
|
|
27
27
|
|
|
28
28
|
function TChannelSubPeers(channel, options) {
|
|
29
29
|
TChannelPeersBase.call(this, channel, options);
|
|
30
|
+
|
|
31
|
+
var self = this;
|
|
30
32
|
this.peerScoreThreshold = this.options.peerScoreThreshold || 0;
|
|
31
|
-
this._heap = new PeerHeap(channel.random);
|
|
32
33
|
this.choosePeerWithHeap = channel.choosePeerWithHeap;
|
|
34
|
+
|
|
35
|
+
this.hasMinConnections = typeof options.minConnections === 'number';
|
|
36
|
+
this.minConnections = options.minConnections;
|
|
37
|
+
|
|
38
|
+
this.currentConnectedPeers = 0;
|
|
39
|
+
this._heap = new PeerHeap(this, channel.random);
|
|
40
|
+
|
|
41
|
+
this.boundOnOutConnectionDelta = boundOnOutConnectionDelta;
|
|
42
|
+
|
|
43
|
+
function boundOnOutConnectionDelta(delta, peer) {
|
|
44
|
+
self.onOutConnectionDelta(peer, delta);
|
|
45
|
+
}
|
|
33
46
|
}
|
|
34
47
|
|
|
35
48
|
inherits(TChannelSubPeers, TChannelPeersBase);
|
|
36
49
|
|
|
50
|
+
TChannelSubPeers.prototype.onOutConnectionDelta =
|
|
51
|
+
function onOutConnectionDelta(peer, delta) {
|
|
52
|
+
var connCount = peer.countConnections('out');
|
|
53
|
+
|
|
54
|
+
if (delta === 1 && connCount === 1) {
|
|
55
|
+
this.currentConnectedPeers++;
|
|
56
|
+
} else if (delta === -1 && connCount === 0) {
|
|
57
|
+
this.currentConnectedPeers--;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
37
61
|
TChannelSubPeers.prototype.close = function close(callback) {
|
|
38
62
|
var self = this;
|
|
39
63
|
|
|
@@ -55,6 +79,12 @@ TChannelSubPeers.prototype.add = function add(hostPort, options) {
|
|
|
55
79
|
peer = topChannel.peers.add(hostPort, options);
|
|
56
80
|
peer.setPreferConnectionDirection(self.preferConnectionDirection);
|
|
57
81
|
|
|
82
|
+
if (peer.countConnections('out') > 0) {
|
|
83
|
+
this.currentConnectedPeers++;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
peer.deltaOutConnectionEvent.on(self.boundOnOutConnectionDelta);
|
|
87
|
+
|
|
58
88
|
self._map[hostPort] = peer;
|
|
59
89
|
self._keys.push(hostPort);
|
|
60
90
|
|
|
@@ -80,6 +110,13 @@ TChannelSubPeers.prototype._delete = function _del(peer) {
|
|
|
80
110
|
return;
|
|
81
111
|
}
|
|
82
112
|
|
|
113
|
+
if (peer.countConnections('out') > 0) {
|
|
114
|
+
this.currentConnectedPeers--;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
peer.deltaOutConnectionEvent
|
|
118
|
+
.removeListener(self.boundOnOutConnectionDelta);
|
|
119
|
+
|
|
83
120
|
delete self._map[peer.hostPort];
|
|
84
121
|
popout(self._keys, index);
|
|
85
122
|
|
|
@@ -109,6 +146,7 @@ TChannelSubPeers.prototype.choosePeer = function choosePeer(req) {
|
|
|
109
146
|
return self.chooseLinearPeer(req);
|
|
110
147
|
};
|
|
111
148
|
|
|
149
|
+
/*eslint max-statements: [2, 40]*/
|
|
112
150
|
TChannelSubPeers.prototype.chooseLinearPeer = function chooseLinearPeer(req) {
|
|
113
151
|
/* eslint complexity: [2, 15]*/
|
|
114
152
|
var self = this;
|
|
@@ -121,11 +159,22 @@ TChannelSubPeers.prototype.chooseLinearPeer = function chooseLinearPeer(req) {
|
|
|
121
159
|
var threshold = self.peerScoreThreshold;
|
|
122
160
|
|
|
123
161
|
var selectedPeer = null;
|
|
162
|
+
var secondaryPeer = null;
|
|
124
163
|
var selectedScore = 0;
|
|
164
|
+
var secondaryScore = 0;
|
|
165
|
+
|
|
166
|
+
var notEnoughPeers = false;
|
|
167
|
+
if (this.hasMinConnections) {
|
|
168
|
+
notEnoughPeers = this.currentConnectedPeers < this.minConnections;
|
|
169
|
+
}
|
|
170
|
+
|
|
125
171
|
for (var i = 0; i < hosts.length; i++) {
|
|
126
172
|
var hostPort = hosts[i];
|
|
127
173
|
var peer = self._map[hostPort];
|
|
128
|
-
|
|
174
|
+
|
|
175
|
+
var shouldSkip = req && req.triedRemoteAddrs && req.triedRemoteAddrs[hostPort];
|
|
176
|
+
if (!shouldSkip) {
|
|
177
|
+
var isSecondary = notEnoughPeers && peer.isConnected('out');
|
|
129
178
|
var score = peer.getScore(req);
|
|
130
179
|
|
|
131
180
|
if (self.channel.topChannel.peerScoredEvent) {
|
|
@@ -136,11 +185,24 @@ TChannelSubPeers.prototype.chooseLinearPeer = function chooseLinearPeer(req) {
|
|
|
136
185
|
});
|
|
137
186
|
}
|
|
138
187
|
|
|
139
|
-
var want
|
|
140
|
-
|
|
188
|
+
var want;
|
|
189
|
+
if (isSecondary) {
|
|
190
|
+
want = score > threshold && (
|
|
191
|
+
secondaryPeer === null || score > secondaryScore
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
want = score > threshold &&
|
|
195
|
+
(selectedPeer === null || score > selectedScore);
|
|
196
|
+
}
|
|
197
|
+
|
|
141
198
|
if (want) {
|
|
142
|
-
|
|
143
|
-
|
|
199
|
+
if (isSecondary) {
|
|
200
|
+
secondaryPeer = peer;
|
|
201
|
+
secondaryScore = score;
|
|
202
|
+
} else {
|
|
203
|
+
selectedPeer = peer;
|
|
204
|
+
selectedScore = score;
|
|
205
|
+
}
|
|
144
206
|
}
|
|
145
207
|
}
|
|
146
208
|
}
|
|
@@ -152,14 +214,21 @@ TChannelSubPeers.prototype.chooseLinearPeer = function chooseLinearPeer(req) {
|
|
|
152
214
|
});
|
|
153
215
|
}
|
|
154
216
|
|
|
155
|
-
|
|
217
|
+
if (secondaryScore > selectedScore && selectedPeer) {
|
|
218
|
+
selectedPeer.tryConnect(noop);
|
|
219
|
+
return secondaryPeer;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return selectedPeer || secondaryPeer;
|
|
156
223
|
};
|
|
157
224
|
|
|
225
|
+
function noop() {}
|
|
226
|
+
|
|
158
227
|
TChannelSubPeers.prototype.chooseHeapPeer = function chooseHeapPeer(req) {
|
|
159
228
|
var self = this;
|
|
160
229
|
|
|
161
230
|
var peer;
|
|
162
|
-
if (req && req.triedRemoteAddrs) {
|
|
231
|
+
if ((req && req.triedRemoteAddrs)) {
|
|
163
232
|
peer = self._choosePeerSkipTried(req);
|
|
164
233
|
} else {
|
|
165
234
|
peer = self._heap.choose(self.peerScoreThreshold);
|
|
@@ -182,7 +251,8 @@ function _choosePeerSkipTried(req) {
|
|
|
182
251
|
return self._heap.choose(self.peerScoreThreshold, filterTriedPeers);
|
|
183
252
|
|
|
184
253
|
function filterTriedPeers(peer) {
|
|
185
|
-
|
|
254
|
+
var shouldSkip = req.triedRemoteAddrs[peer.hostPort];
|
|
255
|
+
return !shouldSkip;
|
|
186
256
|
}
|
|
187
257
|
};
|
|
188
258
|
|
package/test/index.js
CHANGED
|
@@ -77,6 +77,7 @@ require('./relay-clamps-the-ttl.js');
|
|
|
77
77
|
require('./relay-under-timeouts.js');
|
|
78
78
|
require('./relay-kills-socket-after-timeouts.js');
|
|
79
79
|
require('./object_pool.js');
|
|
80
|
+
require('./peer-to-peer-load-balance.js');
|
|
80
81
|
|
|
81
82
|
require('./trace/basic_server.js');
|
|
82
83
|
require('./trace/server_2_requests.js');
|
|
@@ -259,6 +259,10 @@ allocCluster.test.only = function testClusterOnly(desc, opts, t) {
|
|
|
259
259
|
test.only(desc, clusterTester(opts, t));
|
|
260
260
|
};
|
|
261
261
|
|
|
262
|
+
allocCluster.test.skip = function testClusterSkip(desc, opts, t) {
|
|
263
|
+
test.skip(desc, clusterTester(opts, t));
|
|
264
|
+
};
|
|
265
|
+
|
|
262
266
|
function connectChannels(channels, callback) {
|
|
263
267
|
return parallel(channels.map(function (channel) {
|
|
264
268
|
return function connectChannelToHosts(callback) {
|
package/test/lib/batch-client.js
CHANGED
|
@@ -24,6 +24,19 @@ var parallel = require('run-parallel');
|
|
|
24
24
|
var collectParallel = require('collect-parallel/array');
|
|
25
25
|
var setTimeout = require('timers').setTimeout;
|
|
26
26
|
|
|
27
|
+
/*
|
|
28
|
+
BatchClient takes a channel and a set of peers to talk
|
|
29
|
+
to.
|
|
30
|
+
|
|
31
|
+
It has a set of options including:
|
|
32
|
+
- totalRequests
|
|
33
|
+
- delay
|
|
34
|
+
- batchSize
|
|
35
|
+
|
|
36
|
+
If you want to do 100QPS you can set batchSize=1 and delay=10
|
|
37
|
+
If you want to do 1000QPS you can set batchSize=50 and delay=50
|
|
38
|
+
|
|
39
|
+
*/
|
|
27
40
|
module.exports = BatchClient;
|
|
28
41
|
|
|
29
42
|
function BatchClient(channel, hosts, options) {
|
|
@@ -42,11 +55,12 @@ function BatchClient(channel, hosts, options) {
|
|
|
42
55
|
self.serviceName = 'server';
|
|
43
56
|
self.endpoint = options.endpoint || 'echo';
|
|
44
57
|
self.body = 'foobar';
|
|
45
|
-
self.timeout = 500;
|
|
58
|
+
self.timeout = options.timeout || 500;
|
|
46
59
|
|
|
47
60
|
self.subChannel = self.channel.makeSubChannel({
|
|
48
61
|
serviceName: self.serviceName,
|
|
49
|
-
peers: self.hosts
|
|
62
|
+
peers: self.hosts,
|
|
63
|
+
minConnections: options.minConnections || null
|
|
50
64
|
});
|
|
51
65
|
|
|
52
66
|
self.requestOptions = {
|
|
@@ -54,6 +68,7 @@ function BatchClient(channel, hosts, options) {
|
|
|
54
68
|
hasNoParent: true,
|
|
55
69
|
timeout: self.timeout,
|
|
56
70
|
retryFlags: options && options.retryFlags,
|
|
71
|
+
retryLimit: options && options.retryLimit ? options.retryLimit : 5,
|
|
57
72
|
headers: {
|
|
58
73
|
cn: 'client',
|
|
59
74
|
as: 'raw'
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// Copyright (c) 2015 Uber Technologies, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
// furnished to do so, subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in
|
|
11
|
+
// all copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
// THE SOFTWARE.
|
|
20
|
+
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
var collectParallel = require('collect-parallel/array');
|
|
24
|
+
var metrics = require('metrics');
|
|
25
|
+
|
|
26
|
+
var allocCluster = require('./lib/alloc-cluster.js');
|
|
27
|
+
var BatchClient = require('./lib/batch-client.js');
|
|
28
|
+
var CollapsedAssert = require('./lib/collapsed-assert.js');
|
|
29
|
+
|
|
30
|
+
allocCluster.test('p2p requests from 40 -> 40', {
|
|
31
|
+
numPeers: 80
|
|
32
|
+
}, function t(cluster, assert) {
|
|
33
|
+
setup(cluster);
|
|
34
|
+
|
|
35
|
+
collectParallel(cluster.batches, function runRequests(batch, _, cb) {
|
|
36
|
+
batch.sendRequests(cb);
|
|
37
|
+
}, onBatches);
|
|
38
|
+
|
|
39
|
+
/*eslint max-statements: [2, 40]*/
|
|
40
|
+
function onBatches(err, results) {
|
|
41
|
+
var cassert = CollapsedAssert();
|
|
42
|
+
cassert.ifError(err);
|
|
43
|
+
|
|
44
|
+
var statuses = [];
|
|
45
|
+
for (var i = 0; i < results.length; i++) {
|
|
46
|
+
cassert.ifError(results[i].err, 'expect no batch error');
|
|
47
|
+
cassert.ifError(results[i].value.errors.length > 0,
|
|
48
|
+
'expect zero errors in batch');
|
|
49
|
+
statuses.push(results[i].value);
|
|
50
|
+
}
|
|
51
|
+
cassert.report(assert, 'expected no errors');
|
|
52
|
+
|
|
53
|
+
var statusTable = findServerHostDistribution(statuses);
|
|
54
|
+
|
|
55
|
+
var uniqHosts = Object.keys(statusTable);
|
|
56
|
+
if (uniqHosts.length < 35) {
|
|
57
|
+
checkConnections();
|
|
58
|
+
checkDistributions(statusTable);
|
|
59
|
+
} else {
|
|
60
|
+
assert.ok(true, 'SKIP: suprisingly large number of peers reached');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
assert.end();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function checkConnections() {
|
|
67
|
+
var cassert = CollapsedAssert();
|
|
68
|
+
var distribution = new metrics.Histogram();
|
|
69
|
+
for (var i = 0; i < cluster.batches.length; i++) {
|
|
70
|
+
var connCount = countConnections(cluster.batches[i]);
|
|
71
|
+
distribution.update(connCount);
|
|
72
|
+
|
|
73
|
+
cassert.ok(
|
|
74
|
+
connCount >= 1 &&
|
|
75
|
+
connCount <= 5,
|
|
76
|
+
'expected a small number of connections'
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
var info = distribution.printObj();
|
|
81
|
+
cassert.ok(info.min <= 2, 'expected low minimum');
|
|
82
|
+
cassert.ok(info.max <= 5, 'expected low maximum');
|
|
83
|
+
cassert.ok(info.sum <= 100, 'expected low total connections');
|
|
84
|
+
cassert.ok(info.p75 <= 2, 'expected low p75');
|
|
85
|
+
// console.log('conn distribution', info);
|
|
86
|
+
|
|
87
|
+
cassert.report(assert, 'expected batch connections to be fine');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function checkDistributions(statusTable) {
|
|
91
|
+
var uniqHosts = Object.keys(statusTable);
|
|
92
|
+
assert.ok(uniqHosts.length <= 35,
|
|
93
|
+
'Expected host reached (' + uniqHosts.length + ') to <= 35');
|
|
94
|
+
|
|
95
|
+
var distribution = new metrics.Histogram();
|
|
96
|
+
for (var i = 0; i < uniqHosts.length; i++) {
|
|
97
|
+
distribution.update(statusTable[uniqHosts[i]]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
var info = distribution.printObj();
|
|
101
|
+
|
|
102
|
+
var cassert = CollapsedAssert();
|
|
103
|
+
cassert.ok(info.min <= 50,
|
|
104
|
+
'expected minimum to be no more then 50'
|
|
105
|
+
);
|
|
106
|
+
cassert.equal(info.sum, 2000,
|
|
107
|
+
'expected 2000 requests to be made'
|
|
108
|
+
);
|
|
109
|
+
cassert.ok(info.median >= 48,
|
|
110
|
+
'expected median (' + info.median + ') to be larger then 49'
|
|
111
|
+
);
|
|
112
|
+
cassert.ok(info.max >= 100, 'expected maximum to be huge');
|
|
113
|
+
cassert.ok(info.p75 >= 50, 'expected P75 to be huge');
|
|
114
|
+
cassert.ok(info.p95 > 80, 'expected P95 to be huge');
|
|
115
|
+
cassert.ok(info.variance >= 500,
|
|
116
|
+
'expected variance (' + info.variance + ') to be huge'
|
|
117
|
+
);
|
|
118
|
+
// console.log('conn distribution', info);
|
|
119
|
+
|
|
120
|
+
cassert.report(assert, 'expected request distribution to be ok');
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
allocCluster.test('p2p requests from 40 -> 40 with minConnections', {
|
|
125
|
+
numPeers: 80,
|
|
126
|
+
channelOptions: {
|
|
127
|
+
choosePeerWithHeap: true
|
|
128
|
+
}
|
|
129
|
+
}, function t(cluster, assert) {
|
|
130
|
+
setup(cluster, {
|
|
131
|
+
minConnections: 10
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
collectParallel(cluster.batches, function runRequests(batch, _, cb) {
|
|
135
|
+
batch.sendRequests(cb);
|
|
136
|
+
}, onBatches);
|
|
137
|
+
|
|
138
|
+
/*eslint max-statements: [2, 40]*/
|
|
139
|
+
function onBatches(err, results) {
|
|
140
|
+
var cassert = CollapsedAssert();
|
|
141
|
+
cassert.ifError(err);
|
|
142
|
+
|
|
143
|
+
var statuses = [];
|
|
144
|
+
for (var i = 0; i < results.length; i++) {
|
|
145
|
+
cassert.ifError(results[i].err, 'expect no batch error');
|
|
146
|
+
cassert.ifError(results[i].value.errors.length > 0,
|
|
147
|
+
'expect zero errors in batch');
|
|
148
|
+
statuses.push(results[i].value);
|
|
149
|
+
}
|
|
150
|
+
cassert.report(assert, 'expected no errors');
|
|
151
|
+
|
|
152
|
+
var statusTable = findServerHostDistribution(statuses);
|
|
153
|
+
|
|
154
|
+
cassert = verifyConnections(cluster, 10, 12);
|
|
155
|
+
cassert.report(assert, 'expected batch connections to be fine');
|
|
156
|
+
|
|
157
|
+
cassert = verifyDistributions(statusTable, {
|
|
158
|
+
min: 40,
|
|
159
|
+
sum: 2000,
|
|
160
|
+
median: [40, 60],
|
|
161
|
+
mean: [45, 55],
|
|
162
|
+
max: 120,
|
|
163
|
+
p75: [55, 70],
|
|
164
|
+
p95: 100,
|
|
165
|
+
variance: 500
|
|
166
|
+
});
|
|
167
|
+
cassert.report(assert, 'expected request distribution to be ok');
|
|
168
|
+
|
|
169
|
+
assert.end();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
allocCluster.test('p2p requests where minConns > no of servers', {
|
|
174
|
+
numPeers: 45,
|
|
175
|
+
channelOptions: {
|
|
176
|
+
choosePeerWithHeap: true
|
|
177
|
+
}
|
|
178
|
+
}, function t(cluster, assert) {
|
|
179
|
+
setup(cluster, {
|
|
180
|
+
minConnections: 6,
|
|
181
|
+
servers: 5
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
collectParallel(cluster.batches, function runRequests(batch, _, cb) {
|
|
185
|
+
batch.sendRequests(cb);
|
|
186
|
+
}, onBatches);
|
|
187
|
+
|
|
188
|
+
/*eslint max-statements: [2, 40]*/
|
|
189
|
+
function onBatches(err, results) {
|
|
190
|
+
var cassert = CollapsedAssert();
|
|
191
|
+
cassert.ifError(err);
|
|
192
|
+
|
|
193
|
+
var statuses = [];
|
|
194
|
+
for (var i = 0; i < results.length; i++) {
|
|
195
|
+
cassert.ifError(results[i].err, 'expect no batch error');
|
|
196
|
+
cassert.ifError(results[i].value.errors.length > 0,
|
|
197
|
+
'expect zero errors in batch');
|
|
198
|
+
|
|
199
|
+
statuses.push(results[i].value);
|
|
200
|
+
}
|
|
201
|
+
cassert.report(assert, 'expected no errors');
|
|
202
|
+
|
|
203
|
+
var statusTable = findServerHostDistribution(statuses);
|
|
204
|
+
|
|
205
|
+
cassert = verifyConnections(cluster, 5, 5);
|
|
206
|
+
cassert.report(assert, 'expected batch connections to be fine');
|
|
207
|
+
|
|
208
|
+
cassert = verifyDistributions(statusTable, {
|
|
209
|
+
min: 395,
|
|
210
|
+
sum: 2000,
|
|
211
|
+
median: [380, 420],
|
|
212
|
+
mean: [395, 405],
|
|
213
|
+
max: 500,
|
|
214
|
+
p75: [400, 450],
|
|
215
|
+
p95: 475
|
|
216
|
+
});
|
|
217
|
+
cassert.report(assert, 'expected request distribution to be ok');
|
|
218
|
+
|
|
219
|
+
assert.end();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
allocCluster.test('p2p requests where half of servers down', {
|
|
224
|
+
numPeers: 48,
|
|
225
|
+
channelOptions: {
|
|
226
|
+
choosePeerWithHeap: true
|
|
227
|
+
}
|
|
228
|
+
}, function t(cluster, assert) {
|
|
229
|
+
cluster.logger.whitelist('info', 'resetting connection');
|
|
230
|
+
|
|
231
|
+
setup(cluster, {
|
|
232
|
+
minConnections: 5,
|
|
233
|
+
servers: 8,
|
|
234
|
+
retryLimit: 2
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Close half the servers...
|
|
238
|
+
for (var j = 0; j < cluster.servers.length / 2; j++) {
|
|
239
|
+
cluster.servers[j * 2].close();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
collectParallel(cluster.batches, function runRequests(batch, _, cb) {
|
|
243
|
+
batch.sendRequests(cb);
|
|
244
|
+
}, onBatches);
|
|
245
|
+
|
|
246
|
+
/*eslint max-statements: [2, 40]*/
|
|
247
|
+
function onBatches(err, results) {
|
|
248
|
+
var cassert = CollapsedAssert();
|
|
249
|
+
cassert.ifError(err);
|
|
250
|
+
|
|
251
|
+
var statuses = [];
|
|
252
|
+
for (var i = 0; i < results.length; i++) {
|
|
253
|
+
cassert.ifError(results[i].err, 'expect no batch error');
|
|
254
|
+
cassert.ifError(results[i].value.errors.length > 5,
|
|
255
|
+
'expect at most five error in batch(' +
|
|
256
|
+
results[i].value.errors.length + ')');
|
|
257
|
+
|
|
258
|
+
statuses.push(results[i].value);
|
|
259
|
+
}
|
|
260
|
+
cassert.report(assert, 'expected no errors');
|
|
261
|
+
|
|
262
|
+
var statusTable = findServerHostDistribution(statuses);
|
|
263
|
+
|
|
264
|
+
cassert = verifyConnections(cluster, 4, 4);
|
|
265
|
+
cassert.report(assert, 'expected batch connections to be fine');
|
|
266
|
+
|
|
267
|
+
cassert = verifyDistributions(statusTable, {
|
|
268
|
+
min: 495,
|
|
269
|
+
sum: [1985, 2000],
|
|
270
|
+
median: [480, 520],
|
|
271
|
+
mean: [495, 505],
|
|
272
|
+
max: 600,
|
|
273
|
+
p75: [500, 575],
|
|
274
|
+
p95: 600
|
|
275
|
+
});
|
|
276
|
+
cassert.report(assert, 'expected request distribution to be ok');
|
|
277
|
+
|
|
278
|
+
var logs = cluster.logger.items();
|
|
279
|
+
assert.ok(logs.length <= 280 + 160,
|
|
280
|
+
'expected conn reset logs (' + logs.length + ') to be <= 420');
|
|
281
|
+
|
|
282
|
+
assert.end();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
function findServerHostDistribution(statuses) {
|
|
287
|
+
var statusTable = {};
|
|
288
|
+
for (var i = 0; i < statuses.length; i++) {
|
|
289
|
+
var records = statuses[i].results;
|
|
290
|
+
for (var j = 0; j < records.length; j++) {
|
|
291
|
+
var record = records[j];
|
|
292
|
+
if (!statusTable[record.outReqHostPort]) {
|
|
293
|
+
statusTable[record.outReqHostPort] = 0;
|
|
294
|
+
}
|
|
295
|
+
statusTable[record.outReqHostPort]++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return statusTable;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function countConnections(batchClient) {
|
|
302
|
+
var subChannel = batchClient.subChannel;
|
|
303
|
+
var peers = subChannel.peers.values();
|
|
304
|
+
|
|
305
|
+
var conns = [];
|
|
306
|
+
for (var i = 0; i < peers.length; i++) {
|
|
307
|
+
var peer = peers[i];
|
|
308
|
+
for (var j = 0; j < peer.connections.length; j++) {
|
|
309
|
+
conns.push(peer.connections[j]);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return conns.length;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function verifyConnections(cluster, min, max) {
|
|
317
|
+
var MIN = min;
|
|
318
|
+
var MAX = max;
|
|
319
|
+
var COUNT = cluster.batches.length;
|
|
320
|
+
|
|
321
|
+
var cassert = CollapsedAssert();
|
|
322
|
+
var distribution = new metrics.Histogram();
|
|
323
|
+
for (var i = 0; i < cluster.batches.length; i++) {
|
|
324
|
+
var connCount = countConnections(cluster.batches[i]);
|
|
325
|
+
distribution.update(connCount);
|
|
326
|
+
|
|
327
|
+
cassert.ok(
|
|
328
|
+
connCount >= MIN &&
|
|
329
|
+
connCount <= MAX,
|
|
330
|
+
'expected connections(' + connCount + ') to be ' +
|
|
331
|
+
'>= ' + MIN + ' and <= ' + MAX
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
var info = distribution.printObj();
|
|
336
|
+
cassert.ok(info.min <= MAX,
|
|
337
|
+
'expected min connections(' + info.min + ') to be <= ' + MAX);
|
|
338
|
+
cassert.ok(info.max >= MIN,
|
|
339
|
+
'expected max conns(' + info.max + ') to be >= ' + MIN);
|
|
340
|
+
cassert.ok(info.sum >= COUNT * MIN,
|
|
341
|
+
'expected sum of conns(' + info.sum + ') to be at ' +
|
|
342
|
+
COUNT * MIN + ' conns');
|
|
343
|
+
cassert.ok(info.p75 <= MAX,
|
|
344
|
+
'expected p75(' + info.p75 + ') to be <= ' + MAX);
|
|
345
|
+
|
|
346
|
+
return cassert;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function verifyDistributions(statusTable, opts) {
|
|
350
|
+
var uniqHosts = Object.keys(statusTable);
|
|
351
|
+
|
|
352
|
+
var distribution = new metrics.Histogram();
|
|
353
|
+
for (var i = 0; i < uniqHosts.length; i++) {
|
|
354
|
+
distribution.update(statusTable[uniqHosts[i]]);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
var info = distribution.printObj();
|
|
358
|
+
|
|
359
|
+
var cassert = CollapsedAssert();
|
|
360
|
+
cassert.ok(info.min <= opts.min,
|
|
361
|
+
'expected minimum(' + info.min + ') to be no more then ' + opts.min
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
if (Array.isArray(opts.sum)) {
|
|
365
|
+
cassert.ok(
|
|
366
|
+
info.sum >= opts.sum[0] &&
|
|
367
|
+
info.sum <= opts.sum[1],
|
|
368
|
+
'expected sum(' + info.sum + ') to be within ' +
|
|
369
|
+
opts.sum[0] + ' & ' + opts.sum[1]
|
|
370
|
+
);
|
|
371
|
+
} else {
|
|
372
|
+
cassert.equal(info.sum, opts.sum,
|
|
373
|
+
'expected sum(' + info.sum + ') to be ' + opts.sum
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
cassert.ok(
|
|
378
|
+
info.median >= opts.median[0] &&
|
|
379
|
+
info.median <= opts.median[1],
|
|
380
|
+
'expected median(' + info.median + ') to be within ' +
|
|
381
|
+
opts.median[0] + ' & ' + opts.median[1]
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
cassert.ok(
|
|
385
|
+
info.mean >= opts.mean[0] &&
|
|
386
|
+
info.mean <= opts.mean[1],
|
|
387
|
+
'expected mean(' + info.mean + ') to be within ' +
|
|
388
|
+
opts.mean[0] + ' & ' + opts.mean[1]
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
cassert.ok(info.max <= opts.max,
|
|
392
|
+
'expected maximum(' + info.max + ') to no more then ' + opts.max
|
|
393
|
+
);
|
|
394
|
+
cassert.ok(
|
|
395
|
+
info.p75 >= opts.p75[0] &&
|
|
396
|
+
info.p75 <= opts.p75[1],
|
|
397
|
+
'expected P75(' + info.p75 + ') to be within ' +
|
|
398
|
+
opts.p75[0] + ' & ' + opts.p75[1]
|
|
399
|
+
);
|
|
400
|
+
cassert.ok(info.p95 <= opts.p95,
|
|
401
|
+
'expected P95 (' + info.p95 + ') to be less than ' + opts.p95
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
if (opts.variance) {
|
|
405
|
+
cassert.ok(info.variance <= opts.variance,
|
|
406
|
+
'expected variance(' + info.variance + ') to be less than ' + opts.variance
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return cassert;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function setup(cluster, opts) {
|
|
414
|
+
opts = opts || {};
|
|
415
|
+
var NUM_CLIENTS = opts.clients || 40;
|
|
416
|
+
var NUM_SERVERS = opts.servers || cluster.channels.length - NUM_CLIENTS;
|
|
417
|
+
|
|
418
|
+
cluster.clients = cluster.channels.slice(0, NUM_CLIENTS);
|
|
419
|
+
cluster.servers = cluster.channels.slice(
|
|
420
|
+
NUM_CLIENTS, NUM_CLIENTS + NUM_SERVERS
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
var i;
|
|
424
|
+
for (i = 0; i < cluster.servers.length; i++) {
|
|
425
|
+
makeServer(cluster.servers[i], i);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
cluster.serverHosts = [];
|
|
429
|
+
for (i = 0; i < cluster.servers.length; i++) {
|
|
430
|
+
cluster.serverHosts.push(cluster.servers[i].hostPort);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
cluster.batches = [];
|
|
434
|
+
for (i = 0; i < cluster.clients.length; i++) {
|
|
435
|
+
cluster.batches.push(new BatchClient(
|
|
436
|
+
cluster.clients[i], cluster.serverHosts, {
|
|
437
|
+
delay: 40,
|
|
438
|
+
batchSize: 1,
|
|
439
|
+
timeout: 1000,
|
|
440
|
+
totalRequests: 50,
|
|
441
|
+
minConnections: opts.minConnections || null,
|
|
442
|
+
retryLimit: opts.retryLimit || null
|
|
443
|
+
}
|
|
444
|
+
));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function makeServer(channel, index) {
|
|
449
|
+
var chanNum = index + 1;
|
|
450
|
+
|
|
451
|
+
var serverChan = channel.makeSubChannel({
|
|
452
|
+
serviceName: 'server'
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
serverChan.register('echo', function echo(req, res, arg2, arg3) {
|
|
456
|
+
res.headers.as = 'raw';
|
|
457
|
+
res.sendOk(arg2, arg3 + ' served by ' + chanNum);
|
|
458
|
+
});
|
|
459
|
+
}
|