diff options
Diffstat (limited to '')
32 files changed, 5790 insertions, 0 deletions
diff --git a/src/spdk/app/Makefile b/src/spdk/app/Makefile new file mode 100644 index 000000000..8ff318ddf --- /dev/null +++ b/src/spdk/app/Makefile @@ -0,0 +1,55 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +DIRS-y += trace +DIRS-y += trace_record +DIRS-y += nvmf_tgt +DIRS-y += iscsi_top +DIRS-y += iscsi_tgt +DIRS-y += spdk_tgt +DIRS-y += spdk_lspci +DIRS-y += spdk_top +ifeq ($(OS),Linux) +DIRS-$(CONFIG_VHOST) += vhost +DIRS-y += spdk_dd +endif + +.PHONY: all clean $(DIRS-y) + +all: $(DIRS-y) +clean: $(DIRS-y) + +include $(SPDK_ROOT_DIR)/mk/spdk.subdirs.mk diff --git a/src/spdk/app/iscsi_tgt/.gitignore b/src/spdk/app/iscsi_tgt/.gitignore new file mode 100644 index 000000000..14d948c59 --- /dev/null +++ b/src/spdk/app/iscsi_tgt/.gitignore @@ -0,0 +1 @@ +iscsi_tgt diff --git a/src/spdk/app/iscsi_tgt/Makefile b/src/spdk/app/iscsi_tgt/Makefile new file mode 100644 index 000000000..6b695d91a --- /dev/null +++ b/src/spdk/app/iscsi_tgt/Makefile @@ -0,0 +1,59 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = iscsi_tgt + +# Add iSCSI library directory to include path +# TODO: remove this once iSCSI has a public API header +CFLAGS += -I$(SPDK_ROOT_DIR)/lib + +C_SRCS := iscsi_tgt.c + +SPDK_LIB_LIST = $(ALL_MODULES_LIST) +SPDK_LIB_LIST += $(EVENT_BDEV_SUBSYSTEM) event_iscsi event_net event_scsi event +SPDK_LIB_LIST += jsonrpc json rpc bdev_rpc bdev iscsi scsi accel trace conf +SPDK_LIB_LIST += thread util log log_rpc app_rpc net sock notify + +ifeq ($(SPDK_ROOT_DIR)/lib/env_dpdk,$(CONFIG_ENV)) +SPDK_LIB_LIST += env_dpdk_rpc +endif + +ifeq ($(OS),Linux) +SPDK_LIB_LIST += event_nbd nbd +endif + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/app/iscsi_tgt/iscsi_tgt.c b/src/spdk/app/iscsi_tgt/iscsi_tgt.c new file mode 100644 index 000000000..b780a3771 --- /dev/null +++ b/src/spdk/app/iscsi_tgt/iscsi_tgt.c @@ -0,0 +1,120 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * 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 Intel Corporation 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 "spdk/stdinc.h" + +#include "spdk/env.h" +#include "spdk/event.h" +#include "iscsi/iscsi.h" +#include "spdk/log.h" +#include "spdk/net.h" + +static int g_daemon_mode = 0; + +static void +spdk_sigusr1(int signo __attribute__((__unused__))) +{ + char *config_str = NULL; + if (spdk_app_get_running_config(&config_str, "iscsi.conf") < 0) { + fprintf(stderr, "Error getting config\n"); + } else { + fprintf(stdout, "============================\n"); + fprintf(stdout, " iSCSI target running config\n"); + fprintf(stdout, "=============================\n"); + fprintf(stdout, "%s", config_str); + } + free(config_str); +} + +static void +iscsi_usage(void) +{ + printf(" -b run iscsi target background, the default is foreground\n"); +} + +static void +spdk_startup(void *arg1) +{ + if (getenv("MEMZONE_DUMP") != NULL) { + spdk_memzone_dump(stdout); + fflush(stdout); + } +} + +static int +iscsi_parse_arg(int ch, char *arg) +{ + switch (ch) { + case 'b': + g_daemon_mode = 1; + break; + default: + return -EINVAL; + } + return 0; +} + +int +main(int argc, char **argv) +{ + int rc; + struct spdk_app_opts opts = {}; + + spdk_app_opts_init(&opts); + opts.name = "iscsi"; + if ((rc = spdk_app_parse_args(argc, argv, &opts, "b", NULL, + iscsi_parse_arg, iscsi_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + exit(rc); + } + + if (g_daemon_mode) { + if (daemon(1, 0) < 0) { + SPDK_ERRLOG("Start iscsi target daemon failed.\n"); + exit(EXIT_FAILURE); + } + } + + opts.shutdown_cb = NULL; + opts.usr1_handler = spdk_sigusr1; + + /* Blocks until the application is exiting */ + rc = spdk_app_start(&opts, spdk_startup, NULL); + if (rc) { + SPDK_ERRLOG("Start iscsi target daemon: spdk_app_start() retn non-zero\n"); + } + + spdk_app_fini(); + + return rc; +} diff --git a/src/spdk/app/iscsi_top/.gitignore b/src/spdk/app/iscsi_top/.gitignore new file mode 100644 index 000000000..7d90ff343 --- /dev/null +++ b/src/spdk/app/iscsi_top/.gitignore @@ -0,0 +1 @@ +iscsi_top diff --git a/src/spdk/app/iscsi_top/Makefile b/src/spdk/app/iscsi_top/Makefile new file mode 100644 index 000000000..0d66fcfb2 --- /dev/null +++ b/src/spdk/app/iscsi_top/Makefile @@ -0,0 +1,45 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = iscsi_top + +CXXFLAGS += $(ENV_CXXFLAGS) +CXXFLAGS += -I$(SPDK_ROOT_DIR)/lib + +CXX_SRCS := iscsi_top.cpp + +include $(SPDK_ROOT_DIR)/mk/spdk.app_cxx.mk diff --git a/src/spdk/app/iscsi_top/iscsi_top.cpp b/src/spdk/app/iscsi_top/iscsi_top.cpp new file mode 100644 index 000000000..25f444f50 --- /dev/null +++ b/src/spdk/app/iscsi_top/iscsi_top.cpp @@ -0,0 +1,252 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * 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 Intel Corporation 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 "spdk/stdinc.h" + +#include <algorithm> +#include <map> +#include <vector> + +extern "C" { +#include "spdk/trace.h" +#include "iscsi/conn.h" +} + +static char *exe_name; +static int g_shm_id = 0; + +static void usage(void) +{ + fprintf(stderr, "usage:\n"); + fprintf(stderr, " %s <option>\n", exe_name); + fprintf(stderr, " option = '-i' to specify the shared memory ID," + " (required)\n"); +} + +/* Group by poll group */ +static bool +conns_compare(struct spdk_iscsi_conn *first, struct spdk_iscsi_conn *second) +{ + if ((uintptr_t)first->pg < (uintptr_t)second->pg) { + return true; + } + + if ((uintptr_t)first->pg > (uintptr_t)second->pg) { + return false; + } + + if (first->id < second->id) { + return true; + } + + return false; +} + +static void +print_connections(void) +{ + std::vector<struct spdk_iscsi_conn *> v; + std::vector<struct spdk_iscsi_conn *>::iterator iter; + size_t conns_size; + struct spdk_iscsi_conn *conns, *conn; + void *conns_ptr; + int fd, i; + char shm_name[64]; + + snprintf(shm_name, sizeof(shm_name), "/spdk_iscsi_conns.%d", g_shm_id); + fd = shm_open(shm_name, O_RDONLY, 0600); + if (fd < 0) { + fprintf(stderr, "Cannot open shared memory: %s\n", shm_name); + usage(); + exit(1); + } + + conns_size = sizeof(*conns) * MAX_ISCSI_CONNECTIONS; + + conns_ptr = mmap(NULL, conns_size, PROT_READ, MAP_SHARED, fd, 0); + if (conns_ptr == MAP_FAILED) { + fprintf(stderr, "Cannot mmap shared memory (%d)\n", errno); + exit(1); + } + + conns = (struct spdk_iscsi_conn *)conns_ptr; + + for (i = 0; i < MAX_ISCSI_CONNECTIONS; i++) { + if (!conns[i].is_valid) { + continue; + } + v.push_back(&conns[i]); + } + + stable_sort(v.begin(), v.end(), conns_compare); + for (iter = v.begin(); iter != v.end(); iter++) { + conn = *iter; + printf("pg %p conn %3d T:%-8s I:%s (%s)\n", + conn->pg, conn->id, + conn->target_short_name, conn->initiator_name, + conn->initiator_addr); + } + + printf("\n"); + munmap(conns, conns_size); + close(fd); +} + +int main(int argc, char **argv) +{ + void *history_ptr; + struct spdk_trace_histories *histories; + struct spdk_trace_history *history; + + uint64_t tasks_done, last_tasks_done[SPDK_TRACE_MAX_LCORE]; + int delay, old_delay, history_fd, i, quit, rc; + int tasks_done_delta, tasks_done_per_sec; + int total_tasks_done_per_sec; + struct timeval timeout; + fd_set fds; + char ch; + struct termios oldt, newt; + char spdk_trace_shm_name[64]; + int op; + + exe_name = argv[0]; + while ((op = getopt(argc, argv, "i:")) != -1) { + switch (op) { + case 'i': + g_shm_id = atoi(optarg); + break; + default: + usage(); + exit(1); + } + } + + snprintf(spdk_trace_shm_name, sizeof(spdk_trace_shm_name), "/iscsi_trace.%d", g_shm_id); + history_fd = shm_open(spdk_trace_shm_name, O_RDONLY, 0600); + if (history_fd < 0) { + fprintf(stderr, "Unable to open history shm %s\n", spdk_trace_shm_name); + usage(); + exit(1); + } + + history_ptr = mmap(NULL, sizeof(*histories), PROT_READ, MAP_SHARED, history_fd, 0); + if (history_ptr == MAP_FAILED) { + fprintf(stderr, "Unable to mmap history shm (%d).\n", errno); + exit(1); + } + + histories = (struct spdk_trace_histories *)history_ptr; + + memset(last_tasks_done, 0, sizeof(last_tasks_done)); + + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + history = spdk_get_per_lcore_history(histories, i); + last_tasks_done[i] = history->tpoint_count[TRACE_ISCSI_TASK_DONE]; + } + + delay = 1; + quit = 0; + + tcgetattr(0, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON); + tcsetattr(0, TCSANOW, &newt); + + while (1) { + + FD_ZERO(&fds); + FD_SET(0, &fds); + timeout.tv_sec = delay; + timeout.tv_usec = 0; + rc = select(2, &fds, NULL, NULL, &timeout); + + if (rc > 0) { + if (read(0, &ch, 1) != 1) { + fprintf(stderr, "Read error on stdin\n"); + goto cleanup; + } + + printf("\b"); + switch (ch) { + case 'd': + printf("Enter num seconds to delay (1-10): "); + old_delay = delay; + rc = scanf("%d", &delay); + if (rc != 1) { + fprintf(stderr, "Illegal delay value\n"); + delay = old_delay; + } else if (delay < 1 || delay > 10) { + delay = 1; + } + break; + case 'q': + quit = 1; + break; + default: + fprintf(stderr, "'%c' not recognized\n", ch); + break; + } + + if (quit == 1) { + break; + } + } + + printf("\e[1;1H\e[2J"); + print_connections(); + printf("lcore tasks\n"); + printf("=============\n"); + total_tasks_done_per_sec = 0; + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + history = spdk_get_per_lcore_history(histories, i); + tasks_done = history->tpoint_count[TRACE_ISCSI_TASK_DONE]; + tasks_done_delta = tasks_done - last_tasks_done[i]; + if (tasks_done_delta == 0) { + continue; + } + last_tasks_done[i] = tasks_done; + tasks_done_per_sec = tasks_done_delta / delay; + printf("%5d %7d\n", history->lcore, tasks_done_per_sec); + total_tasks_done_per_sec += tasks_done_per_sec; + } + printf("Total %7d\n", total_tasks_done_per_sec); + } + +cleanup: + tcsetattr(0, TCSANOW, &oldt); + + munmap(history_ptr, sizeof(*histories)); + close(history_fd); + + return (0); +} diff --git a/src/spdk/app/nvmf_tgt/.gitignore b/src/spdk/app/nvmf_tgt/.gitignore new file mode 100644 index 000000000..e96d82bef --- /dev/null +++ b/src/spdk/app/nvmf_tgt/.gitignore @@ -0,0 +1 @@ +nvmf_tgt diff --git a/src/spdk/app/nvmf_tgt/Makefile b/src/spdk/app/nvmf_tgt/Makefile new file mode 100644 index 000000000..0a7796d41 --- /dev/null +++ b/src/spdk/app/nvmf_tgt/Makefile @@ -0,0 +1,62 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = nvmf_tgt + +C_SRCS := nvmf_main.c + +SPDK_LIB_LIST = $(ALL_MODULES_LIST) +SPDK_LIB_LIST += $(EVENT_BDEV_SUBSYSTEM) event_nvmf event_net +SPDK_LIB_LIST += nvmf event log trace conf thread util bdev accel rpc jsonrpc json net sock +SPDK_LIB_LIST += app_rpc log_rpc bdev_rpc notify + +ifeq ($(SPDK_ROOT_DIR)/lib/env_dpdk,$(CONFIG_ENV)) +SPDK_LIB_LIST += env_dpdk_rpc +endif + +ifeq ($(OS),Linux) +SPDK_LIB_LIST += event_nbd nbd +endif + +ifeq ($(CONFIG_FC),y) +ifneq ($(strip $(CONFIG_FC_PATH)),) +SYS_LIBS += -L$(CONFIG_FC_PATH) +endif +SYS_LIBS += -lufc +endif + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/app/nvmf_tgt/nvmf_main.c b/src/spdk/app/nvmf_tgt/nvmf_main.c new file mode 100644 index 000000000..87e402149 --- /dev/null +++ b/src/spdk/app/nvmf_tgt/nvmf_main.c @@ -0,0 +1,78 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * 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 Intel Corporation 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 "spdk/stdinc.h" + +#include "spdk/env.h" +#include "spdk/event.h" + +static void +nvmf_usage(void) +{ +} + +static int +nvmf_parse_arg(int ch, char *arg) +{ + return 0; +} + +static void +nvmf_tgt_started(void *arg1) +{ + if (getenv("MEMZONE_DUMP") != NULL) { + spdk_memzone_dump(stdout); + fflush(stdout); + } +} + +int +main(int argc, char **argv) +{ + int rc; + struct spdk_app_opts opts = {}; + + /* default value in opts */ + spdk_app_opts_init(&opts); + opts.name = "nvmf"; + if ((rc = spdk_app_parse_args(argc, argv, &opts, "", NULL, + nvmf_parse_arg, nvmf_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + exit(rc); + } + + /* Blocks until the application is exiting */ + rc = spdk_app_start(&opts, nvmf_tgt_started, NULL); + spdk_app_fini(); + return rc; +} diff --git a/src/spdk/app/spdk_dd/.gitignore b/src/spdk/app/spdk_dd/.gitignore new file mode 100644 index 000000000..8810437a9 --- /dev/null +++ b/src/spdk/app/spdk_dd/.gitignore @@ -0,0 +1 @@ +spdk_dd diff --git a/src/spdk/app/spdk_dd/Makefile b/src/spdk/app/spdk_dd/Makefile new file mode 100644 index 000000000..3bd99f639 --- /dev/null +++ b/src/spdk/app/spdk_dd/Makefile @@ -0,0 +1,47 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = spdk_dd + +C_SRCS := spdk_dd.c + +SPDK_LIB_LIST = $(ALL_MODULES_LIST) +SPDK_LIB_LIST += event_sock event_bdev event_accel event_vmd +SPDK_LIB_LIST += bdev accel event thread util conf trace \ + log jsonrpc json rpc sock notify + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/app/spdk_dd/spdk_dd.c b/src/spdk/app/spdk_dd/spdk_dd.c new file mode 100644 index 000000000..c9f2dd117 --- /dev/null +++ b/src/spdk/app/spdk_dd/spdk_dd.c @@ -0,0 +1,1174 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * 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 Intel Corporation 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 "spdk/stdinc.h" + +#include "spdk/bdev.h" +#include "spdk/event.h" +#include "spdk/fd.h" +#include "spdk/string.h" +#include "spdk/vmd.h" + +#include <libaio.h> + +#ifdef SPDK_CONFIG_URING +#include <liburing.h> +#endif + +#define DD_NSEC_SINCE_X(time_now, time_x) ((1000000000 * time_now.tv_sec + time_now.tv_nsec) \ + - (1000000000 * time_x.tv_sec + time_x.tv_nsec)) + +struct spdk_dd_opts { + char *input_file; + char *output_file; + char *input_file_flags; + char *output_file_flags; + char *input_bdev; + char *output_bdev; + uint64_t input_offset; + uint64_t output_offset; + int64_t io_unit_size; + int64_t io_unit_count; + uint32_t queue_depth; + bool aio; +}; + +static struct spdk_dd_opts g_opts = { + .io_unit_size = 4096, + .queue_depth = 2, +}; + +enum dd_submit_type { + DD_POPULATE, + DD_READ, + DD_WRITE, +}; + +struct dd_io { + uint64_t offset; + uint64_t length; + struct iocb iocb; + enum dd_submit_type type; +#ifdef SPDK_CONFIG_URING + struct iovec iov; +#endif + void *buf; +}; + +enum dd_target_type { + DD_TARGET_TYPE_FILE, + DD_TARGET_TYPE_BDEV, +}; + +struct dd_target { + enum dd_target_type type; + + union { + struct { + struct spdk_bdev *bdev; + struct spdk_bdev_desc *desc; + struct spdk_io_channel *ch; + } bdev; + +#ifdef SPDK_CONFIG_URING + struct { + int fd; + struct io_uring ring; + struct spdk_poller *poller; + } uring; +#endif + struct { + int fd; + io_context_t io_ctx; + struct spdk_poller *poller; + } aio; + } u; + + /* Block size of underlying device. */ + uint32_t block_size; + + /* Position of next I/O in bytes */ + uint64_t pos; + + /* Total size of target in bytes */ + uint64_t total_size; + + bool open; +}; + +struct dd_job { + struct dd_target input; + struct dd_target output; + + struct dd_io *ios; + + uint32_t outstanding; + uint64_t copy_size; +}; + +struct dd_flags { + char *name; + int flag; +}; + +static struct dd_flags g_flags[] = { + {"append", O_APPEND}, + {"direct", O_DIRECT}, + {"directory", O_DIRECTORY}, + {"dsync", O_DSYNC}, + {"noatime", O_NOATIME}, + {"noctty", O_NOCTTY}, + {"nofollow", O_NOFOLLOW}, + {"nonblock", O_NONBLOCK}, + {"sync", O_SYNC}, + {NULL, 0} +}; + +static struct dd_job g_job = {}; +static int g_error = 0; +static struct timespec g_start_time; +static bool g_interrupt; + +static void dd_target_populate_buffer(struct dd_io *io); + +static void +dd_exit(int rc) +{ + if (g_job.input.type == DD_TARGET_TYPE_FILE) { +#ifdef SPDK_CONFIG_URING + if (g_opts.aio == false) { + spdk_poller_unregister(&g_job.input.u.uring.poller); + close(g_job.input.u.uring.fd); + } else +#endif + { + spdk_poller_unregister(&g_job.input.u.aio.poller); + io_destroy(g_job.input.u.aio.io_ctx); + close(g_job.input.u.aio.fd); + } + } else if (g_job.input.type == DD_TARGET_TYPE_BDEV && g_job.input.open) { + spdk_put_io_channel(g_job.input.u.bdev.ch); + spdk_bdev_close(g_job.input.u.bdev.desc); + } + + if (g_job.output.type == DD_TARGET_TYPE_FILE) { +#ifdef SPDK_CONFIG_URING + if (g_opts.aio == false) { + spdk_poller_unregister(&g_job.output.u.uring.poller); + close(g_job.output.u.uring.fd); + } else +#endif + { + spdk_poller_unregister(&g_job.output.u.aio.poller); + io_destroy(g_job.output.u.aio.io_ctx); + close(g_job.output.u.aio.fd); + } + } else if (g_job.output.type == DD_TARGET_TYPE_BDEV && g_job.output.open) { + spdk_put_io_channel(g_job.output.u.bdev.ch); + spdk_bdev_close(g_job.output.u.bdev.desc); + } + + spdk_app_stop(rc); +} + +static void +dd_show_progress(uint64_t offset, uint64_t length, bool finish) +{ + char *unit_str[5] = {"", "k", "M", "G", "T"}; + char *speed_type_str[2] = {"", "average "}; + char *size_unit_str = ""; + char *speed_unit_str = ""; + char *speed_type = ""; + uint64_t size = g_job.copy_size; + uint64_t size_unit = 1; + uint64_t speed_unit = 1; + uint64_t speed, tmp_speed; + static struct timespec g_time_last = {.tv_nsec = 0}; + static uint64_t g_data_last = 0; + struct timespec time_now; + int i = 0; + + clock_gettime(CLOCK_REALTIME, &time_now); + + if (((time_now.tv_sec == g_time_last.tv_sec && offset + length != g_job.copy_size) || + (offset < g_data_last)) && !finish) { + /* refresh every one second */ + return; + } + + /* Find the rigth unit for size displaying (B vs kB vs MB vs GB vs TB) */ + while (size > 1024 * 10) { + size >>= 10; + size_unit <<= 10; + size_unit_str = unit_str[++i]; + if (i == 4) { + break; + } + } + + if (!finish) { + speed_type = speed_type_str[0]; + tmp_speed = speed = (offset - g_data_last) * 1000000000 / DD_NSEC_SINCE_X(time_now, g_time_last); + } else { + speed_type = speed_type_str[1]; + tmp_speed = speed = offset * 1000000000 / DD_NSEC_SINCE_X(time_now, g_start_time); + } + + i = 0; + + /* Find the rigth unit for speed displaying (Bps vs kBps vs MBps vs GBps vs TBps) */ + while (tmp_speed > 1024) { + tmp_speed >>= 10; + speed_unit <<= 10; + speed_unit_str = unit_str[++i]; + if (i == 4) { + break; + } + } + + printf("\33[2K\rCopying: %" PRIu64 "/%" PRIu64 " [%sB] (%s%" PRIu64 " %sBps)", + (offset + length) / size_unit, g_job.copy_size / size_unit, size_unit_str, speed_type, + speed / speed_unit, speed_unit_str); + fflush(stdout); + + g_data_last = offset; + g_time_last = time_now; +} + +#ifdef SPDK_CONFIG_URING +static void +dd_uring_submit(struct dd_io *io, struct dd_target *target, uint64_t length, uint64_t offset) +{ + struct io_uring_sqe *sqe; + + io->iov.iov_base = io->buf; + io->iov.iov_len = length; + sqe = io_uring_get_sqe(&target->u.uring.ring); + if (io->type == DD_READ || io->type == DD_POPULATE) { + io_uring_prep_readv(sqe, target->u.uring.fd, &io->iov, 1, offset); + } else { + io_uring_prep_writev(sqe, target->u.uring.fd, &io->iov, 1, offset); + } + io_uring_sqe_set_data(sqe, io); + io_uring_submit(&target->u.uring.ring); +} +#endif + +static void +_dd_write_bdev_done(struct spdk_bdev_io *bdev_io, + bool success, + void *cb_arg) +{ + struct dd_io *io = cb_arg; + + assert(g_job.outstanding > 0); + g_job.outstanding--; + spdk_bdev_free_io(bdev_io); + dd_target_populate_buffer(io); +} + +static void +dd_target_write(struct dd_io *io) +{ + struct dd_target *target = &g_job.output; + uint64_t length = SPDK_CEIL_DIV(io->length, target->block_size) * target->block_size; + uint64_t read_region_start = g_opts.input_offset * g_opts.io_unit_size; + uint64_t read_offset = io->offset - read_region_start; + uint64_t write_region_start = g_opts.output_offset * g_opts.io_unit_size; + uint64_t write_offset = write_region_start + read_offset; + int rc = 0; + + if (g_error != 0 || g_interrupt == true) { + if (g_job.outstanding == 0) { + if (g_error == 0) { + dd_show_progress(io->offset, io->length, true); + printf("\n\n"); + } + dd_exit(g_error); + } + return; + } + + dd_show_progress(read_offset, io->length, false); + + g_job.outstanding++; + io->type = DD_WRITE; + + if (target->type == DD_TARGET_TYPE_FILE) { +#ifdef SPDK_CONFIG_URING + if (g_opts.aio == false) { + dd_uring_submit(io, target, length, write_offset); + } else +#endif + { + struct iocb *iocb = &io->iocb; + + io_prep_pwrite(iocb, target->u.aio.fd, io->buf, length, write_offset); + iocb->data = io; + if (io_submit(target->u.aio.io_ctx, 1, &iocb) < 0) { + rc = -errno; + } + } + } else if (target->type == DD_TARGET_TYPE_BDEV) { + rc = spdk_bdev_write(target->u.bdev.desc, target->u.bdev.ch, io->buf, write_offset, length, + _dd_write_bdev_done, io); + } + + if (rc != 0) { + SPDK_ERRLOG("%s\n", strerror(-rc)); + assert(g_job.outstanding > 0); + g_job.outstanding--; + g_error = rc; + if (g_job.outstanding == 0) { + dd_exit(rc); + } + return; + } +} + +static void +_dd_read_bdev_done(struct spdk_bdev_io *bdev_io, + bool success, + void *cb_arg) +{ + struct dd_io *io = cb_arg; + + spdk_bdev_free_io(bdev_io); + + assert(g_job.outstanding > 0); + g_job.outstanding--; + dd_target_write(io); +} + +static void +dd_target_read(struct dd_io *io) +{ + struct dd_target *target = &g_job.input; + int rc = 0; + + if (g_error != 0 || g_interrupt == true) { + if (g_job.outstanding == 0) { + dd_exit(g_error); + } + return; + } + + g_job.outstanding++; + io->type = DD_READ; + + if (target->type == DD_TARGET_TYPE_FILE) { +#ifdef SPDK_CONFIG_URING + if (g_opts.aio == false) { + dd_uring_submit(io, target, io->length, io->offset); + } else +#endif + { + struct iocb *iocb = &io->iocb; + + io_prep_pread(iocb, target->u.aio.fd, io->buf, io->length, io->offset); + iocb->data = io; + if (io_submit(target->u.aio.io_ctx, 1, &iocb) < 0) { + rc = -errno; + } + } + } else if (target->type == DD_TARGET_TYPE_BDEV) { + rc = spdk_bdev_read(target->u.bdev.desc, target->u.bdev.ch, io->buf, io->offset, io->length, + _dd_read_bdev_done, io); + } + + if (rc != 0) { + SPDK_ERRLOG("%s\n", strerror(-rc)); + assert(g_job.outstanding > 0); + g_job.outstanding--; + g_error = rc; + if (g_job.outstanding == 0) { + dd_exit(rc); + } + return; + } +} + +static void +_dd_target_populate_buffer_done(struct spdk_bdev_io *bdev_io, + bool success, + void *cb_arg) +{ + struct dd_io *io = cb_arg; + + assert(g_job.outstanding > 0); + g_job.outstanding--; + spdk_bdev_free_io(bdev_io); + dd_target_read(io); +} + +static void +dd_target_populate_buffer(struct dd_io *io) +{ + struct dd_target *target = &g_job.output; + uint64_t read_region_start = g_opts.input_offset * g_opts.io_unit_size; + uint64_t read_offset = g_job.input.pos - read_region_start; + uint64_t write_region_start = g_opts.output_offset * g_opts.io_unit_size; + uint64_t write_offset = write_region_start + read_offset; + uint64_t length; + int rc = 0; + + io->offset = g_job.input.pos; + io->length = spdk_min((uint64_t)g_opts.io_unit_size, g_job.copy_size - read_offset); + + if (io->length == 0 || g_error != 0 || g_interrupt == true) { + if (g_job.outstanding == 0) { + if (g_error == 0) { + dd_show_progress(read_offset, io->length, true); + printf("\n\n"); + } + dd_exit(g_error); + } + return; + } + + g_job.input.pos += io->length; + + if ((io->length % target->block_size) == 0) { + dd_target_read(io); + return; + } + + /* Read whole blocks from output to combine buffers later */ + g_job.outstanding++; + io->type = DD_POPULATE; + + length = SPDK_CEIL_DIV(io->length, target->block_size) * target->block_size; + + if (target->type == DD_TARGET_TYPE_FILE) { +#ifdef SPDK_CONFIG_URING + if (g_opts.aio == false) { + dd_uring_submit(io, target, length, write_offset); + } else +#endif + { + struct iocb *iocb = &io->iocb; + + io_prep_pread(iocb, target->u.aio.fd, io->buf, length, write_offset); + iocb->data = io; + if (io_submit(target->u.aio.io_ctx, 1, &iocb) < 0) { + rc = -errno; + } + } + } else if (target->type == DD_TARGET_TYPE_BDEV) { + rc = spdk_bdev_read(target->u.bdev.desc, target->u.bdev.ch, io->buf, write_offset, length, + _dd_target_populate_buffer_done, io); + } + + if (rc != 0) { + SPDK_ERRLOG("%s\n", strerror(-rc)); + assert(g_job.outstanding > 0); + g_job.outstanding--; + g_error = rc; + if (g_job.outstanding == 0) { + dd_exit(rc); + } + return; + } +} + +static void +dd_complete_poll(struct dd_io *io) +{ + assert(g_job.outstanding > 0); + g_job.outstanding--; + + switch (io->type) { + case DD_POPULATE: + dd_target_read(io); + break; + case DD_READ: + dd_target_write(io); + break; + case DD_WRITE: + dd_target_populate_buffer(io); + break; + default: + assert(false); + break; + } +} + +#ifdef SPDK_CONFIG_URING +static int +dd_uring_poll(void *ctx) +{ + struct dd_target *target = ctx; + struct io_uring_cqe *cqe; + struct dd_io *io; + int rc = 0; + int i; + + for (i = 0; i < (int)g_opts.queue_depth; i++) { + rc = io_uring_peek_cqe(&target->u.uring.ring, &cqe); + if (rc == 0) { + if (cqe->res == -EAGAIN) { + continue; + } else if (cqe->res < 0) { + SPDK_ERRLOG("%s\n", strerror(-cqe->res)); + g_error = cqe->res; + } + + io = io_uring_cqe_get_data(cqe); + io_uring_cqe_seen(&target->u.uring.ring, cqe); + + dd_complete_poll(io); + } else if (rc != - EAGAIN) { + SPDK_ERRLOG("%s\n", strerror(-rc)); + g_error = rc; + } + } + + return rc; +} +#endif + +static int +dd_aio_poll(io_context_t io_ctx) +{ + struct io_event events[32]; + int rc = 0; + int i; + struct timespec timeout; + struct dd_io *io; + + timeout.tv_sec = 0; + timeout.tv_nsec = 0; + + rc = io_getevents(io_ctx, 0, 32, events, &timeout); + + if (rc < 0) { + SPDK_ERRLOG("%s\n", strerror(-rc)); + dd_exit(rc); + } + + for (i = 0; i < rc; i++) { + io = events[i].data; + if (events[i].res != io->length) { + g_error = rc = -ENOSPC; + } + + dd_complete_poll(io); + } + + return rc; +} + +static int +dd_input_poll(void *ctx) +{ + int rc = 0; + + assert(g_job.input.type == DD_TARGET_TYPE_FILE); + + rc = dd_aio_poll(g_job.input.u.aio.io_ctx); + if (rc == -ENOSPC) { + SPDK_ERRLOG("No more file content to read\n"); + } + + return rc; +} + +static int +dd_output_poll(void *ctx) +{ + int rc = 0; + + assert(g_job.output.type == DD_TARGET_TYPE_FILE); + + rc = dd_aio_poll(g_job.output.u.aio.io_ctx); + if (rc == -ENOSPC) { + SPDK_ERRLOG("No space left on device\n"); + } + + return rc; +} + +static int +dd_open_file(struct dd_target *target, const char *fname, int flags, uint64_t skip_blocks, + bool input) +{ + int *fd; + +#ifdef SPDK_CONFIG_URING + if (g_opts.aio == false) { + fd = &target->u.uring.fd; + } else +#endif + { + fd = &target->u.aio.fd; + } + + flags |= O_RDWR; + + if (input == false && ((flags & O_DIRECTORY) == 0)) { + flags |= O_CREAT; + } + + if (input == false && ((flags & O_APPEND) == 0)) { + flags |= O_TRUNC; + } + +#ifdef SPDK_CONFIG_URING + /* io_uring does not work correctly with O_NONBLOCK flag */ + if (flags & O_NONBLOCK && g_opts.aio == false) { + flags &= ~O_NONBLOCK; + SPDK_WARNLOG("Skipping 'nonblock' flag due to existing issue with uring implementation and this flag\n"); + } +#endif + + target->type = DD_TARGET_TYPE_FILE; + *fd = open(fname, flags, 0600); + if (*fd < 0) { + SPDK_ERRLOG("Could not open file %s: %s\n", fname, strerror(errno)); + return *fd; + } + + target->block_size = spdk_max(spdk_fd_get_blocklen(*fd), 1); + target->total_size = spdk_fd_get_size(*fd); + + if (input == true) { + g_opts.queue_depth = spdk_min(g_opts.queue_depth, + (target->total_size / g_opts.io_unit_size) - skip_blocks + 1); + } + + if (g_opts.io_unit_count != 0) { + g_opts.queue_depth = spdk_min(g_opts.queue_depth, g_opts.io_unit_count); + } + +#ifdef SPDK_CONFIG_URING + if (g_opts.aio == false) { + io_uring_queue_init(g_opts.queue_depth, &target->u.uring.ring, 0); + target->open = true; + return 0; + } else +#endif + { + return io_setup(g_opts.queue_depth, &target->u.aio.io_ctx); + } +} + +static int +dd_open_bdev(struct dd_target *target, const char *bdev_name, uint64_t skip_blocks) +{ + int rc; + + target->type = DD_TARGET_TYPE_BDEV; + target->u.bdev.bdev = spdk_bdev_get_by_name(bdev_name); + if (target->u.bdev.bdev == NULL) { + SPDK_ERRLOG("Could not find bdev %s\n", bdev_name); + return -EINVAL; + } + + target->block_size = spdk_bdev_get_block_size(target->u.bdev.bdev); + target->total_size = spdk_bdev_get_num_blocks(target->u.bdev.bdev) * target->block_size; + + rc = spdk_bdev_open(target->u.bdev.bdev, true, NULL, NULL, &target->u.bdev.desc); + if (rc < 0) { + SPDK_ERRLOG("Could not open bdev %s: %s\n", bdev_name, strerror(-rc)); + return rc; + } + + target->open = true; + + target->u.bdev.ch = spdk_bdev_get_io_channel(target->u.bdev.desc); + if (target->u.bdev.ch == NULL) { + spdk_bdev_close(target->u.bdev.desc); + SPDK_ERRLOG("Could not get I/O channel: %s\n", strerror(ENOMEM)); + return -ENOMEM; + } + + g_opts.queue_depth = spdk_min(g_opts.queue_depth, + (target->total_size / g_opts.io_unit_size) - skip_blocks + 1); + + if (g_opts.io_unit_count != 0) { + g_opts.queue_depth = spdk_min(g_opts.queue_depth, g_opts.io_unit_count); + } + + return 0; +} + +static void dd_finish(void) +{ + /* Interrupt operation */ + g_interrupt = true; +} + +static int +parse_flags(char *file_flags) +{ + char *input_flag; + int flags = 0; + int i; + bool found = false; + + /* Translate input flags to file open flags */ + while ((input_flag = strsep(&file_flags, ","))) { + for (i = 0; g_flags[i].name != NULL; i++) { + if (!strcmp(input_flag, g_flags[i].name)) { + flags |= g_flags[i].flag; + found = true; + break; + } + } + + if (found == false) { + SPDK_ERRLOG("Unknown file flag: %s\n", input_flag); + return -EINVAL; + } + + found = false; + } + + return flags; +} + +static void +dd_run(void *arg1) +{ + uint64_t write_size; + uint32_t i; + int rc, flags = 0; + + if (g_opts.input_file) { + if (g_opts.input_file_flags) { + flags = parse_flags(g_opts.input_file_flags); + } + + if (dd_open_file(&g_job.input, g_opts.input_file, flags, g_opts.input_offset, true) < 0) { + SPDK_ERRLOG("%s: %s\n", g_opts.input_file, strerror(errno)); + dd_exit(-errno); + return; + } +#ifdef SPDK_CONFIG_URING + if (g_opts.aio == false) { + g_job.input.u.uring.poller = spdk_poller_register(dd_uring_poll, &g_job.input, 0); + } else +#endif + { + g_job.input.u.aio.poller = spdk_poller_register(dd_input_poll, NULL, 0); + } + } else if (g_opts.input_bdev) { + rc = dd_open_bdev(&g_job.input, g_opts.input_bdev, g_opts.input_offset); + if (rc < 0) { + SPDK_ERRLOG("%s: %s\n", g_opts.input_bdev, strerror(-rc)); + dd_exit(rc); + return; + } + } + + write_size = g_opts.io_unit_count * g_opts.io_unit_size; + g_job.input.pos = g_opts.input_offset * g_opts.io_unit_size; + + /* We cannot check write size for input files because /dev/zeros, /dev/random, etc would not work. + * We will handle that during copying */ + if (g_opts.input_bdev && g_job.input.pos > g_job.input.total_size) { + SPDK_ERRLOG("--skip value too big (%" PRIu64 ") - only %" PRIu64 " blocks available in input\n", + g_opts.input_offset, g_job.input.total_size / g_opts.io_unit_size); + dd_exit(-ENOSPC); + return; + } + + if (g_opts.io_unit_count != 0 && g_opts.input_bdev && + write_size + g_job.input.pos > g_job.input.total_size) { + SPDK_ERRLOG("--count value too big (%" PRIu64 ") - only %" PRIu64 " blocks available from input\n", + g_opts.io_unit_count, (g_job.input.total_size - g_job.input.pos) / g_opts.io_unit_size); + dd_exit(-ENOSPC); + return; + } + + if (g_opts.io_unit_count != 0) { + g_job.copy_size = write_size; + } else { + g_job.copy_size = g_job.input.total_size - g_job.input.pos; + } + + g_job.output.pos = g_opts.output_offset * g_opts.io_unit_size; + + if (g_opts.output_file) { + flags = 0; + + if (g_opts.output_file_flags) { + flags = parse_flags(g_opts.output_file_flags); + } + + if (dd_open_file(&g_job.output, g_opts.output_file, flags, g_opts.output_offset, false) < 0) { + SPDK_ERRLOG("%s: %s\n", g_opts.output_file, strerror(errno)); + dd_exit(-errno); + return; + } +#ifdef SPDK_CONFIG_URING + if (g_opts.aio == false) { + g_job.output.u.uring.poller = spdk_poller_register(dd_uring_poll, &g_job.output, 0); + } else +#endif + { + g_job.output.u.aio.poller = spdk_poller_register(dd_output_poll, NULL, 0); + } + } else if (g_opts.output_bdev) { + rc = dd_open_bdev(&g_job.output, g_opts.output_bdev, g_opts.output_offset); + if (rc < 0) { + SPDK_ERRLOG("%s: %s\n", g_opts.output_bdev, strerror(-rc)); + dd_exit(rc); + return; + } + + if (g_job.output.pos > g_job.output.total_size) { + SPDK_ERRLOG("--seek value too big (%" PRIu64 ") - only %" PRIu64 " blocks available in output\n", + g_opts.output_offset, g_job.output.total_size / g_opts.io_unit_size); + dd_exit(-ENOSPC); + return; + } + + if (g_opts.io_unit_count != 0 && write_size + g_job.output.pos > g_job.output.total_size) { + SPDK_ERRLOG("--count value too big (%" PRIu64 ") - only %" PRIu64 " blocks available in output\n", + g_opts.io_unit_count, (g_job.output.total_size - g_job.output.pos) / g_opts.io_unit_size); + dd_exit(-ENOSPC); + return; + } + } + + if ((g_job.output.block_size > g_opts.io_unit_size) || + (g_job.input.block_size > g_opts.io_unit_size)) { + SPDK_ERRLOG("--bs value cannot be less than input (%d) neither output (%d) native block size\n", + g_job.input.block_size, g_job.output.block_size); + dd_exit(-EINVAL); + return; + } + + g_job.ios = calloc(g_opts.queue_depth, sizeof(struct dd_io)); + if (g_job.ios == NULL) { + SPDK_ERRLOG("%s\n", strerror(ENOMEM)); + dd_exit(-ENOMEM); + return; + } + + for (i = 0; i < g_opts.queue_depth; i++) { + g_job.ios[i].buf = spdk_malloc(g_opts.io_unit_size, 0x1000, NULL, 0, SPDK_MALLOC_DMA); + if (g_job.ios[i].buf == NULL) { + SPDK_ERRLOG("%s - try smaller block size value\n", strerror(ENOMEM)); + dd_exit(-ENOMEM); + return; + } + } + + clock_gettime(CLOCK_REALTIME, &g_start_time); + + for (i = 0; i < g_opts.queue_depth; i++) { + dd_target_populate_buffer(&g_job.ios[i]); + } + +} + +enum dd_cmdline_opts { + DD_OPTION_IF = 0x1000, + DD_OPTION_OF, + DD_OPTION_IFLAGS, + DD_OPTION_OFLAGS, + DD_OPTION_IB, + DD_OPTION_OB, + DD_OPTION_SKIP, + DD_OPTION_SEEK, + DD_OPTION_BS, + DD_OPTION_QD, + DD_OPTION_COUNT, + DD_OPTION_AIO, +}; + +static struct option g_cmdline_opts[] = { + { + .name = "if", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_IF, + }, + { + .name = "of", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_OF, + }, + { + .name = "iflag", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_IFLAGS, + }, + { + .name = "oflag", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_OFLAGS, + }, + { + .name = "ib", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_IB, + }, + { + .name = "ob", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_OB, + }, + { + .name = "skip", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_SKIP, + }, + { + .name = "seek", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_SEEK, + }, + { + .name = "bs", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_BS, + }, + { + .name = "qd", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_QD, + }, + { + .name = "count", + .has_arg = 1, + .flag = NULL, + .val = DD_OPTION_COUNT, + }, + { + .name = "aio", + .has_arg = 0, + .flag = NULL, + .val = DD_OPTION_AIO, + }, + { + .name = NULL + } +}; + +static void +usage(void) +{ + printf("[--------- DD Options ---------]\n"); + printf(" --if Input file. Must specify either --if or --ib.\n"); + printf(" --ib Input bdev. Must specifier either --if or --ib\n"); + printf(" --of Output file. Must specify either --of or --ob.\n"); + printf(" --ob Output bdev. Must specify either --of or --ob.\n"); + printf(" --iflag Input file flags.\n"); + printf(" --oflag Onput file flags.\n"); + printf(" --bs I/O unit size (default: %" PRId64 ")\n", g_opts.io_unit_size); + printf(" --qd Queue depth (default: %d)\n", g_opts.queue_depth); + printf(" --count I/O unit count. The number of I/O units to copy. (default: all)\n"); + printf(" --skip Skip this many I/O units at start of input. (default: 0)\n"); + printf(" --seek Skip this many I/O units at start of output. (default: 0)\n"); + printf(" --aio Force usage of AIO. (by default io_uring is used if available)\n"); + printf(" Available iflag and oflag values:\n"); + printf(" append - append mode\n"); + printf(" direct - use direct I/O for data\n"); + printf(" directory - fail unless a directory\n"); + printf(" dsync - use synchronized I/O for data\n"); + printf(" noatime - do not update access time\n"); + printf(" noctty - do not assign controlling terminal from file\n"); + printf(" nofollow - do not follow symlinks\n"); + printf(" nonblock - use non-blocking I/O\n"); + printf(" sync - use synchronized I/O for data and metadata\n"); +} + +static int +parse_args(int argc, char *argv) +{ + switch (argc) { + case DD_OPTION_IF: + g_opts.input_file = strdup(argv); + break; + case DD_OPTION_OF: + g_opts.output_file = strdup(argv); + break; + case DD_OPTION_IFLAGS: + g_opts.input_file_flags = strdup(argv); + break; + case DD_OPTION_OFLAGS: + g_opts.output_file_flags = strdup(argv); + break; + case DD_OPTION_IB: + g_opts.input_bdev = strdup(argv); + break; + case DD_OPTION_OB: + g_opts.output_bdev = strdup(argv); + break; + case DD_OPTION_SKIP: + g_opts.input_offset = spdk_strtol(optarg, 10); + break; + case DD_OPTION_SEEK: + g_opts.output_offset = spdk_strtol(optarg, 10); + break; + case DD_OPTION_BS: + g_opts.io_unit_size = spdk_strtol(optarg, 10); + break; + case DD_OPTION_QD: + g_opts.queue_depth = spdk_strtol(optarg, 10); + break; + case DD_OPTION_COUNT: + g_opts.io_unit_count = spdk_strtol(optarg, 10); + break; + case DD_OPTION_AIO: + g_opts.aio = true; + break; + default: + usage(); + return 1; + } + return 0; +} + +static void +dd_free(void) +{ + uint32_t i; + + free(g_opts.input_file); + free(g_opts.output_file); + free(g_opts.input_bdev); + free(g_opts.output_bdev); + free(g_opts.input_file_flags); + free(g_opts.output_file_flags); + +#ifdef SPDK_CONFIG_URING + if (g_opts.aio == false) { + if (g_job.input.type == DD_TARGET_TYPE_FILE && g_job.input.open == true) { + io_uring_queue_exit(&g_job.input.u.uring.ring); + } + + if (g_job.output.type == DD_TARGET_TYPE_FILE && g_job.output.open == true) { + io_uring_queue_exit(&g_job.output.u.uring.ring); + } + } +#endif + + if (g_job.ios) { + for (i = 0; i < g_opts.queue_depth; i++) { + spdk_free(g_job.ios[i].buf); + } + + free(g_job.ios); + } +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc = 1; + + spdk_app_opts_init(&opts); + opts.name = "spdk_dd"; + opts.reactor_mask = "0x1"; + opts.shutdown_cb = dd_finish; + rc = spdk_app_parse_args(argc, argv, &opts, "", g_cmdline_opts, parse_args, usage); + if (rc == SPDK_APP_PARSE_ARGS_FAIL) { + SPDK_ERRLOG("Invalid arguments\n"); + goto end; + } else if (rc == SPDK_APP_PARSE_ARGS_HELP) { + goto end; + } + + if (g_opts.input_file != NULL && g_opts.input_bdev != NULL) { + SPDK_ERRLOG("You may specify either --if or --ib, but not both.\n"); + rc = EINVAL; + goto end; + } + + if (g_opts.output_file != NULL && g_opts.output_bdev != NULL) { + SPDK_ERRLOG("You may specify either --of or --ob, but not both.\n"); + rc = EINVAL; + goto end; + } + + if (g_opts.input_file == NULL && g_opts.input_bdev == NULL) { + SPDK_ERRLOG("You must specify either --if or --ib\n"); + rc = EINVAL; + goto end; + } + + if (g_opts.output_file == NULL && g_opts.output_bdev == NULL) { + SPDK_ERRLOG("You must specify either --of or --ob\n"); + rc = EINVAL; + goto end; + } + + if (g_opts.io_unit_size <= 0) { + SPDK_ERRLOG("Invalid --bs value\n"); + rc = EINVAL; + goto end; + } + + if (g_opts.io_unit_count < 0) { + SPDK_ERRLOG("Invalid --count value\n"); + rc = EINVAL; + goto end; + } + + if (g_opts.output_file == NULL && g_opts.output_file_flags != NULL) { + SPDK_ERRLOG("--oflags may be used only with --of\n"); + rc = EINVAL; + goto end; + } + + if (g_opts.input_file == NULL && g_opts.input_file_flags != NULL) { + SPDK_ERRLOG("--iflags may be used only with --if\n"); + rc = EINVAL; + goto end; + } + + rc = spdk_app_start(&opts, dd_run, NULL); + if (rc) { + SPDK_ERRLOG("Error occured while performing copy\n"); + } + + dd_free(); + spdk_app_fini(); + +end: + return rc; +} diff --git a/src/spdk/app/spdk_lspci/.gitignore b/src/spdk/app/spdk_lspci/.gitignore new file mode 100644 index 000000000..f3c7d7c2e --- /dev/null +++ b/src/spdk/app/spdk_lspci/.gitignore @@ -0,0 +1 @@ +spdk_lspci diff --git a/src/spdk/app/spdk_lspci/Makefile b/src/spdk/app/spdk_lspci/Makefile new file mode 100644 index 000000000..d74adc307 --- /dev/null +++ b/src/spdk/app/spdk_lspci/Makefile @@ -0,0 +1,43 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = spdk_lspci + +C_SRCS := spdk_lspci.c + +SPDK_LIB_LIST = vmd log + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/app/spdk_lspci/spdk_lspci.c b/src/spdk/app/spdk_lspci/spdk_lspci.c new file mode 100644 index 000000000..b976facb5 --- /dev/null +++ b/src/spdk/app/spdk_lspci/spdk_lspci.c @@ -0,0 +1,123 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * 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 Intel Corporation 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 "spdk/stdinc.h" +#include "spdk/env.h" +#include "spdk/vmd.h" + +static void +usage(void) +{ + printf("Usage: spdk_lspci\n"); + printf("Print available SPDK PCI devices supported by NVMe driver.\n"); +} + +static int +pci_enum_cb(void *ctx, struct spdk_pci_device *dev) +{ + return 0; +} + +static void +print_pci_dev(struct spdk_pci_device *dev) +{ + struct spdk_pci_addr pci_addr = spdk_pci_device_get_addr(dev); + char addr[32] = { 0 }; + + spdk_pci_addr_fmt(addr, sizeof(addr), &pci_addr); + + printf("%s (%x %x)", addr, + spdk_pci_device_get_vendor_id(dev), + spdk_pci_device_get_device_id(dev)); + + if (strcmp(spdk_pci_device_get_type(dev), "vmd") == 0) { + printf(" (NVMe disk behind VMD) "); + } + + if (dev->internal.driver == spdk_pci_vmd_get_driver()) { + printf(" (VMD) "); + } + + printf("\n"); +} + +int +main(int argc, char **argv) +{ + int op; + struct spdk_env_opts opts; + struct spdk_pci_device *dev; + + while ((op = getopt(argc, argv, "h")) != -1) { + switch (op) { + case 'h': + usage(); + return 0; + default: + usage(); + return 1; + } + } + + spdk_env_opts_init(&opts); + opts.name = "spdk_lspci"; + + if (spdk_env_init(&opts) < 0) { + printf("Unable to initialize SPDK env\n"); + return 1; + } + + if (spdk_vmd_init()) { + printf("Failed to initialize VMD. Some NVMe devices can be unavailable.\n"); + } + + if (spdk_pci_enumerate(spdk_pci_nvme_get_driver(), pci_enum_cb, NULL)) { + printf("Unable to enumerate PCI nvme driver\n"); + return 1; + } + + dev = spdk_pci_get_first_device(); + if (!dev) { + printf("\nLack of PCI devices available for SPDK!\n"); + } + + printf("\nList of available PCI devices:\n"); + while (dev) { + print_pci_dev(dev); + dev = spdk_pci_get_next_device(dev); + } + + spdk_vmd_fini(); + + return 0; +} diff --git a/src/spdk/app/spdk_tgt/.gitignore b/src/spdk/app/spdk_tgt/.gitignore new file mode 100644 index 000000000..0309c6879 --- /dev/null +++ b/src/spdk/app/spdk_tgt/.gitignore @@ -0,0 +1 @@ +spdk_tgt diff --git a/src/spdk/app/spdk_tgt/Makefile b/src/spdk/app/spdk_tgt/Makefile new file mode 100644 index 000000000..43583f855 --- /dev/null +++ b/src/spdk/app/spdk_tgt/Makefile @@ -0,0 +1,78 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = spdk_tgt + +C_SRCS := spdk_tgt.c + +SPDK_LIB_LIST = $(ALL_MODULES_LIST) + +ifeq ($(OS),Linux) +ifeq ($(CONFIG_VHOST),y) +SPDK_LIB_LIST += vhost event_vhost +ifeq ($(CONFIG_VHOST_INTERNAL_LIB),y) +SPDK_LIB_LIST += rte_vhost +endif +endif +endif + +SPDK_LIB_LIST += $(EVENT_BDEV_SUBSYSTEM) event_iscsi event_net event_scsi event_nvmf event +SPDK_LIB_LIST += nvmf trace log conf thread util bdev iscsi scsi accel rpc jsonrpc json +SPDK_LIB_LIST += app_rpc log_rpc bdev_rpc net sock notify + +ifeq ($(SPDK_ROOT_DIR)/lib/env_dpdk,$(CONFIG_ENV)) +SPDK_LIB_LIST += env_dpdk_rpc +endif + +ifeq ($(OS),Linux) +SPDK_LIB_LIST += event_nbd nbd +endif + +ifeq ($(CONFIG_FC),y) +ifneq ($(strip $(CONFIG_FC_PATH)),) +SYS_LIBS += -L$(CONFIG_FC_PATH) +endif +SYS_LIBS += -lufc +endif + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk + +install: $(APP) + $(INSTALL_APP) + +uninstall: + $(UNINSTALL_APP) diff --git a/src/spdk/app/spdk_tgt/spdk_tgt.c b/src/spdk/app/spdk_tgt/spdk_tgt.c new file mode 100644 index 000000000..03f3098cd --- /dev/null +++ b/src/spdk/app/spdk_tgt/spdk_tgt.c @@ -0,0 +1,124 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * 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 Intel Corporation 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 "spdk/stdinc.h" + +#include "spdk/config.h" +#include "spdk/env.h" +#include "spdk/event.h" +#include "spdk/vhost.h" + +#ifdef SPDK_CONFIG_VHOST +#define SPDK_VHOST_OPTS "S:" +#else +#define SPDK_VHOST_OPTS +#endif + +static const char *g_pid_path = NULL; +static const char g_spdk_tgt_get_opts_string[] = "f:" SPDK_VHOST_OPTS; + +static void +spdk_tgt_usage(void) +{ + printf(" -f <file> pidfile save pid to file under given path\n"); +#ifdef SPDK_CONFIG_VHOST + printf(" -S <path> directory where to create vhost sockets (default: pwd)\n"); +#endif +} + +static void +spdk_tgt_save_pid(const char *pid_path) +{ + FILE *pid_file; + + pid_file = fopen(pid_path, "w"); + if (pid_file == NULL) { + fprintf(stderr, "Couldn't create pid file '%s': %s\n", pid_path, strerror(errno)); + exit(EXIT_FAILURE); + } + + fprintf(pid_file, "%d\n", getpid()); + fclose(pid_file); +} + + +static int +spdk_tgt_parse_arg(int ch, char *arg) +{ + switch (ch) { + case 'f': + g_pid_path = arg; + break; +#ifdef SPDK_CONFIG_VHOST + case 'S': + spdk_vhost_set_socket_path(arg); + break; +#endif + default: + return -EINVAL; + } + return 0; +} + +static void +spdk_tgt_started(void *arg1) +{ + if (g_pid_path) { + spdk_tgt_save_pid(g_pid_path); + } + + if (getenv("MEMZONE_DUMP") != NULL) { + spdk_memzone_dump(stdout); + fflush(stdout); + } +} + +int +main(int argc, char **argv) +{ + struct spdk_app_opts opts = {}; + int rc; + + spdk_app_opts_init(&opts); + opts.name = "spdk_tgt"; + if ((rc = spdk_app_parse_args(argc, argv, &opts, g_spdk_tgt_get_opts_string, + NULL, spdk_tgt_parse_arg, spdk_tgt_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + return rc; + } + + rc = spdk_app_start(&opts, spdk_tgt_started, NULL); + spdk_app_fini(); + + return rc; +} diff --git a/src/spdk/app/spdk_top/.gitignore b/src/spdk/app/spdk_top/.gitignore new file mode 100644 index 000000000..5452afcab --- /dev/null +++ b/src/spdk/app/spdk_top/.gitignore @@ -0,0 +1 @@ +spdk_top diff --git a/src/spdk/app/spdk_top/Makefile b/src/spdk/app/spdk_top/Makefile new file mode 100644 index 000000000..b5dfc4f19 --- /dev/null +++ b/src/spdk/app/spdk_top/Makefile @@ -0,0 +1,44 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +APP = spdk_top + +C_SRCS := spdk_top.c + +SPDK_LIB_LIST = jsonrpc json rpc log util +LIBS=-lncurses -lpanel -lmenu + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/app/spdk_top/README b/src/spdk/app/spdk_top/README new file mode 100644 index 000000000..d78e21cff --- /dev/null +++ b/src/spdk/app/spdk_top/README @@ -0,0 +1,74 @@ +Contents +======== + +- Overview +- Installation +- Usage + + +Overview +======== + +This application provides SPDK live statistics regarding usage of cores, +threads, pollers, execution times, and relations between those. All data +is being gathered from SPDK by calling appropriate RPC calls. Application +consists of three selectable tabs providing statistics related to three +main topics: + +- Threads +- Pollers +- Cores + + +Installation +============ + +spdk_top requires Ncurses library (can by installed by running +spdk/scripts/pkgdep.sh) and is compiled by default when SPDK compiles. + + + +Usage +===== + +To run spdk_top: + +sudo spdk_top [options] + +options: + -r <path> RPC listen address (optional, default: /var/tmp/spdk.sock) + -h show help message + +Application consists of: +- Tabs list (on top) +- Statistics window (main windows in the middle) +- Options window (below statistics window) +- Page indicator / Error status + +Tabs list shows available tabs and highlights currently selected tab. +Statistics window displays current statistics. Available statistics +depend on which tab is currently selected. All time and run counter +related statistics are relative - show elapsed time / number of runs +since previous data refresh. Options windows provide hotkeys list +to change application settings. Available options are: + +- [q] Quit - quit the application +- [1-3] TAB selection - select tab to be displayed +- [PgUp] Previous page - go to previous page +- [PgDown] Next page - go to next page +- [c] Columns - select which columns should be visible / hidden: + Use arrow up / down and space / enter keys to select which columns + should be visible. Select 'CLOSE' to confirm changes and close + the window. +- [s] Sorting - change data sorting: + Use arrow up / down to select based on which column data should be + sorted. Use enter key to confirm or esc key to exit without + changing current sorting scheme. +- [r] Refresh rate - change data refresh rate: + Enter new data refresh rate value. Refresh rate accepts value + between 0 and 255 seconds. Use enter key to apply or escape key + to cancel. + +Page indicator show current data page. Error status can be displayed +on bottom right side of the screen when the application encountered +an error. diff --git a/src/spdk/app/spdk_top/spdk_top.c b/src/spdk/app/spdk_top/spdk_top.c new file mode 100644 index 000000000..8531cd32a --- /dev/null +++ b/src/spdk/app/spdk_top/spdk_top.c @@ -0,0 +1,1982 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * 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 Intel Corporation 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 "spdk/stdinc.h" +#include "spdk/jsonrpc.h" +#include "spdk/rpc.h" +#include "spdk/event.h" +#include "spdk/util.h" +#include "spdk/env.h" + +#if defined __has_include +#if __has_include(<ncurses/panel.h>) +#include <ncurses/ncurses.h> +#include <ncurses/panel.h> +#include <ncurses/menu.h> +#else +#include <ncurses.h> +#include <panel.h> +#include <menu.h> +#endif +#else +#include <ncurses.h> +#include <panel.h> +#include <menu.h> +#endif + +#define RPC_MAX_THREADS 1024 +#define RPC_MAX_POLLERS 1024 +#define RPC_MAX_CORES 255 +#define MAX_THREAD_NAME 128 +#define MAX_POLLER_NAME 128 +#define MAX_THREADS 4096 +#define RR_MAX_VALUE 255 + +#define MAX_STRING_LEN 12289 /* 3x 4k monitors + 1 */ +#define TAB_WIN_HEIGHT 3 +#define TAB_WIN_LOCATION_ROW 1 +#define TABS_SPACING 2 +#define TABS_LOCATION_ROW 4 +#define TABS_LOCATION_COL 0 +#define TABS_DATA_START_ROW 3 +#define TABS_DATA_START_COL 2 +#define TABS_COL_COUNT 10 +#define MENU_WIN_HEIGHT 3 +#define MENU_WIN_SPACING 4 +#define MENU_WIN_LOCATION_COL 0 +#define RR_WIN_WIDTH 32 +#define RR_WIN_HEIGHT 5 +#define MAX_THREAD_NAME_LEN 26 +#define MAX_THREAD_COUNT_STR_LEN 14 +#define MAX_POLLER_NAME_LEN 36 +#define MAX_POLLER_COUNT_STR_LEN 16 +#define MAX_POLLER_TYPE_STR_LEN 8 +#define MAX_CORE_MASK_STR_LEN 16 +#define MAX_CORE_STR_LEN 6 +#define MAX_TIME_STR_LEN 10 +#define MAX_PERIOD_STR_LEN 12 +#define WINDOW_HEADER 12 +#define FROM_HEX 16 + +enum tabs { + THREADS_TAB, + POLLERS_TAB, + CORES_TAB, + NUMBER_OF_TABS, +}; + +enum spdk_poller_type { + SPDK_ACTIVE_POLLER, + SPDK_TIMED_POLLER, + SPDK_PAUSED_POLLER, + SPDK_POLLER_TYPES_COUNT, +}; + +struct col_desc { + const char *name; + uint8_t name_len; + uint8_t max_data_string; + bool disabled; +}; + +struct run_counter_history { + char *poller_name; + uint64_t thread_id; + uint64_t last_run_counter; + TAILQ_ENTRY(run_counter_history) link; +}; + +struct core_info { + uint32_t core; + char core_mask[MAX_CORE_MASK_STR_LEN]; + uint64_t threads_count; + uint64_t pollers_count; + uint64_t idle; + uint64_t last_idle; + uint64_t busy; + uint64_t last_busy; +}; + +uint8_t g_sleep_time = 1; +struct rpc_thread_info *g_thread_info[MAX_THREADS]; +const char *poller_type_str[SPDK_POLLER_TYPES_COUNT] = {"Active", "Timed", "Paused"}; +const char *g_tab_title[NUMBER_OF_TABS] = {"[1] THREADS", "[2] POLLERS", "[3] CORES"}; +struct spdk_jsonrpc_client *g_rpc_client; +static TAILQ_HEAD(, run_counter_history) g_run_counter_history = TAILQ_HEAD_INITIALIZER( + g_run_counter_history); +struct core_info g_cores_history[RPC_MAX_CORES]; +WINDOW *g_menu_win, *g_tab_win[NUMBER_OF_TABS], *g_tabs[NUMBER_OF_TABS]; +PANEL *g_panels[NUMBER_OF_TABS]; +uint16_t g_max_row, g_max_col; +uint16_t g_data_win_size, g_max_data_rows; +uint32_t g_last_threads_count, g_last_pollers_count, g_last_cores_count; +uint8_t g_current_sort_col[NUMBER_OF_TABS] = {0, 0, 0}; +static struct col_desc g_col_desc[NUMBER_OF_TABS][TABS_COL_COUNT] = { + { {.name = "Thread name", .max_data_string = MAX_THREAD_NAME_LEN}, + {.name = "Core", .max_data_string = MAX_CORE_STR_LEN}, + {.name = "Active pollers", .max_data_string = MAX_POLLER_COUNT_STR_LEN}, + {.name = "Timed pollers", .max_data_string = MAX_POLLER_COUNT_STR_LEN}, + {.name = "Paused pollers", .max_data_string = MAX_POLLER_COUNT_STR_LEN}, + {.name = "Idle [us]", .max_data_string = MAX_TIME_STR_LEN}, + {.name = "Busy [us]", .max_data_string = MAX_TIME_STR_LEN}, + {.name = (char *)NULL} + }, + { {.name = "Poller name", .max_data_string = MAX_POLLER_NAME_LEN}, + {.name = "Type", .max_data_string = MAX_POLLER_TYPE_STR_LEN}, + {.name = "On thread", .max_data_string = MAX_THREAD_NAME_LEN}, + {.name = "Run count", .max_data_string = MAX_TIME_STR_LEN}, + {.name = "Period [us]", .max_data_string = MAX_PERIOD_STR_LEN}, + {.name = (char *)NULL} + }, + { {.name = "Core", .max_data_string = MAX_CORE_STR_LEN}, + {.name = "Thread count", .max_data_string = MAX_THREAD_COUNT_STR_LEN}, + {.name = "Poller count", .max_data_string = MAX_POLLER_COUNT_STR_LEN}, + {.name = "Idle [us]", .max_data_string = MAX_TIME_STR_LEN}, + {.name = "Busy [us]", .max_data_string = MAX_TIME_STR_LEN}, + {.name = (char *)NULL} + } +}; + +struct rpc_thread_info { + char *name; + uint64_t id; + uint32_t core_num; + char *cpumask; + uint64_t busy; + uint64_t last_busy; + uint64_t idle; + uint64_t last_idle; + uint64_t active_pollers_count; + uint64_t timed_pollers_count; + uint64_t paused_pollers_count; +}; + +struct rpc_threads { + uint64_t threads_count; + struct rpc_thread_info thread_info[RPC_MAX_THREADS]; +}; + +struct rpc_threads_stats { + uint64_t tick_rate; + struct rpc_threads threads; +}; + +struct rpc_poller_info { + char *name; + char *state; + uint64_t run_count; + uint64_t busy_count; + uint64_t period_ticks; + enum spdk_poller_type type; + char thread_name[MAX_THREAD_NAME]; + uint64_t thread_id; +}; + +struct rpc_pollers { + uint64_t pollers_count; + struct rpc_poller_info pollers[RPC_MAX_POLLERS]; +}; + +struct rpc_poller_thread_info { + char *name; + uint64_t id; + struct rpc_pollers active_pollers; + struct rpc_pollers timed_pollers; + struct rpc_pollers paused_pollers; +}; + +struct rpc_pollers_threads { + uint64_t threads_count; + struct rpc_poller_thread_info threads[RPC_MAX_THREADS]; +}; + +struct rpc_pollers_stats { + uint64_t tick_rate; + struct rpc_pollers_threads pollers_threads; +}; + +struct rpc_core_thread_info { + char *name; + uint64_t id; + char *cpumask; + uint64_t elapsed; +}; + +struct rpc_core_threads { + uint64_t threads_count; + struct rpc_core_thread_info thread[RPC_MAX_THREADS]; +}; + +struct rpc_core_info { + uint32_t lcore; + uint64_t busy; + uint64_t idle; + struct rpc_core_threads threads; +}; + +struct rpc_cores { + uint64_t cores_count; + struct rpc_core_info core[RPC_MAX_CORES]; +}; + +struct rpc_cores_stats { + uint64_t tick_rate; + struct rpc_cores cores; +}; + +struct rpc_threads_stats g_threads_stats; +struct rpc_pollers_stats g_pollers_stats; +struct rpc_cores_stats g_cores_stats; + +static void +init_str_len(void) +{ + int i, j; + + for (i = 0; i < NUMBER_OF_TABS; i++) { + for (j = 0; g_col_desc[i][j].name != NULL; j++) { + g_col_desc[i][j].name_len = strlen(g_col_desc[i][j].name); + } + } +} + +static void +free_rpc_threads_stats(struct rpc_threads_stats *req) +{ + uint64_t i; + + for (i = 0; i < req->threads.threads_count; i++) { + free(req->threads.thread_info[i].name); + req->threads.thread_info[i].name = NULL; + free(req->threads.thread_info[i].cpumask); + req->threads.thread_info[i].cpumask = NULL; + } +} + +static const struct spdk_json_object_decoder rpc_thread_info_decoders[] = { + {"name", offsetof(struct rpc_thread_info, name), spdk_json_decode_string}, + {"id", offsetof(struct rpc_thread_info, id), spdk_json_decode_uint64}, + {"cpumask", offsetof(struct rpc_thread_info, cpumask), spdk_json_decode_string}, + {"busy", offsetof(struct rpc_thread_info, busy), spdk_json_decode_uint64}, + {"idle", offsetof(struct rpc_thread_info, idle), spdk_json_decode_uint64}, + {"active_pollers_count", offsetof(struct rpc_thread_info, active_pollers_count), spdk_json_decode_uint64}, + {"timed_pollers_count", offsetof(struct rpc_thread_info, timed_pollers_count), spdk_json_decode_uint64}, + {"paused_pollers_count", offsetof(struct rpc_thread_info, paused_pollers_count), spdk_json_decode_uint64}, +}; + +static int +rpc_decode_threads_object(const struct spdk_json_val *val, void *out) +{ + struct rpc_thread_info *info = out; + + return spdk_json_decode_object(val, rpc_thread_info_decoders, + SPDK_COUNTOF(rpc_thread_info_decoders), info); +} + +static int +rpc_decode_threads_array(const struct spdk_json_val *val, void *out) +{ + struct rpc_threads *threads = out; + + return spdk_json_decode_array(val, rpc_decode_threads_object, threads->thread_info, RPC_MAX_THREADS, + &threads->threads_count, sizeof(struct rpc_thread_info)); +} + +static const struct spdk_json_object_decoder rpc_threads_stats_decoders[] = { + {"tick_rate", offsetof(struct rpc_threads_stats, tick_rate), spdk_json_decode_uint64}, + {"threads", offsetof(struct rpc_threads_stats, threads), rpc_decode_threads_array}, +}; + +static void +free_rpc_poller(struct rpc_poller_info *poller) +{ + free(poller->name); + poller->name = NULL; + free(poller->state); + poller->state = NULL; +} + +static void +free_rpc_pollers_stats(struct rpc_pollers_stats *req) +{ + struct rpc_poller_thread_info *thread; + uint64_t i, j; + + for (i = 0; i < req->pollers_threads.threads_count; i++) { + thread = &req->pollers_threads.threads[i]; + + for (j = 0; j < thread->active_pollers.pollers_count; j++) { + free_rpc_poller(&thread->active_pollers.pollers[j]); + } + + for (j = 0; j < thread->timed_pollers.pollers_count; j++) { + free_rpc_poller(&thread->timed_pollers.pollers[j]); + } + + for (j = 0; j < thread->paused_pollers.pollers_count; j++) { + free_rpc_poller(&thread->paused_pollers.pollers[j]); + } + + free(thread->name); + thread->name = NULL; + } +} + +static void +free_rpc_cores_stats(struct rpc_cores_stats *req) +{ + struct rpc_core_info *core; + struct rpc_core_thread_info *thread; + uint64_t i, j; + + for (i = 0; i < req->cores.cores_count; i++) { + core = &req->cores.core[i]; + + for (j = 0; j < core->threads.threads_count; j++) { + thread = &core->threads.thread[j]; + + free(thread->name); + free(thread->cpumask); + } + } +} + +static const struct spdk_json_object_decoder rpc_pollers_decoders[] = { + {"name", offsetof(struct rpc_poller_info, name), spdk_json_decode_string}, + {"state", offsetof(struct rpc_poller_info, state), spdk_json_decode_string}, + {"run_count", offsetof(struct rpc_poller_info, run_count), spdk_json_decode_uint64}, + {"busy_count", offsetof(struct rpc_poller_info, busy_count), spdk_json_decode_uint64}, + {"period_ticks", offsetof(struct rpc_poller_info, period_ticks), spdk_json_decode_uint64, true}, +}; + +static int +rpc_decode_pollers_object(const struct spdk_json_val *val, void *out) +{ + struct rpc_poller_info *info = out; + + return spdk_json_decode_object(val, rpc_pollers_decoders, SPDK_COUNTOF(rpc_pollers_decoders), info); +} + +static int +rpc_decode_pollers_array(const struct spdk_json_val *val, void *out) +{ + struct rpc_pollers *pollers = out; + + return spdk_json_decode_array(val, rpc_decode_pollers_object, pollers->pollers, RPC_MAX_THREADS, + &pollers->pollers_count, sizeof(struct rpc_poller_info)); +} + +static const struct spdk_json_object_decoder rpc_pollers_threads_decoders[] = { + {"name", offsetof(struct rpc_poller_thread_info, name), spdk_json_decode_string}, + {"id", offsetof(struct rpc_poller_thread_info, id), spdk_json_decode_uint64}, + {"active_pollers", offsetof(struct rpc_poller_thread_info, active_pollers), rpc_decode_pollers_array}, + {"timed_pollers", offsetof(struct rpc_poller_thread_info, timed_pollers), rpc_decode_pollers_array}, + {"paused_pollers", offsetof(struct rpc_poller_thread_info, paused_pollers), rpc_decode_pollers_array}, +}; + +static int +rpc_decode_pollers_threads_object(const struct spdk_json_val *val, void *out) +{ + struct rpc_poller_thread_info *info = out; + + return spdk_json_decode_object(val, rpc_pollers_threads_decoders, + SPDK_COUNTOF(rpc_pollers_threads_decoders), info); +} + +static int +rpc_decode_pollers_threads_array(const struct spdk_json_val *val, void *out) +{ + struct rpc_pollers_threads *pollers_threads = out; + + return spdk_json_decode_array(val, rpc_decode_pollers_threads_object, pollers_threads->threads, + RPC_MAX_THREADS, &pollers_threads->threads_count, sizeof(struct rpc_poller_thread_info)); +} + +static const struct spdk_json_object_decoder rpc_pollers_stats_decoders[] = { + {"tick_rate", offsetof(struct rpc_pollers_stats, tick_rate), spdk_json_decode_uint64}, + {"threads", offsetof(struct rpc_pollers_stats, pollers_threads), rpc_decode_pollers_threads_array}, +}; + +static const struct spdk_json_object_decoder rpc_core_thread_info_decoders[] = { + {"name", offsetof(struct rpc_core_thread_info, name), spdk_json_decode_string}, + {"id", offsetof(struct rpc_core_thread_info, id), spdk_json_decode_uint64}, + {"cpumask", offsetof(struct rpc_core_thread_info, cpumask), spdk_json_decode_string}, + {"elapsed", offsetof(struct rpc_core_thread_info, elapsed), spdk_json_decode_uint64}, +}; + +static int +rpc_decode_core_threads_object(const struct spdk_json_val *val, void *out) +{ + struct rpc_core_thread_info *info = out; + + return spdk_json_decode_object(val, rpc_core_thread_info_decoders, + SPDK_COUNTOF(rpc_core_thread_info_decoders), info); +} + +static int +rpc_decode_cores_lw_threads(const struct spdk_json_val *val, void *out) +{ + struct rpc_core_threads *threads = out; + + return spdk_json_decode_array(val, rpc_decode_core_threads_object, threads->thread, RPC_MAX_THREADS, + &threads->threads_count, sizeof(struct rpc_core_thread_info)); +} + +static const struct spdk_json_object_decoder rpc_core_info_decoders[] = { + {"lcore", offsetof(struct rpc_core_info, lcore), spdk_json_decode_uint32}, + {"busy", offsetof(struct rpc_core_info, busy), spdk_json_decode_uint64}, + {"idle", offsetof(struct rpc_core_info, idle), spdk_json_decode_uint64}, + {"lw_threads", offsetof(struct rpc_core_info, threads), rpc_decode_cores_lw_threads}, +}; + +static int +rpc_decode_core_object(const struct spdk_json_val *val, void *out) +{ + struct rpc_core_info *info = out; + + return spdk_json_decode_object(val, rpc_core_info_decoders, + SPDK_COUNTOF(rpc_core_info_decoders), info); +} + +static int +rpc_decode_cores_array(const struct spdk_json_val *val, void *out) +{ + struct rpc_cores *cores = out; + + return spdk_json_decode_array(val, rpc_decode_core_object, cores->core, + RPC_MAX_THREADS, &cores->cores_count, sizeof(struct rpc_core_info)); +} + +static const struct spdk_json_object_decoder rpc_cores_stats_decoders[] = { + {"tick_rate", offsetof(struct rpc_cores_stats, tick_rate), spdk_json_decode_uint64}, + {"reactors", offsetof(struct rpc_cores_stats, cores), rpc_decode_cores_array}, +}; + + +static int +rpc_send_req(char *rpc_name, struct spdk_jsonrpc_client_response **resp) +{ + struct spdk_jsonrpc_client_response *json_resp = NULL; + struct spdk_json_write_ctx *w; + struct spdk_jsonrpc_client_request *request; + int rc; + + request = spdk_jsonrpc_client_create_request(); + if (request == NULL) { + return -ENOMEM; + } + + w = spdk_jsonrpc_begin_request(request, 1, rpc_name); + spdk_jsonrpc_end_request(request, w); + spdk_jsonrpc_client_send_request(g_rpc_client, request); + + do { + rc = spdk_jsonrpc_client_poll(g_rpc_client, 1); + } while (rc == 0 || rc == -ENOTCONN); + + if (rc <= 0) { + return -1; + } + + json_resp = spdk_jsonrpc_client_get_response(g_rpc_client); + if (json_resp == NULL) { + return -1; + } + + /* Check for error response */ + if (json_resp->error != NULL) { + return -1; + } + + assert(json_resp->result); + + *resp = json_resp; + + return 0; +} + +static int +get_data(void) +{ + struct spdk_jsonrpc_client_response *json_resp = NULL; + struct rpc_thread_info *thread_info; + struct rpc_core_info *core_info; + uint64_t i, j; + int rc = 0; + + rc = rpc_send_req("thread_get_stats", &json_resp); + if (rc) { + goto end; + } + + /* Decode json */ + if (spdk_json_decode_object(json_resp->result, rpc_threads_stats_decoders, + SPDK_COUNTOF(rpc_threads_stats_decoders), &g_threads_stats)) { + rc = -EINVAL; + goto end; + } + + spdk_jsonrpc_client_free_response(json_resp); + + for (i = 0; i < g_threads_stats.threads.threads_count; i++) { + thread_info = &g_threads_stats.threads.thread_info[i]; + g_thread_info[thread_info->id] = thread_info; + } + + rc = rpc_send_req("thread_get_pollers", &json_resp); + if (rc) { + goto end; + } + + /* Decode json */ + memset(&g_pollers_stats, 0, sizeof(g_pollers_stats)); + if (spdk_json_decode_object(json_resp->result, rpc_pollers_stats_decoders, + SPDK_COUNTOF(rpc_pollers_stats_decoders), &g_pollers_stats)) { + rc = -EINVAL; + goto end; + } + + spdk_jsonrpc_client_free_response(json_resp); + + rc = rpc_send_req("framework_get_reactors", &json_resp); + if (rc) { + goto end; + } + + /* Decode json */ + memset(&g_cores_stats, 0, sizeof(g_cores_stats)); + if (spdk_json_decode_object(json_resp->result, rpc_cores_stats_decoders, + SPDK_COUNTOF(rpc_cores_stats_decoders), &g_cores_stats)) { + rc = -EINVAL; + goto end; + } + + for (i = 0; i < g_cores_stats.cores.cores_count; i++) { + core_info = &g_cores_stats.cores.core[i]; + + for (j = 0; j < core_info->threads.threads_count; j++) { + g_thread_info[core_info->threads.thread[j].id]->core_num = core_info->lcore; + } + } + +end: + spdk_jsonrpc_client_free_response(json_resp); + return rc; +} + +static void +free_data(void) +{ + free_rpc_threads_stats(&g_threads_stats); + free_rpc_pollers_stats(&g_pollers_stats); + free_rpc_cores_stats(&g_cores_stats); +} + +enum str_alignment { + ALIGN_LEFT, + ALIGN_RIGHT, +}; + +static void +print_max_len(WINDOW *win, int row, uint16_t col, uint16_t max_len, enum str_alignment alignment, + const char *string) +{ + const char dots[] = "..."; + int DOTS_STR_LEN = sizeof(dots) / sizeof(dots[0]); + char tmp_str[MAX_STRING_LEN]; + int len, max_col, max_str, cmp_len; + int max_row; + + len = strlen(string); + getmaxyx(win, max_row, max_col); + + if (row > max_row) { + /* We are in a process of resizing and this may happen */ + return; + } + + if (max_len != 0 && col + max_len < max_col) { + max_col = col + max_len; + } + + max_str = max_col - col; + + if (max_str <= DOTS_STR_LEN + 1) { + /* No space to print anything, but we have to let a user know about it */ + mvwprintw(win, row, max_col - DOTS_STR_LEN - 1, "..."); + refresh(); + wrefresh(win); + return; + } + + if (max_len) { + if (alignment == ALIGN_LEFT) { + snprintf(tmp_str, max_str, "%s%*c", string, max_len - len - 1, ' '); + } else { + snprintf(tmp_str, max_str, "%*c%s", max_len - len - 1, ' ', string); + } + cmp_len = max_len - 1; + } else { + snprintf(tmp_str, max_str, "%s", string); + cmp_len = len; + } + + if (col + cmp_len > max_col - 1) { + snprintf(&tmp_str[max_str - DOTS_STR_LEN - 2], DOTS_STR_LEN, "%s", dots); + } + + mvwprintw(win, row, col, tmp_str); + + refresh(); + wrefresh(win); +} + + +static void +draw_menu_win(void) +{ + wbkgd(g_menu_win, COLOR_PAIR(2)); + box(g_menu_win, 0, 0); + print_max_len(g_menu_win, 1, 1, 0, ALIGN_LEFT, + " [q] Quit | [1-3] TAB selection | [PgUp] Previous page | [PgDown] Next page | [c] Columns | [s] Sorting | [r] Refresh rate"); +} + +static void +draw_tab_win(enum tabs tab) +{ + uint16_t col; + uint8_t white_spaces = TABS_SPACING * NUMBER_OF_TABS; + + wbkgd(g_tab_win[tab], COLOR_PAIR(2)); + box(g_tab_win[tab], 0, 0); + + col = ((g_max_col - white_spaces) / NUMBER_OF_TABS / 2) - (strlen(g_tab_title[tab]) / 2) - + TABS_SPACING; + print_max_len(g_tab_win[tab], 1, col, 0, ALIGN_LEFT, g_tab_title[tab]); +} + +static void +draw_tabs(enum tabs tab_index, uint8_t sort_col) +{ + struct col_desc *col_desc = g_col_desc[tab_index]; + WINDOW *tab = g_tabs[tab_index]; + int i, j; + uint16_t offset, draw_offset; + + for (i = 0; col_desc[i].name != NULL; i++) { + if (col_desc[i].disabled) { + continue; + } + + offset = 1; + for (j = i; j != 0; j--) { + if (!col_desc[j - 1].disabled) { + offset += col_desc[j - 1].max_data_string; + offset += col_desc[j - 1].name_len % 2 + 1; + } + } + + draw_offset = offset + (col_desc[i].max_data_string / 2) - (col_desc[i].name_len / 2); + + if (i == sort_col) { + wattron(tab, COLOR_PAIR(3)); + print_max_len(tab, 1, draw_offset, 0, ALIGN_LEFT, col_desc[i].name); + wattroff(tab, COLOR_PAIR(3)); + } else { + print_max_len(tab, 1, draw_offset, 0, ALIGN_LEFT, col_desc[i].name); + } + + if (offset != 1) { + print_max_len(tab, 1, offset - 1, 0, ALIGN_LEFT, "|"); + } + } + + print_max_len(tab, 2, 1, 0, ALIGN_LEFT, ""); /* Move to next line */ + whline(tab, ACS_HLINE, MAX_STRING_LEN); + box(tab, 0, 0); + wrefresh(tab); +} + +static void +resize_interface(enum tabs tab) +{ + int i; + + clear(); + wclear(g_menu_win); + mvwin(g_menu_win, g_max_row - MENU_WIN_SPACING, MENU_WIN_LOCATION_COL); + wresize(g_menu_win, MENU_WIN_HEIGHT, g_max_col); + draw_menu_win(); + + for (i = 0; i < NUMBER_OF_TABS; i++) { + wclear(g_tabs[i]); + wresize(g_tabs[i], g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - 2, g_max_col); + mvwin(g_tabs[i], TABS_LOCATION_ROW, TABS_LOCATION_COL); + draw_tabs(i, g_current_sort_col[i]); + } + + draw_tabs(tab, g_current_sort_col[tab]); + + for (i = 0; i < NUMBER_OF_TABS; i++) { + wclear(g_tab_win[i]); + wresize(g_tab_win[i], TAB_WIN_HEIGHT, + (g_max_col - (TABS_SPACING * NUMBER_OF_TABS)) / NUMBER_OF_TABS); + mvwin(g_tab_win[i], TAB_WIN_LOCATION_ROW, 1 + (g_max_col / NUMBER_OF_TABS) * i); + draw_tab_win(i); + } + + update_panels(); + doupdate(); +} + +static void +switch_tab(enum tabs tab) +{ + top_panel(g_panels[tab]); + update_panels(); + doupdate(); +} + +static void +get_time_str(uint64_t ticks, char *time_str) +{ + uint64_t time; + + time = ticks * SPDK_SEC_TO_USEC / g_cores_stats.tick_rate; + snprintf(time_str, MAX_TIME_STR_LEN, "%" PRIu64, time); +} + +static int +sort_threads(const void *p1, const void *p2) +{ + const struct rpc_thread_info *thread_info1 = *(struct rpc_thread_info **)p1; + const struct rpc_thread_info *thread_info2 = *(struct rpc_thread_info **)p2; + uint64_t count1, count2; + + switch (g_current_sort_col[THREADS_TAB]) { + case 0: /* Sort by name */ + return strcmp(thread_info1->name, thread_info2->name); + case 1: /* Sort by core */ + count2 = thread_info1->core_num; + count1 = thread_info2->core_num; + break; + case 2: /* Sort by active pollers number */ + count1 = thread_info1->active_pollers_count; + count2 = thread_info2->active_pollers_count; + break; + case 3: /* Sort by timed pollers number */ + count1 = thread_info1->timed_pollers_count; + count2 = thread_info2->timed_pollers_count; + break; + case 4: /* Sort by paused pollers number */ + count1 = thread_info1->paused_pollers_count; + count2 = thread_info2->paused_pollers_count; + break; + case 5: /* Sort by idle time */ + count1 = thread_info1->idle - thread_info1->last_idle; + count2 = thread_info2->idle - thread_info2->last_idle; + break; + case 6: /* Sort by busy time */ + count1 = thread_info1->busy - thread_info1->last_busy; + count2 = thread_info2->busy - thread_info2->last_busy; + break; + default: + return 0; + } + + if (count2 > count1) { + return 1; + } else if (count2 < count1) { + return -1; + } else { + return 0; + } +} + +static uint8_t +refresh_threads_tab(uint8_t current_page) +{ + struct col_desc *col_desc = g_col_desc[THREADS_TAB]; + uint64_t i, threads_count; + uint16_t j; + uint16_t col; + uint8_t max_pages, item_index; + static uint8_t last_page = 0; + char pollers_number[MAX_POLLER_COUNT_STR_LEN], idle_time[MAX_TIME_STR_LEN], + busy_time[MAX_TIME_STR_LEN], core_str[MAX_CORE_MASK_STR_LEN]; + struct rpc_thread_info *thread_info[g_threads_stats.threads.threads_count]; + + threads_count = g_threads_stats.threads.threads_count; + + /* Clear screen if number of threads changed */ + if (g_last_threads_count != threads_count) { + for (i = TABS_DATA_START_ROW; i < g_data_win_size; i++) { + for (j = 1; j < (uint64_t)g_max_col - 1; j++) { + mvwprintw(g_tabs[THREADS_TAB], i, j, " "); + } + } + + g_last_threads_count = threads_count; + } + + /* Thread IDs starts from '1', so we have to take this into account when copying. + * TODO: In future we can have gaps in ID list, so we will need to change the way we + * handle copying threads list below */ + memcpy(thread_info, &g_thread_info[1], sizeof(struct rpc_thread_info *) * threads_count); + + if (last_page != current_page) { + for (i = 0; i < threads_count; i++) { + /* Thread IDs start from 1, so we have to do i + 1 */ + g_threads_stats.threads.thread_info[i].last_idle = g_thread_info[i + 1]->idle; + g_threads_stats.threads.thread_info[i].last_busy = g_thread_info[i + 1]->busy; + } + + last_page = current_page; + } + + max_pages = (threads_count + g_max_data_rows - 1) / g_max_data_rows; + + qsort(thread_info, threads_count, sizeof(thread_info[0]), sort_threads); + + for (i = current_page * g_max_data_rows; + i < spdk_min(threads_count, (uint64_t)((current_page + 1) * g_max_data_rows)); + i++) { + item_index = i - (current_page * g_max_data_rows); + + col = TABS_DATA_START_COL; + + if (!col_desc[0].disabled) { + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[0].max_data_string, ALIGN_LEFT, thread_info[i]->name); + col += col_desc[0].max_data_string; + } + + if (!col_desc[1].disabled) { + snprintf(core_str, MAX_CORE_STR_LEN, "%d", thread_info[i]->core_num); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, + col, col_desc[1].max_data_string, ALIGN_RIGHT, core_str); + col += col_desc[1].max_data_string + 2; + } + + if (!col_desc[2].disabled) { + snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", thread_info[i]->active_pollers_count); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, + col + (col_desc[2].name_len / 2), col_desc[2].max_data_string, ALIGN_LEFT, pollers_number); + col += col_desc[2].max_data_string + 2; + } + + if (!col_desc[3].disabled) { + snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", thread_info[i]->timed_pollers_count); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, + col + (col_desc[3].name_len / 2), col_desc[3].max_data_string, ALIGN_LEFT, pollers_number); + col += col_desc[3].max_data_string + 1; + } + + if (!col_desc[4].disabled) { + snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", thread_info[i]->paused_pollers_count); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, + col + (col_desc[4].name_len / 2), col_desc[4].max_data_string, ALIGN_LEFT, pollers_number); + col += col_desc[4].max_data_string + 2; + } + + if (!col_desc[5].disabled) { + get_time_str(thread_info[i]->idle - thread_info[i]->last_idle, idle_time); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[5].max_data_string, ALIGN_RIGHT, idle_time); + col += col_desc[5].max_data_string; + thread_info[i]->last_idle = thread_info[i]->idle; + } + + if (!col_desc[6].disabled) { + get_time_str(thread_info[i]->busy - thread_info[i]->last_busy, busy_time); + print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[6].max_data_string, ALIGN_RIGHT, busy_time); + thread_info[i]->last_busy = thread_info[i]->busy; + } + } + + return max_pages; +} + +static uint64_t * +get_last_run_counter(const char *poller_name, uint64_t thread_id) +{ + struct run_counter_history *history; + + TAILQ_FOREACH(history, &g_run_counter_history, link) { + if (!strcmp(history->poller_name, poller_name) && history->thread_id == thread_id) { + return &history->last_run_counter; + } + } + + return NULL; +} + +static void +store_last_run_counter(const char *poller_name, uint64_t thread_id, uint64_t last_run_counter) +{ + struct run_counter_history *history; + + TAILQ_FOREACH(history, &g_run_counter_history, link) { + if (!strcmp(history->poller_name, poller_name) && history->thread_id == thread_id) { + history->last_run_counter = last_run_counter; + return; + } + } + + history = calloc(1, sizeof(*history)); + if (history == NULL) { + fprintf(stderr, "Unable to allocate a history object in store_last_run_counter.\n"); + return; + } + history->poller_name = strdup(poller_name); + history->thread_id = thread_id; + history->last_run_counter = last_run_counter; + + TAILQ_INSERT_TAIL(&g_run_counter_history, history, link); +} + +enum sort_type { + BY_NAME, + USE_GLOBAL, +}; + +static int +#ifdef __FreeBSD__ +sort_pollers(void *arg, const void *p1, const void *p2) +#else +sort_pollers(const void *p1, const void *p2, void *arg) +#endif +{ + const struct rpc_poller_info *poller1 = *(struct rpc_poller_info **)p1; + const struct rpc_poller_info *poller2 = *(struct rpc_poller_info **)p2; + enum sort_type sorting = *(enum sort_type *)arg; + uint64_t count1, count2; + uint64_t *last_run_counter; + + if (sorting == BY_NAME) { + /* Sorting by name requested explicitly */ + return strcmp(poller1->name, poller2->name); + } else { + /* Use globaly set sorting */ + switch (g_current_sort_col[POLLERS_TAB]) { + case 0: /* Sort by name */ + return strcmp(poller1->name, poller2->name); + case 1: /* Sort by type */ + return poller1->type - poller2->type; + case 2: /* Sort by thread */ + return strcmp(poller1->thread_name, poller2->thread_name); + case 3: /* Sort by run counter */ + last_run_counter = get_last_run_counter(poller1->name, poller1->thread_id); + assert(last_run_counter != NULL); + count1 = poller1->run_count - *last_run_counter; + last_run_counter = get_last_run_counter(poller2->name, poller2->thread_id); + assert(last_run_counter != NULL); + count2 = poller2->run_count - *last_run_counter; + break; + case 4: /* Sort by period */ + count1 = poller1->period_ticks; + count2 = poller2->period_ticks; + break; + default: + return 0; + } + } + + if (count2 > count1) { + return 1; + } else if (count2 < count1) { + return -1; + } else { + return 0; + } +} + +static void +copy_pollers(struct rpc_pollers *pollers, uint64_t pollers_count, enum spdk_poller_type type, + struct rpc_poller_thread_info *thread, uint64_t *current_count, bool reset_last_counter, + struct rpc_poller_info **pollers_info) +{ + uint64_t *last_run_counter; + uint64_t i; + + for (i = 0; i < pollers_count; i++) { + if (reset_last_counter) { + last_run_counter = get_last_run_counter(pollers->pollers[i].name, thread->id); + if (last_run_counter == NULL) { + store_last_run_counter(pollers->pollers[i].name, thread->id, pollers->pollers[i].run_count); + last_run_counter = get_last_run_counter(pollers->pollers[i].name, thread->id); + } + + assert(last_run_counter != NULL); + *last_run_counter = pollers->pollers[i].run_count; + } + pollers_info[*current_count] = &pollers->pollers[i]; + snprintf(pollers_info[*current_count]->thread_name, MAX_POLLER_NAME - 1, "%s", thread->name); + pollers_info[*current_count]->thread_id = thread->id; + pollers_info[(*current_count)++]->type = type; + } +} + +static uint8_t +refresh_pollers_tab(uint8_t current_page) +{ + struct col_desc *col_desc = g_col_desc[POLLERS_TAB]; + struct rpc_poller_thread_info *thread; + uint64_t *last_run_counter; + uint64_t i, count = 0; + uint16_t col, j; + uint8_t max_pages, item_index; + /* Init g_last_page with value != 0 to force store_last_run_counter() call in copy_pollers() + * so that initial values for run_counter are stored in g_run_counter_history */ + static uint8_t g_last_page = 0xF; + enum sort_type sorting; + char run_count[MAX_TIME_STR_LEN], period_ticks[MAX_PERIOD_STR_LEN]; + struct rpc_poller_info *pollers[RPC_MAX_POLLERS]; + bool reset_last_counter = false; + + for (i = 0; i < g_pollers_stats.pollers_threads.threads_count; i++) { + thread = &g_pollers_stats.pollers_threads.threads[i]; + if (g_last_page != current_page) { + reset_last_counter = true; + } + + copy_pollers(&thread->active_pollers, thread->active_pollers.pollers_count, SPDK_ACTIVE_POLLER, + thread, &count, reset_last_counter, pollers); + copy_pollers(&thread->timed_pollers, thread->timed_pollers.pollers_count, SPDK_TIMED_POLLER, thread, + &count, reset_last_counter, pollers); + copy_pollers(&thread->paused_pollers, thread->paused_pollers.pollers_count, SPDK_PAUSED_POLLER, + thread, &count, reset_last_counter, pollers); + } + + if (g_last_page != current_page) { + g_last_page = current_page; + } + + max_pages = (count + g_max_data_rows - 1) / g_max_data_rows; + + /* Clear screen if number of pollers changed */ + if (g_last_pollers_count != count) { + for (i = TABS_DATA_START_ROW; i < g_data_win_size; i++) { + for (j = 1; j < (uint64_t)g_max_col - 1; j++) { + mvwprintw(g_tabs[POLLERS_TAB], i, j, " "); + } + } + + g_last_pollers_count = count; + + /* We need to run store_last_run_counter() again, so the easiest way is to call this function + * again with changed g_last_page value */ + g_last_page = 0xF; + refresh_pollers_tab(current_page); + return max_pages; + } + + /* Timed pollers can switch their possition on a list because of how they work. + * Let's sort them by name first so that they won't switch on data refresh */ + sorting = BY_NAME; + qsort_r(pollers, count, sizeof(pollers[0]), sort_pollers, (void *)&sorting); + sorting = USE_GLOBAL; + qsort_r(pollers, count, sizeof(pollers[0]), sort_pollers, (void *)&sorting); + + /* Display info */ + for (i = current_page * g_max_data_rows; + i < spdk_min(count, (uint64_t)((current_page + 1) * g_max_data_rows)); + i++) { + item_index = i - (current_page * g_max_data_rows); + + col = TABS_DATA_START_COL; + + if (!col_desc[0].disabled) { + print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col + 1, + col_desc[0].max_data_string, ALIGN_LEFT, pollers[i]->name); + col += col_desc[0].max_data_string + 2; + } + + if (!col_desc[1].disabled) { + print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[1].max_data_string, ALIGN_LEFT, poller_type_str[pollers[i]->type]); + col += col_desc[1].max_data_string + 2; + } + + if (!col_desc[2].disabled) { + print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[2].max_data_string, ALIGN_LEFT, pollers[i]->thread_name); + col += col_desc[2].max_data_string + 1; + } + + if (!col_desc[3].disabled) { + last_run_counter = get_last_run_counter(pollers[i]->name, pollers[i]->thread_id); + assert(last_run_counter != NULL); + + snprintf(run_count, MAX_TIME_STR_LEN, "%" PRIu64, pollers[i]->run_count - *last_run_counter); + print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[3].max_data_string, ALIGN_RIGHT, run_count); + col += col_desc[3].max_data_string; + + store_last_run_counter(pollers[i]->name, pollers[i]->thread_id, pollers[i]->run_count); + } + + if (!col_desc[4].disabled) { + if (pollers[i]->period_ticks != 0) { + get_time_str(pollers[i]->period_ticks, period_ticks); + print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col, + col_desc[4].max_data_string, ALIGN_RIGHT, period_ticks); + } + } + } + + return max_pages; +} + +static int +sort_cores(const void *p1, const void *p2) +{ + const struct core_info core_info1 = *(struct core_info *)p1; + const struct core_info core_info2 = *(struct core_info *)p2; + uint64_t count1, count2; + + switch (g_current_sort_col[CORES_TAB]) { + case 0: /* Sort by core */ + count1 = core_info2.core; + count2 = core_info1.core; + break; + case 1: /* Sort by threads number */ + count1 = core_info1.threads_count; + count2 = core_info2.threads_count; + break; + case 2: /* Sort by pollers number */ + count1 = core_info1.pollers_count; + count2 = core_info2.pollers_count; + break; + case 3: /* Sort by idle time */ + count2 = g_cores_history[core_info1.core].last_idle - core_info1.idle; + count1 = g_cores_history[core_info2.core].last_idle - core_info2.idle; + break; + case 4: /* Sort by busy time */ + count2 = g_cores_history[core_info1.core].last_busy - core_info1.busy; + count1 = g_cores_history[core_info2.core].last_busy - core_info2.busy; + break; + default: + return 0; + } + + if (count2 > count1) { + return 1; + } else if (count2 < count1) { + return -1; + } else { + return 0; + } +} + +static void +store_core_last_stats(uint32_t core, uint64_t idle, uint64_t busy) +{ + g_cores_history[core].last_idle = idle; + g_cores_history[core].last_busy = busy; +} + +static void +get_core_last_stats(uint32_t core, uint64_t *idle, uint64_t *busy) +{ + *idle = g_cores_history[core].last_idle; + *busy = g_cores_history[core].last_busy; +} + +static uint8_t +refresh_cores_tab(uint8_t current_page) +{ + struct col_desc *col_desc = g_col_desc[CORES_TAB]; + uint64_t i, j; + uint16_t offset, count = 0; + uint8_t max_pages, item_index; + static uint8_t last_page = 0; + char core[MAX_CORE_STR_LEN], threads_number[MAX_THREAD_COUNT_STR_LEN], + pollers_number[MAX_POLLER_COUNT_STR_LEN], idle_time[MAX_TIME_STR_LEN], busy_time[MAX_TIME_STR_LEN]; + struct core_info cores[RPC_MAX_CORES]; + struct spdk_cpuset tmp_cpumask = {}; + bool found = false; + + for (i = 0; i < g_threads_stats.threads.threads_count; i++) { + if (i == 0) { + snprintf(cores[0].core_mask, MAX_CORE_MASK_STR_LEN, "%s", + g_threads_stats.threads.thread_info[0].cpumask); + cores[0].threads_count = 1; + cores[0].pollers_count = g_threads_stats.threads.thread_info[0].active_pollers_count + + g_threads_stats.threads.thread_info[0].timed_pollers_count + + g_threads_stats.threads.thread_info[0].paused_pollers_count; + count++; + continue; + } + for (j = 0; j < count; j++) { + if (!strcmp(cores[j].core_mask, g_threads_stats.threads.thread_info[i].cpumask)) { + found = true; + break; + } + } + + if (found) { + cores[j].threads_count++; + cores[j].pollers_count += g_threads_stats.threads.thread_info[i].active_pollers_count + + g_threads_stats.threads.thread_info[i].timed_pollers_count + + g_threads_stats.threads.thread_info[i].paused_pollers_count; + found = false; + } else { + snprintf(cores[count].core_mask, MAX_CORE_MASK_STR_LEN, "%s", + g_threads_stats.threads.thread_info[i].cpumask); + cores[count].threads_count = 1; + cores[count].pollers_count = g_threads_stats.threads.thread_info[i].active_pollers_count + + g_threads_stats.threads.thread_info[i].timed_pollers_count + + g_threads_stats.threads.thread_info[i].paused_pollers_count; + count++; + } + } + + assert(g_cores_stats.cores.cores_count == count); + + for (i = 0; i < count; i++) { + for (j = 0; j < count; j++) { + spdk_cpuset_zero(&tmp_cpumask); + spdk_cpuset_set_cpu(&tmp_cpumask, g_cores_stats.cores.core[j].lcore, true); + if (!strcmp(cores[i].core_mask, spdk_cpuset_fmt(&tmp_cpumask))) { + cores[i].core = g_cores_stats.cores.core[j].lcore; + cores[i].busy = g_cores_stats.cores.core[j].busy; + cores[i].idle = g_cores_stats.cores.core[j].idle; + if (last_page != current_page) { + store_core_last_stats(cores[i].core, cores[i].idle, cores[i].busy); + } + } + } + } + + if (last_page != current_page) { + last_page = current_page; + } + + max_pages = (count + g_max_row - WINDOW_HEADER - 1) / (g_max_row - WINDOW_HEADER); + + qsort(&cores, count, sizeof(cores[0]), sort_cores); + + for (i = current_page * g_max_data_rows; + i < spdk_min(count, (uint64_t)((current_page + 1) * g_max_data_rows)); + i++) { + item_index = i - (current_page * g_max_data_rows); + + snprintf(threads_number, MAX_THREAD_COUNT_STR_LEN, "%ld", cores[i].threads_count); + snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", cores[i].pollers_count); + get_core_last_stats(cores[i].core, &cores[i].last_idle, &cores[i].last_busy); + + offset = 1; + + if (!col_desc[0].disabled) { + snprintf(core, MAX_CORE_STR_LEN, "%d", cores[i].core); + print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, offset, + col_desc[0].max_data_string, ALIGN_RIGHT, core); + offset += col_desc[0].max_data_string + 2; + } + + if (!col_desc[1].disabled) { + print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, + offset + (col_desc[1].name_len / 2), col_desc[1].max_data_string, ALIGN_LEFT, threads_number); + offset += col_desc[1].max_data_string + 2; + } + + if (!col_desc[2].disabled) { + print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, + offset + (col_desc[2].name_len / 2), col_desc[2].max_data_string, ALIGN_LEFT, pollers_number); + offset += col_desc[2].max_data_string; + } + + if (!col_desc[3].disabled) { + get_time_str(cores[i].idle - cores[i].last_idle, idle_time); + print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, offset, + col_desc[3].max_data_string, ALIGN_RIGHT, idle_time); + offset += col_desc[3].max_data_string + 2; + } + + if (!col_desc[4].disabled) { + get_time_str(cores[i].busy - cores[i].last_busy, busy_time); + print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, offset, + col_desc[4].max_data_string, ALIGN_RIGHT, busy_time); + } + + store_core_last_stats(cores[i].core, cores[i].idle, cores[i].busy); + } + + return max_pages; +} + +static uint8_t +refresh_tab(enum tabs tab, uint8_t current_page) +{ + uint8_t (*refresh_function[NUMBER_OF_TABS])(uint8_t current_page) = {refresh_threads_tab, refresh_pollers_tab, refresh_cores_tab}; + int color_pair[NUMBER_OF_TABS] = {COLOR_PAIR(2), COLOR_PAIR(2), COLOR_PAIR(2)}; + int i; + uint8_t max_pages = 0; + + color_pair[tab] = COLOR_PAIR(1); + + for (i = 0; i < NUMBER_OF_TABS; i++) { + wbkgd(g_tab_win[i], color_pair[i]); + } + + max_pages = (*refresh_function[tab])(current_page); + refresh(); + + for (i = 0; i < NUMBER_OF_TABS; i++) { + wrefresh(g_tab_win[i]); + } + + return max_pages; +} + +static void +print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color) +{ + int length, temp; + + length = strlen(string); + temp = (width - length) / 2; + wattron(win, color); + mvwprintw(win, starty, startx + temp, "%s", string); + wattroff(win, color); + refresh(); +} + +static void +apply_filters(enum tabs tab) +{ + wclear(g_tabs[tab]); + draw_tabs(tab, g_current_sort_col[tab]); +} + +static ITEM ** +draw_filtering_menu(uint8_t position, WINDOW *filter_win, uint8_t tab, MENU **my_menu) +{ + const int ADDITIONAL_ELEMENTS = 3; + const int ROW_PADDING = 6; + const int WINDOW_START_X = 1; + const int WINDOW_START_Y = 3; + const int WINDOW_COLUMNS = 2; + struct col_desc *col_desc = g_col_desc[tab]; + ITEM **my_items; + MENU *menu; + int i, elements; + uint8_t len = 0; + + for (i = 0; col_desc[i].name != NULL; ++i) { + len = spdk_max(col_desc[i].name_len, len); + } + + elements = i; + + my_items = (ITEM **)calloc(elements * WINDOW_COLUMNS + ADDITIONAL_ELEMENTS, sizeof(ITEM *)); + if (my_items == NULL) { + fprintf(stderr, "Unable to allocate an item list in draw_filtering_menu.\n"); + return NULL; + } + + for (i = 0; i < elements * 2; i++) { + my_items[i] = new_item(col_desc[i / WINDOW_COLUMNS].name, NULL); + i++; + my_items[i] = new_item(col_desc[i / WINDOW_COLUMNS].disabled ? "[ ]" : "[*]", NULL); + } + + my_items[i] = new_item(" CLOSE", NULL); + set_item_userptr(my_items[i], apply_filters); + + menu = new_menu((ITEM **)my_items); + + menu_opts_off(menu, O_SHOWDESC); + set_menu_format(menu, elements + 1, WINDOW_COLUMNS); + + set_menu_win(menu, filter_win); + set_menu_sub(menu, derwin(filter_win, elements + 1, len + ROW_PADDING, WINDOW_START_Y, + WINDOW_START_X)); + + *my_menu = menu; + + post_menu(menu); + refresh(); + wrefresh(filter_win); + + for (i = 0; i < position / WINDOW_COLUMNS; i++) { + menu_driver(menu, REQ_DOWN_ITEM); + } + + return my_items; +} + +static void +delete_filtering_menu(MENU *my_menu, ITEM **my_items, uint8_t elements) +{ + int i; + + unpost_menu(my_menu); + free_menu(my_menu); + for (i = 0; i < elements * 2 + 2; ++i) { + free_item(my_items[i]); + } + free(my_items); +} + +static ITEM ** +refresh_filtering_menu(MENU **my_menu, WINDOW *filter_win, uint8_t tab, ITEM **my_items, + uint8_t elements, uint8_t position) +{ + delete_filtering_menu(*my_menu, my_items, elements); + return draw_filtering_menu(position, filter_win, tab, my_menu); +} + +static void +filter_columns(uint8_t tab) +{ + const int WINDOW_HEADER_LEN = 5; + const int WINDOW_BORDER_LEN = 8; + const int WINDOW_HEADER_END_LINE = 2; + const int WINDOW_COLUMNS = 2; + struct col_desc *col_desc = g_col_desc[tab]; + PANEL *filter_panel; + WINDOW *filter_win; + ITEM **my_items; + MENU *my_menu = NULL; + int i, c, elements; + bool stop_loop = false; + ITEM *cur; + void (*p)(enum tabs tab); + uint8_t current_index, len = 0; + + for (i = 0; col_desc[i].name != NULL; ++i) { + len = spdk_max(col_desc[i].name_len, len); + } + + elements = i; + + filter_win = newwin(elements + WINDOW_HEADER_LEN, len + WINDOW_BORDER_LEN, + (g_max_row - elements - 1) / 2, (g_max_col - len) / 2); + keypad(filter_win, TRUE); + filter_panel = new_panel(filter_win); + + top_panel(filter_panel); + update_panels(); + doupdate(); + + box(filter_win, 0, 0); + + print_in_middle(filter_win, 1, 0, len + WINDOW_BORDER_LEN, "Filtering", COLOR_PAIR(3)); + mvwaddch(filter_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE); + mvwhline(filter_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, len + WINDOW_BORDER_LEN - 2); + mvwaddch(filter_win, WINDOW_HEADER_END_LINE, len + WINDOW_BORDER_LEN - 1, ACS_RTEE); + + my_items = draw_filtering_menu(0, filter_win, tab, &my_menu); + if (my_items == NULL || my_menu == NULL) { + goto fail; + } + + while (!stop_loop) { + c = wgetch(filter_win); + + switch (c) { + case KEY_DOWN: + menu_driver(my_menu, REQ_DOWN_ITEM); + break; + case KEY_UP: + menu_driver(my_menu, REQ_UP_ITEM); + break; + case 27: /* ESC */ + case 'q': + stop_loop = true; + break; + case ' ': /* Space */ + cur = current_item(my_menu); + current_index = item_index(cur) / WINDOW_COLUMNS; + col_desc[current_index].disabled = !col_desc[current_index].disabled; + my_items = refresh_filtering_menu(&my_menu, filter_win, tab, my_items, elements, + item_index(cur) + 1); + if (my_items == NULL || my_menu == NULL) { + goto fail; + } + break; + case 10: /* Enter */ + cur = current_item(my_menu); + current_index = item_index(cur) / WINDOW_COLUMNS; + if (current_index == elements) { + stop_loop = true; + p = item_userptr(cur); + p(tab); + } else { + col_desc[current_index].disabled = !col_desc[current_index].disabled; + my_items = refresh_filtering_menu(&my_menu, filter_win, tab, my_items, elements, + item_index(cur) + 1); + if (my_items == NULL || my_menu == NULL) { + goto fail; + } + } + break; + } + wrefresh(filter_win); + } + + delete_filtering_menu(my_menu, my_items, elements); + + del_panel(filter_panel); + delwin(filter_win); + + wclear(g_menu_win); + draw_menu_win(); + return; + +fail: + fprintf(stderr, "Unable to filter the columns due to allocation failure.\n"); + assert(false); +} + +static void +sort_type(enum tabs tab, int item_index) +{ + g_current_sort_col[tab] = item_index; + wclear(g_tabs[tab]); + draw_tabs(tab, g_current_sort_col[tab]); +} + +static void +change_sorting(uint8_t tab) +{ + const int WINDOW_HEADER_LEN = 4; + const int WINDOW_BORDER_LEN = 3; + const int WINDOW_START_X = 1; + const int WINDOW_START_Y = 3; + const int WINDOW_HEADER_END_LINE = 2; + PANEL *sort_panel; + WINDOW *sort_win; + ITEM **my_items; + MENU *my_menu; + int i, c, elements; + bool stop_loop = false; + ITEM *cur; + void (*p)(enum tabs tab, int item_index); + uint8_t len = 0; + + for (i = 0; g_col_desc[tab][i].name != NULL; ++i) { + len = spdk_max(len, g_col_desc[tab][i].name_len); + } + + elements = i; + + my_items = (ITEM **)calloc(elements + 1, sizeof(ITEM *)); + if (my_items == NULL) { + fprintf(stderr, "Unable to allocate an item list in change_sorting.\n"); + return; + } + + for (i = 0; i < elements; ++i) { + my_items[i] = new_item(g_col_desc[tab][i].name, NULL); + set_item_userptr(my_items[i], sort_type); + } + + my_menu = new_menu((ITEM **)my_items); + + menu_opts_off(my_menu, O_SHOWDESC); + + sort_win = newwin(elements + WINDOW_HEADER_LEN, len + WINDOW_BORDER_LEN, (g_max_row - elements) / 2, + (g_max_col - len) / 2); + keypad(sort_win, TRUE); + sort_panel = new_panel(sort_win); + + top_panel(sort_panel); + update_panels(); + doupdate(); + + set_menu_win(my_menu, sort_win); + set_menu_sub(my_menu, derwin(sort_win, elements, len + 1, WINDOW_START_Y, WINDOW_START_X)); + box(sort_win, 0, 0); + + print_in_middle(sort_win, 1, 0, len + WINDOW_BORDER_LEN, "Sorting", COLOR_PAIR(3)); + mvwaddch(sort_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE); + mvwhline(sort_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, len + 1); + mvwaddch(sort_win, WINDOW_HEADER_END_LINE, len + WINDOW_BORDER_LEN - 1, ACS_RTEE); + + post_menu(my_menu); + refresh(); + wrefresh(sort_win); + + while (!stop_loop) { + c = wgetch(sort_win); + + switch (c) { + case KEY_DOWN: + menu_driver(my_menu, REQ_DOWN_ITEM); + break; + case KEY_UP: + menu_driver(my_menu, REQ_UP_ITEM); + break; + case 27: /* ESC */ + stop_loop = true; + break; + case 10: /* Enter */ + stop_loop = true; + cur = current_item(my_menu); + p = item_userptr(cur); + p(tab, item_index(cur)); + break; + } + wrefresh(sort_win); + } + + unpost_menu(my_menu); + free_menu(my_menu); + + for (i = 0; i < elements; ++i) { + free_item(my_items[i]); + } + + free(my_items); + + del_panel(sort_panel); + delwin(sort_win); + + wclear(g_menu_win); + draw_menu_win(); +} + +static void +change_refresh_rate(void) +{ + const int WINDOW_HEADER_END_LINE = 2; + PANEL *refresh_panel; + WINDOW *refresh_win; + int c; + bool stop_loop = false; + uint32_t rr_tmp, refresh_rate = 0; + char refresh_rate_str[MAX_STRING_LEN]; + + refresh_win = newwin(RR_WIN_HEIGHT, RR_WIN_WIDTH, (g_max_row - RR_WIN_HEIGHT - 1) / 2, + (g_max_col - RR_WIN_WIDTH) / 2); + keypad(refresh_win, TRUE); + refresh_panel = new_panel(refresh_win); + + top_panel(refresh_panel); + update_panels(); + doupdate(); + + box(refresh_win, 0, 0); + + print_in_middle(refresh_win, 1, 0, RR_WIN_WIDTH + 1, "Enter refresh rate value [s]", COLOR_PAIR(3)); + mvwaddch(refresh_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE); + mvwhline(refresh_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, RR_WIN_WIDTH - 2); + mvwaddch(refresh_win, WINDOW_HEADER_END_LINE, RR_WIN_WIDTH, ACS_RTEE); + mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1, (RR_WIN_WIDTH - 1) / 2, "%d", refresh_rate); + + refresh(); + wrefresh(refresh_win); + + while (!stop_loop) { + c = wgetch(refresh_win); + + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + rr_tmp = refresh_rate * 10 + c - '0'; + + if (rr_tmp <= RR_MAX_VALUE) { + refresh_rate = rr_tmp; + snprintf(refresh_rate_str, MAX_STRING_LEN - 1, "%d", refresh_rate); + mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1, + (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str)) / 2, "%d", refresh_rate); + refresh(); + wrefresh(refresh_win); + } + break; + case KEY_BACKSPACE: + case 127: + case '\b': + refresh_rate = refresh_rate / 10; + snprintf(refresh_rate_str, MAX_STRING_LEN - 1, "%d", refresh_rate); + mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1, + (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str) - 2) / 2, " "); + mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1, + (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str)) / 2, "%d", refresh_rate); + refresh(); + wrefresh(refresh_win); + break; + case 27: /* ESC */ + case 'q': + stop_loop = true; + break; + case 10: /* Enter */ + g_sleep_time = refresh_rate; + stop_loop = true; + break; + } + wrefresh(refresh_win); + } + + del_panel(refresh_panel); + delwin(refresh_win); +} + +static void +free_resources(void) +{ + struct run_counter_history *history, *tmp; + + TAILQ_FOREACH_SAFE(history, &g_run_counter_history, link, tmp) { + TAILQ_REMOVE(&g_run_counter_history, history, link); + free(history->poller_name); + free(history); + } +} + +static void +show_stats(void) +{ + const int CURRENT_PAGE_STR_LEN = 50; + const char *refresh_error = "ERROR occurred while getting data"; + long int time_last, time_dif; + struct timespec time_now; + int c, rc; + int max_row, max_col; + uint8_t active_tab = THREADS_TAB; + uint8_t current_page = 0; + uint8_t max_pages = 1; + char current_page_str[CURRENT_PAGE_STR_LEN]; + bool force_refresh = true; + + clock_gettime(CLOCK_REALTIME, &time_now); + time_last = time_now.tv_sec; + + switch_tab(THREADS_TAB); + + while (1) { + /* Check if interface has to be resized (terminal size changed) */ + getmaxyx(stdscr, max_row, max_col); + + if (max_row != g_max_row || max_col != g_max_col) { + g_max_row = max_row; + g_max_col = max_col; + g_data_win_size = g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - TABS_DATA_START_ROW; + g_max_data_rows = g_max_row - WINDOW_HEADER; + resize_interface(active_tab); + } + + c = getch(); + if (c == 'q') { + free_resources(); + break; + } + + force_refresh = true; + + switch (c) { + case '1': + case '2': + case '3': + active_tab = c - '1'; + current_page = 0; + switch_tab(active_tab); + break; + case '\t': + if (active_tab < NUMBER_OF_TABS - 1) { + active_tab++; + } else { + active_tab = THREADS_TAB; + } + current_page = 0; + switch_tab(active_tab); + break; + case 's': + change_sorting(active_tab); + break; + case 'c': + filter_columns(active_tab); + break; + case 'r': + change_refresh_rate(); + break; + case 54: /* PgDown */ + if (current_page + 1 < max_pages) { + current_page++; + } + wclear(g_tabs[active_tab]); + draw_tabs(active_tab, g_current_sort_col[active_tab]); + break; + case 53: /* PgUp */ + if (current_page > 0) { + current_page--; + } + wclear(g_tabs[active_tab]); + draw_tabs(active_tab, g_current_sort_col[active_tab]); + break; + default: + force_refresh = false; + break; + } + + clock_gettime(CLOCK_REALTIME, &time_now); + time_dif = time_now.tv_sec - time_last; + if (time_dif < 0) { + time_dif = g_sleep_time; + } + + if (time_dif >= g_sleep_time || force_refresh) { + time_last = time_now.tv_sec; + rc = get_data(); + if (rc) { + mvprintw(g_max_row - 1, g_max_col - strlen(refresh_error) - 2, refresh_error); + } + + max_pages = refresh_tab(active_tab, current_page); + + snprintf(current_page_str, CURRENT_PAGE_STR_LEN - 1, "Page: %d/%d", current_page + 1, max_pages); + mvprintw(g_max_row - 1, 1, current_page_str); + + free_data(); + + refresh(); + } + } +} + +static void +draw_interface(void) +{ + int i; + + getmaxyx(stdscr, g_max_row, g_max_col); + g_data_win_size = g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - TABS_DATA_START_ROW; + g_max_data_rows = g_max_row - WINDOW_HEADER; + + g_menu_win = newwin(MENU_WIN_HEIGHT, g_max_col, g_max_row - MENU_WIN_HEIGHT - 1, + MENU_WIN_LOCATION_COL); + draw_menu_win(); + + for (i = 0; i < NUMBER_OF_TABS; i++) { + g_tab_win[i] = newwin(TAB_WIN_HEIGHT, g_max_col / NUMBER_OF_TABS - TABS_SPACING, + TAB_WIN_LOCATION_ROW, g_max_col / NUMBER_OF_TABS * i + 1); + draw_tab_win(i); + + g_tabs[i] = newwin(g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - 2, g_max_col, TABS_LOCATION_ROW, + TABS_LOCATION_COL); + draw_tabs(i, g_current_sort_col[i]); + g_panels[i] = new_panel(g_tabs[i]); + } + + update_panels(); + doupdate(); +} + +static void finish(int sig) +{ + /* End ncurses mode */ + endwin(); + spdk_jsonrpc_client_close(g_rpc_client); + exit(0); +} + +static void +setup_ncurses(void) +{ + clear(); + noecho(); + timeout(1); + curs_set(0); + start_color(); + init_pair(1, COLOR_BLACK, COLOR_GREEN); + init_pair(2, COLOR_BLACK, COLOR_WHITE); + init_pair(3, COLOR_YELLOW, COLOR_BLACK); + init_pair(4, COLOR_BLACK, COLOR_YELLOW); + + if (has_colors() == FALSE) { + endwin(); + printf("Your terminal does not support color\n"); + exit(1); + } + + /* Handle signals to exit gracfully cleaning up ncurses */ + (void) signal(SIGINT, finish); + (void) signal(SIGPIPE, finish); + (void) signal(SIGABRT, finish); +} + +static void +usage(const char *program_name) +{ + printf("%s [options]", program_name); + printf("\n"); + printf("options:\n"); + printf(" -r <path> RPC listen address (default: /var/tmp/spdk.sock\n"); + printf(" -h show this usage\n"); +} + +int main(int argc, char **argv) +{ + int op; + char *socket = SPDK_DEFAULT_RPC_ADDR; + + while ((op = getopt(argc, argv, "r:h")) != -1) { + switch (op) { + case 'r': + socket = optarg; + break; + case 'H': + default: + usage(argv[0]); + return 1; + } + } + + g_rpc_client = spdk_jsonrpc_client_connect(socket, AF_UNIX); + if (!g_rpc_client) { + fprintf(stderr, "spdk_jsonrpc_client_connect() failed: %d\n", errno); + return 1; + } + + initscr(); + init_str_len(); + setup_ncurses(); + draw_interface(); + show_stats(); + + finish(0); + + return (0); +} diff --git a/src/spdk/app/trace/.gitignore b/src/spdk/app/trace/.gitignore new file mode 100644 index 000000000..bbb611b88 --- /dev/null +++ b/src/spdk/app/trace/.gitignore @@ -0,0 +1 @@ +spdk_trace diff --git a/src/spdk/app/trace/Makefile b/src/spdk/app/trace/Makefile new file mode 100644 index 000000000..92df0857f --- /dev/null +++ b/src/spdk/app/trace/Makefile @@ -0,0 +1,42 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = spdk_trace + +CXX_SRCS := trace.cpp + +include $(SPDK_ROOT_DIR)/mk/spdk.app_cxx.mk diff --git a/src/spdk/app/trace/trace.cpp b/src/spdk/app/trace/trace.cpp new file mode 100644 index 000000000..71350e85c --- /dev/null +++ b/src/spdk/app/trace/trace.cpp @@ -0,0 +1,462 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * 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 Intel Corporation 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 "spdk/stdinc.h" + +#include <map> + +extern "C" { +#include "spdk/trace.h" +#include "spdk/util.h" +} + +static struct spdk_trace_histories *g_histories; +static bool g_print_tsc = false; + +static void usage(void); + +struct entry_key { + entry_key(uint16_t _lcore, uint64_t _tsc) : lcore(_lcore), tsc(_tsc) {} + uint16_t lcore; + uint64_t tsc; +}; + +class compare_entry_key +{ +public: + bool operator()(const entry_key &first, const entry_key &second) const + { + if (first.tsc == second.tsc) { + return first.lcore < second.lcore; + } else { + return first.tsc < second.tsc; + } + } +}; + +typedef std::map<entry_key, spdk_trace_entry *, compare_entry_key> entry_map; + +entry_map g_entry_map; + +struct object_stats { + + std::map<uint64_t, uint64_t> start; + std::map<uint64_t, uint64_t> index; + std::map<uint64_t, uint64_t> size; + std::map<uint64_t, uint64_t> tpoint_id; + uint64_t counter; + + object_stats() : start(), index(), size(), tpoint_id(), counter(0) {} +}; + +struct object_stats g_stats[SPDK_TRACE_MAX_OBJECT]; + +static char *g_exe_name; +static int g_verbose = 1; + +static uint64_t g_tsc_rate; +static uint64_t g_first_tsc = 0x0; + +static float +get_us_from_tsc(uint64_t tsc, uint64_t tsc_rate) +{ + return ((float)tsc) * 1000 * 1000 / tsc_rate; +} + +static void +print_ptr(const char *arg_string, uint64_t arg) +{ + printf("%-7.7s0x%-14jx ", arg_string, arg); +} + +static void +print_uint64(const char *arg_string, uint64_t arg) +{ + /* + * Print arg as signed, since -1 is a common value especially + * for FLUSH WRITEBUF when writev() returns -1 due to full + * socket buffer. + */ + printf("%-7.7s%-16jd ", arg_string, arg); +} + +static void +print_string(const char *arg_string, uint64_t arg) +{ + char *str = (char *)&arg; + printf("%-7.7s%.8s ", arg_string, str); +} + +static void +print_size(uint32_t size) +{ + if (size > 0) { + printf("size: %6u ", size); + } else { + printf("%13s", " "); + } +} + +static void +print_object_id(uint8_t type, uint64_t id) +{ + printf("id: %c%-15jd ", g_histories->flags.object[type].id_prefix, id); +} + +static void +print_float(const char *arg_string, float arg) +{ + printf("%-7s%-16.3f ", arg_string, arg); +} + +static void +print_arg(uint8_t arg_type, const char *arg_string, uint64_t arg) +{ + if (arg_string[0] == 0) { + printf("%24s", ""); + return; + } + + switch (arg_type) { + case SPDK_TRACE_ARG_TYPE_PTR: + print_ptr(arg_string, arg); + break; + case SPDK_TRACE_ARG_TYPE_INT: + print_uint64(arg_string, arg); + break; + case SPDK_TRACE_ARG_TYPE_STR: + print_string(arg_string, arg); + break; + } +} + +static void +print_event(struct spdk_trace_entry *e, uint64_t tsc_rate, + uint64_t tsc_offset, uint16_t lcore) +{ + struct spdk_trace_tpoint *d; + struct object_stats *stats; + float us; + + d = &g_histories->flags.tpoint[e->tpoint_id]; + stats = &g_stats[d->object_type]; + + if (d->new_object) { + stats->index[e->object_id] = stats->counter++; + stats->tpoint_id[e->object_id] = e->tpoint_id; + stats->start[e->object_id] = e->tsc; + stats->size[e->object_id] = e->size; + } + + us = get_us_from_tsc(e->tsc - tsc_offset, tsc_rate); + + printf("%2d: %10.3f ", lcore, us); + if (g_print_tsc) { + printf("(%9ju) ", e->tsc - tsc_offset); + } + if (g_histories->flags.owner[d->owner_type].id_prefix) { + printf("%c%02d ", g_histories->flags.owner[d->owner_type].id_prefix, e->poller_id); + } else { + printf("%4s", " "); + } + + printf("%-*s ", (int)sizeof(d->name), d->name); + print_size(e->size); + + print_arg(d->arg1_type, d->arg1_name, e->arg1); + if (d->new_object) { + print_object_id(d->object_type, stats->index[e->object_id]); + } else if (d->object_type != OBJECT_NONE) { + if (stats->start.find(e->object_id) != stats->start.end()) { + us = get_us_from_tsc(e->tsc - stats->start[e->object_id], + tsc_rate); + print_object_id(d->object_type, stats->index[e->object_id]); + print_float("time:", us); + } else { + printf("id: N/A"); + } + } else if (e->object_id != 0) { + print_arg(SPDK_TRACE_ARG_TYPE_PTR, "object: ", e->object_id); + } + printf("\n"); +} + +static void +process_event(struct spdk_trace_entry *e, uint64_t tsc_rate, + uint64_t tsc_offset, uint16_t lcore) +{ + if (g_verbose) { + print_event(e, tsc_rate, tsc_offset, lcore); + } +} + +static int +populate_events(struct spdk_trace_history *history, int num_entries) +{ + int i, num_entries_filled; + struct spdk_trace_entry *e; + int first, last, lcore; + + lcore = history->lcore; + + e = history->entries; + + num_entries_filled = num_entries; + while (e[num_entries_filled - 1].tsc == 0) { + num_entries_filled--; + } + + if (num_entries == num_entries_filled) { + first = last = 0; + for (i = 1; i < num_entries; i++) { + if (e[i].tsc < e[first].tsc) { + first = i; + } + if (e[i].tsc > e[last].tsc) { + last = i; + } + } + } else { + first = 0; + last = num_entries_filled - 1; + } + + /* + * We keep track of the highest first TSC out of all reactors. + * We will ignore any events that occured before this TSC on any + * other reactors. This will ensure we only print data for the + * subset of time where we have data across all reactors. + */ + if (e[first].tsc > g_first_tsc) { + g_first_tsc = e[first].tsc; + } + + i = first; + while (1) { + g_entry_map[entry_key(lcore, e[i].tsc)] = &e[i]; + if (i == last) { + break; + } + i++; + if (i == num_entries_filled) { + i = 0; + } + } + + return (0); +} + +static void usage(void) +{ + fprintf(stderr, "usage:\n"); + fprintf(stderr, " %s <option> <lcore#>\n", g_exe_name); + fprintf(stderr, " option = '-q' to disable verbose mode\n"); + fprintf(stderr, " '-c' to display single lcore history\n"); + fprintf(stderr, " '-t' to display TSC offset for each event\n"); + fprintf(stderr, " '-s' to specify spdk_trace shm name for a\n"); + fprintf(stderr, " currently running process\n"); + fprintf(stderr, " '-i' to specify the shared memory ID\n"); + fprintf(stderr, " '-p' to specify the trace PID\n"); + fprintf(stderr, " (If -s is specified, then one of\n"); + fprintf(stderr, " -i or -p must be specified)\n"); + fprintf(stderr, " '-f' to specify a tracepoint file name\n"); + fprintf(stderr, " (-s and -f are mutually exclusive)\n"); +} + +int main(int argc, char **argv) +{ + void *history_ptr; + struct spdk_trace_history *history; + int fd, i, rc; + int lcore = SPDK_TRACE_MAX_LCORE; + uint64_t tsc_offset; + const char *app_name = NULL; + const char *file_name = NULL; + int op; + char shm_name[64]; + int shm_id = -1, shm_pid = -1; + uint64_t trace_histories_size; + struct stat _stat; + + g_exe_name = argv[0]; + while ((op = getopt(argc, argv, "c:f:i:p:qs:t")) != -1) { + switch (op) { + case 'c': + lcore = atoi(optarg); + if (lcore > SPDK_TRACE_MAX_LCORE) { + fprintf(stderr, "Selected lcore: %d " + "exceeds maximum %d\n", lcore, + SPDK_TRACE_MAX_LCORE); + exit(1); + } + break; + case 'i': + shm_id = atoi(optarg); + break; + case 'p': + shm_pid = atoi(optarg); + break; + case 'q': + g_verbose = 0; + break; + case 's': + app_name = optarg; + break; + case 'f': + file_name = optarg; + break; + case 't': + g_print_tsc = true; + break; + default: + usage(); + exit(1); + } + } + + if (file_name != NULL && app_name != NULL) { + fprintf(stderr, "-f and -s are mutually exclusive\n"); + usage(); + exit(1); + } + + if (file_name == NULL && app_name == NULL) { + fprintf(stderr, "One of -f and -s must be specified\n"); + usage(); + exit(1); + } + + if (file_name) { + fd = open(file_name, O_RDONLY); + } else { + if (shm_id >= 0) { + snprintf(shm_name, sizeof(shm_name), "/%s_trace.%d", app_name, shm_id); + } else { + snprintf(shm_name, sizeof(shm_name), "/%s_trace.pid%d", app_name, shm_pid); + } + fd = shm_open(shm_name, O_RDONLY, 0600); + file_name = shm_name; + } + if (fd < 0) { + fprintf(stderr, "Could not open %s.\n", file_name); + usage(); + exit(-1); + } + + rc = fstat(fd, &_stat); + if (rc < 0) { + fprintf(stderr, "Could not get size of %s.\n", file_name); + usage(); + exit(-1); + } + if ((size_t)_stat.st_size < sizeof(*g_histories)) { + fprintf(stderr, "%s is not a valid trace file\n", file_name); + usage(); + exit(-1); + } + + /* Map the header of trace file */ + history_ptr = mmap(NULL, sizeof(*g_histories), PROT_READ, MAP_SHARED, fd, 0); + if (history_ptr == MAP_FAILED) { + fprintf(stderr, "Could not mmap %s.\n", file_name); + usage(); + exit(-1); + } + + g_histories = (struct spdk_trace_histories *)history_ptr; + + g_tsc_rate = g_histories->flags.tsc_rate; + if (g_tsc_rate == 0) { + fprintf(stderr, "Invalid tsc_rate %ju\n", g_tsc_rate); + usage(); + exit(-1); + } + + if (g_verbose) { + printf("TSC Rate: %ju\n", g_tsc_rate); + } + + /* Remap the entire trace file */ + trace_histories_size = spdk_get_trace_histories_size(g_histories); + munmap(history_ptr, sizeof(*g_histories)); + if ((size_t)_stat.st_size < trace_histories_size) { + fprintf(stderr, "%s is not a valid trace file\n", file_name); + usage(); + exit(-1); + } + history_ptr = mmap(NULL, trace_histories_size, PROT_READ, MAP_SHARED, fd, 0); + if (history_ptr == MAP_FAILED) { + fprintf(stderr, "Could not mmap %s.\n", file_name); + usage(); + exit(-1); + } + + g_histories = (struct spdk_trace_histories *)history_ptr; + + if (lcore == SPDK_TRACE_MAX_LCORE) { + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + history = spdk_get_per_lcore_history(g_histories, i); + if (history->num_entries == 0 || history->entries[0].tsc == 0) { + continue; + } + + if (g_verbose && history->num_entries) { + printf("Trace Size of lcore (%d): %ju\n", i, history->num_entries); + } + + populate_events(history, history->num_entries); + } + } else { + history = spdk_get_per_lcore_history(g_histories, lcore); + if (history->num_entries > 0 && history->entries[0].tsc != 0) { + if (g_verbose && history->num_entries) { + printf("Trace Size of lcore (%d): %ju\n", lcore, history->num_entries); + } + + populate_events(history, history->num_entries); + } + } + + tsc_offset = g_first_tsc; + for (entry_map::iterator it = g_entry_map.begin(); it != g_entry_map.end(); it++) { + if (it->first.tsc < g_first_tsc) { + continue; + } + process_event(it->second, g_tsc_rate, tsc_offset, it->first.lcore); + } + + munmap(history_ptr, trace_histories_size); + close(fd); + + return (0); +} diff --git a/src/spdk/app/trace_record/.gitignore b/src/spdk/app/trace_record/.gitignore new file mode 100644 index 000000000..a0fd6b1e0 --- /dev/null +++ b/src/spdk/app/trace_record/.gitignore @@ -0,0 +1 @@ +spdk_trace_record diff --git a/src/spdk/app/trace_record/Makefile b/src/spdk/app/trace_record/Makefile new file mode 100644 index 000000000..a198b0e72 --- /dev/null +++ b/src/spdk/app/trace_record/Makefile @@ -0,0 +1,43 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk + +SPDK_LIB_LIST = util log + +APP = spdk_trace_record + +C_SRCS := trace_record.c + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/app/trace_record/trace_record.c b/src/spdk/app/trace_record/trace_record.c new file mode 100644 index 000000000..ac815b020 --- /dev/null +++ b/src/spdk/app/trace_record/trace_record.c @@ -0,0 +1,704 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * 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 Intel Corporation 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 "spdk/stdinc.h" + +#include "spdk/env.h" +#include "spdk/string.h" +#include "spdk/trace.h" +#include "spdk/util.h" +#include "spdk/barrier.h" + +#define TRACE_FILE_COPY_SIZE (32 * 1024) +#define TRACE_PATH_MAX 2048 + +static char *g_exe_name; +static int g_verbose = 1; +static uint64_t g_tsc_rate; +static uint64_t g_utsc_rate; +static bool g_shutdown = false; +static uint64_t g_histories_size; + +struct lcore_trace_record_ctx { + char lcore_file[TRACE_PATH_MAX]; + int fd; + struct spdk_trace_history *in_history; + struct spdk_trace_history *out_history; + + /* Recorded next entry index in record */ + uint64_t rec_next_entry; + + /* Record tsc for report */ + uint64_t first_entry_tsc; + uint64_t last_entry_tsc; + + /* Total number of entries in lcore trace file */ + uint64_t num_entries; +}; + +struct aggr_trace_record_ctx { + const char *out_file; + int out_fd; + int shm_fd; + struct lcore_trace_record_ctx lcore_ports[SPDK_TRACE_MAX_LCORE]; + struct spdk_trace_histories *trace_histories; +}; + +static int +input_trace_file_mmap(struct aggr_trace_record_ctx *ctx, const char *shm_name) +{ + void *history_ptr; + int i; + + ctx->shm_fd = shm_open(shm_name, O_RDONLY, 0); + if (ctx->shm_fd < 0) { + fprintf(stderr, "Could not open %s.\n", shm_name); + return -1; + } + + /* Map the header of trace file */ + history_ptr = mmap(NULL, sizeof(struct spdk_trace_histories), PROT_READ, MAP_SHARED, ctx->shm_fd, + 0); + if (history_ptr == MAP_FAILED) { + fprintf(stderr, "Could not mmap shm %s.\n", shm_name); + close(ctx->shm_fd); + return -1; + } + + ctx->trace_histories = (struct spdk_trace_histories *)history_ptr; + + g_tsc_rate = ctx->trace_histories->flags.tsc_rate; + g_utsc_rate = g_tsc_rate / 1000; + if (g_tsc_rate == 0) { + fprintf(stderr, "Invalid tsc_rate %ju\n", g_tsc_rate); + munmap(history_ptr, sizeof(struct spdk_trace_histories)); + close(ctx->shm_fd); + return -1; + } + + if (g_verbose) { + printf("TSC Rate: %ju\n", g_tsc_rate); + } + + /* Remap the entire trace file */ + g_histories_size = spdk_get_trace_histories_size(ctx->trace_histories); + munmap(history_ptr, sizeof(struct spdk_trace_histories)); + history_ptr = mmap(NULL, g_histories_size, PROT_READ, MAP_SHARED, ctx->shm_fd, 0); + if (history_ptr == MAP_FAILED) { + fprintf(stderr, "Could not remmap shm %s.\n", shm_name); + close(ctx->shm_fd); + return -1; + } + + ctx->trace_histories = (struct spdk_trace_histories *)history_ptr; + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + ctx->lcore_ports[i].in_history = spdk_get_per_lcore_history(ctx->trace_histories, i); + + if (g_verbose) { + printf("Number of trace entries for lcore (%d): %ju\n", i, + ctx->lcore_ports[i].in_history->num_entries); + } + } + + return 0; +} + +static int +output_trace_files_prepare(struct aggr_trace_record_ctx *ctx, const char *aggr_path) +{ + int flags = O_CREAT | O_EXCL | O_RDWR; + struct lcore_trace_record_ctx *port_ctx; + int name_len; + int i, rc; + + /* Assign file names for related trace files */ + ctx->out_file = aggr_path; + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + port_ctx = &ctx->lcore_ports[i]; + + /* Get the length of trace file name for each lcore with format "%s-%d" */ + name_len = snprintf(port_ctx->lcore_file, TRACE_PATH_MAX, "%s-%d", ctx->out_file, i); + if (name_len >= TRACE_PATH_MAX) { + fprintf(stderr, "Length of file path (%s) exceeds limitation for lcore file.\n", + aggr_path); + goto err; + } + } + + /* If output trace file already exists, try to unlink it together with its temporary files */ + if (access(ctx->out_file, F_OK) == 0) { + rc = unlink(ctx->out_file); + if (rc) { + goto err; + } + + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + port_ctx = &ctx->lcore_ports[i]; + if (access(port_ctx->lcore_file, F_OK) == 0) { + rc = unlink(port_ctx->lcore_file); + if (rc) { + goto err; + } + } + } + + } + + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + port_ctx = &ctx->lcore_ports[i]; + + port_ctx->fd = open(port_ctx->lcore_file, flags, 0600); + if (port_ctx->fd < 0) { + fprintf(stderr, "Could not open lcore file %s.\n", port_ctx->lcore_file); + goto err; + } + + if (g_verbose) { + printf("Create tmp lcore trace file %s for lcore %d\n", port_ctx->lcore_file, i); + } + + port_ctx->out_history = calloc(1, sizeof(struct spdk_trace_history)); + if (port_ctx->out_history == NULL) { + fprintf(stderr, "Failed to allocate memory for out_history.\n"); + goto err; + } + } + + return 0; + +err: + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + port_ctx = &ctx->lcore_ports[i]; + free(port_ctx->out_history); + + if (port_ctx->fd > 0) { + close(port_ctx->fd); + } + } + + return -1; +} + +static void +output_trace_files_finish(struct aggr_trace_record_ctx *ctx) +{ + struct lcore_trace_record_ctx *port_ctx; + int i; + + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + port_ctx = &ctx->lcore_ports[i]; + + free(port_ctx->out_history); + close(port_ctx->fd); + unlink(port_ctx->lcore_file); + + if (g_verbose) { + printf("Remove tmp lcore trace file %s for lcore %d\n", port_ctx->lcore_file, i); + } + } +} + +static int +cont_write(int fildes, const void *buf, size_t nbyte) +{ + int rc; + int _nbyte = nbyte; + + while (_nbyte) { + rc = write(fildes, buf, _nbyte); + if (rc < 0) { + if (errno != EINTR) { + return -1; + } + + continue; + } + + _nbyte -= rc; + } + + return nbyte; +} + +static int +cont_read(int fildes, void *buf, size_t nbyte) +{ + int rc; + int _nbyte = nbyte; + + while (_nbyte) { + rc = read(fildes, buf, _nbyte); + if (rc == 0) { + return nbyte - _nbyte; + } else if (rc < 0) { + if (errno != EINTR) { + return -1; + } + + continue; + } + + _nbyte -= rc; + } + + return nbyte; +} + +static int +lcore_trace_last_entry_idx(struct spdk_trace_history *in_history, int cir_next_idx) +{ + int last_idx; + + if (cir_next_idx == 0) { + last_idx = in_history->num_entries - 1; + } else { + last_idx = cir_next_idx - 1; + } + + return last_idx; +} + +static int +circular_buffer_padding_backward(int fd, struct spdk_trace_history *in_history, + int cir_start, int cir_end) +{ + int rc; + + if (cir_end <= cir_start) { + fprintf(stderr, "Wrong using of circular_buffer_padding_back\n"); + return -1; + } + + rc = cont_write(fd, &in_history->entries[cir_start], + sizeof(struct spdk_trace_entry) * (cir_end - cir_start)); + if (rc < 0) { + fprintf(stderr, "Failed to append entries into lcore file\n"); + return rc; + } + + return 0; +} + +static int +circular_buffer_padding_across(int fd, struct spdk_trace_history *in_history, + int cir_start, int cir_end) +{ + int rc; + int num_entries = in_history->num_entries; + + if (cir_end > cir_start) { + fprintf(stderr, "Wrong using of circular_buffer_padding_across\n"); + return -1; + } + + rc = cont_write(fd, &in_history->entries[cir_start], + sizeof(struct spdk_trace_entry) * (num_entries - cir_start)); + if (rc < 0) { + fprintf(stderr, "Failed to append entries into lcore file backward\n"); + return rc; + } + + if (cir_end == 0) { + return 0; + } + + rc = cont_write(fd, &in_history->entries[0], sizeof(struct spdk_trace_entry) * cir_end); + if (rc < 0) { + fprintf(stderr, "Failed to append entries into lcore file forward\n"); + return rc; + } + + return 0; +} + +static int +circular_buffer_padding_all(int fd, struct spdk_trace_history *in_history, + int cir_end) +{ + return circular_buffer_padding_across(fd, in_history, cir_end, cir_end); +} + +static int +lcore_trace_record(struct lcore_trace_record_ctx *lcore_port) +{ + struct spdk_trace_history *in_history = lcore_port->in_history; + uint64_t rec_next_entry = lcore_port->rec_next_entry; + uint64_t rec_num_entries = lcore_port->num_entries; + int fd = lcore_port->fd; + uint64_t shm_next_entry; + uint64_t num_cir_entries; + uint64_t shm_cir_next; + uint64_t rec_cir_next; + int rc; + int last_idx; + + shm_next_entry = in_history->next_entry; + + /* Ensure all entries of spdk_trace_history are latest to next_entry */ + spdk_smp_rmb(); + + if (shm_next_entry == rec_next_entry) { + /* There is no update */ + return 0; + } else if (shm_next_entry < rec_next_entry) { + /* Error branch */ + fprintf(stderr, "Trace porting error in lcore %d, trace rollback occurs.\n", in_history->lcore); + fprintf(stderr, "shm_next_entry is %ju, record_next_entry is %ju.\n", shm_next_entry, + rec_next_entry); + return -1; + } + + num_cir_entries = in_history->num_entries; + shm_cir_next = shm_next_entry & (num_cir_entries - 1); + + /* Record first entry's tsc and corresponding entries when recording first time. */ + if (lcore_port->first_entry_tsc == 0) { + if (shm_next_entry < num_cir_entries) { + /* Updates haven't been across circular buffer yet. + * The first entry in shared memory is the eldest one. + */ + lcore_port->first_entry_tsc = in_history->entries[0].tsc; + + lcore_port->num_entries += shm_cir_next; + rc = circular_buffer_padding_backward(fd, in_history, 0, shm_cir_next); + } else { + /* Updates have already been across circular buffer. + * The eldest entry in shared memory is pointed by shm_cir_next. + */ + lcore_port->first_entry_tsc = in_history->entries[shm_cir_next].tsc; + + lcore_port->num_entries += num_cir_entries; + rc = circular_buffer_padding_all(fd, in_history, shm_cir_next); + } + + goto out; + } + + if (shm_next_entry - rec_next_entry > num_cir_entries) { + /* There must be missed updates */ + fprintf(stderr, "Trace-record missed %ju trace entries\n", + shm_next_entry - rec_next_entry - num_cir_entries); + + lcore_port->num_entries += num_cir_entries; + rc = circular_buffer_padding_all(fd, in_history, shm_cir_next); + } else if (shm_next_entry - rec_next_entry == num_cir_entries) { + /* All circular buffer is updated */ + lcore_port->num_entries += num_cir_entries; + rc = circular_buffer_padding_all(fd, in_history, shm_cir_next); + } else { + /* Part of circular buffer is updated */ + rec_cir_next = rec_next_entry & (num_cir_entries - 1); + + if (shm_cir_next > rec_cir_next) { + /* Updates are not across circular buffer */ + lcore_port->num_entries += shm_cir_next - rec_cir_next; + rc = circular_buffer_padding_backward(fd, in_history, rec_cir_next, shm_cir_next); + } else { + /* Updates are across circular buffer */ + lcore_port->num_entries += num_cir_entries - rec_cir_next + shm_cir_next; + rc = circular_buffer_padding_across(fd, in_history, rec_cir_next, shm_cir_next); + } + } + +out: + if (rc) { + return rc; + } + + if (g_verbose) { + printf("Append %ju trace_entry for lcore %d\n", lcore_port->num_entries - rec_num_entries, + in_history->lcore); + } + + /* Update tpoint_count info */ + memcpy(lcore_port->out_history, lcore_port->in_history, sizeof(struct spdk_trace_history)); + + /* Update last_entry_tsc to align with appended entries */ + last_idx = lcore_trace_last_entry_idx(in_history, shm_cir_next); + lcore_port->last_entry_tsc = in_history->entries[last_idx].tsc; + lcore_port->rec_next_entry = shm_next_entry; + + return rc; +} + +static int +trace_files_aggregate(struct aggr_trace_record_ctx *ctx) +{ + int flags = O_CREAT | O_EXCL | O_RDWR; + struct lcore_trace_record_ctx *lcore_port; + char copy_buff[TRACE_FILE_COPY_SIZE]; + uint64_t lcore_offsets[SPDK_TRACE_MAX_LCORE + 1]; + int rc, i; + ssize_t len = 0; + uint64_t len_sum; + + ctx->out_fd = open(ctx->out_file, flags, 0600); + if (ctx->out_fd < 0) { + fprintf(stderr, "Could not open aggregation file %s.\n", ctx->out_file); + return -1; + } + + if (g_verbose) { + printf("Create trace file %s for output\n", ctx->out_file); + } + + /* Write flags of histories into head of converged trace file, except num_entriess */ + rc = cont_write(ctx->out_fd, ctx->trace_histories, + sizeof(struct spdk_trace_histories) - sizeof(lcore_offsets)); + if (rc < 0) { + fprintf(stderr, "Failed to write trace header into trace file\n"); + goto out; + } + + /* Update and append lcore offsets converged trace file */ + lcore_offsets[0] = sizeof(struct spdk_trace_flags); + for (i = 1; i < (int)SPDK_COUNTOF(lcore_offsets); i++) { + lcore_offsets[i] = spdk_get_trace_history_size(ctx->lcore_ports[i - 1].num_entries) + + lcore_offsets[i - 1]; + } + + rc = cont_write(ctx->out_fd, lcore_offsets, sizeof(lcore_offsets)); + if (rc < 0) { + fprintf(stderr, "Failed to write lcore offsets into trace file\n"); + goto out; + } + + /* Append each lcore trace file into converged trace file */ + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + lcore_port = &ctx->lcore_ports[i]; + + lcore_port->out_history->num_entries = lcore_port->num_entries; + rc = cont_write(ctx->out_fd, lcore_port->out_history, sizeof(struct spdk_trace_history)); + if (rc < 0) { + fprintf(stderr, "Failed to write lcore trace header into trace file\n"); + goto out; + } + + /* Move file offset to the start of trace_entries */ + rc = lseek(lcore_port->fd, 0, SEEK_SET); + if (rc != 0) { + fprintf(stderr, "Failed to lseek lcore trace file\n"); + goto out; + } + + len_sum = 0; + while ((len = cont_read(lcore_port->fd, copy_buff, TRACE_FILE_COPY_SIZE)) > 0) { + len_sum += len; + rc = cont_write(ctx->out_fd, copy_buff, len); + if (rc != len) { + fprintf(stderr, "Failed to write lcore trace entries into trace file\n"); + goto out; + } + } + + if (len_sum != lcore_port->num_entries * sizeof(struct spdk_trace_entry)) { + fprintf(stderr, "Len of lcore trace file doesn't match number of entries for lcore\n"); + } + } + + printf("All lcores trace entries are aggregated into trace file %s\n", ctx->out_file); + +out: + close(ctx->out_fd); + + return rc; +} + +static void +__shutdown_signal(int signo) +{ + g_shutdown = true; +} + +static int +setup_exit_signal_handler(void) +{ + struct sigaction sigact; + int rc; + + memset(&sigact, 0, sizeof(sigact)); + sigemptyset(&sigact.sa_mask); + /* Install the same handler for SIGINT and SIGTERM */ + sigact.sa_handler = __shutdown_signal; + + rc = sigaction(SIGINT, &sigact, NULL); + if (rc < 0) { + fprintf(stderr, "sigaction(SIGINT) failed\n"); + + return rc; + } + + rc = sigaction(SIGTERM, &sigact, NULL); + if (rc < 0) { + fprintf(stderr, "sigaction(SIGTERM) failed\n"); + } + + return rc; +} + +static void usage(void) +{ + printf("\n%s is used to record all SPDK generated trace entries\n", g_exe_name); + printf("from SPDK trace shared-memory to specified file.\n\n"); + printf("usage:\n"); + printf(" %s <option>\n", g_exe_name); + printf(" option = '-q' to disable verbose mode\n"); + printf(" '-s' to specify spdk_trace shm name for a\n"); + printf(" currently running process\n"); + printf(" '-i' to specify the shared memory ID\n"); + printf(" '-p' to specify the trace PID\n"); + printf(" (one of -i or -p must be specified)\n"); + printf(" '-f' to specify output trace file name\n"); + printf(" '-h' to print usage information\n"); +} + +int main(int argc, char **argv) +{ + const char *app_name = NULL; + const char *file_name = NULL; + int op; + char shm_name[64]; + int shm_id = -1, shm_pid = -1; + int rc = 0; + int i; + struct aggr_trace_record_ctx ctx = {}; + struct lcore_trace_record_ctx *lcore_port; + + g_exe_name = argv[0]; + while ((op = getopt(argc, argv, "f:i:p:qs:h")) != -1) { + switch (op) { + case 'i': + shm_id = spdk_strtol(optarg, 10); + break; + case 'p': + shm_pid = spdk_strtol(optarg, 10); + break; + case 'q': + g_verbose = 0; + break; + case 's': + app_name = optarg; + break; + case 'f': + file_name = optarg; + break; + case 'h': + default: + usage(); + exit(1); + } + } + + if (file_name == NULL) { + fprintf(stderr, "-f must be specified\n"); + usage(); + exit(1); + } + + if (app_name == NULL) { + fprintf(stderr, "-s must be specified\n"); + usage(); + exit(1); + } + + if (shm_id == -1 && shm_pid == -1) { + fprintf(stderr, "-i or -p must be specified\n"); + usage(); + exit(1); + } + + if (shm_id >= 0) { + snprintf(shm_name, sizeof(shm_name), "/%s_trace.%d", app_name, shm_id); + } else { + snprintf(shm_name, sizeof(shm_name), "/%s_trace.pid%d", app_name, shm_pid); + } + + rc = setup_exit_signal_handler(); + if (rc) { + exit(1); + } + + rc = input_trace_file_mmap(&ctx, shm_name); + if (rc) { + exit(1); + } + + rc = output_trace_files_prepare(&ctx, file_name); + if (rc) { + exit(1); + } + + printf("Start to poll trace shm file %s\n", shm_name); + while (!g_shutdown && rc == 0) { + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + lcore_port = &ctx.lcore_ports[i]; + + rc = lcore_trace_record(lcore_port); + if (rc) { + break; + } + } + } + + if (rc) { + exit(1); + } + + printf("Start to aggregate lcore trace files\n"); + rc = trace_files_aggregate(&ctx); + if (rc) { + exit(1); + } + + /* Summary report */ + printf("TSC Rate: %ju\n", g_tsc_rate); + for (i = 0; i < SPDK_TRACE_MAX_LCORE; i++) { + lcore_port = &ctx.lcore_ports[i]; + + if (lcore_port->num_entries == 0) { + continue; + } + + printf("Port %ju trace entries for lcore (%d) in %ju usec\n", + lcore_port->num_entries, i, + (lcore_port->last_entry_tsc - lcore_port->first_entry_tsc) / g_utsc_rate); + + } + + munmap(ctx.trace_histories, g_histories_size); + close(ctx.shm_fd); + + output_trace_files_finish(&ctx); + + return 0; +} diff --git a/src/spdk/app/vhost/.gitignore b/src/spdk/app/vhost/.gitignore new file mode 100644 index 000000000..5746bdd3a --- /dev/null +++ b/src/spdk/app/vhost/.gitignore @@ -0,0 +1 @@ +vhost diff --git a/src/spdk/app/vhost/Makefile b/src/spdk/app/vhost/Makefile new file mode 100644 index 000000000..4bb28b20e --- /dev/null +++ b/src/spdk/app/vhost/Makefile @@ -0,0 +1,58 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# 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 Intel Corporation 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. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..) +include $(SPDK_ROOT_DIR)/mk/spdk.common.mk +include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk + +APP = vhost + +C_SRCS := vhost.c + +SPDK_LIB_LIST = $(ALL_MODULES_LIST) +SPDK_LIB_LIST += vhost event_vhost + +ifeq ($(CONFIG_VHOST_INTERNAL_LIB),y) +SPDK_LIB_LIST += rte_vhost +endif + +SPDK_LIB_LIST += $(EVENT_BDEV_SUBSYSTEM) event_net event_scsi event +SPDK_LIB_LIST += jsonrpc json rpc bdev_rpc bdev scsi accel trace conf +SPDK_LIB_LIST += thread util log log_rpc app_rpc +SPDK_LIB_LIST += event_nbd nbd net sock notify + +ifeq ($(SPDK_ROOT_DIR)/lib/env_dpdk,$(CONFIG_ENV)) +SPDK_LIB_LIST += env_dpdk_rpc +endif + +include $(SPDK_ROOT_DIR)/mk/spdk.app.mk diff --git a/src/spdk/app/vhost/vhost.c b/src/spdk/app/vhost/vhost.c new file mode 100644 index 000000000..4457edd7d --- /dev/null +++ b/src/spdk/app/vhost/vhost.c @@ -0,0 +1,111 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * 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 Intel Corporation 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 "spdk/stdinc.h" + +#include "spdk/conf.h" +#include "spdk/event.h" + +#include "spdk/vhost.h" + +static const char *g_pid_path = NULL; + +static void +vhost_usage(void) +{ + printf(" -f <path> save pid to file under given path\n"); + printf(" -S <path> directory where to create vhost sockets (default: pwd)\n"); +} + +static void +save_pid(const char *pid_path) +{ + FILE *pid_file; + + pid_file = fopen(pid_path, "w"); + if (pid_file == NULL) { + fprintf(stderr, "Couldn't create pid file '%s': %s\n", pid_path, strerror(errno)); + exit(EXIT_FAILURE); + } + + fprintf(pid_file, "%d\n", getpid()); + fclose(pid_file); +} + +static int +vhost_parse_arg(int ch, char *arg) +{ + switch (ch) { + case 'f': + g_pid_path = arg; + break; + case 'S': + spdk_vhost_set_socket_path(arg); + break; + default: + return -EINVAL; + } + return 0; +} + +static void +vhost_started(void *arg1) +{ +} + +int +main(int argc, char *argv[]) +{ + struct spdk_app_opts opts = {}; + int rc; + + spdk_app_opts_init(&opts); + opts.name = "vhost"; + + if ((rc = spdk_app_parse_args(argc, argv, &opts, "f:S:", NULL, + vhost_parse_arg, vhost_usage)) != + SPDK_APP_PARSE_ARGS_SUCCESS) { + exit(rc); + } + + if (g_pid_path) { + save_pid(g_pid_path); + } + + /* Blocks until the application is exiting */ + rc = spdk_app_start(&opts, vhost_started, NULL); + + spdk_app_fini(); + + return rc; +} |