diff --git a/doc/jsonrpc.md b/doc/jsonrpc.md index 0af04a021..574827af4 100644 --- a/doc/jsonrpc.md +++ b/doc/jsonrpc.md @@ -632,6 +632,88 @@ Example response: } ~~~ +## enable_bdev_histogram {#rpc_enable_bdev_histogram} + +Control whether collecting data for histogram is enabled for specified bdev. + +### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +name | Required | string | Block device name +enable | Required | boolean | Enable or disable histogram on specified device + +### Example + +Example request: +~~~ +{ + "jsonrpc": "2.0", + "id": 1, + "method": "enable_bdev_histogram", + "params": { + "name": "Nvme0n1" + "enable": true + } +} +~~~ + +Example response: +~~~ +{ + "jsonrpc": "2.0", + "id": 1, + "result": true +} +~~~ + +## get_bdev_histogram {#rpc_get_bdev_histogram} + +Get latency histogram for specified bdev. + +### Parameters + +Name | Optional | Type | Description +----------------------- | -------- | ----------- | ----------- +name | Required | string | Block device name + +### Result + +Name | Description +------------------------| ----------- +histogram | Base64 encoded histogram +bucket_shift | Granularity of the histogram buckets +tsc_rate | Ticks per second + +### Example + +Example request: +~~~ +{ + "jsonrpc": "2.0", + "id": 1, + "method": "get_bdev_histogram", + "params": { + "name": "Nvme0n1" + } +} +~~~ + +Example response: +Note that histogram field is trimmed, actual encoded histogram length is ~80kb. + +~~~ +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "histogram": "AAAAAAAAAAAAAA...AAAAAAAAA==", + "tsc_rate": 2300000000, + "bucket_shift": 7 + } +} +~~~ + ## delete_bdev {#rpc_delete_bdev} Unregister a block device. diff --git a/lib/bdev/rpc/bdev_rpc.c b/lib/bdev/rpc/bdev_rpc.c index df631d844..5e645e20f 100644 --- a/lib/bdev/rpc/bdev_rpc.c +++ b/lib/bdev/rpc/bdev_rpc.c @@ -36,6 +36,8 @@ #include "spdk/rpc.h" #include "spdk/string.h" #include "spdk/util.h" +#include "spdk/histogram_data.h" +#include "spdk/base64.h" #include "spdk/bdev_module.h" @@ -602,3 +604,179 @@ exit: } SPDK_RPC_REGISTER("set_bdev_qos_limit", spdk_rpc_set_bdev_qos_limit, SPDK_RPC_RUNTIME) + +/* SPDK_RPC_ENABLE_BDEV_HISTOGRAM */ + +struct rpc_enable_bdev_histogram_request { + char *name; + bool enable; +}; + +static void +free_rpc_enable_bdev_histogram_request(struct rpc_enable_bdev_histogram_request *r) +{ + free(r->name); +} + +static const struct spdk_json_object_decoder rpc_enable_bdev_histogram_request_decoders[] = { + {"name", offsetof(struct rpc_enable_bdev_histogram_request, name), spdk_json_decode_string}, + {"enable", offsetof(struct rpc_enable_bdev_histogram_request, enable), spdk_json_decode_bool}, +}; + +static void +_spdk_bdev_histogram_status_cb(void *cb_arg, int status) +{ + struct spdk_jsonrpc_request *request = cb_arg; + struct spdk_json_write_ctx *w; + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + return; + } + + spdk_json_write_bool(w, status == 0); + spdk_jsonrpc_end_result(request, w); +} + +static void +spdk_rpc_enable_bdev_histogram(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_enable_bdev_histogram_request req = {NULL}; + struct spdk_bdev *bdev; + int rc; + + if (spdk_json_decode_object(params, rpc_enable_bdev_histogram_request_decoders, + SPDK_COUNTOF(rpc_enable_bdev_histogram_request_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + rc = -EINVAL; + goto invalid; + } + + bdev = spdk_bdev_get_by_name(req.name); + if (bdev == NULL) { + rc = -ENODEV; + goto invalid; + } + + spdk_bdev_histogram_enable(bdev, _spdk_bdev_histogram_status_cb, request, req.enable); + + free_rpc_enable_bdev_histogram_request(&req); + + return; + +invalid: + free_rpc_enable_bdev_histogram_request(&req); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, spdk_strerror(-rc)); +} + +SPDK_RPC_REGISTER("enable_bdev_histogram", spdk_rpc_enable_bdev_histogram, SPDK_RPC_RUNTIME) + +/* SPDK_RPC_GET_BDEV_HISTOGRAM */ + +struct rpc_get_bdev_histogram_request { + char *name; +}; + +static const struct spdk_json_object_decoder rpc_get_bdev_histogram_request_decoders[] = { + {"name", offsetof(struct rpc_get_bdev_histogram_request, name), spdk_json_decode_string} +}; + +static void +free_rpc_get_bdev_histogram_request(struct rpc_get_bdev_histogram_request *r) +{ + free(r->name); +} + +static void +_spdk_rpc_bdev_histogram_data_cb(void *cb_arg, int status, struct spdk_histogram_data *histogram) +{ + struct spdk_jsonrpc_request *request = cb_arg; + struct spdk_json_write_ctx *w; + int rc; + char *encoded_histogram; + size_t src_len, dst_len; + + + if (status != 0) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + spdk_strerror(-status)); + goto invalid; + } + + src_len = SPDK_HISTOGRAM_NUM_BUCKETS(histogram) * sizeof(uint64_t); + dst_len = spdk_base64_get_encoded_strlen(src_len) + 1; + + encoded_histogram = malloc(dst_len); + if (encoded_histogram == NULL) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + spdk_strerror(ENOMEM)); + goto invalid; + } + + rc = spdk_base64_encode(encoded_histogram, histogram->bucket, src_len); + if (rc != 0) { + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR, + spdk_strerror(-rc)); + goto free_encoded_histogram; + } + + w = spdk_jsonrpc_begin_result(request); + if (w == NULL) { + goto free_encoded_histogram; + } + + spdk_json_write_object_begin(w); + spdk_json_write_named_string(w, "histogram", encoded_histogram); + spdk_json_write_named_int64(w, "bucket_shift", histogram->bucket_shift); + spdk_json_write_named_int64(w, "tsc_rate", spdk_get_ticks_hz()); + spdk_json_write_object_end(w); + spdk_jsonrpc_end_result(request, w); + +free_encoded_histogram: + free(encoded_histogram); +invalid: + spdk_histogram_data_free(histogram); +} + +static void +spdk_rpc_get_bdev_histogram(struct spdk_jsonrpc_request *request, + const struct spdk_json_val *params) +{ + struct rpc_get_bdev_histogram_request req = {NULL}; + struct spdk_histogram_data *histogram; + struct spdk_bdev *bdev; + int rc; + + if (spdk_json_decode_object(params, rpc_get_bdev_histogram_request_decoders, + SPDK_COUNTOF(rpc_get_bdev_histogram_request_decoders), + &req)) { + SPDK_ERRLOG("spdk_json_decode_object failed\n"); + rc = -EINVAL; + goto invalid; + } + + bdev = spdk_bdev_get_by_name(req.name); + if (bdev == NULL) { + rc = -ENODEV; + goto invalid; + } + + histogram = spdk_histogram_data_alloc(); + if (histogram == NULL) { + rc = -ENOMEM; + goto invalid; + } + + spdk_bdev_histogram_get(bdev, histogram, _spdk_rpc_bdev_histogram_data_cb, request); + + free_rpc_get_bdev_histogram_request(&req); + return; + +invalid: + free_rpc_get_bdev_histogram_request(&req); + spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS, spdk_strerror(-rc)); +} + +SPDK_RPC_REGISTER("get_bdev_histogram", spdk_rpc_get_bdev_histogram, SPDK_RPC_RUNTIME) diff --git a/scripts/rpc.py b/scripts/rpc.py index 38920b496..4ac9515db 100755 --- a/scripts/rpc.py +++ b/scripts/rpc.py @@ -431,6 +431,22 @@ if __name__ == "__main__": 'bdev_name', help='Blockdev name to be deleted. Example: Malloc0.') p.set_defaults(func=delete_bdev) + def enable_bdev_histogram(args): + rpc.bdev.enable_bdev_histogram(args.client, name=args.name, enable=args.enable) + + p = subparsers.add_parser('enable_bdev_histogram', help='Enable or disable histogram for specified bdev') + p.add_argument('-e', '--enable', default=True, dest='enable', action='store_true', help='Enable histograms on specified device') + p.add_argument('-d', '--disable', dest='enable', action='store_false', help='Disable histograms on specified device') + p.add_argument('name', help='bdev name') + p.set_defaults(func=enable_bdev_histogram) + + def get_bdev_histogram(args): + print_dict(rpc.bdev.get_bdev_histogram(args.client, name=args.name)) + + p = subparsers.add_parser('get_bdev_histogram', help='Get histogram for specified bdev') + p.add_argument('name', help='bdev name') + p.set_defaults(func=get_bdev_histogram) + def set_bdev_qd_sampling_period(args): rpc.bdev.set_bdev_qd_sampling_period(args.client, name=args.name, diff --git a/scripts/rpc/bdev.py b/scripts/rpc/bdev.py index 68e66792b..0e915296d 100644 --- a/scripts/rpc/bdev.py +++ b/scripts/rpc/bdev.py @@ -485,6 +485,26 @@ def delete_bdev(client, bdev_name): return client.call('delete_bdev', params) +def enable_bdev_histogram(client, name, enable): + """Control whether histogram is enabled for specified bdev. + + Args: + bdev_name: name of bdev + """ + params = {'name': name, "enable": enable} + return client.call('enable_bdev_histogram', params) + + +def get_bdev_histogram(client, name): + """Get histogram for specified bdev. + + Args: + bdev_name: name of bdev + """ + params = {'name': name} + return client.call('get_bdev_histogram', params) + + def bdev_inject_error(client, name, io_type, error_type, num=1): """Inject an error via an error bdev.