summaryrefslogtreecommitdiffstats
path: root/src/spdk/app
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/spdk/app
parentInitial commit. (diff)
downloadceph-upstream/16.2.11+ds.tar.xz
ceph-upstream/16.2.11+ds.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/spdk/app')
-rw-r--r--src/spdk/app/Makefile55
-rw-r--r--src/spdk/app/iscsi_tgt/.gitignore1
-rw-r--r--src/spdk/app/iscsi_tgt/Makefile59
-rw-r--r--src/spdk/app/iscsi_tgt/iscsi_tgt.c120
-rw-r--r--src/spdk/app/iscsi_top/.gitignore1
-rw-r--r--src/spdk/app/iscsi_top/Makefile45
-rw-r--r--src/spdk/app/iscsi_top/iscsi_top.cpp252
-rw-r--r--src/spdk/app/nvmf_tgt/.gitignore1
-rw-r--r--src/spdk/app/nvmf_tgt/Makefile62
-rw-r--r--src/spdk/app/nvmf_tgt/nvmf_main.c78
-rw-r--r--src/spdk/app/spdk_dd/.gitignore1
-rw-r--r--src/spdk/app/spdk_dd/Makefile47
-rw-r--r--src/spdk/app/spdk_dd/spdk_dd.c1174
-rw-r--r--src/spdk/app/spdk_lspci/.gitignore1
-rw-r--r--src/spdk/app/spdk_lspci/Makefile43
-rw-r--r--src/spdk/app/spdk_lspci/spdk_lspci.c123
-rw-r--r--src/spdk/app/spdk_tgt/.gitignore1
-rw-r--r--src/spdk/app/spdk_tgt/Makefile78
-rw-r--r--src/spdk/app/spdk_tgt/spdk_tgt.c124
-rw-r--r--src/spdk/app/spdk_top/.gitignore1
-rw-r--r--src/spdk/app/spdk_top/Makefile44
-rw-r--r--src/spdk/app/spdk_top/README74
-rw-r--r--src/spdk/app/spdk_top/spdk_top.c1982
-rw-r--r--src/spdk/app/trace/.gitignore1
-rw-r--r--src/spdk/app/trace/Makefile42
-rw-r--r--src/spdk/app/trace/trace.cpp462
-rw-r--r--src/spdk/app/trace_record/.gitignore1
-rw-r--r--src/spdk/app/trace_record/Makefile43
-rw-r--r--src/spdk/app/trace_record/trace_record.c704
-rw-r--r--src/spdk/app/vhost/.gitignore1
-rw-r--r--src/spdk/app/vhost/Makefile58
-rw-r--r--src/spdk/app/vhost/vhost.c111
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;
+}