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.
@@ -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,10000,20000
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 \
@@ -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: [DESTINATION_SERVER]
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.19",
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 100",
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
- if (self.array.length === 1) {
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
- chosenPeer = self.array[0].peer;
94
- highestProbability = firstScore;
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 (self.rangehis[i] <= self.maxRangeStart) {
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
- } else if (!filter || filter(el.peer)) {
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
- if ((probability > highestProbability) && (probability > threshold)) {
124
- highestProbability = probability;
125
- chosenPeer = el.peer;
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
- return chosenPeer;
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
- if (!req || !req.triedRemoteAddrs || !req.triedRemoteAddrs[hostPort]) {
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 = score > threshold &&
140
- (selectedPeer === null || score > selectedScore);
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
- selectedPeer = peer;
143
- selectedScore = score;
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
- return selectedPeer;
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
- return !req.triedRemoteAddrs[peer.hostPort];
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) {
@@ -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
+ }