diff --git a/test/bdev/bdevperf/bdevperf.c b/test/bdev/bdevperf/bdevperf.c index 004956a98..b878678bf 100644 --- a/test/bdev/bdevperf/bdevperf.c +++ b/test/bdev/bdevperf/bdevperf.c @@ -42,6 +42,7 @@ #include "spdk/util.h" #include "spdk/thread.h" #include "spdk/string.h" +#include "spdk/rpc.h" struct bdevperf_task { struct iovec iov; @@ -79,11 +80,14 @@ static unsigned g_master_core; static int g_time_in_sec; static bool g_mix_specified; 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 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 rpc_perform_tests_cb(int rc); struct io_target { char *name; @@ -395,6 +399,7 @@ static void end_run(void *arg1, void *arg2) { struct io_target *target = arg1; + int rc = 0; spdk_put_io_channel(target->ch); spdk_bdev_close(target->bdev_desc); @@ -417,9 +422,13 @@ end_run(void *arg1, void *arg2) } if (g_run_failed) { - spdk_app_stop(1); + rc = 1; + } + + if (g_request && !g_shutdown) { + rpc_perform_tests_cb(rc); } 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(" -S show performance result in real time every seconds\n"); printf(" -T target bdev\n"); + printf(" -z start bdevperf, but wait for RPC to start tests\n"); } /* @@ -999,6 +1009,13 @@ ret: static int 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) { spdk_app_usage(); bdevperf_usage(); @@ -1180,6 +1197,11 @@ bdevperf_run(void *arg1) 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(); if (g_target_count == 0) { @@ -1247,6 +1269,8 @@ bdevperf_parse_arg(int ch, char *arg) g_workload_type = optarg; } else if (ch == 'T') { g_target_bdev_name = optarg; + } else if (ch == 'z') { + g_wait_for_tests = true; } else { tmp = spdk_strtoll(optarg, 10); if (tmp < 0) { @@ -1287,6 +1311,67 @@ bdevperf_parse_arg(int ch, char *arg) 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 main(int argc, char **argv) { @@ -1306,7 +1391,7 @@ main(int argc, char **argv) g_time_in_sec = 0; 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)) != SPDK_APP_PARSE_ARGS_SUCCESS) { return rc; diff --git a/test/bdev/bdevperf/bdevperf.py b/test/bdev/bdevperf/bdevperf.py new file mode 100755 index 000000000..178d90c34 --- /dev/null +++ b/test/bdev/bdevperf/bdevperf.py @@ -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)