bdevperf: do not start tests until RPC is sent

Starting with this patch it is possible to issue
"perform_tests" RPC to bdevperf application.
It will allow to configure the bdevs after startup,
but before starting the tests.

bdevperf in addition to usual cmd line params for tests,
need to be started with '-z' argument.
After that it is possible to start test using
'./bdevperf.py perform_tests'.
Tests can be issued multiple times in the same app run.

At this time the RPC does not take any arguments
for the tests. All are assumed to be set in
the command line.

This is series for adding RPC to bdevperf app.

Change-Id: If71853b1aa742f9cbf3d65c8d694a0437aad4500
Signed-off-by: Tomasz Zawadzki <tomasz.zawadzki@intel.com>
Reviewed-on: https://review.gerrithub.io/c/spdk/spdk/+/458598
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Konrad Sztyber <konrad.sztyber@intel.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
Reviewed-by: Paul Luse <paul.e.luse@intel.com>
Reviewed-by: Darek Stojaczyk <dariusz.stojaczyk@intel.com>
This commit is contained in:
Tomasz Zawadzki 2019-07-03 09:56:14 -04:00 committed by Darek Stojaczyk
parent d1d82757d2
commit 294e8a2fb6
2 changed files with 174 additions and 3 deletions

View File

@ -42,6 +42,7 @@
#include "spdk/util.h" #include "spdk/util.h"
#include "spdk/thread.h" #include "spdk/thread.h"
#include "spdk/string.h" #include "spdk/string.h"
#include "spdk/rpc.h"
struct bdevperf_task { struct bdevperf_task {
struct iovec iov; struct iovec iov;
@ -79,11 +80,14 @@ static unsigned g_master_core;
static int g_time_in_sec; static int g_time_in_sec;
static bool g_mix_specified; static bool g_mix_specified;
static const char *g_target_bdev_name; static const char *g_target_bdev_name;
static bool g_wait_for_tests = false;
static struct spdk_jsonrpc_request *g_request = NULL;
static struct spdk_poller *g_perf_timer = NULL; static struct spdk_poller *g_perf_timer = NULL;
static void bdevperf_submit_single(struct io_target *target, struct bdevperf_task *task); static void bdevperf_submit_single(struct io_target *target, struct bdevperf_task *task);
static void performance_dump(uint64_t io_time_in_usec, uint64_t ema_period); static void performance_dump(uint64_t io_time_in_usec, uint64_t ema_period);
static void rpc_perform_tests_cb(int rc);
struct io_target { struct io_target {
char *name; char *name;
@ -395,6 +399,7 @@ static void
end_run(void *arg1, void *arg2) end_run(void *arg1, void *arg2)
{ {
struct io_target *target = arg1; struct io_target *target = arg1;
int rc = 0;
spdk_put_io_channel(target->ch); spdk_put_io_channel(target->ch);
spdk_bdev_close(target->bdev_desc); spdk_bdev_close(target->bdev_desc);
@ -417,9 +422,13 @@ end_run(void *arg1, void *arg2)
} }
if (g_run_failed) { if (g_run_failed) {
spdk_app_stop(1); rc = 1;
}
if (g_request && !g_shutdown) {
rpc_perform_tests_cb(rc);
} else { } else {
spdk_app_stop(0); spdk_app_stop(rc);
} }
} }
} }
@ -840,6 +849,7 @@ bdevperf_usage(void)
printf("\t\t(only valid with -S)\n"); printf("\t\t(only valid with -S)\n");
printf(" -S <period> show performance result in real time every <period> seconds\n"); printf(" -S <period> show performance result in real time every <period> seconds\n");
printf(" -T <target> target bdev\n"); printf(" -T <target> target bdev\n");
printf(" -z start bdevperf, but wait for RPC to start tests\n");
} }
/* /*
@ -999,6 +1009,13 @@ ret:
static int static int
verify_test_params(struct spdk_app_opts *opts) verify_test_params(struct spdk_app_opts *opts)
{ {
/* When RPC is used for starting tests and
* no rpc_addr was configured for the app,
* use the default address. */
if (g_wait_for_tests && opts->rpc_addr == NULL) {
opts->rpc_addr = SPDK_DEFAULT_RPC_ADDR;
}
if (g_queue_depth <= 0) { if (g_queue_depth <= 0) {
spdk_app_usage(); spdk_app_usage();
bdevperf_usage(); bdevperf_usage();
@ -1180,6 +1197,11 @@ bdevperf_run(void *arg1)
g_master_core = spdk_env_get_current_core(); g_master_core = spdk_env_get_current_core();
if (g_wait_for_tests) {
/* Do not perform any tests until RPC is received */
return;
}
bdevperf_construct_targets(); bdevperf_construct_targets();
if (g_target_count == 0) { if (g_target_count == 0) {
@ -1247,6 +1269,8 @@ bdevperf_parse_arg(int ch, char *arg)
g_workload_type = optarg; g_workload_type = optarg;
} else if (ch == 'T') { } else if (ch == 'T') {
g_target_bdev_name = optarg; g_target_bdev_name = optarg;
} else if (ch == 'z') {
g_wait_for_tests = true;
} else { } else {
tmp = spdk_strtoll(optarg, 10); tmp = spdk_strtoll(optarg, 10);
if (tmp < 0) { if (tmp < 0) {
@ -1287,6 +1311,67 @@ bdevperf_parse_arg(int ch, char *arg)
return 0; return 0;
} }
static void
rpc_perform_tests_cb(int rc)
{
struct spdk_json_write_ctx *w;
struct spdk_jsonrpc_request *request = g_request;
g_request = NULL;
if (rc == 0) {
w = spdk_jsonrpc_begin_result(request);
if (w == NULL) {
return;
}
spdk_json_write_uint32(w, rc);
spdk_jsonrpc_end_result(request, w);
} else {
spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
"bdevperf failed with error %s", spdk_strerror(-rc));
}
bdevperf_free_targets();
}
static void
rpc_perform_tests(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params)
{
int rc;
if (params != NULL) {
spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS,
"perform_tests method requires no parameters");
return;
}
if (g_request != NULL) {
fprintf(stderr, "Another test is already in progress.\n");
spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
spdk_strerror(-EINPROGRESS));
return;
}
g_request = request;
bdevperf_construct_targets();
if (g_target_count == 0) {
g_request = NULL;
fprintf(stderr, "No valid bdevs found.\n");
spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
spdk_strerror(-ENODEV));
return;
}
rc = bdevperf_test();
if (rc) {
g_request = NULL;
bdevperf_free_targets();
spdk_jsonrpc_send_error_response_fmt(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
"Could not perform tests due to error: %s", spdk_strerror(-rc));
return;
}
}
SPDK_RPC_REGISTER("perform_tests", rpc_perform_tests, SPDK_RPC_RUNTIME)
int int
main(int argc, char **argv) main(int argc, char **argv)
{ {
@ -1306,7 +1391,7 @@ main(int argc, char **argv)
g_time_in_sec = 0; g_time_in_sec = 0;
g_mix_specified = false; g_mix_specified = false;
if ((rc = spdk_app_parse_args(argc, argv, &opts, "q:o:t:w:M:P:S:T:", NULL, if ((rc = spdk_app_parse_args(argc, argv, &opts, "zq:o:t:w:M:P:S:T:", NULL,
bdevperf_parse_arg, bdevperf_usage)) != bdevperf_parse_arg, bdevperf_usage)) !=
SPDK_APP_PARSE_ARGS_SUCCESS) { SPDK_APP_PARSE_ARGS_SUCCESS) {
return rc; return rc;

86
test/bdev/bdevperf/bdevperf.py Executable file
View File

@ -0,0 +1,86 @@
#!/usr/bin/env python3
import logging
import argparse
import sys
import shlex
try:
from rpc.client import print_dict, JSONRPCException
import rpc
except ImportError:
print("SPDK RPC library missing. Please add spdk/scripts/ directory to PYTHONPATH:")
print("'export PYTHONPATH=$PYTHONPATH:./spdk/scripts/'")
exit(1)
try:
from shlex import quote
except ImportError:
from pipes import quote
def print_array(a):
print(" ".join((quote(v) for v in a)))
def perform_tests_func(client):
"""Perform bdevperf tests according to command line arguments when application was started.
Args:
none
Returns:
On success, 0 is returned. On error, -1 is returned.
"""
params = {}
return client.call('perform_tests', params)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description='SPDK RPC command line interface. NOTE: spdk/scripts/ is expected in PYTHONPATH')
parser.add_argument('-s', dest='server_addr',
help='RPC domain socket path or IP address', default='/var/tmp/spdk.sock')
parser.add_argument('-p', dest='port',
help='RPC port number (if server_addr is IP address)',
default=5260, type=int)
parser.add_argument('-t', dest='timeout',
help='Timeout as a floating point number expressed in seconds waiting for response. Default: 60.0',
default=60.0, type=float)
parser.add_argument('-v', dest='verbose', action='store_const', const="INFO",
help='Set verbose mode to INFO', default="ERROR")
parser.add_argument('--verbose', dest='verbose', choices=['DEBUG', 'INFO', 'ERROR'],
help="""Set verbose level. """)
subparsers = parser.add_subparsers(help='RPC methods')
def perform_tests(args):
print_dict(perform_tests_func(args.client))
p = subparsers.add_parser('perform_tests', help='Perform bdevperf tests')
p.set_defaults(func=perform_tests)
def call_rpc_func(args):
try:
args.func(args)
except JSONRPCException as ex:
print(ex.message)
exit(1)
def execute_script(parser, client, fd):
for rpc_call in map(str.rstrip, fd):
if not rpc_call.strip():
continue
args = parser.parse_args(shlex.split(rpc_call))
args.client = client
call_rpc_func(args)
args = parser.parse_args()
args.client = rpc.client.JSONRPCClient(args.server_addr, args.port, args.timeout, log_level=getattr(logging, args.verbose.upper()))
if hasattr(args, 'func'):
call_rpc_func(args)
elif sys.stdin.isatty():
# No arguments and no data piped through stdin
parser.print_help()
exit(1)
else:
execute_script(parser, args.client, sys.stdin)