mirror of https://github.com/grpc/grpc.git
Merge pull request #6032 from murgatroid99/node_stress_client
Node stress test client and metrics clientpull/6042/head
commit
25058c965e
4 changed files with 276 additions and 0 deletions
@ -0,0 +1,61 @@ |
||||
/* |
||||
* |
||||
* Copyright 2016, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
'use strict'; |
||||
|
||||
var grpc = require('../../..'); |
||||
|
||||
var proto = grpc.load(__dirname + '/../../proto/grpc/testing/metrics.proto'); |
||||
var metrics = proto.grpc.testing; |
||||
|
||||
function main() { |
||||
var parseArgs = require('minimist'); |
||||
var argv = parseArgs(process.argv, { |
||||
string: 'metrics_server_address', |
||||
boolean: 'total_only' |
||||
}); |
||||
var client = new metrics.MetricsService(argv.metrics_server_address, |
||||
grpc.credentials.createInsecure()); |
||||
if (argv.total_only) { |
||||
client.getGauge({name: 'qps'}, function(err, data) { |
||||
console.log(data.name + ':', data.long_value); |
||||
}); |
||||
} else { |
||||
var call = client.getAllGauges({}); |
||||
call.on('data', function(data) { |
||||
console.log(data.name + ':', data.long_value); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
main(); |
@ -0,0 +1,87 @@ |
||||
/* |
||||
* |
||||
* Copyright 2016, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
'use strict'; |
||||
|
||||
var _ = require('lodash'); |
||||
|
||||
var grpc = require('../../..'); |
||||
|
||||
var proto = grpc.load(__dirname + '/../../proto/grpc/testing/metrics.proto'); |
||||
var metrics = proto.grpc.testing; |
||||
|
||||
function getGauge(call, callback) { |
||||
/* jshint validthis: true */ |
||||
// Should be bound to a MetricsServer object
|
||||
var name = call.request.name; |
||||
if (this.gauges.hasOwnProperty(name)) { |
||||
callback(null, _.assign({name: name}, this.gauges[name]())); |
||||
} else { |
||||
callback({code: grpc.status.NOT_FOUND, |
||||
details: 'No such gauge: ' + name}); |
||||
} |
||||
} |
||||
|
||||
function getAllGauges(call) { |
||||
/* jshint validthis: true */ |
||||
// Should be bound to a MetricsServer object
|
||||
_.each(this.gauges, function(getter, name) { |
||||
call.write(_.assign({name: name}, getter())); |
||||
}); |
||||
call.end(); |
||||
} |
||||
|
||||
function MetricsServer(port) { |
||||
var server = new grpc.Server(); |
||||
server.addProtoService(metrics.MetricsService.service, { |
||||
getGauge: _.bind(getGauge, this), |
||||
getAllGauges: _.bind(getAllGauges, this) |
||||
}); |
||||
server.bind('localhost:' + port, grpc.ServerCredentials.createInsecure()); |
||||
this.server = server; |
||||
this.gauges = {}; |
||||
} |
||||
|
||||
MetricsServer.prototype.start = function() { |
||||
this.server.start(); |
||||
} |
||||
|
||||
MetricsServer.prototype.registerGauge = function(name, getter) { |
||||
this.gauges[name] = getter; |
||||
}; |
||||
|
||||
MetricsServer.prototype.shutdown = function() { |
||||
this.server.forceShutdown(); |
||||
}; |
||||
|
||||
module.exports = MetricsServer; |
@ -0,0 +1,126 @@ |
||||
/* |
||||
* |
||||
* Copyright 2016, Google Inc. |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or without |
||||
* modification, are permitted provided that the following conditions are |
||||
* met: |
||||
* |
||||
* * Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* * Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following disclaimer |
||||
* in the documentation and/or other materials provided with the |
||||
* distribution. |
||||
* * Neither the name of Google Inc. nor the names of its |
||||
* contributors may be used to endorse or promote products derived from |
||||
* this software without specific prior written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
* |
||||
*/ |
||||
|
||||
'use strict'; |
||||
|
||||
var _ = require('lodash'); |
||||
|
||||
var grpc = require('../../..'); |
||||
|
||||
var interop_client = require('../interop/interop_client'); |
||||
var MetricsServer = require('./metrics_server'); |
||||
|
||||
var running; |
||||
|
||||
var metrics_server; |
||||
|
||||
var start_time; |
||||
var query_count; |
||||
|
||||
function makeCall(client, test_cases) { |
||||
if (!running) { |
||||
return; |
||||
} |
||||
var test_case = test_cases[_.random(test_cases.length - 1)]; |
||||
interop_client.test_cases[test_case].run(client, function() { |
||||
query_count += 1; |
||||
makeCall(client, test_cases); |
||||
}); |
||||
} |
||||
|
||||
function makeCalls(client, test_cases, parallel_calls_per_channel) { |
||||
_.times(parallel_calls_per_channel, function() { |
||||
makeCall(client, test_cases); |
||||
}); |
||||
} |
||||
|
||||
function getQps() { |
||||
var diff = process.hrtime(start_time); |
||||
var seconds = diff[0] + diff[1] / 1e9; |
||||
return {long_value: query_count / seconds}; |
||||
} |
||||
|
||||
function start(server_addresses, test_cases, channels_per_server, |
||||
parallel_calls_per_channel, metrics_port) { |
||||
running = true; |
||||
/* Assuming that we are not calling unimplemented_method. The client class |
||||
* used by empty_unary is (currently) the client class used by every interop |
||||
* test except unimplemented_method */ |
||||
var Client = interop_client.test_cases.empty_unary.Client; |
||||
/* Make channels_per_server clients connecting to each server address */ |
||||
var channels = _.flatten(_.times( |
||||
channels_per_server, _.partial(_.map, server_addresses, function(address) { |
||||
return new Client(address, grpc.credentials.createInsecure()); |
||||
}))); |
||||
metrics_server = new MetricsServer(metrics_port); |
||||
metrics_server.registerGauge('qps', getQps); |
||||
start_time = process.hrtime(); |
||||
query_count = 0; |
||||
_.each(channels, _.partial(makeCalls, _, test_cases, |
||||
parallel_calls_per_channel)); |
||||
metrics_server.start(); |
||||
} |
||||
|
||||
function stop() { |
||||
running = false; |
||||
metrics_server.shutdown(); |
||||
console.log('QPS: ' + getQps().long_value); |
||||
} |
||||
|
||||
function main() { |
||||
var parseArgs = require('minimist'); |
||||
var argv = parseArgs(process.argv, { |
||||
string: ['server_addresses', 'test_cases', 'metrics_port'], |
||||
default: {'server_addresses': 'localhost:8080', |
||||
'test_duration-secs': -1, |
||||
'num_channels_per_server': 1, |
||||
'num_stubs_per_channel': 1, |
||||
'metrics_port': '8081'} |
||||
}); |
||||
var server_addresses = argv.server_addresses.split(','); |
||||
/* Generate an array of test cases, where the number of instances of each name |
||||
* corresponds to the number given in the argument. |
||||
* e.g. 'empty_unary:1,large_unary:2' => |
||||
* ['empty_unary', 'large_unary', 'large_unary'] */ |
||||
var test_cases = _.flatten(_.map(argv.test_cases.split(','), function(value) { |
||||
var split = value.split(':'); |
||||
return _.times(split[1], _.constant(split[0])); |
||||
})); |
||||
start(server_addresses, test_cases, argv.num_channels_per_server, |
||||
argv.num_stubs_per_channel, argv.metrics_port); |
||||
if (argv['test_duration-secs'] > -1) { |
||||
setTimeout(stop, argv['test_duration-secs'] * 1000); |
||||
} |
||||
} |
||||
|
||||
main(); |
Loading…
Reference in new issue