mirror of https://github.com/grpc/grpc.git
commit
8ddad0f485
78 changed files with 1652 additions and 819 deletions
After Width: | Height: | Size: 62 KiB |
@ -0,0 +1,92 @@ |
||||
# Stress Test framework for gRPC |
||||
|
||||
(Sree Kuchibhotla - sreek@) |
||||
|
||||
> Status: This is implemented. More details at [README.md](https://github.com/grpc/grpc/blob/master/tools/run_tests/stress_test/README.md) |
||||
|
||||
|
||||
**I. GOALS** |
||||
|
||||
1) Build a stress test suite for gRPC: |
||||
|
||||
* Build a stress test suite that can Identify bugs by testing the system (gRPC server/client) under extreme conditions: |
||||
* High load |
||||
* High concurrency |
||||
* Limited resources |
||||
* Intermittent failures |
||||
* Should be integrated with Jenkins CI |
||||
|
||||
2) Make it generic enough (i.e build a generic test framework) that can be used for: |
||||
|
||||
* Executing M instances of a client against N instances of a server with an arbitrarily defined connection matrix |
||||
* Execute heterogenous test configurations - for example: Java stress test clients against C++ servers or Node clients against Python servers or TSAN C++ clients vs ASAN C++ Servers etc. |
||||
* Easy and Flexible enough that Devs can use it to recreate complex test scenarios |
||||
|
||||
The implementation effort is divided into two parts: |
||||
|
||||
* Building a "Stress Test Framework" to run the stress test suites- More details in **Section II** (The idea is that the Stress Test framework is generic enough that it would be easier to modify it to run other suites like interop-tests or custom test scenarios) |
||||
* Building a 'Stress test suite' - More details in **section III** |
||||
|
||||
**Terminology:** |
||||
|
||||
GCE - Google compute engine |
||||
GKE - Google Container engine |
||||
Kubernetes - Google's open source service scheduler / orchestrator. |
||||
|
||||
**Note:** The terms GKE and Kubernetes are used interchangeably in this document |
||||
|
||||
# II. STRESS TEST FRAMEWORK |
||||
|
||||
(The details of each step are explained below)) |
||||
![image](images/stress_test_framework.png) |
||||
**Figure 1** |
||||
|
||||
### Step 1 Read the test config, generate base docker images |
||||
|
||||
**_Test Config:_** The test configuration contains the following information: |
||||
|
||||
* _GKE info:_ GKE project and cluster info |
||||
* _Docker images:_ Instructions to build docker images |
||||
* _Client templates:_ One or more client templates each containing the following information: |
||||
* Which docker image to use |
||||
* Path to the client program to launch (within the docker image) |
||||
* Parameters to the client program |
||||
* _Server templates:_ Similar to Client templates - except that these are for servers |
||||
* Test matrix containing the following: |
||||
* _Server groups:_ One or more groups of servers containing the following info for each group |
||||
* Which server template to use |
||||
* How many instances to launch |
||||
* _Client groups:_ One or more groups of clients containing the following (for each group): |
||||
* Which client template to use |
||||
* How many instances to launch |
||||
* Which server group to talk to (all clients in this group will talk to all servers in the server group) |
||||
|
||||
The first step is to read the test config and build the docker images |
||||
|
||||
**_Stress server docker image:_** The following are the main files in the server docker images |
||||
|
||||
* _Interop_server:_ The server program |
||||
* `run_server.py`: This is a python script which is the entry point of the docker image (i.e this is the script that is called when the docker image is run in GKE). This script launches the interop server and also updates the status in BigQuery. If the interop_server fails for whatever reason, the script launch_server.py logs that status in BigQuery |
||||
|
||||
**_Stress client docker image:_** |
||||
|
||||
* Stress client: The stress test client. In addition to talking to the interop_server, the stress client also exports metrics (which can be queried by the metrics_client described below) |
||||
* Metrics client: Metrics client connects to the stress_client to get the current qps metrics. |
||||
* `run_client.py`: This is a python script which is the entry point of the docker image (i.e this is the script that is called when the docker image is run in GKE). This script launches the stress client and also updates the status in BigQuery. The script then periodically launches metrics client to query the qps from the stress client and then uploads the qps to BigQuery. |
||||
|
||||
### Step 2) Upload the docker images to GKE |
||||
The docker images are uploaded to the GKE registry |
||||
|
||||
### Step 3) Launch the tests in GKE |
||||
The test driver reads the test matrix (described in step 1) and creates the necessary server and client pods in GKE. |
||||
|
||||
### Step 4) Tests are run in GKE |
||||
GKE starts running the tests by calling the entry points in *each* docker image (i.e `run_server.py` or `run_client.py` depending on whcih docker image it is) |
||||
|
||||
### Step 5) Upload the status to GKE and Monitor the status in GKE |
||||
* 5.1 The tests periodically update their status in BigQuery |
||||
* 5.2 The test driver periodically checks the status in Bigquery to see if any tests failed. If any tests failed, the driver immediately stops the tests. If not, the driver continues to run the tests for a configurable amount of time. |
||||
|
||||
### Step 6) Create a summary report |
||||
The test driver creates a final summary report containing details about any test failures and information about how to connect the failed pods in GKE for debugging. |
||||
|
@ -1,94 +0,0 @@ |
||||
//
|
||||
// Copyright 2015, 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.
|
||||
//
|
||||
|
||||
#include "src/core/ext/client_channel/resolver_result.h" |
||||
|
||||
#include <string.h> |
||||
|
||||
#include <grpc/support/alloc.h> |
||||
#include <grpc/support/string_util.h> |
||||
|
||||
#include "src/core/lib/channel/channel_args.h" |
||||
|
||||
struct grpc_resolver_result { |
||||
gpr_refcount refs; |
||||
char* server_name; |
||||
grpc_lb_addresses* addresses; |
||||
char* lb_policy_name; |
||||
grpc_channel_args* lb_policy_args; |
||||
}; |
||||
|
||||
grpc_resolver_result* grpc_resolver_result_create( |
||||
const char* server_name, grpc_lb_addresses* addresses, |
||||
const char* lb_policy_name, grpc_channel_args* lb_policy_args) { |
||||
grpc_resolver_result* result = gpr_malloc(sizeof(*result)); |
||||
memset(result, 0, sizeof(*result)); |
||||
gpr_ref_init(&result->refs, 1); |
||||
result->server_name = gpr_strdup(server_name); |
||||
result->addresses = addresses; |
||||
result->lb_policy_name = gpr_strdup(lb_policy_name); |
||||
result->lb_policy_args = lb_policy_args; |
||||
return result; |
||||
} |
||||
|
||||
void grpc_resolver_result_ref(grpc_resolver_result* result) { |
||||
gpr_ref(&result->refs); |
||||
} |
||||
|
||||
void grpc_resolver_result_unref(grpc_exec_ctx* exec_ctx, |
||||
grpc_resolver_result* result) { |
||||
if (gpr_unref(&result->refs)) { |
||||
gpr_free(result->server_name); |
||||
grpc_lb_addresses_destroy(result->addresses, NULL /* user_data_destroy */); |
||||
gpr_free(result->lb_policy_name); |
||||
grpc_channel_args_destroy(result->lb_policy_args); |
||||
gpr_free(result); |
||||
} |
||||
} |
||||
|
||||
const char* grpc_resolver_result_get_server_name(grpc_resolver_result* result) { |
||||
return result->server_name; |
||||
} |
||||
|
||||
grpc_lb_addresses* grpc_resolver_result_get_addresses( |
||||
grpc_resolver_result* result) { |
||||
return result->addresses; |
||||
} |
||||
|
||||
const char* grpc_resolver_result_get_lb_policy_name( |
||||
grpc_resolver_result* result) { |
||||
return result->lb_policy_name; |
||||
} |
||||
|
||||
grpc_channel_args* grpc_resolver_result_get_lb_policy_args( |
||||
grpc_resolver_result* result) { |
||||
return result->lb_policy_args; |
||||
} |
@ -1,68 +0,0 @@ |
||||
//
|
||||
// Copyright 2015, 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.
|
||||
//
|
||||
|
||||
#ifndef GRPC_CORE_EXT_CLIENT_CHANNEL_RESOLVER_RESULT_H |
||||
#define GRPC_CORE_EXT_CLIENT_CHANNEL_RESOLVER_RESULT_H |
||||
|
||||
#include "src/core/ext/client_channel/lb_policy_factory.h" |
||||
|
||||
// TODO(roth, ctiller): In the long term, we are considering replacing
|
||||
// the resolver_result data structure with grpc_channel_args. The idea is
|
||||
// that the resolver will return a set of channel args that contains the
|
||||
// information that is currently in the resolver_result struct. For
|
||||
// example, there will be specific args indicating the set of addresses
|
||||
// and the name of the LB policy to instantiate. Note that if we did
|
||||
// this, we would probably want to change the data structure of
|
||||
// grpc_channel_args such to a hash table or AVL or some other data
|
||||
// structure that does not require linear search to find keys.
|
||||
|
||||
/// Results reported from a grpc_resolver.
|
||||
typedef struct grpc_resolver_result grpc_resolver_result; |
||||
|
||||
/// Takes ownership of \a addresses and \a lb_policy_args.
|
||||
grpc_resolver_result* grpc_resolver_result_create( |
||||
const char* server_name, grpc_lb_addresses* addresses, |
||||
const char* lb_policy_name, grpc_channel_args* lb_policy_args); |
||||
|
||||
void grpc_resolver_result_ref(grpc_resolver_result* result); |
||||
void grpc_resolver_result_unref(grpc_exec_ctx* exec_ctx, |
||||
grpc_resolver_result* result); |
||||
|
||||
/// Accessors. Caller does NOT take ownership of results.
|
||||
const char* grpc_resolver_result_get_server_name(grpc_resolver_result* result); |
||||
grpc_lb_addresses* grpc_resolver_result_get_addresses( |
||||
grpc_resolver_result* result); |
||||
const char* grpc_resolver_result_get_lb_policy_name( |
||||
grpc_resolver_result* result); |
||||
grpc_channel_args* grpc_resolver_result_get_lb_policy_args( |
||||
grpc_resolver_result* result); |
||||
|
||||
#endif /* GRPC_CORE_EXT_CLIENT_CHANNEL_RESOLVER_RESULT_H */ |
@ -0,0 +1,291 @@ |
||||
/* |
||||
* |
||||
* Copyright 2015, 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. |
||||
* |
||||
*/ |
||||
|
||||
/** |
||||
* Benchmark client module |
||||
* @module |
||||
*/ |
||||
|
||||
'use strict'; |
||||
|
||||
var fs = require('fs'); |
||||
var path = require('path'); |
||||
var util = require('util'); |
||||
var EventEmitter = require('events'); |
||||
var http = require('http'); |
||||
var https = require('https'); |
||||
|
||||
var async = require('async'); |
||||
var _ = require('lodash'); |
||||
var PoissonProcess = require('poisson-process'); |
||||
var Histogram = require('./histogram'); |
||||
|
||||
/** |
||||
* Convert a time difference, as returned by process.hrtime, to a number of |
||||
* nanoseconds. |
||||
* @param {Array.<number>} time_diff The time diff, represented as |
||||
* [seconds, nanoseconds] |
||||
* @return {number} The total number of nanoseconds |
||||
*/ |
||||
function timeDiffToNanos(time_diff) { |
||||
return time_diff[0] * 1e9 + time_diff[1]; |
||||
} |
||||
|
||||
function BenchmarkClient(server_targets, channels, histogram_params, |
||||
security_params) { |
||||
var options = { |
||||
method: 'PUT', |
||||
headers: { |
||||
'Content-Type': 'application/json' |
||||
} |
||||
}; |
||||
var protocol; |
||||
if (security_params) { |
||||
var ca_path; |
||||
protocol = https; |
||||
this.request = _.bind(https.request, https); |
||||
if (security_params.use_test_ca) { |
||||
ca_path = path.join(__dirname, '../test/data/ca.pem'); |
||||
var ca_data = fs.readFileSync(ca_path); |
||||
options.ca = ca_data; |
||||
} |
||||
if (security_params.server_host_override) { |
||||
var host_override = security_params.server_host_override; |
||||
options.servername = host_override; |
||||
} |
||||
} else { |
||||
protocol = http; |
||||
} |
||||
|
||||
this.request = _.bind(protocol.request, protocol); |
||||
|
||||
this.client_options = []; |
||||
|
||||
for (var i = 0; i < channels; i++) { |
||||
var host_port; |
||||
host_port = server_targets[i % server_targets.length].split(':') |
||||
var new_options = _.assign({hostname: host_port[0], port: +host_port[1]}, options); |
||||
new_options.agent = new protocol.Agent(new_options); |
||||
this.client_options[i] = new_options; |
||||
} |
||||
|
||||
this.histogram = new Histogram(histogram_params.resolution, |
||||
histogram_params.max_possible); |
||||
|
||||
this.running = false; |
||||
|
||||
this.pending_calls = 0; |
||||
} |
||||
|
||||
util.inherits(BenchmarkClient, EventEmitter); |
||||
|
||||
function startAllClients(client_options_list, outstanding_rpcs_per_channel, |
||||
makeCall, emitter) { |
||||
_.each(client_options_list, function(client_options) { |
||||
_.times(outstanding_rpcs_per_channel, function() { |
||||
makeCall(client_options); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
BenchmarkClient.prototype.startClosedLoop = function( |
||||
outstanding_rpcs_per_channel, rpc_type, req_size, resp_size, generic) { |
||||
var self = this; |
||||
|
||||
var options = {}; |
||||
|
||||
self.running = true; |
||||
|
||||
if (rpc_type == 'UNARY') { |
||||
options.path = '/serviceProto.BenchmarkService.service/unaryCall'; |
||||
} else { |
||||
self.emit('error', new Error('Unsupported rpc_type: ' + rpc_type)); |
||||
} |
||||
|
||||
if (generic) { |
||||
self.emit('error', new Error('Generic client not supported')); |
||||
} |
||||
|
||||
self.last_wall_time = process.hrtime(); |
||||
|
||||
var argument = { |
||||
response_size: resp_size, |
||||
payload: { |
||||
body: '0'.repeat(req_size) |
||||
} |
||||
}; |
||||
|
||||
function makeCall(client_options) { |
||||
if (self.running) { |
||||
self.pending_calls++; |
||||
var start_time = process.hrtime(); |
||||
var req = self.request(client_options, function(res) { |
||||
var res_data = ''; |
||||
res.on('data', function(data) { |
||||
res_data += data; |
||||
}); |
||||
res.on('end', function() { |
||||
JSON.parse(res_data); |
||||
var time_diff = process.hrtime(start_time); |
||||
self.histogram.add(timeDiffToNanos(time_diff)); |
||||
makeCall(client_options); |
||||
self.pending_calls--; |
||||
if ((!self.running) && self.pending_calls == 0) { |
||||
self.emit('finished'); |
||||
} |
||||
}); |
||||
}); |
||||
req.write(JSON.stringify(argument)); |
||||
req.end(); |
||||
req.on('error', function(error) { |
||||
self.emit('error', new Error('Client error: ' + error.message)); |
||||
self.running = false; |
||||
}); |
||||
} |
||||
} |
||||
|
||||
startAllClients(_.map(self.client_options, _.partial(_.assign, options)), |
||||
outstanding_rpcs_per_channel, makeCall, self); |
||||
}; |
||||
|
||||
BenchmarkClient.prototype.startPoisson = function( |
||||
outstanding_rpcs_per_channel, rpc_type, req_size, resp_size, offered_load, |
||||
generic) { |
||||
var self = this; |
||||
|
||||
var options = {}; |
||||
|
||||
self.running = true; |
||||
|
||||
if (rpc_type == 'UNARY') { |
||||
options.path = '/serviceProto.BenchmarkService.service/unaryCall'; |
||||
} else { |
||||
self.emit('error', new Error('Unsupported rpc_type: ' + rpc_type)); |
||||
} |
||||
|
||||
if (generic) { |
||||
self.emit('error', new Error('Generic client not supported')); |
||||
} |
||||
|
||||
self.last_wall_time = process.hrtime(); |
||||
|
||||
var argument = { |
||||
response_size: resp_size, |
||||
payload: { |
||||
body: '0'.repeat(req_size) |
||||
} |
||||
}; |
||||
|
||||
function makeCall(client_options, poisson) { |
||||
if (self.running) { |
||||
self.pending_calls++; |
||||
var start_time = process.hrtime(); |
||||
var req = self.request(client_options, function(res) { |
||||
var res_data = ''; |
||||
res.on('data', function(data) { |
||||
res_data += data; |
||||
}); |
||||
res.on('end', function() { |
||||
JSON.parse(res_data); |
||||
var time_diff = process.hrtime(start_time); |
||||
self.histogram.add(timeDiffToNanos(time_diff)); |
||||
self.pending_calls--; |
||||
if ((!self.running) && self.pending_calls == 0) { |
||||
self.emit('finished'); |
||||
} |
||||
}); |
||||
}); |
||||
req.write(JSON.stringify(argument)); |
||||
req.end(); |
||||
req.on('error', function(error) { |
||||
self.emit('error', new Error('Client error: ' + error.message)); |
||||
self.running = false; |
||||
}); |
||||
} else { |
||||
poisson.stop(); |
||||
} |
||||
} |
||||
|
||||
var averageIntervalMs = (1 / offered_load) * 1000; |
||||
|
||||
startAllClients(_.map(self.client_options, _.partial(_.assign, options)), |
||||
outstanding_rpcs_per_channel, function(opts){ |
||||
var p = PoissonProcess.create(averageIntervalMs, function() { |
||||
makeCall(opts, p); |
||||
}); |
||||
p.start(); |
||||
}, self); |
||||
}; |
||||
|
||||
/** |
||||
* Return curent statistics for the client. If reset is set, restart |
||||
* statistic collection. |
||||
* @param {boolean} reset Indicates that statistics should be reset |
||||
* @return {object} Client statistics |
||||
*/ |
||||
BenchmarkClient.prototype.mark = function(reset) { |
||||
var wall_time_diff = process.hrtime(this.last_wall_time); |
||||
var histogram = this.histogram; |
||||
if (reset) { |
||||
this.last_wall_time = process.hrtime(); |
||||
this.histogram = new Histogram(histogram.resolution, |
||||
histogram.max_possible); |
||||
} |
||||
|
||||
return { |
||||
latencies: { |
||||
bucket: histogram.getContents(), |
||||
min_seen: histogram.minimum(), |
||||
max_seen: histogram.maximum(), |
||||
sum: histogram.getSum(), |
||||
sum_of_squares: histogram.sumOfSquares(), |
||||
count: histogram.getCount() |
||||
}, |
||||
time_elapsed: wall_time_diff[0] + wall_time_diff[1] / 1e9, |
||||
// Not sure how to measure these values
|
||||
time_user: 0, |
||||
time_system: 0 |
||||
}; |
||||
}; |
||||
|
||||
/** |
||||
* Stop the clients. |
||||
* @param {function} callback Called when the clients have finished shutting |
||||
* down |
||||
*/ |
||||
BenchmarkClient.prototype.stop = function(callback) { |
||||
this.running = false; |
||||
this.on('finished', callback); |
||||
}; |
||||
|
||||
module.exports = BenchmarkClient; |
@ -0,0 +1,109 @@ |
||||
/* |
||||
* |
||||
* 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. |
||||
* |
||||
*/ |
||||
|
||||
/** |
||||
* Benchmark server module |
||||
* @module |
||||
*/ |
||||
|
||||
'use strict'; |
||||
|
||||
var fs = require('fs'); |
||||
var path = require('path'); |
||||
var http = require('http'); |
||||
var https = require('https'); |
||||
var EventEmitter = require('events'); |
||||
var util = require('util'); |
||||
|
||||
var express = require('express'); |
||||
var bodyParser = require('body-parser') |
||||
|
||||
function unaryCall(req, res) { |
||||
var reqObj = req.body; |
||||
var payload = {body: '0'.repeat(reqObj.response_size)}; |
||||
res.json(payload); |
||||
} |
||||
|
||||
function BenchmarkServer(host, port, tls, generic, response_size) { |
||||
var app = express(); |
||||
app.use(bodyParser.json()) |
||||
app.put('/serviceProto.BenchmarkService.service/unaryCall', unaryCall); |
||||
this.input_host = host; |
||||
this.input_port = port; |
||||
if (tls) { |
||||
var credentials = {}; |
||||
var key_path = path.join(__dirname, '../test/data/server1.key'); |
||||
var pem_path = path.join(__dirname, '../test/data/server1.pem'); |
||||
|
||||
var key_data = fs.readFileSync(key_path); |
||||
var pem_data = fs.readFileSync(pem_path); |
||||
credentials['key'] = key_data; |
||||
credentials['cert'] = pem_data; |
||||
this.server = https.createServer(credentials, app); |
||||
} else { |
||||
this.server = http.createServer(app); |
||||
} |
||||
} |
||||
|
||||
util.inherits(BenchmarkServer, EventEmitter); |
||||
|
||||
BenchmarkServer.prototype.start = function() { |
||||
var self = this; |
||||
this.server.listen(this.input_port, this.input_hostname, function() { |
||||
self.last_wall_time = process.hrtime(); |
||||
self.emit('started'); |
||||
}); |
||||
}; |
||||
|
||||
BenchmarkServer.prototype.getPort = function() { |
||||
return this.server.address().port; |
||||
}; |
||||
|
||||
BenchmarkServer.prototype.mark = function(reset) { |
||||
var wall_time_diff = process.hrtime(this.last_wall_time); |
||||
if (reset) { |
||||
this.last_wall_time = process.hrtime(); |
||||
} |
||||
return { |
||||
time_elapsed: wall_time_diff[0] + wall_time_diff[1] / 1e9, |
||||
// Not sure how to measure these values
|
||||
time_user: 0, |
||||
time_system: 0 |
||||
}; |
||||
}; |
||||
|
||||
BenchmarkServer.prototype.stop = function(callback) { |
||||
this.server.close(callback); |
||||
}; |
||||
|
||||
module.exports = BenchmarkServer; |
Loading…
Reference in new issue