xref: /spdk/app/spdk_top/spdk_top.c (revision e1d06d9954b871531c9b376069d620d2c6cee854)
1 /*-
2  *   BSD LICENSE
3  *
4  *   Copyright (c) Intel Corporation.
5  *   All rights reserved.
6  *
7  *   Redistribution and use in source and binary forms, with or without
8  *   modification, are permitted provided that the following conditions
9  *   are met:
10  *
11  *     * Redistributions of source code must retain the above copyright
12  *       notice, this list of conditions and the following disclaimer.
13  *     * Redistributions in binary form must reproduce the above copyright
14  *       notice, this list of conditions and the following disclaimer in
15  *       the documentation and/or other materials provided with the
16  *       distribution.
17  *     * Neither the name of Intel Corporation nor the names of its
18  *       contributors may be used to endorse or promote products derived
19  *       from this software without specific prior written permission.
20  *
21  *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24  *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25  *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26  *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27  *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28  *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29  *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30  *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31  *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 #include "spdk/stdinc.h"
35 #include "spdk/jsonrpc.h"
36 #include "spdk/rpc.h"
37 #include "spdk/event.h"
38 #include "spdk/util.h"
39 #include "spdk/env.h"
40 
41 #if defined __has_include
42 #if __has_include(<ncurses/panel.h>)
43 #include <ncurses/ncurses.h>
44 #include <ncurses/panel.h>
45 #include <ncurses/menu.h>
46 #else
47 #include <ncurses.h>
48 #include <panel.h>
49 #include <menu.h>
50 #endif
51 #else
52 #include <ncurses.h>
53 #include <panel.h>
54 #include <menu.h>
55 #endif
56 
57 #define RPC_MAX_THREADS 1024
58 #define RPC_MAX_POLLERS 1024
59 #define RPC_MAX_CORES 255
60 #define MAX_THREAD_NAME 128
61 #define MAX_POLLER_NAME 128
62 #define MAX_THREADS 4096
63 #define RR_MAX_VALUE 255
64 
65 #define MAX_STRING_LEN 12289 /* 3x 4k monitors + 1 */
66 #define TAB_WIN_HEIGHT 3
67 #define TAB_WIN_LOCATION_ROW 1
68 #define TABS_SPACING 2
69 #define TABS_LOCATION_ROW 4
70 #define TABS_LOCATION_COL 0
71 #define TABS_DATA_START_ROW 3
72 #define TABS_DATA_START_COL 2
73 #define TABS_COL_COUNT 10
74 #define MENU_WIN_HEIGHT 3
75 #define MENU_WIN_SPACING 4
76 #define MENU_WIN_LOCATION_COL 0
77 #define RR_WIN_WIDTH 32
78 #define RR_WIN_HEIGHT 5
79 #define MAX_THREAD_NAME_LEN 26
80 #define MAX_THREAD_COUNT_STR_LEN 14
81 #define MAX_POLLER_NAME_LEN 36
82 #define MAX_POLLER_COUNT_STR_LEN 16
83 #define MAX_POLLER_TYPE_STR_LEN 8
84 #define MAX_POLLER_IND_STR_LEN 8
85 #define MAX_CORE_MASK_STR_LEN 16
86 #define MAX_CORE_STR_LEN 6
87 #define MAX_CORE_FREQ_STR_LEN 18
88 #define MAX_TIME_STR_LEN 12
89 #define MAX_POLLER_RUN_COUNT 20
90 #define MAX_PERIOD_STR_LEN 12
91 #define WINDOW_HEADER 12
92 #define FROM_HEX 16
93 #define THREAD_WIN_WIDTH 69
94 #define THREAD_WIN_HEIGHT 9
95 #define THREAD_WIN_FIRST_COL 2
96 #define CORE_WIN_FIRST_COL 16
97 #define CORE_WIN_WIDTH 48
98 #define CORE_WIN_HEIGHT 11
99 #define POLLER_WIN_HEIGHT 8
100 #define POLLER_WIN_WIDTH 64
101 #define POLLER_WIN_FIRST_COL 14
102 #define FIRST_DATA_ROW 7
103 #define HELP_WIN_WIDTH 88
104 #define HELP_WIN_HEIGHT 22
105 
106 enum tabs {
107 	THREADS_TAB,
108 	POLLERS_TAB,
109 	CORES_TAB,
110 	NUMBER_OF_TABS,
111 };
112 
113 enum spdk_poller_type {
114 	SPDK_ACTIVE_POLLER,
115 	SPDK_TIMED_POLLER,
116 	SPDK_PAUSED_POLLER,
117 	SPDK_POLLER_TYPES_COUNT,
118 };
119 
120 struct col_desc {
121 	const char *name;
122 	uint8_t name_len;
123 	uint8_t max_data_string;
124 	bool disabled;
125 };
126 
127 struct run_counter_history {
128 	char *poller_name;
129 	uint64_t thread_id;
130 	uint64_t last_run_counter;
131 	TAILQ_ENTRY(run_counter_history) link;
132 };
133 
134 struct core_info {
135 	uint32_t core;
136 	uint64_t threads_count;
137 	uint64_t pollers_count;
138 	uint64_t idle;
139 	uint64_t last_idle;
140 	uint64_t busy;
141 	uint64_t last_busy;
142 	uint32_t core_freq;
143 };
144 
145 uint8_t g_sleep_time = 1;
146 uint16_t g_selected_row;
147 uint16_t g_max_selected_row;
148 const char *poller_type_str[SPDK_POLLER_TYPES_COUNT] = {"Active", "Timed", "Paused"};
149 const char *g_tab_title[NUMBER_OF_TABS] = {"[1] THREADS", "[2] POLLERS", "[3] CORES"};
150 struct spdk_jsonrpc_client *g_rpc_client;
151 static TAILQ_HEAD(, run_counter_history) g_run_counter_history = TAILQ_HEAD_INITIALIZER(
152 			g_run_counter_history);
153 struct core_info g_cores_history[RPC_MAX_CORES];
154 WINDOW *g_menu_win, *g_tab_win[NUMBER_OF_TABS], *g_tabs[NUMBER_OF_TABS];
155 PANEL *g_panels[NUMBER_OF_TABS];
156 uint16_t g_max_row, g_max_col;
157 uint16_t g_data_win_size, g_max_data_rows;
158 uint32_t g_last_threads_count, g_last_pollers_count, g_last_cores_count;
159 uint8_t g_current_sort_col[NUMBER_OF_TABS] = {0, 0, 0};
160 bool g_interval_data = true;
161 bool g_quit_app = false;
162 pthread_mutex_t g_thread_lock;
163 static struct col_desc g_col_desc[NUMBER_OF_TABS][TABS_COL_COUNT] = {
164 	{	{.name = "Thread name", .max_data_string = MAX_THREAD_NAME_LEN},
165 		{.name = "Core", .max_data_string = MAX_CORE_STR_LEN},
166 		{.name = "Active pollers", .max_data_string = MAX_POLLER_COUNT_STR_LEN},
167 		{.name = "Timed pollers", .max_data_string = MAX_POLLER_COUNT_STR_LEN},
168 		{.name = "Paused pollers", .max_data_string = MAX_POLLER_COUNT_STR_LEN},
169 		{.name = "Idle [us]", .max_data_string = MAX_TIME_STR_LEN},
170 		{.name = "Busy [us]", .max_data_string = MAX_TIME_STR_LEN},
171 		{.name = (char *)NULL}
172 	},
173 	{	{.name = "Poller name", .max_data_string = MAX_POLLER_NAME_LEN},
174 		{.name = "Type", .max_data_string = MAX_POLLER_TYPE_STR_LEN},
175 		{.name = "On thread", .max_data_string = MAX_THREAD_NAME_LEN},
176 		{.name = "Run count", .max_data_string = MAX_POLLER_RUN_COUNT},
177 		{.name = "Period [us]", .max_data_string = MAX_PERIOD_STR_LEN},
178 		{.name = "Status", .max_data_string = MAX_POLLER_IND_STR_LEN},
179 		{.name = (char *)NULL}
180 	},
181 	{	{.name = "Core", .max_data_string = MAX_CORE_STR_LEN},
182 		{.name = "Thread count", .max_data_string = MAX_THREAD_COUNT_STR_LEN},
183 		{.name = "Poller count", .max_data_string = MAX_POLLER_COUNT_STR_LEN},
184 		{.name = "Idle [us]", .max_data_string = MAX_TIME_STR_LEN},
185 		{.name = "Busy [us]", .max_data_string = MAX_TIME_STR_LEN},
186 		{.name = "Frequency [MHz]", .max_data_string = MAX_CORE_FREQ_STR_LEN},
187 		{.name = (char *)NULL}
188 	}
189 };
190 
191 struct rpc_thread_info {
192 	char *name;
193 	uint64_t id;
194 	int core_num;
195 	char *cpumask;
196 	uint64_t busy;
197 	uint64_t last_busy;
198 	uint64_t idle;
199 	uint64_t last_idle;
200 	uint64_t active_pollers_count;
201 	uint64_t timed_pollers_count;
202 	uint64_t paused_pollers_count;
203 };
204 
205 struct rpc_threads {
206 	uint64_t threads_count;
207 	struct rpc_thread_info thread_info[RPC_MAX_THREADS];
208 };
209 
210 struct rpc_threads_stats {
211 	uint64_t tick_rate;
212 	struct rpc_threads threads;
213 };
214 
215 struct rpc_poller_info {
216 	char *name;
217 	char *state;
218 	uint64_t run_count;
219 	uint64_t busy_count;
220 	uint64_t period_ticks;
221 	enum spdk_poller_type type;
222 	char thread_name[MAX_THREAD_NAME];
223 	uint64_t thread_id;
224 };
225 
226 struct rpc_pollers {
227 	uint64_t pollers_count;
228 	struct rpc_poller_info pollers[RPC_MAX_POLLERS];
229 };
230 
231 struct rpc_poller_thread_info {
232 	char *name;
233 	uint64_t id;
234 	struct rpc_pollers active_pollers;
235 	struct rpc_pollers timed_pollers;
236 	struct rpc_pollers paused_pollers;
237 };
238 
239 struct rpc_pollers_threads {
240 	uint64_t threads_count;
241 	struct rpc_poller_thread_info threads[RPC_MAX_THREADS];
242 };
243 
244 struct rpc_pollers_stats {
245 	uint64_t tick_rate;
246 	struct rpc_pollers_threads pollers_threads;
247 };
248 
249 struct rpc_core_thread_info {
250 	char *name;
251 	uint64_t id;
252 	char *cpumask;
253 	uint64_t elapsed;
254 };
255 
256 struct rpc_core_threads {
257 	uint64_t threads_count;
258 	struct rpc_core_thread_info thread[RPC_MAX_THREADS];
259 };
260 
261 struct rpc_core_info {
262 	uint32_t lcore;
263 	uint64_t busy;
264 	uint64_t idle;
265 	uint32_t core_freq;
266 	struct rpc_core_threads threads;
267 };
268 
269 struct rpc_cores {
270 	uint64_t cores_count;
271 	struct rpc_core_info core[RPC_MAX_CORES];
272 };
273 
274 struct rpc_cores_stats {
275 	uint64_t tick_rate;
276 	struct rpc_cores cores;
277 };
278 
279 struct rpc_threads_stats g_threads_stats;
280 struct rpc_pollers_stats g_pollers_stats;
281 struct rpc_cores_stats g_cores_stats;
282 
283 static void
284 init_str_len(void)
285 {
286 	int i, j;
287 
288 	for (i = 0; i < NUMBER_OF_TABS; i++) {
289 		for (j = 0; g_col_desc[i][j].name != NULL; j++) {
290 			g_col_desc[i][j].name_len = strlen(g_col_desc[i][j].name);
291 		}
292 	}
293 }
294 
295 static void
296 free_rpc_threads_stats(struct rpc_threads_stats *req)
297 {
298 	uint64_t i;
299 
300 	for (i = 0; i < req->threads.threads_count; i++) {
301 		free(req->threads.thread_info[i].name);
302 		req->threads.thread_info[i].name = NULL;
303 		free(req->threads.thread_info[i].cpumask);
304 		req->threads.thread_info[i].cpumask = NULL;
305 	}
306 }
307 
308 static const struct spdk_json_object_decoder rpc_thread_info_decoders[] = {
309 	{"name", offsetof(struct rpc_thread_info, name), spdk_json_decode_string},
310 	{"id", offsetof(struct rpc_thread_info, id), spdk_json_decode_uint64},
311 	{"cpumask", offsetof(struct rpc_thread_info, cpumask), spdk_json_decode_string},
312 	{"busy", offsetof(struct rpc_thread_info, busy), spdk_json_decode_uint64},
313 	{"idle", offsetof(struct rpc_thread_info, idle), spdk_json_decode_uint64},
314 	{"active_pollers_count", offsetof(struct rpc_thread_info, active_pollers_count), spdk_json_decode_uint64},
315 	{"timed_pollers_count", offsetof(struct rpc_thread_info, timed_pollers_count), spdk_json_decode_uint64},
316 	{"paused_pollers_count", offsetof(struct rpc_thread_info, paused_pollers_count), spdk_json_decode_uint64},
317 };
318 
319 static int
320 rpc_decode_threads_object(const struct spdk_json_val *val, void *out)
321 {
322 	struct rpc_thread_info *info = out;
323 
324 	return spdk_json_decode_object(val, rpc_thread_info_decoders,
325 				       SPDK_COUNTOF(rpc_thread_info_decoders), info);
326 }
327 
328 static int
329 rpc_decode_threads_array(const struct spdk_json_val *val, void *out)
330 {
331 	struct rpc_threads *threads = out;
332 
333 	return spdk_json_decode_array(val, rpc_decode_threads_object, threads->thread_info, RPC_MAX_THREADS,
334 				      &threads->threads_count, sizeof(struct rpc_thread_info));
335 }
336 
337 static const struct spdk_json_object_decoder rpc_threads_stats_decoders[] = {
338 	{"tick_rate", offsetof(struct rpc_threads_stats, tick_rate), spdk_json_decode_uint64},
339 	{"threads", offsetof(struct rpc_threads_stats, threads), rpc_decode_threads_array},
340 };
341 
342 static void
343 free_rpc_poller(struct rpc_poller_info *poller)
344 {
345 	free(poller->name);
346 	poller->name = NULL;
347 	free(poller->state);
348 	poller->state = NULL;
349 }
350 
351 static void
352 free_rpc_pollers_stats(struct rpc_pollers_stats *req)
353 {
354 	struct rpc_poller_thread_info *thread;
355 	uint64_t i, j;
356 
357 	for (i = 0; i < req->pollers_threads.threads_count; i++) {
358 		thread = &req->pollers_threads.threads[i];
359 
360 		for (j = 0; j < thread->active_pollers.pollers_count; j++) {
361 			free_rpc_poller(&thread->active_pollers.pollers[j]);
362 		}
363 
364 		for (j = 0; j < thread->timed_pollers.pollers_count; j++) {
365 			free_rpc_poller(&thread->timed_pollers.pollers[j]);
366 		}
367 
368 		for (j = 0; j < thread->paused_pollers.pollers_count; j++) {
369 			free_rpc_poller(&thread->paused_pollers.pollers[j]);
370 		}
371 
372 		free(thread->name);
373 		thread->name = NULL;
374 	}
375 }
376 
377 static void
378 free_rpc_cores_stats(struct rpc_cores_stats *req)
379 {
380 	struct rpc_core_info *core;
381 	struct rpc_core_thread_info *thread;
382 	uint64_t i, j;
383 
384 	for (i = 0; i < req->cores.cores_count; i++) {
385 		core = &req->cores.core[i];
386 
387 		for (j = 0; j < core->threads.threads_count; j++) {
388 			thread = &core->threads.thread[j];
389 
390 			free(thread->name);
391 			free(thread->cpumask);
392 		}
393 	}
394 }
395 
396 static const struct spdk_json_object_decoder rpc_pollers_decoders[] = {
397 	{"name", offsetof(struct rpc_poller_info, name), spdk_json_decode_string},
398 	{"state", offsetof(struct rpc_poller_info, state), spdk_json_decode_string},
399 	{"run_count", offsetof(struct rpc_poller_info, run_count), spdk_json_decode_uint64},
400 	{"busy_count", offsetof(struct rpc_poller_info, busy_count), spdk_json_decode_uint64},
401 	{"period_ticks", offsetof(struct rpc_poller_info, period_ticks), spdk_json_decode_uint64, true},
402 };
403 
404 static int
405 rpc_decode_pollers_object(const struct spdk_json_val *val, void *out)
406 {
407 	struct rpc_poller_info *info = out;
408 
409 	return spdk_json_decode_object(val, rpc_pollers_decoders, SPDK_COUNTOF(rpc_pollers_decoders), info);
410 }
411 
412 static int
413 rpc_decode_pollers_array(const struct spdk_json_val *val, void *out)
414 {
415 	struct rpc_pollers *pollers = out;
416 
417 	return spdk_json_decode_array(val, rpc_decode_pollers_object, pollers->pollers, RPC_MAX_THREADS,
418 				      &pollers->pollers_count, sizeof(struct rpc_poller_info));
419 }
420 
421 static const struct spdk_json_object_decoder rpc_pollers_threads_decoders[] = {
422 	{"name", offsetof(struct rpc_poller_thread_info, name), spdk_json_decode_string},
423 	{"id", offsetof(struct rpc_poller_thread_info, id), spdk_json_decode_uint64},
424 	{"active_pollers", offsetof(struct rpc_poller_thread_info, active_pollers), rpc_decode_pollers_array},
425 	{"timed_pollers", offsetof(struct rpc_poller_thread_info, timed_pollers), rpc_decode_pollers_array},
426 	{"paused_pollers", offsetof(struct rpc_poller_thread_info, paused_pollers), rpc_decode_pollers_array},
427 };
428 
429 static int
430 rpc_decode_pollers_threads_object(const struct spdk_json_val *val, void *out)
431 {
432 	struct rpc_poller_thread_info *info = out;
433 
434 	return spdk_json_decode_object(val, rpc_pollers_threads_decoders,
435 				       SPDK_COUNTOF(rpc_pollers_threads_decoders), info);
436 }
437 
438 static int
439 rpc_decode_pollers_threads_array(const struct spdk_json_val *val, void *out)
440 {
441 	struct rpc_pollers_threads *pollers_threads = out;
442 
443 	return spdk_json_decode_array(val, rpc_decode_pollers_threads_object, pollers_threads->threads,
444 				      RPC_MAX_THREADS, &pollers_threads->threads_count, sizeof(struct rpc_poller_thread_info));
445 }
446 
447 static const struct spdk_json_object_decoder rpc_pollers_stats_decoders[] = {
448 	{"tick_rate", offsetof(struct rpc_pollers_stats, tick_rate), spdk_json_decode_uint64},
449 	{"threads", offsetof(struct rpc_pollers_stats, pollers_threads), rpc_decode_pollers_threads_array},
450 };
451 
452 static const struct spdk_json_object_decoder rpc_core_thread_info_decoders[] = {
453 	{"name", offsetof(struct rpc_core_thread_info, name), spdk_json_decode_string},
454 	{"id", offsetof(struct rpc_core_thread_info, id), spdk_json_decode_uint64},
455 	{"cpumask", offsetof(struct rpc_core_thread_info, cpumask), spdk_json_decode_string},
456 	{"elapsed", offsetof(struct rpc_core_thread_info, elapsed), spdk_json_decode_uint64},
457 };
458 
459 static int
460 rpc_decode_core_threads_object(const struct spdk_json_val *val, void *out)
461 {
462 	struct rpc_core_thread_info *info = out;
463 
464 	return spdk_json_decode_object(val, rpc_core_thread_info_decoders,
465 				       SPDK_COUNTOF(rpc_core_thread_info_decoders), info);
466 }
467 
468 static int
469 rpc_decode_cores_lw_threads(const struct spdk_json_val *val, void *out)
470 {
471 	struct rpc_core_threads *threads = out;
472 
473 	return spdk_json_decode_array(val, rpc_decode_core_threads_object, threads->thread, RPC_MAX_THREADS,
474 				      &threads->threads_count, sizeof(struct rpc_core_thread_info));
475 }
476 
477 static const struct spdk_json_object_decoder rpc_core_info_decoders[] = {
478 	{"lcore", offsetof(struct rpc_core_info, lcore), spdk_json_decode_uint32},
479 	{"busy", offsetof(struct rpc_core_info, busy), spdk_json_decode_uint64},
480 	{"idle", offsetof(struct rpc_core_info, idle), spdk_json_decode_uint64},
481 	{"core_freq", offsetof(struct rpc_core_info, core_freq), spdk_json_decode_uint32, true},
482 	{"lw_threads", offsetof(struct rpc_core_info, threads), rpc_decode_cores_lw_threads},
483 };
484 
485 static int
486 rpc_decode_core_object(const struct spdk_json_val *val, void *out)
487 {
488 	struct rpc_core_info *info = out;
489 
490 	return spdk_json_decode_object(val, rpc_core_info_decoders,
491 				       SPDK_COUNTOF(rpc_core_info_decoders), info);
492 }
493 
494 static int
495 rpc_decode_cores_array(const struct spdk_json_val *val, void *out)
496 {
497 	struct rpc_cores *cores = out;
498 
499 	return spdk_json_decode_array(val, rpc_decode_core_object, cores->core,
500 				      RPC_MAX_THREADS, &cores->cores_count, sizeof(struct rpc_core_info));
501 }
502 
503 static const struct spdk_json_object_decoder rpc_cores_stats_decoders[] = {
504 	{"tick_rate", offsetof(struct rpc_cores_stats, tick_rate), spdk_json_decode_uint64},
505 	{"reactors", offsetof(struct rpc_cores_stats, cores), rpc_decode_cores_array},
506 };
507 
508 
509 static int
510 rpc_send_req(char *rpc_name, struct spdk_jsonrpc_client_response **resp)
511 {
512 	struct spdk_jsonrpc_client_response *json_resp = NULL;
513 	struct spdk_json_write_ctx *w;
514 	struct spdk_jsonrpc_client_request *request;
515 	int rc;
516 
517 	request = spdk_jsonrpc_client_create_request();
518 	if (request == NULL) {
519 		return -ENOMEM;
520 	}
521 
522 	w = spdk_jsonrpc_begin_request(request, 1, rpc_name);
523 	spdk_jsonrpc_end_request(request, w);
524 	spdk_jsonrpc_client_send_request(g_rpc_client, request);
525 
526 	do {
527 		rc = spdk_jsonrpc_client_poll(g_rpc_client, 1);
528 	} while (rc == 0 || rc == -ENOTCONN);
529 
530 	if (rc <= 0) {
531 		return -1;
532 	}
533 
534 	json_resp = spdk_jsonrpc_client_get_response(g_rpc_client);
535 	if (json_resp == NULL) {
536 		return -1;
537 	}
538 
539 	/* Check for error response */
540 	if (json_resp->error != NULL) {
541 		return -1;
542 	}
543 
544 	assert(json_resp->result);
545 
546 	*resp = json_resp;
547 
548 	return 0;
549 }
550 
551 
552 static int
553 sort_threads(const void *p1, const void *p2)
554 {
555 	const struct rpc_thread_info thread_info1 = *(struct rpc_thread_info *)p1;
556 	const struct rpc_thread_info thread_info2 = *(struct rpc_thread_info *)p2;
557 	uint64_t count1, count2;
558 
559 	switch (g_current_sort_col[THREADS_TAB]) {
560 	case 0: /* Sort by name */
561 		return strcmp(thread_info1.name, thread_info2.name);
562 	case 1: /* Sort by core */
563 		count2 = thread_info1.core_num;
564 		count1 = thread_info2.core_num;
565 		break;
566 	case 2: /* Sort by active pollers number */
567 		count1 = thread_info1.active_pollers_count;
568 		count2 = thread_info2.active_pollers_count;
569 		break;
570 	case 3: /* Sort by timed pollers number */
571 		count1 = thread_info1.timed_pollers_count;
572 		count2 = thread_info2.timed_pollers_count;
573 		break;
574 	case 4: /* Sort by paused pollers number */
575 		count1 = thread_info1.paused_pollers_count;
576 		count2 = thread_info2.paused_pollers_count;
577 		break;
578 	case 5: /* Sort by idle time */
579 		count1 = thread_info1.idle - thread_info1.last_idle;
580 		count2 = thread_info2.idle - thread_info2.last_idle;
581 		break;
582 	case 6: /* Sort by busy time */
583 		count1 = thread_info1.busy - thread_info1.last_busy;
584 		count2 = thread_info2.busy - thread_info2.last_busy;
585 		break;
586 	default:
587 		return 0;
588 	}
589 
590 	if (count2 > count1) {
591 		return 1;
592 	} else if (count2 < count1) {
593 		return -1;
594 	} else {
595 		return 0;
596 	}
597 }
598 
599 static void
600 store_last_run_counter(const char *poller_name, uint64_t thread_id, uint64_t last_run_counter)
601 {
602 	struct run_counter_history *history;
603 
604 	TAILQ_FOREACH(history, &g_run_counter_history, link) {
605 		if (!strcmp(history->poller_name, poller_name) && history->thread_id == thread_id) {
606 			history->last_run_counter = last_run_counter;
607 			return;
608 		}
609 	}
610 
611 	history = calloc(1, sizeof(*history));
612 	if (history == NULL) {
613 		fprintf(stderr, "Unable to allocate a history object in store_last_run_counter.\n");
614 		return;
615 	}
616 	history->poller_name = strdup(poller_name);
617 	if (!history->poller_name) {
618 		fprintf(stderr, "Unable to allocate poller_name of a history object in store_last_run_counter.\n");
619 		free(history);
620 		return;
621 	}
622 	history->thread_id = thread_id;
623 	history->last_run_counter = last_run_counter;
624 
625 	TAILQ_INSERT_TAIL(&g_run_counter_history, history, link);
626 }
627 
628 static int
629 get_thread_data(void)
630 {
631 	struct spdk_jsonrpc_client_response *json_resp = NULL;
632 	struct rpc_threads_stats threads_stats;
633 	struct rpc_thread_info *thread_info;
634 	struct rpc_core_info *core_info;
635 	uint64_t i, j, k;
636 	int rc = 0;
637 
638 	rc = rpc_send_req("thread_get_stats", &json_resp);
639 	if (rc) {
640 		return rc;
641 	}
642 
643 	/* Decode json */
644 	memset(&threads_stats, 0, sizeof(threads_stats));
645 	if (spdk_json_decode_object(json_resp->result, rpc_threads_stats_decoders,
646 				    SPDK_COUNTOF(rpc_threads_stats_decoders), &threads_stats)) {
647 		rc = -EINVAL;
648 		goto end;
649 	}
650 
651 	pthread_mutex_lock(&g_thread_lock);
652 
653 	/* This is to free allocated char arrays with old thread names */
654 	free_rpc_threads_stats(&g_threads_stats);
655 
656 	for (i = 0; i < threads_stats.threads.threads_count; i++) {
657 		for (j = 0; j < g_threads_stats.threads.threads_count; j++) {
658 			if (g_threads_stats.threads.thread_info[j].id == threads_stats.threads.thread_info[i].id) {
659 				threads_stats.threads.thread_info[i].last_busy = g_threads_stats.threads.thread_info[j].busy;
660 				threads_stats.threads.thread_info[i].last_idle = g_threads_stats.threads.thread_info[j].idle;
661 			}
662 		}
663 	}
664 	memcpy(&g_threads_stats, &threads_stats, sizeof(struct rpc_threads_stats));
665 	qsort(&g_threads_stats.threads.thread_info, threads_stats.threads.threads_count,
666 	      sizeof(g_threads_stats.threads.thread_info[0]), sort_threads);
667 
668 	for (i = 0; i < g_threads_stats.threads.threads_count; i++) {
669 		g_threads_stats.threads.thread_info[i].core_num = -1;
670 	}
671 
672 	for (i = 0; i < g_cores_stats.cores.cores_count; i++) {
673 		core_info = &g_cores_stats.cores.core[i];
674 
675 		for (j = 0; j < core_info->threads.threads_count; j++) {
676 			for (k = 0; k < g_threads_stats.threads.threads_count; k++) {
677 				/* For each thread on current core: check if it's ID also exists
678 				 * in g_thread_info data structure. If it does then assign current
679 				 * core's number to that thread, otherwise application state is inconsistent
680 				 * (e.g. scheduler is moving threads between cores). */
681 				thread_info = &g_threads_stats.threads.thread_info[k];
682 				if (thread_info->id == core_info->threads.thread[j].id) {
683 					thread_info->core_num = core_info->lcore;
684 					break;
685 				}
686 			}
687 		}
688 	}
689 
690 	pthread_mutex_unlock(&g_thread_lock);
691 
692 end:
693 	spdk_jsonrpc_client_free_response(json_resp);
694 	return rc;
695 }
696 
697 static int
698 get_pollers_data(void)
699 {
700 	struct spdk_jsonrpc_client_response *json_resp = NULL;
701 	int rc = 0;
702 	struct rpc_pollers *pollers;
703 	struct rpc_poller_info *poller;
704 	uint64_t pollers_count = 0;
705 	uint64_t i, j;
706 
707 	rc = rpc_send_req("thread_get_pollers", &json_resp);
708 	if (rc) {
709 		return rc;
710 	}
711 
712 	pthread_mutex_lock(&g_thread_lock);
713 
714 	/* Save last run counter of each poller before updating g_pollers_stats */
715 	for (i = 0; i < g_pollers_stats.pollers_threads.threads_count; i++) {
716 		pollers = &g_pollers_stats.pollers_threads.threads[i].active_pollers;
717 		for (j = 0; j < pollers->pollers_count; j++) {
718 			poller = &pollers->pollers[j];
719 			store_last_run_counter(poller->name, poller->thread_id, poller->run_count);
720 		}
721 		pollers_count += pollers->pollers_count;
722 
723 		pollers = &g_pollers_stats.pollers_threads.threads[i].timed_pollers;
724 		for (j = 0; j < pollers->pollers_count; j++) {
725 			poller = &pollers->pollers[j];
726 			store_last_run_counter(poller->name, poller->thread_id, poller->run_count);
727 		}
728 		pollers_count += pollers->pollers_count;
729 
730 		pollers = &g_pollers_stats.pollers_threads.threads[i].paused_pollers;
731 		for (j = 0; j < pollers->pollers_count; j++) {
732 			poller = &pollers->pollers[j];
733 			store_last_run_counter(poller->name, poller->thread_id, poller->run_count);
734 		}
735 		pollers_count += pollers->pollers_count;
736 	}
737 	g_last_pollers_count = pollers_count;
738 
739 	/* Free old pollers values before allocating memory for new ones */
740 	free_rpc_pollers_stats(&g_pollers_stats);
741 
742 	/* Decode json */
743 	memset(&g_pollers_stats, 0, sizeof(g_pollers_stats));
744 	if (spdk_json_decode_object(json_resp->result, rpc_pollers_stats_decoders,
745 				    SPDK_COUNTOF(rpc_pollers_stats_decoders), &g_pollers_stats)) {
746 		rc = -EINVAL;
747 	}
748 
749 	pthread_mutex_unlock(&g_thread_lock);
750 	spdk_jsonrpc_client_free_response(json_resp);
751 	return rc;
752 }
753 
754 static int
755 get_cores_data(void)
756 {
757 	struct spdk_jsonrpc_client_response *json_resp = NULL;
758 	struct rpc_core_info *core_info;
759 	uint64_t i, j, k;
760 	int rc = 0;
761 
762 	rc = rpc_send_req("framework_get_reactors", &json_resp);
763 	if (rc) {
764 		return rc;
765 	}
766 
767 	pthread_mutex_lock(&g_thread_lock);
768 
769 	/* Free old cores values before allocating memory for new ones */
770 	free_rpc_cores_stats(&g_cores_stats);
771 
772 	/* Decode json */
773 	memset(&g_cores_stats, 0, sizeof(g_cores_stats));
774 	if (spdk_json_decode_object(json_resp->result, rpc_cores_stats_decoders,
775 				    SPDK_COUNTOF(rpc_cores_stats_decoders), &g_cores_stats)) {
776 		rc = -EINVAL;
777 		goto end;
778 	}
779 
780 	for (i = 0; i < g_cores_stats.cores.cores_count; i++) {
781 		core_info = &g_cores_stats.cores.core[i];
782 
783 		for (j = 0; j < core_info->threads.threads_count; j++) {
784 			for (k = 0; k < g_threads_stats.threads.threads_count; k++) {
785 				if (core_info->threads.thread[j].id == g_threads_stats.threads.thread_info[k].id) {
786 					g_threads_stats.threads.thread_info[k].core_num = core_info->lcore;
787 				}
788 			}
789 		}
790 	}
791 
792 end:
793 	pthread_mutex_unlock(&g_thread_lock);
794 	spdk_jsonrpc_client_free_response(json_resp);
795 	return rc;
796 }
797 
798 enum str_alignment {
799 	ALIGN_LEFT,
800 	ALIGN_RIGHT,
801 };
802 
803 static void
804 print_max_len(WINDOW *win, int row, uint16_t col, uint16_t max_len, enum str_alignment alignment,
805 	      const char *string)
806 {
807 	const char dots[] = "...";
808 	int DOTS_STR_LEN = sizeof(dots) / sizeof(dots[0]);
809 	char tmp_str[MAX_STRING_LEN];
810 	int len, max_col, max_str, cmp_len;
811 	int max_row;
812 
813 	len = strlen(string);
814 	getmaxyx(win, max_row, max_col);
815 
816 	if (row > max_row) {
817 		/* We are in a process of resizing and this may happen */
818 		return;
819 	}
820 
821 	if (max_len != 0 && col + max_len < max_col) {
822 		max_col = col + max_len;
823 	}
824 
825 	max_str = max_col - col;
826 
827 	if (max_str <= DOTS_STR_LEN + 1) {
828 		/* No space to print anything, but we have to let a user know about it */
829 		mvwprintw(win, row, max_col - DOTS_STR_LEN - 1, "...");
830 		refresh();
831 		wrefresh(win);
832 		return;
833 	}
834 
835 	if (max_len) {
836 		if (alignment == ALIGN_LEFT) {
837 			snprintf(tmp_str, max_str, "%s%*c", string, max_len - len - 1, ' ');
838 		} else {
839 			snprintf(tmp_str, max_str, "%*c%s", max_len - len - 1, ' ', string);
840 		}
841 		cmp_len = max_len - 1;
842 	} else {
843 		snprintf(tmp_str, max_str, "%s", string);
844 		cmp_len = len;
845 	}
846 
847 	if (col + cmp_len > max_col - 1) {
848 		snprintf(&tmp_str[max_str - DOTS_STR_LEN - 2], DOTS_STR_LEN, "%s", dots);
849 	}
850 
851 	mvwprintw(win, row, col, tmp_str);
852 
853 	refresh();
854 	wrefresh(win);
855 }
856 
857 static void
858 draw_menu_win(void)
859 {
860 	wbkgd(g_menu_win, COLOR_PAIR(2));
861 	box(g_menu_win, 0, 0);
862 	print_max_len(g_menu_win, 1, 1, 0, ALIGN_LEFT,
863 		      "  [q] Quit  |  [1-3][Tab] Switch tab  |  [PgUp] Previous page  |  [PgDown] Next page  |  [Enter] Item details  |  [h] Help");
864 }
865 
866 static void
867 draw_tab_win(enum tabs tab)
868 {
869 	uint16_t col;
870 	uint8_t white_spaces = TABS_SPACING * NUMBER_OF_TABS;
871 
872 	wbkgd(g_tab_win[tab], COLOR_PAIR(2));
873 	box(g_tab_win[tab], 0, 0);
874 
875 	col = ((g_max_col - white_spaces) / NUMBER_OF_TABS / 2) - (strlen(g_tab_title[tab]) / 2) -
876 	      TABS_SPACING;
877 	print_max_len(g_tab_win[tab], 1, col, 0, ALIGN_LEFT, g_tab_title[tab]);
878 }
879 
880 static void
881 draw_tabs(enum tabs tab_index, uint8_t sort_col)
882 {
883 	struct col_desc *col_desc = g_col_desc[tab_index];
884 	WINDOW *tab = g_tabs[tab_index];
885 	int i, j;
886 	uint16_t offset, draw_offset;
887 
888 	for (i = 0; col_desc[i].name != NULL; i++) {
889 		if (col_desc[i].disabled) {
890 			continue;
891 		}
892 
893 		offset = 1;
894 		for (j = i; j != 0; j--) {
895 			if (!col_desc[j - 1].disabled) {
896 				offset += col_desc[j - 1].max_data_string;
897 				offset += col_desc[j - 1].name_len % 2 + 1;
898 			}
899 		}
900 
901 		draw_offset = offset + (col_desc[i].max_data_string / 2) - (col_desc[i].name_len / 2);
902 
903 		if (i == sort_col) {
904 			wattron(tab, COLOR_PAIR(3));
905 			print_max_len(tab, 1, draw_offset, 0, ALIGN_LEFT, col_desc[i].name);
906 			wattroff(tab, COLOR_PAIR(3));
907 		} else {
908 			print_max_len(tab, 1, draw_offset, 0, ALIGN_LEFT, col_desc[i].name);
909 		}
910 
911 		if (offset != 1) {
912 			print_max_len(tab, 1, offset - 1, 0, ALIGN_LEFT, "|");
913 		}
914 	}
915 
916 	print_max_len(tab, 2, 1, 0, ALIGN_LEFT, ""); /* Move to next line */
917 	whline(tab, ACS_HLINE, MAX_STRING_LEN);
918 	box(tab, 0, 0);
919 	wrefresh(tab);
920 }
921 
922 static void
923 resize_interface(enum tabs tab)
924 {
925 	int i;
926 
927 	clear();
928 	wclear(g_menu_win);
929 	mvwin(g_menu_win, g_max_row - MENU_WIN_SPACING, MENU_WIN_LOCATION_COL);
930 	wresize(g_menu_win, MENU_WIN_HEIGHT, g_max_col);
931 	draw_menu_win();
932 
933 	for (i = 0; i < NUMBER_OF_TABS; i++) {
934 		wclear(g_tabs[i]);
935 		wresize(g_tabs[i], g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - 2, g_max_col);
936 		mvwin(g_tabs[i], TABS_LOCATION_ROW, TABS_LOCATION_COL);
937 		draw_tabs(i, g_current_sort_col[i]);
938 	}
939 
940 	draw_tabs(tab, g_current_sort_col[tab]);
941 
942 	for (i = 0; i < NUMBER_OF_TABS; i++) {
943 		wclear(g_tab_win[i]);
944 		wresize(g_tab_win[i], TAB_WIN_HEIGHT,
945 			(g_max_col - (TABS_SPACING * NUMBER_OF_TABS)) / NUMBER_OF_TABS);
946 		mvwin(g_tab_win[i], TAB_WIN_LOCATION_ROW, 1 + (g_max_col / NUMBER_OF_TABS) * i);
947 		draw_tab_win(i);
948 	}
949 
950 	update_panels();
951 	doupdate();
952 }
953 
954 static void
955 switch_tab(enum tabs tab)
956 {
957 	top_panel(g_panels[tab]);
958 	update_panels();
959 	doupdate();
960 }
961 
962 static void
963 get_time_str(uint64_t ticks, char *time_str)
964 {
965 	uint64_t time;
966 
967 	time = ticks * SPDK_SEC_TO_USEC / g_cores_stats.tick_rate;
968 	snprintf(time_str, MAX_TIME_STR_LEN, "%" PRIu64, time);
969 }
970 
971 static void
972 draw_row_background(uint8_t item_index, uint8_t tab)
973 {
974 	int k;
975 
976 	if (item_index == g_selected_row) {
977 		wattron(g_tabs[tab], COLOR_PAIR(2));
978 	}
979 	for (k = 1; k < g_max_col - 1; k++) {
980 		mvwprintw(g_tabs[tab], TABS_DATA_START_ROW + item_index, k, " ");
981 	}
982 }
983 
984 static uint8_t
985 refresh_threads_tab(uint8_t current_page)
986 {
987 	struct col_desc *col_desc = g_col_desc[THREADS_TAB];
988 	uint64_t i, threads_count;
989 	uint16_t j;
990 	uint16_t col;
991 	uint8_t max_pages, item_index;
992 	static uint8_t last_page = 0;
993 	char pollers_number[MAX_POLLER_COUNT_STR_LEN], idle_time[MAX_TIME_STR_LEN],
994 	     busy_time[MAX_TIME_STR_LEN], core_str[MAX_CORE_MASK_STR_LEN];
995 	struct rpc_thread_info *thread_info[RPC_MAX_THREADS];
996 
997 	threads_count = g_threads_stats.threads.threads_count;
998 
999 	/* Clear screen if number of threads changed */
1000 	if (g_last_threads_count != threads_count) {
1001 		for (i = TABS_DATA_START_ROW; i < g_data_win_size; i++) {
1002 			for (j = 1; j < (uint64_t)g_max_col - 1; j++) {
1003 				mvwprintw(g_tabs[THREADS_TAB], i, j, " ");
1004 			}
1005 		}
1006 
1007 		g_last_threads_count = threads_count;
1008 	}
1009 
1010 	for (i = 0; i < threads_count; i++) {
1011 		thread_info[i] = &g_threads_stats.threads.thread_info[i];
1012 	}
1013 
1014 	if (last_page != current_page) {
1015 		last_page = current_page;
1016 	}
1017 
1018 	max_pages = (threads_count + g_max_data_rows - 1) / g_max_data_rows;
1019 
1020 	for (i = current_page * g_max_data_rows;
1021 	     i < spdk_min(threads_count, (uint64_t)((current_page + 1) * g_max_data_rows));
1022 	     i++) {
1023 		item_index = i - (current_page * g_max_data_rows);
1024 
1025 		col = TABS_DATA_START_COL;
1026 
1027 		draw_row_background(item_index, THREADS_TAB);
1028 
1029 		if (!col_desc[0].disabled) {
1030 			print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col,
1031 				      col_desc[0].max_data_string, ALIGN_LEFT, thread_info[i]->name);
1032 			col += col_desc[0].max_data_string;
1033 		}
1034 
1035 		if (!col_desc[1].disabled) {
1036 			snprintf(core_str, MAX_CORE_STR_LEN, "%d", thread_info[i]->core_num);
1037 			print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index,
1038 				      col, col_desc[1].max_data_string, ALIGN_RIGHT, core_str);
1039 			col += col_desc[1].max_data_string + 2;
1040 		}
1041 
1042 		if (!col_desc[2].disabled) {
1043 			snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", thread_info[i]->active_pollers_count);
1044 			print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index,
1045 				      col + (col_desc[2].name_len / 2), col_desc[2].max_data_string, ALIGN_LEFT, pollers_number);
1046 			col += col_desc[2].max_data_string + 2;
1047 		}
1048 
1049 		if (!col_desc[3].disabled) {
1050 			snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", thread_info[i]->timed_pollers_count);
1051 			print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index,
1052 				      col + (col_desc[3].name_len / 2), col_desc[3].max_data_string, ALIGN_LEFT, pollers_number);
1053 			col += col_desc[3].max_data_string + 1;
1054 		}
1055 
1056 		if (!col_desc[4].disabled) {
1057 			snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", thread_info[i]->paused_pollers_count);
1058 			print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index,
1059 				      col + (col_desc[4].name_len / 2), col_desc[4].max_data_string, ALIGN_LEFT, pollers_number);
1060 			col += col_desc[4].max_data_string + 2;
1061 		}
1062 
1063 		if (!col_desc[5].disabled) {
1064 			if (g_interval_data == true) {
1065 				get_time_str(thread_info[i]->idle - thread_info[i]->last_idle, idle_time);
1066 			} else {
1067 				get_time_str(thread_info[i]->idle, idle_time);
1068 			}
1069 			print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col,
1070 				      col_desc[5].max_data_string, ALIGN_RIGHT, idle_time);
1071 			col += col_desc[5].max_data_string;
1072 		}
1073 
1074 		if (!col_desc[6].disabled) {
1075 			if (g_interval_data == true) {
1076 				get_time_str(thread_info[i]->busy - thread_info[i]->last_busy, busy_time);
1077 			} else {
1078 				get_time_str(thread_info[i]->busy, busy_time);
1079 			}
1080 			print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col,
1081 				      col_desc[6].max_data_string, ALIGN_RIGHT, busy_time);
1082 		}
1083 
1084 		if (item_index == g_selected_row) {
1085 			wattroff(g_tabs[THREADS_TAB], COLOR_PAIR(2));
1086 		}
1087 	}
1088 
1089 	g_max_selected_row = i - current_page * g_max_data_rows - 1;
1090 
1091 	return max_pages;
1092 }
1093 
1094 static uint64_t *
1095 get_last_run_counter(const char *poller_name, uint64_t thread_id)
1096 {
1097 	struct run_counter_history *history;
1098 
1099 	TAILQ_FOREACH(history, &g_run_counter_history, link) {
1100 		if (!strcmp(history->poller_name, poller_name) && history->thread_id == thread_id) {
1101 			return &history->last_run_counter;
1102 		}
1103 	}
1104 
1105 	return NULL;
1106 }
1107 
1108 enum sort_type {
1109 	BY_NAME,
1110 	USE_GLOBAL,
1111 };
1112 
1113 static int
1114 #ifdef __FreeBSD__
1115 sort_pollers(void *arg, const void *p1, const void *p2)
1116 #else
1117 sort_pollers(const void *p1, const void *p2, void *arg)
1118 #endif
1119 {
1120 	const struct rpc_poller_info *poller1 = *(struct rpc_poller_info **)p1;
1121 	const struct rpc_poller_info *poller2 = *(struct rpc_poller_info **)p2;
1122 	enum sort_type sorting = *(enum sort_type *)arg;
1123 	uint64_t count1, count2;
1124 	uint64_t *last_run_counter;
1125 
1126 	if (sorting == BY_NAME) {
1127 		/* Sorting by name requested explicitly */
1128 		return strcmp(poller1->name, poller2->name);
1129 	} else {
1130 		/* Use globaly set sorting */
1131 		switch (g_current_sort_col[POLLERS_TAB]) {
1132 		case 0: /* Sort by name */
1133 			return strcmp(poller1->name, poller2->name);
1134 		case 1: /* Sort by type */
1135 			return poller1->type - poller2->type;
1136 		case 2: /* Sort by thread */
1137 			return strcmp(poller1->thread_name, poller2->thread_name);
1138 		case 3: /* Sort by run counter */
1139 			last_run_counter = get_last_run_counter(poller1->name, poller1->thread_id);
1140 			assert(last_run_counter != NULL);
1141 			count1 = poller1->run_count - *last_run_counter;
1142 			last_run_counter = get_last_run_counter(poller2->name, poller2->thread_id);
1143 			assert(last_run_counter != NULL);
1144 			count2 = poller2->run_count - *last_run_counter;
1145 			break;
1146 		case 4: /* Sort by period */
1147 			count1 = poller1->period_ticks;
1148 			count2 = poller2->period_ticks;
1149 			break;
1150 		default:
1151 			return 0;
1152 		}
1153 	}
1154 
1155 	if (count2 > count1) {
1156 		return 1;
1157 	} else if (count2 < count1) {
1158 		return -1;
1159 	} else {
1160 		return 0;
1161 	}
1162 }
1163 
1164 static void
1165 copy_pollers(struct rpc_pollers *pollers, uint64_t pollers_count, enum spdk_poller_type type,
1166 	     struct rpc_poller_thread_info *thread, uint64_t *current_count, bool reset_last_counter,
1167 	     struct rpc_poller_info **pollers_info)
1168 {
1169 	uint64_t *last_run_counter;
1170 	uint64_t i, j;
1171 	struct rpc_thread_info *thread_info;
1172 
1173 	for (i = 0; i < pollers_count; i++) {
1174 		for (j = 0; j < g_threads_stats.threads.threads_count; j++) {
1175 			thread_info = &g_threads_stats.threads.thread_info[j];
1176 			/* Check if poller's thread exists in g_threads_stats
1177 			 * (if poller is not "hanging" without a thread). */
1178 			if (thread_info->id != thread->id) {
1179 				continue;
1180 			}
1181 
1182 			if (reset_last_counter) {
1183 				last_run_counter = get_last_run_counter(pollers->pollers[i].name, thread->id);
1184 				if (last_run_counter == NULL) {
1185 					store_last_run_counter(pollers->pollers[i].name, thread->id, pollers->pollers[i].run_count);
1186 					last_run_counter = get_last_run_counter(pollers->pollers[i].name, thread->id);
1187 				}
1188 
1189 				assert(last_run_counter != NULL);
1190 				*last_run_counter = pollers->pollers[i].run_count;
1191 			}
1192 			pollers_info[*current_count] = &pollers->pollers[i];
1193 			snprintf(pollers_info[*current_count]->thread_name, MAX_POLLER_NAME - 1, "%s", thread->name);
1194 			pollers_info[*current_count]->thread_id = thread->id;
1195 			pollers_info[(*current_count)++]->type = type;
1196 			break;
1197 		}
1198 	}
1199 }
1200 
1201 static uint8_t
1202 prepare_poller_data(uint8_t current_page, struct rpc_poller_info **pollers,
1203 		    uint64_t *count, uint8_t last_page)
1204 {
1205 	struct rpc_poller_thread_info *thread;
1206 	uint64_t i;
1207 	bool reset_last_counter = false;
1208 	enum sort_type sorting;
1209 
1210 	for (i = 0; i < g_pollers_stats.pollers_threads.threads_count; i++) {
1211 		thread = &g_pollers_stats.pollers_threads.threads[i];
1212 		if (last_page != current_page) {
1213 			reset_last_counter = true;
1214 		}
1215 
1216 		copy_pollers(&thread->active_pollers, thread->active_pollers.pollers_count, SPDK_ACTIVE_POLLER,
1217 			     thread, count, reset_last_counter, pollers);
1218 		copy_pollers(&thread->timed_pollers, thread->timed_pollers.pollers_count, SPDK_TIMED_POLLER, thread,
1219 			     count, reset_last_counter, pollers);
1220 		copy_pollers(&thread->paused_pollers, thread->paused_pollers.pollers_count, SPDK_PAUSED_POLLER,
1221 			     thread, count, reset_last_counter, pollers);
1222 	}
1223 
1224 	if (last_page != current_page) {
1225 		last_page = current_page;
1226 	}
1227 
1228 	/* Timed pollers can switch their possition on a list because of how they work.
1229 	 * Let's sort them by name first so that they won't switch on data refresh */
1230 	sorting = BY_NAME;
1231 	qsort_r(pollers, *count, sizeof(pollers[0]), sort_pollers, (void *)&sorting);
1232 	sorting = USE_GLOBAL;
1233 	qsort_r(pollers, *count, sizeof(pollers[0]), sort_pollers, (void *)&sorting);
1234 
1235 	return last_page;
1236 }
1237 
1238 static uint8_t
1239 refresh_pollers_tab(uint8_t current_page)
1240 {
1241 	struct col_desc *col_desc = g_col_desc[POLLERS_TAB];
1242 	uint64_t *last_run_counter;
1243 	uint64_t i, count = 0;
1244 	uint16_t col, j;
1245 	uint8_t max_pages, item_index;
1246 	static uint8_t g_last_page = 0xF;
1247 	/* Init g_last_page with value != 0 to force store_last_run_counter() call in copy_pollers()
1248 	 * so that initial values for run_counter are stored in g_run_counter_history */
1249 	char run_count[MAX_TIME_STR_LEN], period_ticks[MAX_PERIOD_STR_LEN];
1250 	struct rpc_poller_info *pollers[RPC_MAX_POLLERS];
1251 
1252 	g_last_page = prepare_poller_data(current_page, pollers, &count, g_last_page);
1253 
1254 	max_pages = (count + g_max_data_rows - 1) / g_max_data_rows;
1255 
1256 	/* Clear screen if number of pollers changed */
1257 	if (g_last_pollers_count != count) {
1258 		for (i = TABS_DATA_START_ROW; i < g_data_win_size; i++) {
1259 			for (j = 1; j < (uint64_t)g_max_col - 1; j++) {
1260 				mvwprintw(g_tabs[POLLERS_TAB], i, j, " ");
1261 			}
1262 		}
1263 
1264 		/* We need to run store_last_run_counter() again, so the easiest way is to call this function
1265 		 * again with changed g_last_page value */
1266 		g_last_page = 0xF;
1267 		refresh_pollers_tab(current_page);
1268 		return max_pages;
1269 	}
1270 
1271 	/* Display info */
1272 	for (i = current_page * g_max_data_rows;
1273 	     i < spdk_min(count, (uint64_t)((current_page + 1) * g_max_data_rows));
1274 	     i++) {
1275 		item_index = i - (current_page * g_max_data_rows);
1276 
1277 		col = TABS_DATA_START_COL;
1278 
1279 		draw_row_background(item_index, POLLERS_TAB);
1280 
1281 		last_run_counter = get_last_run_counter(pollers[i]->name, pollers[i]->thread_id);
1282 		assert(last_run_counter != NULL);
1283 
1284 		if (!col_desc[0].disabled) {
1285 			print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col + 1,
1286 				      col_desc[0].max_data_string, ALIGN_LEFT, pollers[i]->name);
1287 			col += col_desc[0].max_data_string + 2;
1288 		}
1289 
1290 		if (!col_desc[1].disabled) {
1291 			print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1292 				      col_desc[1].max_data_string, ALIGN_LEFT, poller_type_str[pollers[i]->type]);
1293 			col += col_desc[1].max_data_string + 2;
1294 		}
1295 
1296 		if (!col_desc[2].disabled) {
1297 			print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1298 				      col_desc[2].max_data_string, ALIGN_LEFT, pollers[i]->thread_name);
1299 			col += col_desc[2].max_data_string + 1;
1300 		}
1301 
1302 		if (!col_desc[3].disabled) {
1303 			if (g_interval_data == true) {
1304 				snprintf(run_count, MAX_TIME_STR_LEN, "%" PRIu64, pollers[i]->run_count - *last_run_counter);
1305 			} else {
1306 				snprintf(run_count, MAX_TIME_STR_LEN, "%" PRIu64, pollers[i]->run_count);
1307 			}
1308 			print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1309 				      col_desc[3].max_data_string, ALIGN_RIGHT, run_count);
1310 			col += col_desc[3].max_data_string;
1311 		}
1312 
1313 		if (!col_desc[4].disabled) {
1314 			if (pollers[i]->period_ticks != 0) {
1315 				get_time_str(pollers[i]->period_ticks, period_ticks);
1316 				print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1317 					      col_desc[4].max_data_string, ALIGN_RIGHT, period_ticks);
1318 			}
1319 			col += col_desc[3].max_data_string + 4;
1320 		}
1321 
1322 		if (!col_desc[5].disabled) {
1323 			if (pollers[i]->busy_count > 0) {
1324 				if (item_index != g_selected_row) {
1325 					wattron(g_tabs[POLLERS_TAB], COLOR_PAIR(6));
1326 					print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1327 						      col_desc[5].max_data_string, ALIGN_RIGHT, "Busy");
1328 					wattroff(g_tabs[POLLERS_TAB], COLOR_PAIR(6));
1329 				} else {
1330 					wattron(g_tabs[POLLERS_TAB], COLOR_PAIR(8));
1331 					print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1332 						      col_desc[5].max_data_string, ALIGN_RIGHT, "Busy");
1333 					wattroff(g_tabs[POLLERS_TAB], COLOR_PAIR(8));
1334 				}
1335 			} else {
1336 				if (item_index != g_selected_row) {
1337 					wattron(g_tabs[POLLERS_TAB], COLOR_PAIR(7));
1338 					print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1339 						      col_desc[5].max_data_string, ALIGN_RIGHT, "Idle");
1340 					wattroff(g_tabs[POLLERS_TAB], COLOR_PAIR(7));
1341 				} else {
1342 					wattron(g_tabs[POLLERS_TAB], COLOR_PAIR(9));
1343 					print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1344 						      col_desc[5].max_data_string, ALIGN_RIGHT, "Idle");
1345 					wattroff(g_tabs[POLLERS_TAB], COLOR_PAIR(9));
1346 				}
1347 			}
1348 		}
1349 
1350 		if (item_index == g_selected_row) {
1351 			wattroff(g_tabs[POLLERS_TAB], COLOR_PAIR(2));
1352 		}
1353 	}
1354 
1355 	g_max_selected_row = i - current_page * g_max_data_rows - 1;
1356 
1357 	return max_pages;
1358 }
1359 
1360 static int
1361 sort_cores(const void *p1, const void *p2)
1362 {
1363 	const struct core_info core_info1 = *(struct core_info *)p1;
1364 	const struct core_info core_info2 = *(struct core_info *)p2;
1365 	uint64_t count1, count2;
1366 
1367 	switch (g_current_sort_col[CORES_TAB]) {
1368 	case 0: /* Sort by core */
1369 		count1 = core_info2.core;
1370 		count2 = core_info1.core;
1371 		break;
1372 	case 1: /* Sort by threads number */
1373 		count1 = core_info1.threads_count;
1374 		count2 = core_info2.threads_count;
1375 		break;
1376 	case 2: /* Sort by pollers number */
1377 		count1 = core_info1.pollers_count;
1378 		count2 = core_info2.pollers_count;
1379 		break;
1380 	case 3: /* Sort by idle time */
1381 		count2 = g_cores_history[core_info1.core].last_idle - core_info1.idle;
1382 		count1 = g_cores_history[core_info2.core].last_idle - core_info2.idle;
1383 		break;
1384 	case 4: /* Sort by busy time */
1385 		count2 = g_cores_history[core_info1.core].last_busy - core_info1.busy;
1386 		count1 = g_cores_history[core_info2.core].last_busy - core_info2.busy;
1387 		break;
1388 	default:
1389 		return 0;
1390 	}
1391 
1392 	if (count2 > count1) {
1393 		return 1;
1394 	} else if (count2 < count1) {
1395 		return -1;
1396 	} else {
1397 		return 0;
1398 	}
1399 }
1400 
1401 static void
1402 store_core_last_stats(uint32_t core, uint64_t idle, uint64_t busy)
1403 {
1404 	g_cores_history[core].last_idle = idle;
1405 	g_cores_history[core].last_busy = busy;
1406 }
1407 
1408 static void
1409 get_core_last_stats(uint32_t core, uint64_t *idle, uint64_t *busy)
1410 {
1411 	*idle = g_cores_history[core].last_idle;
1412 	*busy = g_cores_history[core].last_busy;
1413 }
1414 
1415 static void
1416 store_core_stats(uint32_t core, uint64_t threads, uint64_t pollers, uint64_t idle, uint64_t busy)
1417 {
1418 	g_cores_history[core].threads_count = threads;
1419 	g_cores_history[core].pollers_count = pollers;
1420 	g_cores_history[core].idle = idle;
1421 	g_cores_history[core].busy = busy;
1422 }
1423 
1424 static uint8_t
1425 refresh_cores_tab(uint8_t current_page)
1426 {
1427 	struct col_desc *col_desc = g_col_desc[CORES_TAB];
1428 	uint64_t i;
1429 	uint32_t core_num;
1430 	uint16_t offset, count = 0;
1431 	uint8_t max_pages, item_index;
1432 	static uint8_t last_page = 0;
1433 	char core[MAX_CORE_STR_LEN], threads_number[MAX_THREAD_COUNT_STR_LEN],
1434 	     pollers_number[MAX_POLLER_COUNT_STR_LEN], idle_time[MAX_TIME_STR_LEN],
1435 	     busy_time[MAX_TIME_STR_LEN], core_freq[MAX_CORE_FREQ_STR_LEN];
1436 	struct core_info cores[RPC_MAX_CORES];
1437 
1438 	memset(&cores, 0, sizeof(cores));
1439 
1440 	count = g_cores_stats.cores.cores_count;
1441 
1442 	for (i = 0; i < count; i++) {
1443 		cores[i].core = g_cores_stats.cores.core[i].lcore;
1444 		cores[i].busy = g_cores_stats.cores.core[i].busy;
1445 		cores[i].idle = g_cores_stats.cores.core[i].idle;
1446 		cores[i].core_freq = g_cores_stats.cores.core[i].core_freq;
1447 		if (last_page != current_page) {
1448 			store_core_last_stats(cores[i].core, cores[i].idle, cores[i].busy);
1449 		}
1450 	}
1451 
1452 	for (i = 0; i < g_threads_stats.threads.threads_count; i++) {
1453 		core_num = g_threads_stats.threads.thread_info[i].core_num;
1454 		for (int j = 0; j < count; j++) {
1455 			if (cores[j].core == core_num) {
1456 				cores[j].threads_count++;
1457 				cores[j].pollers_count += g_threads_stats.threads.thread_info[i].active_pollers_count +
1458 							  g_threads_stats.threads.thread_info[i].timed_pollers_count +
1459 							  g_threads_stats.threads.thread_info[i].paused_pollers_count;
1460 			}
1461 		}
1462 	}
1463 
1464 	if (last_page != current_page) {
1465 		last_page = current_page;
1466 	}
1467 
1468 	max_pages = (count + g_max_row - WINDOW_HEADER - 1) / (g_max_row - WINDOW_HEADER);
1469 
1470 	qsort(&cores, count, sizeof(cores[0]), sort_cores);
1471 
1472 	for (i = current_page * g_max_data_rows;
1473 	     i < spdk_min(count, (uint64_t)((current_page + 1) * g_max_data_rows));
1474 	     i++) {
1475 		item_index = i - (current_page * g_max_data_rows);
1476 
1477 		snprintf(threads_number, MAX_THREAD_COUNT_STR_LEN, "%ld", cores[i].threads_count);
1478 		snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", cores[i].pollers_count);
1479 		get_core_last_stats(cores[i].core, &cores[i].last_idle, &cores[i].last_busy);
1480 
1481 		offset = 1;
1482 
1483 		draw_row_background(item_index, CORES_TAB);
1484 
1485 		if (!col_desc[0].disabled) {
1486 			snprintf(core, MAX_CORE_STR_LEN, "%d", cores[i].core);
1487 			print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, offset,
1488 				      col_desc[0].max_data_string, ALIGN_RIGHT, core);
1489 			offset += col_desc[0].max_data_string + 2;
1490 		}
1491 
1492 		if (!col_desc[1].disabled) {
1493 			print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index,
1494 				      offset + (col_desc[1].name_len / 2), col_desc[1].max_data_string, ALIGN_LEFT, threads_number);
1495 			offset += col_desc[1].max_data_string + 2;
1496 		}
1497 
1498 		if (!col_desc[2].disabled) {
1499 			print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index,
1500 				      offset + (col_desc[2].name_len / 2), col_desc[2].max_data_string, ALIGN_LEFT, pollers_number);
1501 			offset += col_desc[2].max_data_string;
1502 		}
1503 
1504 		if (!col_desc[3].disabled) {
1505 			if (g_interval_data == true) {
1506 				get_time_str(cores[i].idle - cores[i].last_idle, idle_time);
1507 			} else {
1508 				get_time_str(cores[i].idle, idle_time);
1509 			}
1510 			print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, offset,
1511 				      col_desc[3].max_data_string, ALIGN_RIGHT, idle_time);
1512 			offset += col_desc[3].max_data_string + 2;
1513 		}
1514 
1515 		if (!col_desc[4].disabled) {
1516 			if (g_interval_data == true) {
1517 				get_time_str(cores[i].busy - cores[i].last_busy, busy_time);
1518 			} else {
1519 				get_time_str(cores[i].busy, busy_time);
1520 			}
1521 			print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, offset,
1522 				      col_desc[4].max_data_string, ALIGN_RIGHT, busy_time);
1523 			offset += col_desc[4].max_data_string + 2;
1524 		}
1525 
1526 		if (!col_desc[5].disabled) {
1527 			if (!cores[i].core_freq) {
1528 				snprintf(core_freq,  MAX_CORE_FREQ_STR_LEN, "%s", "N/A");
1529 			} else {
1530 				snprintf(core_freq, MAX_CORE_FREQ_STR_LEN, "%" PRIu32,
1531 					 cores[i].core_freq);
1532 			}
1533 			print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, offset,
1534 				      col_desc[5].max_data_string, ALIGN_RIGHT, core_freq);
1535 		}
1536 
1537 		store_core_last_stats(cores[i].core, cores[i].idle, cores[i].busy);
1538 		store_core_stats(cores[i].core, cores[i].threads_count, cores[i].pollers_count,
1539 				 cores[i].idle - cores[i].last_idle, cores[i].busy - cores[i].last_busy);
1540 
1541 		if (item_index == g_selected_row) {
1542 			wattroff(g_tabs[CORES_TAB], COLOR_PAIR(2));
1543 		}
1544 	}
1545 
1546 	g_max_selected_row = i - current_page * g_max_data_rows - 1;
1547 
1548 	return max_pages;
1549 }
1550 
1551 static uint8_t
1552 refresh_tab(enum tabs tab, uint8_t current_page)
1553 {
1554 	uint8_t (*refresh_function[NUMBER_OF_TABS])(uint8_t current_page) = {refresh_threads_tab, refresh_pollers_tab, refresh_cores_tab};
1555 	int color_pair[NUMBER_OF_TABS] = {COLOR_PAIR(2), COLOR_PAIR(2), COLOR_PAIR(2)};
1556 	int i;
1557 	uint8_t max_pages = 0;
1558 
1559 	color_pair[tab] = COLOR_PAIR(1);
1560 
1561 	for (i = 0; i < NUMBER_OF_TABS; i++) {
1562 		wbkgd(g_tab_win[i], color_pair[i]);
1563 	}
1564 
1565 	max_pages = (*refresh_function[tab])(current_page);
1566 	refresh();
1567 
1568 	for (i = 0; i < NUMBER_OF_TABS; i++) {
1569 		wrefresh(g_tab_win[i]);
1570 	}
1571 	draw_menu_win();
1572 
1573 	return max_pages;
1574 }
1575 
1576 static void
1577 print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color)
1578 {
1579 	int length, temp;
1580 
1581 	length = strlen(string);
1582 	temp = (width - length) / 2;
1583 	wattron(win, color);
1584 	mvwprintw(win, starty, startx + temp, "%s", string);
1585 	wattroff(win, color);
1586 	refresh();
1587 }
1588 
1589 static void
1590 print_left(WINDOW *win, int starty, int startx, int width, char *string, chtype color)
1591 {
1592 	wattron(win, color);
1593 	mvwprintw(win, starty, startx, "%s", string);
1594 	wattroff(win, color);
1595 	refresh();
1596 }
1597 
1598 static void
1599 apply_filters(enum tabs tab)
1600 {
1601 	wclear(g_tabs[tab]);
1602 	draw_tabs(tab, g_current_sort_col[tab]);
1603 }
1604 
1605 static ITEM **
1606 draw_filtering_menu(uint8_t position, WINDOW *filter_win, uint8_t tab, MENU **my_menu)
1607 {
1608 	const int ADDITIONAL_ELEMENTS = 3;
1609 	const int ROW_PADDING = 6;
1610 	const int WINDOW_START_X = 1;
1611 	const int WINDOW_START_Y = 3;
1612 	const int WINDOW_COLUMNS = 2;
1613 	struct col_desc *col_desc = g_col_desc[tab];
1614 	ITEM **my_items;
1615 	MENU *menu;
1616 	int i, elements;
1617 	uint8_t len = 0;
1618 
1619 	for (i = 0; col_desc[i].name != NULL; ++i) {
1620 		len = spdk_max(col_desc[i].name_len, len);
1621 	}
1622 
1623 	elements = i;
1624 
1625 	my_items = (ITEM **)calloc(elements * WINDOW_COLUMNS + ADDITIONAL_ELEMENTS, sizeof(ITEM *));
1626 	if (my_items == NULL) {
1627 		fprintf(stderr, "Unable to allocate an item list in draw_filtering_menu.\n");
1628 		return NULL;
1629 	}
1630 
1631 	for (i = 0; i < elements * 2; i++) {
1632 		my_items[i] = new_item(col_desc[i / WINDOW_COLUMNS].name, NULL);
1633 		i++;
1634 		my_items[i] = new_item(col_desc[i / WINDOW_COLUMNS].disabled ? "[ ]" : "[*]", NULL);
1635 	}
1636 
1637 	my_items[i] = new_item("     CLOSE", NULL);
1638 	set_item_userptr(my_items[i], apply_filters);
1639 
1640 	menu = new_menu((ITEM **)my_items);
1641 
1642 	menu_opts_off(menu, O_SHOWDESC);
1643 	set_menu_format(menu, elements + 1, WINDOW_COLUMNS);
1644 
1645 	set_menu_win(menu, filter_win);
1646 	set_menu_sub(menu, derwin(filter_win, elements + 1, len + ROW_PADDING, WINDOW_START_Y,
1647 				  WINDOW_START_X));
1648 
1649 	*my_menu = menu;
1650 
1651 	post_menu(menu);
1652 	refresh();
1653 	wrefresh(filter_win);
1654 
1655 	for (i = 0; i < position / WINDOW_COLUMNS; i++) {
1656 		menu_driver(menu, REQ_DOWN_ITEM);
1657 	}
1658 
1659 	return my_items;
1660 }
1661 
1662 static void
1663 delete_filtering_menu(MENU *my_menu, ITEM **my_items, uint8_t elements)
1664 {
1665 	int i;
1666 
1667 	unpost_menu(my_menu);
1668 	free_menu(my_menu);
1669 	for (i = 0; i < elements * 2 + 2; ++i) {
1670 		free_item(my_items[i]);
1671 	}
1672 	free(my_items);
1673 }
1674 
1675 static ITEM **
1676 refresh_filtering_menu(MENU **my_menu, WINDOW *filter_win, uint8_t tab, ITEM **my_items,
1677 		       uint8_t elements, uint8_t position)
1678 {
1679 	delete_filtering_menu(*my_menu, my_items, elements);
1680 	return draw_filtering_menu(position, filter_win, tab, my_menu);
1681 }
1682 
1683 static void
1684 filter_columns(uint8_t tab)
1685 {
1686 	const int WINDOW_HEADER_LEN = 5;
1687 	const int WINDOW_BORDER_LEN = 8;
1688 	const int WINDOW_HEADER_END_LINE = 2;
1689 	const int WINDOW_COLUMNS = 2;
1690 	struct col_desc *col_desc = g_col_desc[tab];
1691 	PANEL *filter_panel;
1692 	WINDOW *filter_win;
1693 	ITEM **my_items;
1694 	MENU *my_menu = NULL;
1695 	int i, c, elements;
1696 	bool stop_loop = false;
1697 	ITEM *cur;
1698 	void (*p)(enum tabs tab);
1699 	uint8_t current_index, len = 0;
1700 	bool disabled[TABS_COL_COUNT];
1701 
1702 	for (i = 0; col_desc[i].name != NULL; ++i) {
1703 		len = spdk_max(col_desc[i].name_len, len);
1704 	}
1705 
1706 	elements = i;
1707 
1708 	filter_win = newwin(elements + WINDOW_HEADER_LEN, len + WINDOW_BORDER_LEN,
1709 			    (g_max_row - elements - 1) / 2, (g_max_col - len) / 2);
1710 	assert(filter_win != NULL);
1711 	keypad(filter_win, TRUE);
1712 	filter_panel = new_panel(filter_win);
1713 	assert(filter_panel != NULL);
1714 
1715 	top_panel(filter_panel);
1716 	update_panels();
1717 	doupdate();
1718 
1719 	box(filter_win, 0, 0);
1720 
1721 	print_in_middle(filter_win, 1, 0, len + WINDOW_BORDER_LEN, "Filtering", COLOR_PAIR(3));
1722 	mvwaddch(filter_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE);
1723 	mvwhline(filter_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, len + WINDOW_BORDER_LEN - 2);
1724 	mvwaddch(filter_win, WINDOW_HEADER_END_LINE, len + WINDOW_BORDER_LEN - 1, ACS_RTEE);
1725 
1726 	my_items = draw_filtering_menu(0, filter_win, tab, &my_menu);
1727 	if (my_items == NULL || my_menu == NULL) {
1728 		goto fail;
1729 	}
1730 
1731 	for (int i = 0; i < TABS_COL_COUNT; i++) {
1732 		disabled[i] = col_desc[i].disabled;
1733 	}
1734 
1735 	while (!stop_loop) {
1736 		c = wgetch(filter_win);
1737 
1738 		switch (c) {
1739 		case KEY_DOWN:
1740 			menu_driver(my_menu, REQ_DOWN_ITEM);
1741 			break;
1742 		case KEY_UP:
1743 			menu_driver(my_menu, REQ_UP_ITEM);
1744 			break;
1745 		case 27: /* ESC */
1746 		case 'q':
1747 			for (int i = 0; i < TABS_COL_COUNT; i++) {
1748 				cur = current_item(my_menu);
1749 				col_desc[i].disabled = disabled[i];
1750 
1751 				my_items = refresh_filtering_menu(&my_menu, filter_win, tab, my_items, elements,
1752 								  item_index(cur) + 1);
1753 				if (my_items == NULL || my_menu == NULL) {
1754 					goto fail;
1755 				}
1756 			}
1757 
1758 			stop_loop = true;
1759 			break;
1760 		case ' ': /* Space */
1761 			cur = current_item(my_menu);
1762 			current_index = item_index(cur) / WINDOW_COLUMNS;
1763 			col_desc[current_index].disabled = !col_desc[current_index].disabled;
1764 			my_items = refresh_filtering_menu(&my_menu, filter_win, tab, my_items, elements,
1765 							  item_index(cur) + 1);
1766 			if (my_items == NULL || my_menu == NULL) {
1767 				goto fail;
1768 			}
1769 			break;
1770 		case 10: /* Enter */
1771 			cur = current_item(my_menu);
1772 			current_index = item_index(cur) / WINDOW_COLUMNS;
1773 			if (current_index == elements) {
1774 				stop_loop = true;
1775 				p = item_userptr(cur);
1776 				p(tab);
1777 			} else {
1778 				col_desc[current_index].disabled = !col_desc[current_index].disabled;
1779 				my_items = refresh_filtering_menu(&my_menu, filter_win, tab, my_items, elements,
1780 								  item_index(cur) + 1);
1781 				if (my_items == NULL || my_menu == NULL) {
1782 					goto fail;
1783 				}
1784 			}
1785 			break;
1786 		}
1787 		wrefresh(filter_win);
1788 	}
1789 
1790 	delete_filtering_menu(my_menu, my_items, elements);
1791 
1792 	del_panel(filter_panel);
1793 	delwin(filter_win);
1794 
1795 	wclear(g_menu_win);
1796 	draw_menu_win();
1797 	return;
1798 
1799 fail:
1800 	fprintf(stderr, "Unable to filter the columns due to allocation failure.\n");
1801 	assert(false);
1802 }
1803 
1804 static void
1805 sort_type(enum tabs tab, int item_index)
1806 {
1807 	g_current_sort_col[tab] = item_index;
1808 	wclear(g_tabs[tab]);
1809 	draw_tabs(tab, g_current_sort_col[tab]);
1810 }
1811 
1812 static void
1813 change_sorting(uint8_t tab)
1814 {
1815 	const int WINDOW_HEADER_LEN = 4;
1816 	const int WINDOW_BORDER_LEN = 3;
1817 	const int WINDOW_START_X = 1;
1818 	const int WINDOW_START_Y = 3;
1819 	const int WINDOW_HEADER_END_LINE = 2;
1820 	PANEL *sort_panel;
1821 	WINDOW *sort_win;
1822 	ITEM **my_items;
1823 	MENU *my_menu;
1824 	int i, c, elements;
1825 	bool stop_loop = false;
1826 	ITEM *cur;
1827 	void (*p)(enum tabs tab, int item_index);
1828 	uint8_t len = 0;
1829 
1830 	for (i = 0; g_col_desc[tab][i].name != NULL; ++i) {
1831 		len = spdk_max(len, g_col_desc[tab][i].name_len);
1832 	}
1833 
1834 	elements = i;
1835 
1836 	my_items = (ITEM **)calloc(elements + 1, sizeof(ITEM *));
1837 	if (my_items == NULL) {
1838 		fprintf(stderr, "Unable to allocate an item list in change_sorting.\n");
1839 		return;
1840 	}
1841 
1842 	for (i = 0; i < elements; ++i) {
1843 		my_items[i] = new_item(g_col_desc[tab][i].name, NULL);
1844 		set_item_userptr(my_items[i], sort_type);
1845 	}
1846 
1847 	my_menu = new_menu((ITEM **)my_items);
1848 
1849 	menu_opts_off(my_menu, O_SHOWDESC);
1850 
1851 	sort_win = newwin(elements + WINDOW_HEADER_LEN, len + WINDOW_BORDER_LEN, (g_max_row - elements) / 2,
1852 			  (g_max_col - len) / 2);
1853 	assert(sort_win != NULL);
1854 	keypad(sort_win, TRUE);
1855 	sort_panel = new_panel(sort_win);
1856 	assert(sort_panel != NULL);
1857 
1858 	top_panel(sort_panel);
1859 	update_panels();
1860 	doupdate();
1861 
1862 	set_menu_win(my_menu, sort_win);
1863 	set_menu_sub(my_menu, derwin(sort_win, elements, len + 1, WINDOW_START_Y, WINDOW_START_X));
1864 	box(sort_win, 0, 0);
1865 
1866 	print_in_middle(sort_win, 1, 0, len + WINDOW_BORDER_LEN, "Sorting", COLOR_PAIR(3));
1867 	mvwaddch(sort_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE);
1868 	mvwhline(sort_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, len + 1);
1869 	mvwaddch(sort_win, WINDOW_HEADER_END_LINE, len + WINDOW_BORDER_LEN - 1, ACS_RTEE);
1870 
1871 	post_menu(my_menu);
1872 	refresh();
1873 	wrefresh(sort_win);
1874 
1875 	while (!stop_loop) {
1876 		c = wgetch(sort_win);
1877 
1878 		switch (c) {
1879 		case KEY_DOWN:
1880 			menu_driver(my_menu, REQ_DOWN_ITEM);
1881 			break;
1882 		case KEY_UP:
1883 			menu_driver(my_menu, REQ_UP_ITEM);
1884 			break;
1885 		case 27: /* ESC */
1886 			stop_loop = true;
1887 			break;
1888 		case 10: /* Enter */
1889 			stop_loop = true;
1890 			cur = current_item(my_menu);
1891 			p = item_userptr(cur);
1892 			p(tab, item_index(cur));
1893 			break;
1894 		}
1895 		wrefresh(sort_win);
1896 	}
1897 
1898 	unpost_menu(my_menu);
1899 	free_menu(my_menu);
1900 
1901 	for (i = 0; i < elements; ++i) {
1902 		free_item(my_items[i]);
1903 	}
1904 
1905 	free(my_items);
1906 
1907 	del_panel(sort_panel);
1908 	delwin(sort_win);
1909 
1910 	wclear(g_menu_win);
1911 	draw_menu_win();
1912 }
1913 
1914 static void
1915 change_refresh_rate(void)
1916 {
1917 	const int WINDOW_HEADER_END_LINE = 2;
1918 	PANEL *refresh_panel;
1919 	WINDOW *refresh_win;
1920 	int c;
1921 	bool stop_loop = false;
1922 	uint32_t rr_tmp, refresh_rate = 0;
1923 	char refresh_rate_str[MAX_STRING_LEN];
1924 
1925 	refresh_win = newwin(RR_WIN_HEIGHT, RR_WIN_WIDTH, (g_max_row - RR_WIN_HEIGHT - 1) / 2,
1926 			     (g_max_col - RR_WIN_WIDTH) / 2);
1927 	assert(refresh_win != NULL);
1928 	keypad(refresh_win, TRUE);
1929 	refresh_panel = new_panel(refresh_win);
1930 	assert(refresh_panel != NULL);
1931 
1932 	top_panel(refresh_panel);
1933 	update_panels();
1934 	doupdate();
1935 
1936 	box(refresh_win, 0, 0);
1937 
1938 	print_in_middle(refresh_win, 1, 0, RR_WIN_WIDTH + 1, "Enter refresh rate value [s]", COLOR_PAIR(3));
1939 	mvwaddch(refresh_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE);
1940 	mvwhline(refresh_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, RR_WIN_WIDTH - 2);
1941 	mvwaddch(refresh_win, WINDOW_HEADER_END_LINE, RR_WIN_WIDTH, ACS_RTEE);
1942 	mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1, (RR_WIN_WIDTH - 1) / 2, "%d", refresh_rate);
1943 
1944 	refresh();
1945 	wrefresh(refresh_win);
1946 
1947 	while (!stop_loop) {
1948 		c = wgetch(refresh_win);
1949 
1950 		switch (c) {
1951 		case '0':
1952 		case '1':
1953 		case '2':
1954 		case '3':
1955 		case '4':
1956 		case '5':
1957 		case '6':
1958 		case '7':
1959 		case '8':
1960 		case '9':
1961 			rr_tmp = refresh_rate * 10 + c - '0';
1962 
1963 			if (rr_tmp <= RR_MAX_VALUE) {
1964 				refresh_rate = rr_tmp;
1965 				snprintf(refresh_rate_str, MAX_STRING_LEN - 1, "%d", refresh_rate);
1966 				mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1,
1967 					  (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str)) / 2, "%d", refresh_rate);
1968 				refresh();
1969 				wrefresh(refresh_win);
1970 			}
1971 			break;
1972 		case KEY_BACKSPACE:
1973 		case 127:
1974 		case '\b':
1975 			refresh_rate = refresh_rate / 10;
1976 			snprintf(refresh_rate_str, MAX_STRING_LEN - 1, "%d", refresh_rate);
1977 			mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1,
1978 				  (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str) - 2) / 2, "       ");
1979 			mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1,
1980 				  (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str)) / 2, "%d", refresh_rate);
1981 			refresh();
1982 			wrefresh(refresh_win);
1983 			break;
1984 		case 27: /* ESC */
1985 		case 'q':
1986 			stop_loop = true;
1987 			break;
1988 		case 10: /* Enter */
1989 			g_sleep_time = refresh_rate;
1990 			stop_loop = true;
1991 			break;
1992 		}
1993 		wrefresh(refresh_win);
1994 	}
1995 
1996 	del_panel(refresh_panel);
1997 	delwin(refresh_win);
1998 }
1999 
2000 static void
2001 free_poller_history(void)
2002 {
2003 	struct run_counter_history *history, *tmp;
2004 
2005 	TAILQ_FOREACH_SAFE(history, &g_run_counter_history, link, tmp) {
2006 		TAILQ_REMOVE(&g_run_counter_history, history, link);
2007 		free(history->poller_name);
2008 		free(history);
2009 	}
2010 }
2011 
2012 static uint64_t
2013 get_position_for_window(uint64_t window_size, uint64_t max_size)
2014 {
2015 	/* This function calculates position for pop-up detail window.
2016 	 * Since horizontal and vertical positions are calculated the same way
2017 	 * there is no need for separate functions. */
2018 	window_size = spdk_min(window_size, max_size);
2019 
2020 	return (max_size - window_size) / 2;
2021 }
2022 
2023 static void
2024 display_thread(struct rpc_thread_info *thread_info)
2025 {
2026 	PANEL *thread_panel;
2027 	WINDOW *thread_win;
2028 	struct rpc_poller_thread_info *thread;
2029 	struct rpc_pollers *pollers;
2030 	struct rpc_poller_info *poller;
2031 	uint64_t pollers_count, current_row, i, j, time;
2032 	int c;
2033 	bool stop_loop = false;
2034 	char idle_time[MAX_TIME_STR_LEN], busy_time[MAX_TIME_STR_LEN], run_count[MAX_POLLER_COUNT_STR_LEN];
2035 
2036 	pthread_mutex_lock(&g_thread_lock);
2037 	pollers_count = thread_info->active_pollers_count +
2038 			thread_info->timed_pollers_count +
2039 			thread_info->paused_pollers_count;
2040 
2041 	thread_win = newwin(pollers_count + THREAD_WIN_HEIGHT, THREAD_WIN_WIDTH,
2042 			    get_position_for_window(THREAD_WIN_HEIGHT + pollers_count, g_max_row),
2043 			    get_position_for_window(THREAD_WIN_WIDTH, g_max_col));
2044 	keypad(thread_win, TRUE);
2045 	thread_panel = new_panel(thread_win);
2046 
2047 	top_panel(thread_panel);
2048 	update_panels();
2049 	doupdate();
2050 
2051 	box(thread_win, 0, 0);
2052 
2053 	print_in_middle(thread_win, 1, 0, THREAD_WIN_WIDTH, thread_info->name,
2054 			COLOR_PAIR(3));
2055 	mvwhline(thread_win, 2, 1, ACS_HLINE, THREAD_WIN_WIDTH - 2);
2056 	mvwaddch(thread_win, 2, THREAD_WIN_WIDTH, ACS_RTEE);
2057 
2058 	print_left(thread_win, 3, THREAD_WIN_FIRST_COL, THREAD_WIN_WIDTH,
2059 		   "Core:                Idle [us]:            Busy [us]:", COLOR_PAIR(5));
2060 	mvwprintw(thread_win, 3, THREAD_WIN_FIRST_COL + 6, "%" PRIu64,
2061 		  thread_info->core_num);
2062 
2063 	if (g_interval_data) {
2064 		get_time_str(thread_info->idle - thread_info->last_idle, idle_time);
2065 		mvwprintw(thread_win, 3, THREAD_WIN_FIRST_COL + 32, idle_time);
2066 		get_time_str(thread_info->busy - thread_info->last_busy, busy_time);
2067 		mvwprintw(thread_win, 3, THREAD_WIN_FIRST_COL + 54, busy_time);
2068 	} else {
2069 		get_time_str(thread_info->idle, idle_time);
2070 		mvwprintw(thread_win, 3, THREAD_WIN_FIRST_COL + 32, idle_time);
2071 		get_time_str(thread_info->busy, busy_time);
2072 		mvwprintw(thread_win, 3, THREAD_WIN_FIRST_COL + 54, busy_time);
2073 	}
2074 
2075 	print_left(thread_win, 4, THREAD_WIN_FIRST_COL, THREAD_WIN_WIDTH,
2076 		   "Active pollers:      Timed pollers:        Paused pollers:", COLOR_PAIR(5));
2077 	mvwprintw(thread_win, 4, THREAD_WIN_FIRST_COL + 17, "%" PRIu64,
2078 		  thread_info->active_pollers_count);
2079 	mvwprintw(thread_win, 4, THREAD_WIN_FIRST_COL + 36, "%" PRIu64,
2080 		  thread_info->timed_pollers_count);
2081 	mvwprintw(thread_win, 4, THREAD_WIN_FIRST_COL + 59, "%" PRIu64,
2082 		  thread_info->paused_pollers_count);
2083 
2084 	mvwhline(thread_win, 5, 1, ACS_HLINE, THREAD_WIN_WIDTH - 2);
2085 
2086 	print_in_middle(thread_win, 6, 0, THREAD_WIN_WIDTH,
2087 			"Pollers                          Type    Total run count   Period", COLOR_PAIR(5));
2088 
2089 	mvwhline(thread_win, 7, 1, ACS_HLINE, THREAD_WIN_WIDTH - 2);
2090 
2091 	current_row = 8;
2092 
2093 	for (i = 0; i < g_pollers_stats.pollers_threads.threads_count; i++) {
2094 		thread = &g_pollers_stats.pollers_threads.threads[i];
2095 		if (thread->id == thread_info->id) {
2096 			pollers = &thread->active_pollers;
2097 			for (j = 0; j < pollers->pollers_count; j++) {
2098 				poller = &pollers->pollers[j];
2099 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL, "%s", poller->name);
2100 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 33, "Active");
2101 				snprintf(run_count, MAX_POLLER_COUNT_STR_LEN, "%" PRIu64, poller->run_count);
2102 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 41, run_count);
2103 				current_row++;
2104 			}
2105 			pollers = &thread->timed_pollers;
2106 			for (j = 0; j < pollers->pollers_count; j++) {
2107 				poller = &pollers->pollers[j];
2108 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL, "%s", poller->name);
2109 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 33, "Timed");
2110 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 41, "%" PRIu64, poller->run_count);
2111 				time = poller->period_ticks * SPDK_SEC_TO_USEC / g_cores_stats.tick_rate;
2112 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 59, "%" PRIu64, time);
2113 				current_row++;
2114 			}
2115 			pollers = &thread->paused_pollers;
2116 			for (j = 0; j < pollers->pollers_count; j++) {
2117 				poller = &pollers->pollers[j];
2118 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL, "%s", poller->name);
2119 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 33, "Paused");
2120 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 41, "%" PRIu64, poller->run_count);
2121 				current_row++;
2122 			}
2123 		}
2124 	}
2125 
2126 	refresh();
2127 	wrefresh(thread_win);
2128 
2129 	pthread_mutex_unlock(&g_thread_lock);
2130 	while (!stop_loop) {
2131 		c = wgetch(thread_win);
2132 
2133 		switch (c) {
2134 		case 27: /* ESC */
2135 			stop_loop = true;
2136 			break;
2137 		default:
2138 			break;
2139 		}
2140 	}
2141 
2142 	del_panel(thread_panel);
2143 	delwin(thread_win);
2144 }
2145 
2146 static void
2147 show_thread(uint8_t current_page)
2148 {
2149 	struct rpc_thread_info thread_info;
2150 	uint64_t thread_number = current_page * g_max_data_rows + g_selected_row;
2151 
2152 	pthread_mutex_lock(&g_thread_lock);
2153 	assert(thread_number < g_threads_stats.threads.threads_count);
2154 	thread_info = g_threads_stats.threads.thread_info[thread_number];
2155 	pthread_mutex_unlock(&g_thread_lock);
2156 
2157 	display_thread(&thread_info);
2158 }
2159 
2160 static void
2161 show_single_thread(uint64_t thread_id)
2162 {
2163 	uint64_t i;
2164 	struct rpc_thread_info thread_info;
2165 
2166 	pthread_mutex_lock(&g_thread_lock);
2167 	for (i = 0; i < g_threads_stats.threads.threads_count; i++) {
2168 		if (g_threads_stats.threads.thread_info[i].id == thread_id) {
2169 			thread_info = g_threads_stats.threads.thread_info[i];
2170 			pthread_mutex_unlock(&g_thread_lock);
2171 			display_thread(&thread_info);
2172 			return;
2173 		}
2174 	}
2175 	pthread_mutex_unlock(&g_thread_lock);
2176 }
2177 
2178 static void
2179 show_core(uint8_t current_page)
2180 {
2181 	PANEL *core_panel;
2182 	WINDOW *core_win;
2183 	uint64_t core_number = current_page * g_max_data_rows + g_selected_row;
2184 	struct rpc_core_info *core_info[g_cores_stats.cores.cores_count];
2185 	uint64_t threads_count, i, j;
2186 	uint64_t thread_id;
2187 	uint16_t current_threads_row;
2188 	int c;
2189 	char core_win_title[25];
2190 	bool stop_loop = false;
2191 	char idle_time[MAX_TIME_STR_LEN], busy_time[MAX_TIME_STR_LEN];
2192 
2193 	pthread_mutex_lock(&g_thread_lock);
2194 	assert(core_number < g_cores_stats.cores.cores_count);
2195 	for (i = 0; i < g_cores_stats.cores.cores_count; i++) {
2196 		core_info[i] = &g_cores_stats.cores.core[i];
2197 	}
2198 
2199 	threads_count = g_cores_stats.cores.core[core_number].threads.threads_count;
2200 
2201 	core_win = newwin(threads_count + CORE_WIN_HEIGHT, CORE_WIN_WIDTH,
2202 			  get_position_for_window(CORE_WIN_HEIGHT + threads_count, g_max_row),
2203 			  get_position_for_window(CORE_WIN_WIDTH, g_max_col));
2204 
2205 	keypad(core_win, TRUE);
2206 	core_panel = new_panel(core_win);
2207 
2208 	top_panel(core_panel);
2209 	update_panels();
2210 	doupdate();
2211 
2212 	box(core_win, 0, 0);
2213 	snprintf(core_win_title, sizeof(core_win_title), "Core %" PRIu32 " details",
2214 		 core_info[core_number]->lcore);
2215 	print_in_middle(core_win, 1, 0, CORE_WIN_WIDTH, core_win_title, COLOR_PAIR(3));
2216 
2217 	mvwaddch(core_win, -1, 0, ACS_LTEE);
2218 	mvwhline(core_win, 2, 1, ACS_HLINE, CORE_WIN_WIDTH - 2);
2219 	mvwaddch(core_win, 2, CORE_WIN_WIDTH, ACS_RTEE);
2220 	print_in_middle(core_win, 3, 0, CORE_WIN_WIDTH - (CORE_WIN_WIDTH / 3), "Frequency:", COLOR_PAIR(5));
2221 	if (core_info[core_number]->core_freq) {
2222 		mvwprintw(core_win, 3, CORE_WIN_FIRST_COL + 15, "%" PRIu32,
2223 			  core_info[core_number]->core_freq);
2224 	} else {
2225 		mvwprintw(core_win, 3, CORE_WIN_FIRST_COL + 15, "%s", "N/A");
2226 	}
2227 
2228 	mvwaddch(core_win, -1, 0, ACS_LTEE);
2229 	mvwhline(core_win, 4, 1, ACS_HLINE, CORE_WIN_WIDTH - 2);
2230 	mvwaddch(core_win, 4, CORE_WIN_WIDTH, ACS_RTEE);
2231 	print_left(core_win, 5, 1, CORE_WIN_WIDTH, "Thread count:          Idle time:", COLOR_PAIR(5));
2232 
2233 	mvwprintw(core_win, 5, CORE_WIN_FIRST_COL, "%" PRIu64,
2234 		  g_cores_history[core_info[core_number]->lcore].threads_count);
2235 
2236 	if (g_interval_data == true) {
2237 		get_time_str(g_cores_history[core_info[core_number]->lcore].idle, idle_time);
2238 		get_time_str(g_cores_history[core_info[core_number]->lcore].busy, busy_time);
2239 	} else {
2240 		get_time_str(core_info[core_number]->idle, idle_time);
2241 		get_time_str(core_info[core_number]->busy, busy_time);
2242 	}
2243 	mvwprintw(core_win, 5, CORE_WIN_FIRST_COL + 20, idle_time);
2244 	mvwhline(core_win, 6, 1, ACS_HLINE, CORE_WIN_WIDTH - 2);
2245 
2246 	print_left(core_win, 7, 1, CORE_WIN_WIDTH, "Poller count:          Busy time:", COLOR_PAIR(5));
2247 	mvwprintw(core_win, 7, CORE_WIN_FIRST_COL, "%" PRIu64,
2248 		  g_cores_history[core_info[core_number]->lcore].pollers_count);
2249 
2250 	mvwprintw(core_win, 7, CORE_WIN_FIRST_COL + 20, busy_time);
2251 
2252 	mvwhline(core_win, 8, 1, ACS_HLINE, CORE_WIN_WIDTH - 2);
2253 	print_left(core_win, 9, 1, CORE_WIN_WIDTH, "Threads on this core", COLOR_PAIR(5));
2254 
2255 	for (j = 0; j < core_info[core_number]->threads.threads_count; j++) {
2256 		mvwprintw(core_win, j + 10, 1, core_info[core_number]->threads.thread[j].name);
2257 	}
2258 	pthread_mutex_unlock(&g_thread_lock);
2259 
2260 	refresh();
2261 	wrefresh(core_win);
2262 
2263 	current_threads_row = 0;
2264 
2265 	while (!stop_loop) {
2266 		pthread_mutex_lock(&g_thread_lock);
2267 		for (j = 0; j < core_info[core_number]->threads.threads_count; j++) {
2268 			if (j != current_threads_row) {
2269 				mvwprintw(core_win, j + 10, 1, core_info[core_number]->threads.thread[j].name);
2270 			} else {
2271 				print_left(core_win, j + 10, 1, CORE_WIN_WIDTH - 2,
2272 					   core_info[core_number]->threads.thread[j].name, COLOR_PAIR(2));
2273 			}
2274 		}
2275 		pthread_mutex_unlock(&g_thread_lock);
2276 
2277 		wrefresh(core_win);
2278 
2279 		c = wgetch(core_win);
2280 		switch (c) {
2281 		case 10: /* ENTER */
2282 			pthread_mutex_lock(&g_thread_lock);
2283 			thread_id = core_info[core_number]->threads.thread[current_threads_row].id;
2284 			pthread_mutex_unlock(&g_thread_lock);
2285 			show_single_thread(thread_id);
2286 			break;
2287 		case 27: /* ESC */
2288 			stop_loop = true;
2289 			break;
2290 		case KEY_UP:
2291 			if (current_threads_row != 0) {
2292 				current_threads_row--;
2293 			}
2294 			break;
2295 		case KEY_DOWN:
2296 			pthread_mutex_lock(&g_thread_lock);
2297 			if (current_threads_row != core_info[core_number]->threads.threads_count - 1) {
2298 				current_threads_row++;
2299 			}
2300 			pthread_mutex_unlock(&g_thread_lock);
2301 			break;
2302 		default:
2303 			break;
2304 		}
2305 	}
2306 
2307 	del_panel(core_panel);
2308 	delwin(core_win);
2309 }
2310 
2311 static void
2312 show_poller(uint8_t current_page)
2313 {
2314 	PANEL *poller_panel;
2315 	WINDOW *poller_win;
2316 	uint64_t count = 0;
2317 	uint64_t *last_run_counter;
2318 	uint64_t poller_number = current_page * g_max_data_rows + g_selected_row;
2319 	struct rpc_poller_info *pollers[RPC_MAX_POLLERS];
2320 	bool stop_loop = false;
2321 	char poller_period[MAX_TIME_STR_LEN];
2322 	int c;
2323 
2324 	pthread_mutex_lock(&g_thread_lock);
2325 	prepare_poller_data(current_page, pollers, &count, current_page);
2326 	assert(poller_number < count);
2327 
2328 	last_run_counter = get_last_run_counter(pollers[poller_number]->name,
2329 						pollers[poller_number]->thread_id);
2330 	assert(last_run_counter != NULL);
2331 
2332 	poller_win = newwin(POLLER_WIN_HEIGHT, POLLER_WIN_WIDTH,
2333 			    get_position_for_window(POLLER_WIN_HEIGHT, g_max_row),
2334 			    get_position_for_window(POLLER_WIN_WIDTH, g_max_col));
2335 
2336 	keypad(poller_win, TRUE);
2337 	poller_panel = new_panel(poller_win);
2338 
2339 	top_panel(poller_panel);
2340 	update_panels();
2341 	doupdate();
2342 
2343 	box(poller_win, 0, 0);
2344 
2345 	print_in_middle(poller_win, 1, 0, POLLER_WIN_WIDTH, pollers[poller_number]->name, COLOR_PAIR(3));
2346 	mvwhline(poller_win, 2, 1, ACS_HLINE, POLLER_WIN_WIDTH - 2);
2347 	mvwaddch(poller_win, 2, POLLER_WIN_WIDTH, ACS_RTEE);
2348 
2349 	print_left(poller_win, 3, 2, POLLER_WIN_WIDTH, "Type:                  On thread:", COLOR_PAIR(5));
2350 	mvwprintw(poller_win, 3, POLLER_WIN_FIRST_COL,
2351 		  poller_type_str[pollers[poller_number]->type]);
2352 	mvwprintw(poller_win, 3, POLLER_WIN_FIRST_COL + 23, pollers[poller_number]->thread_name);
2353 
2354 	print_left(poller_win, 4, 2, POLLER_WIN_WIDTH, "Run count:", COLOR_PAIR(5));
2355 
2356 	if (g_interval_data) {
2357 		mvwprintw(poller_win, 4, POLLER_WIN_FIRST_COL, "%" PRIu64,
2358 			  pollers[poller_number]->run_count - *last_run_counter);
2359 	} else {
2360 		mvwprintw(poller_win, 4, POLLER_WIN_FIRST_COL, "%" PRIu64,
2361 			  pollers[poller_number]->run_count);
2362 	}
2363 
2364 	if (pollers[poller_number]->period_ticks != 0) {
2365 		print_left(poller_win, 4, 28, POLLER_WIN_WIDTH, "Period:", COLOR_PAIR(5));
2366 		get_time_str(pollers[poller_number]->period_ticks, poller_period);
2367 		mvwprintw(poller_win, 4, POLLER_WIN_FIRST_COL + 23, poller_period);
2368 	}
2369 	mvwhline(poller_win, 5, 1, ACS_HLINE, POLLER_WIN_WIDTH - 2);
2370 	print_in_middle(poller_win, 6, 1, POLLER_WIN_WIDTH - 7, "Status:", COLOR_PAIR(5));
2371 
2372 	if (pollers[poller_number]->busy_count > 0) {
2373 		print_in_middle(poller_win, 6, 1, POLLER_WIN_WIDTH + 6, "Busy", COLOR_PAIR(6));
2374 	} else {
2375 		print_in_middle(poller_win, 6, 1, POLLER_WIN_WIDTH + 6, "Idle", COLOR_PAIR(7));
2376 	}
2377 
2378 	refresh();
2379 	wrefresh(poller_win);
2380 
2381 	pthread_mutex_unlock(&g_thread_lock);
2382 	while (!stop_loop) {
2383 		c = wgetch(poller_win);
2384 		switch (c) {
2385 		case 27: /* ESC */
2386 			stop_loop = true;
2387 			break;
2388 		default:
2389 			break;
2390 		}
2391 	}
2392 
2393 	del_panel(poller_panel);
2394 	delwin(poller_win);
2395 }
2396 
2397 static void
2398 print_bottom_error_message(char *msg)
2399 {
2400 	mvprintw(g_max_row - 1, g_max_col - strlen(msg) - 2, msg);
2401 }
2402 
2403 static void *
2404 data_thread_routine(void *arg)
2405 {
2406 	int rc;
2407 
2408 	while (1) {
2409 		pthread_mutex_lock(&g_thread_lock);
2410 		if (g_quit_app) {
2411 			pthread_mutex_unlock(&g_thread_lock);
2412 			break;
2413 		}
2414 		pthread_mutex_unlock(&g_thread_lock);
2415 
2416 		/* Get data from RPC for each object type.
2417 		 * Start with cores since their number should not change. */
2418 		rc = get_cores_data();
2419 		if (rc) {
2420 			print_bottom_error_message("ERROR occurred while getting cores data");
2421 		}
2422 		rc = get_thread_data();
2423 		if (rc) {
2424 			print_bottom_error_message("ERROR occurred while getting threads data");
2425 		}
2426 
2427 		rc = get_pollers_data();
2428 		if (rc) {
2429 			print_bottom_error_message("ERROR occurred while getting pollers data");
2430 		}
2431 
2432 		usleep(g_sleep_time * SPDK_SEC_TO_USEC);
2433 	}
2434 
2435 	return NULL;
2436 }
2437 
2438 static void
2439 help_window_display(void)
2440 {
2441 	PANEL *help_panel;
2442 	WINDOW *help_win;
2443 	bool stop_loop = false;
2444 	int c;
2445 	uint64_t row = 1, col = 2, desc_second_row_col = 26, header_footer_col = 0;
2446 
2447 	help_win = newwin(HELP_WIN_HEIGHT, HELP_WIN_WIDTH,
2448 			  get_position_for_window(HELP_WIN_HEIGHT, g_max_row),
2449 			  get_position_for_window(HELP_WIN_WIDTH, g_max_col));
2450 	help_panel = new_panel(help_win);
2451 	top_panel(help_panel);
2452 	update_panels();
2453 	doupdate();
2454 
2455 	box(help_win, 0, 0);
2456 
2457 	/* Header */
2458 	print_in_middle(help_win, row, header_footer_col, HELP_WIN_WIDTH, "HELP", COLOR_PAIR(3));
2459 	mvwhline(help_win, 2, 1, ACS_HLINE, HELP_WIN_WIDTH - 2);
2460 	mvwaddch(help_win, 2, HELP_WIN_WIDTH, ACS_RTEE);
2461 	row = 3;
2462 
2463 	/* Content */
2464 	print_left(help_win, row, col, HELP_WIN_WIDTH, "MENU options", COLOR_PAIR(5));
2465 	print_left(help_win, ++row, ++col, HELP_WIN_WIDTH, "[q] Quit		- quit this application",
2466 		   COLOR_PAIR(10));
2467 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2468 		   "[Tab] Next tab	- switch to next tab", COLOR_PAIR(10));
2469 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2470 		   "[1-3] Select tab	- switch to THREADS, POLLERS or CORES tab", COLOR_PAIR(10));
2471 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2472 		   "[PgUp] Previous page	- scroll up to previous page", COLOR_PAIR(10));
2473 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2474 		   "[PgDown] Next page	- scroll down to next page", COLOR_PAIR(10));
2475 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2476 		   "[Up] Arrow key	- go to previous data row", COLOR_PAIR(10));
2477 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2478 		   "[Down] Arrow key	- go to next data row", COLOR_PAIR(10));
2479 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2480 		   "[c] Columns		- choose data columns to display", COLOR_PAIR(10));
2481 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2482 		   "[s] Sorting		- change sorting by column", COLOR_PAIR(10));
2483 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2484 		   "[r] Refresh rate	- set refresh rate <0, 255> in seconds", COLOR_PAIR(10));
2485 	print_left(help_win, ++row, desc_second_row_col,  HELP_WIN_WIDTH, "that value in seconds",
2486 		   COLOR_PAIR(10));
2487 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2488 		   "[Enter] Item details	- show current data row details (Enter to open, Esc to close)",
2489 		   COLOR_PAIR(10));
2490 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
2491 		   "[t] Total/Interval	- switch to display data measured from the start of SPDK", COLOR_PAIR(10));
2492 	print_left(help_win, ++row, desc_second_row_col,  HELP_WIN_WIDTH,
2493 		   "application or last refresh", COLOR_PAIR(10));
2494 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH, "[h] Help		- show this help window",
2495 		   COLOR_PAIR(10));
2496 
2497 	/* Footer */
2498 	mvwhline(help_win, HELP_WIN_HEIGHT - 3, 1, ACS_HLINE, HELP_WIN_WIDTH - 2);
2499 	mvwaddch(help_win, HELP_WIN_HEIGHT - 3, HELP_WIN_WIDTH, ACS_RTEE);
2500 
2501 	print_in_middle(help_win, HELP_WIN_HEIGHT - 2, header_footer_col, HELP_WIN_WIDTH,
2502 			"[Esc] Close this window", COLOR_PAIR(10));
2503 
2504 	refresh();
2505 	wrefresh(help_win);
2506 
2507 	while (!stop_loop) {
2508 		c = wgetch(help_win);
2509 
2510 		switch (c) {
2511 		case 27: /* ESC */
2512 			stop_loop = true;
2513 			break;
2514 		default:
2515 			break;
2516 		}
2517 	}
2518 
2519 	del_panel(help_panel);
2520 	delwin(help_win);
2521 
2522 }
2523 
2524 static void
2525 show_stats(pthread_t *data_thread)
2526 {
2527 	const int CURRENT_PAGE_STR_LEN = 50;
2528 	long int time_last, time_dif;
2529 	struct timespec time_now;
2530 	int c;
2531 	int max_row, max_col;
2532 	uint8_t active_tab = THREADS_TAB;
2533 	uint8_t current_page = 0;
2534 	uint8_t max_pages = 1;
2535 	uint16_t required_size = WINDOW_HEADER + 1;
2536 	char current_page_str[CURRENT_PAGE_STR_LEN];
2537 	bool force_refresh = true;
2538 
2539 	clock_gettime(CLOCK_REALTIME, &time_now);
2540 	time_last = time_now.tv_sec;
2541 
2542 	switch_tab(THREADS_TAB);
2543 
2544 	while (1) {
2545 		/* Check if interface has to be resized (terminal size changed) */
2546 		getmaxyx(stdscr, max_row, max_col);
2547 
2548 		if (max_row != g_max_row || max_col != g_max_col) {
2549 			g_max_row = spdk_max(max_row, required_size);
2550 			g_max_col = max_col;
2551 			g_data_win_size = g_max_row - required_size + 1;
2552 			g_max_data_rows = g_max_row - WINDOW_HEADER;
2553 			resize_interface(active_tab);
2554 		}
2555 
2556 		clock_gettime(CLOCK_REALTIME, &time_now);
2557 		time_dif = time_now.tv_sec - time_last;
2558 		if (time_dif < 0) {
2559 			time_dif = g_sleep_time;
2560 		}
2561 
2562 		if (time_dif >= g_sleep_time || force_refresh) {
2563 			time_last = time_now.tv_sec;
2564 			pthread_mutex_lock(&g_thread_lock);
2565 			max_pages = refresh_tab(active_tab, current_page);
2566 			pthread_mutex_unlock(&g_thread_lock);
2567 
2568 			snprintf(current_page_str, CURRENT_PAGE_STR_LEN - 1, "Page: %d/%d", current_page + 1, max_pages);
2569 			mvprintw(g_max_row - 1, 1, current_page_str);
2570 
2571 			refresh();
2572 		}
2573 
2574 		c = getch();
2575 		if (c == 'q') {
2576 			pthread_mutex_lock(&g_thread_lock);
2577 			g_quit_app = true;
2578 			pthread_mutex_unlock(&g_thread_lock);
2579 			break;
2580 		}
2581 
2582 		force_refresh = true;
2583 
2584 		switch (c) {
2585 		case '1':
2586 		case '2':
2587 		case '3':
2588 			active_tab = c - '1';
2589 			current_page = 0;
2590 			g_selected_row = 0;
2591 			switch_tab(active_tab);
2592 			break;
2593 		case '\t':
2594 			if (active_tab < NUMBER_OF_TABS - 1) {
2595 				active_tab++;
2596 			} else {
2597 				active_tab = THREADS_TAB;
2598 			}
2599 			g_selected_row = 0;
2600 			current_page = 0;
2601 			switch_tab(active_tab);
2602 			break;
2603 		case 's':
2604 			change_sorting(active_tab);
2605 			break;
2606 		case 'c':
2607 			filter_columns(active_tab);
2608 			break;
2609 		case 'r':
2610 			change_refresh_rate();
2611 			break;
2612 		case 't':
2613 			g_interval_data = !g_interval_data;
2614 			break;
2615 		case KEY_NPAGE: /* PgDown */
2616 			if (current_page + 1 < max_pages) {
2617 				current_page++;
2618 			}
2619 			wclear(g_tabs[active_tab]);
2620 			g_selected_row = 0;
2621 			draw_tabs(active_tab, g_current_sort_col[active_tab]);
2622 			break;
2623 		case KEY_PPAGE: /* PgUp */
2624 			if (current_page > 0) {
2625 				current_page--;
2626 			}
2627 			wclear(g_tabs[active_tab]);
2628 			g_selected_row = 0;
2629 			draw_tabs(active_tab, g_current_sort_col[active_tab]);
2630 			break;
2631 		case KEY_UP: /* Arrow up */
2632 			if (g_selected_row > 0) {
2633 				g_selected_row--;
2634 			}
2635 			break;
2636 		case KEY_DOWN: /* Arrow down */
2637 			if (g_selected_row < g_max_selected_row) {
2638 				g_selected_row++;
2639 			}
2640 			break;
2641 		case 10: /* Enter */
2642 			if (active_tab == THREADS_TAB) {
2643 				show_thread(current_page);
2644 			} else if (active_tab == CORES_TAB) {
2645 				show_core(current_page);
2646 			} else if (active_tab == POLLERS_TAB) {
2647 				show_poller(current_page);
2648 			}
2649 			break;
2650 		case 'h':
2651 			help_window_display();
2652 			break;
2653 		default:
2654 			force_refresh = false;
2655 			break;
2656 		}
2657 	}
2658 
2659 	pthread_join(*data_thread, NULL);
2660 
2661 	free_poller_history();
2662 
2663 	/* Free memory holding current data states before quitting application */
2664 	free_rpc_threads_stats(&g_threads_stats);
2665 	free_rpc_pollers_stats(&g_pollers_stats);
2666 	free_rpc_cores_stats(&g_cores_stats);
2667 }
2668 
2669 static void
2670 draw_interface(void)
2671 {
2672 	int i;
2673 	uint16_t required_size =  WINDOW_HEADER + 1;
2674 
2675 	getmaxyx(stdscr, g_max_row, g_max_col);
2676 	g_max_row = spdk_max(g_max_row, required_size);
2677 	g_data_win_size = g_max_row - required_size;
2678 	g_max_data_rows = g_max_row - WINDOW_HEADER;
2679 
2680 	g_menu_win = newwin(MENU_WIN_HEIGHT, g_max_col, g_max_row - MENU_WIN_HEIGHT - 1,
2681 			    MENU_WIN_LOCATION_COL);
2682 	assert(g_menu_win != NULL);
2683 	draw_menu_win();
2684 
2685 	for (i = 0; i < NUMBER_OF_TABS; i++) {
2686 		g_tab_win[i] = newwin(TAB_WIN_HEIGHT, g_max_col / NUMBER_OF_TABS - TABS_SPACING,
2687 				      TAB_WIN_LOCATION_ROW, g_max_col / NUMBER_OF_TABS * i + 1);
2688 		assert(g_tab_win[i] != NULL);
2689 		draw_tab_win(i);
2690 
2691 		g_tabs[i] = newwin(g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - 2, g_max_col, TABS_LOCATION_ROW,
2692 				   TABS_LOCATION_COL);
2693 		draw_tabs(i, g_current_sort_col[i]);
2694 		g_panels[i] = new_panel(g_tabs[i]);
2695 		assert(g_panels[i] != NULL);
2696 	}
2697 
2698 	update_panels();
2699 	doupdate();
2700 }
2701 
2702 static void
2703 finish(int sig)
2704 {
2705 	/* End ncurses mode */
2706 	endwin();
2707 	spdk_jsonrpc_client_close(g_rpc_client);
2708 	exit(0);
2709 }
2710 
2711 static void
2712 setup_ncurses(void)
2713 {
2714 	clear();
2715 	noecho();
2716 	timeout(1);
2717 	curs_set(0);
2718 	keypad(stdscr, TRUE);
2719 	start_color();
2720 	init_pair(1, COLOR_BLACK, COLOR_GREEN);
2721 	init_pair(2, COLOR_BLACK, COLOR_WHITE);
2722 	init_pair(3, COLOR_YELLOW, COLOR_BLACK);
2723 	init_pair(4, COLOR_BLACK, COLOR_YELLOW);
2724 	init_pair(5, COLOR_GREEN, COLOR_BLACK);
2725 	init_pair(6, COLOR_RED, COLOR_BLACK);
2726 	init_pair(7, COLOR_BLUE, COLOR_BLACK);
2727 	init_pair(8, COLOR_RED, COLOR_WHITE);
2728 	init_pair(9, COLOR_BLUE, COLOR_WHITE);
2729 	init_pair(10, COLOR_WHITE, COLOR_BLACK);
2730 
2731 	if (has_colors() == FALSE) {
2732 		endwin();
2733 		printf("Your terminal does not support color\n");
2734 		exit(1);
2735 	}
2736 
2737 	/* Handle signals to exit gracfully cleaning up ncurses */
2738 	(void) signal(SIGINT, finish);
2739 	(void) signal(SIGPIPE, finish);
2740 	(void) signal(SIGABRT, finish);
2741 }
2742 
2743 static void
2744 usage(const char *program_name)
2745 {
2746 	printf("%s [options]", program_name);
2747 	printf("\n");
2748 	printf("options:\n");
2749 	printf(" -r <path>  RPC connect address (default: /var/tmp/spdk.sock)\n");
2750 	printf(" -h         show this usage\n");
2751 }
2752 
2753 static int
2754 wait_init(pthread_t *data_thread)
2755 {
2756 	struct spdk_jsonrpc_client_response *json_resp = NULL;
2757 	char *uninit_log = "Waiting for SPDK target application to initialize...",
2758 	      *uninit_error = "Unable to read SPDK application state!";
2759 	int c, max_col, rc = 0;
2760 
2761 	max_col = getmaxx(stdscr);
2762 	print_in_middle(stdscr, FIRST_DATA_ROW, 1, max_col, uninit_log, COLOR_PAIR(5));
2763 	rc = rpc_send_req("framework_wait_init", &json_resp);
2764 	if (rc) {
2765 		spdk_jsonrpc_client_free_response(json_resp);
2766 
2767 		while (1) {
2768 			print_in_middle(stdscr, FIRST_DATA_ROW, 1, max_col, uninit_error, COLOR_PAIR(8));
2769 			c = getch();
2770 			if (c == 'q') {
2771 				return -1;
2772 			}
2773 		}
2774 	}
2775 
2776 	spdk_jsonrpc_client_free_response(json_resp);
2777 
2778 	rc = pthread_mutex_init(&g_thread_lock, NULL);
2779 	if (rc) {
2780 		fprintf(stderr, "mutex lock failed to initialize: %d\n", errno);
2781 		return -1;
2782 	}
2783 
2784 	memset(&g_threads_stats, 0, sizeof(g_threads_stats));
2785 
2786 	/* This is to get first batch of data for display functions.
2787 	 * Since data thread makes RPC calls that take more time than
2788 	 * startup of display functions on main thread, without these
2789 	 * calls both threads would be subject to a race condition. */
2790 	rc = get_thread_data();
2791 	if (rc) {
2792 		return -1;
2793 	}
2794 
2795 	rc = get_pollers_data();
2796 	if (rc) {
2797 		return -1;
2798 	}
2799 
2800 	rc = get_cores_data();
2801 	if (rc) {
2802 		return -1;
2803 	}
2804 
2805 	rc = pthread_create(data_thread, NULL, &data_thread_routine, NULL);
2806 	if (rc) {
2807 		fprintf(stderr, "data thread creation failed: %d\n", errno);
2808 		return -1;
2809 	}
2810 	return 0;
2811 }
2812 
2813 int main(int argc, char **argv)
2814 {
2815 	int op, rc;
2816 	char *socket = SPDK_DEFAULT_RPC_ADDR;
2817 	pthread_t data_thread;
2818 
2819 	while ((op = getopt(argc, argv, "r:h")) != -1) {
2820 		switch (op) {
2821 		case 'r':
2822 			socket = optarg;
2823 			break;
2824 		default:
2825 			usage(argv[0]);
2826 			return op == 'h' ? 0 : 1;
2827 		}
2828 	}
2829 
2830 	g_rpc_client = spdk_jsonrpc_client_connect(socket, AF_UNIX);
2831 	if (!g_rpc_client) {
2832 		fprintf(stderr, "spdk_jsonrpc_client_connect() failed: %d\n", errno);
2833 		return 1;
2834 	}
2835 
2836 	initscr();
2837 	init_str_len();
2838 	setup_ncurses();
2839 	draw_interface();
2840 
2841 	rc = wait_init(&data_thread);
2842 	if (!rc) {
2843 		show_stats(&data_thread);
2844 	}
2845 
2846 	finish(0);
2847 
2848 	return (0);
2849 }
2850