xref: /spdk/app/spdk_top/spdk_top.c (revision 15c68d557dc6efaccd64dc8aa14005b6b694608e)
1 /*   SPDX-License-Identifier: BSD-3-Clause
2  *   Copyright (C) 2020 Intel Corporation.
3  *   All rights reserved.
4  */
5 
6 #include "spdk/stdinc.h"
7 #include "spdk/jsonrpc.h"
8 #include "spdk/rpc.h"
9 #include "spdk/event.h"
10 #include "spdk/util.h"
11 #include "spdk/env.h"
12 
13 #if defined __has_include
14 #if __has_include(<ncurses/panel.h>)
15 #include <ncurses/ncurses.h>
16 #include <ncurses/panel.h>
17 #include <ncurses/menu.h>
18 #else
19 #include <ncurses.h>
20 #include <panel.h>
21 #include <menu.h>
22 #endif
23 #else
24 #include <ncurses.h>
25 #include <panel.h>
26 #include <menu.h>
27 #endif
28 
29 #define RPC_MAX_THREADS 1024
30 #define RPC_MAX_POLLERS 1024
31 #define RPC_MAX_CORES 1024
32 #define MAX_THREAD_NAME 128
33 #define MAX_POLLER_NAME 128
34 #define MAX_THREADS 4096
35 #define RR_MAX_VALUE 255
36 
37 #define MAX_STRING_LEN 12289 /* 3x 4k monitors + 1 */
38 #define TAB_WIN_HEIGHT 3
39 #define TAB_WIN_LOCATION_ROW 1
40 #define TABS_SPACING 2
41 #define TABS_LOCATION_ROW 4
42 #define TABS_LOCATION_COL 0
43 #define TABS_DATA_START_ROW 3
44 #define TABS_DATA_START_COL 2
45 #define TABS_COL_COUNT 13
46 #define MENU_WIN_HEIGHT 3
47 #define MENU_WIN_SPACING 4
48 #define MENU_WIN_LOCATION_COL 0
49 #define RR_WIN_WIDTH 32
50 #define RR_WIN_HEIGHT 5
51 #define MAX_THREAD_NAME_LEN 26
52 #define MAX_THREAD_COUNT_STR_LEN 10
53 #define MAX_POLLER_NAME_LEN 36
54 #define MAX_POLLER_TYPE_COUNT_STR_LEN 16
55 #define MAX_POLLER_TYPE_STR_LEN 8
56 #define MAX_POLLER_COUNT_STR_LEN 10
57 #define MAX_STATUS_IND_STR_LEN 8
58 #define MAX_POLLER_IND_STR_LEN 28
59 #define MAX_CORE_MASK_STR_LEN 16
60 #define MAX_CORE_STR_LEN 6
61 #define MAX_CORE_FREQ_STR_LEN 13
62 #define MAX_TIME_STR_LEN 12
63 #define MAX_FLOAT_STR_LEN 8
64 #define MAX_POLLER_RUN_COUNT 20
65 #define MAX_PERIOD_STR_LEN 12
66 #define MAX_INTR_LEN 6
67 #define WINDOW_HEADER 12
68 #define FROM_HEX 16
69 #define THREAD_WIN_WIDTH 69
70 #define THREAD_WIN_HEIGHT 9
71 #define THREAD_WIN_FIRST_COL 2
72 #define CORE_WIN_FIRST_COL 16
73 #define CORE_WIN_WIDTH 48
74 #define CORE_WIN_HEIGHT 11
75 #define POLLER_WIN_HEIGHT 8
76 #define POLLER_WIN_WIDTH 64
77 #define POLLER_WIN_FIRST_COL 14
78 #define FIRST_DATA_ROW 7
79 #define HELP_WIN_WIDTH 88
80 #define HELP_WIN_HEIGHT 24
81 #define SCHEDULER_WIN_HEIGHT 7
82 #define SCHEDULER_WIN_FIRST_COL 2
83 #define MAX_SCHEDULER_PERIOD_STR_LEN 10
84 
85 enum tabs {
86 	THREADS_TAB,
87 	POLLERS_TAB,
88 	CORES_TAB,
89 	NUMBER_OF_TABS,
90 };
91 
92 enum column_threads_type {
93 	COL_THREADS_NAME,
94 	COL_THREADS_CORE,
95 	COL_THREADS_ACTIVE_POLLERS,
96 	COL_THREADS_TIMED_POLLERS,
97 	COL_THREADS_PAUSED_POLLERS,
98 	COL_THREADS_IDLE_TIME,
99 	COL_THREADS_BUSY_TIME,
100 	COL_THREADS_CPU_USAGE,
101 	COL_THREADS_STATUS,
102 	COL_THREADS_NONE = 255,
103 };
104 
105 enum column_pollers_type {
106 	COL_POLLERS_NAME,
107 	COL_POLLERS_TYPE,
108 	COL_POLLERS_THREAD_NAME,
109 	COL_POLLERS_RUN_COUNTER,
110 	COL_POLLERS_PERIOD,
111 	COL_POLLERS_BUSY_COUNT,
112 	COL_POLLERS_NONE = 255,
113 };
114 
115 enum column_cores_type {
116 	COL_CORES_CORE,
117 	COL_CORES_THREADS,
118 	COL_CORES_POLLERS,
119 	COL_CORES_IDLE_TIME,
120 	COL_CORES_BUSY_TIME,
121 	COL_CORES_BUSY_PCT,
122 	COL_CORES_STATUS,
123 	COL_CORES_INTR,
124 	COL_CORES_SYS_PCT,
125 	COL_CORES_IRQ_PCT,
126 	COL_CORES_CPU_PCT,
127 	COL_CORES_CORE_FREQ,
128 	COL_CORES_NONE = 255,
129 };
130 
131 enum spdk_poller_type {
132 	SPDK_ACTIVE_POLLER,
133 	SPDK_TIMED_POLLER,
134 	SPDK_PAUSED_POLLER,
135 	SPDK_POLLER_TYPES_COUNT,
136 };
137 
138 struct col_desc {
139 	const char *name;
140 	uint8_t name_len;
141 	uint8_t max_data_string;
142 	bool disabled;
143 };
144 
145 struct run_counter_history {
146 	uint64_t poller_id;
147 	uint64_t thread_id;
148 	uint64_t last_run_counter;
149 	uint64_t last_busy_counter;
150 	TAILQ_ENTRY(run_counter_history) link;
151 };
152 
153 uint8_t g_sleep_time = 1;
154 uint16_t g_selected_row;
155 uint16_t g_max_selected_row;
156 uint64_t g_tick_rate;
157 const char *poller_type_str[SPDK_POLLER_TYPES_COUNT] = {"Active", "Timed", "Paused"};
158 const char *g_tab_title[NUMBER_OF_TABS] = {"[1] THREADS", "[2] POLLERS", "[3] CORES"};
159 struct spdk_jsonrpc_client *g_rpc_client;
160 static TAILQ_HEAD(, run_counter_history) g_run_counter_history = TAILQ_HEAD_INITIALIZER(
161 			g_run_counter_history);
162 WINDOW *g_menu_win, *g_tab_win[NUMBER_OF_TABS], *g_tabs[NUMBER_OF_TABS];
163 PANEL *g_panels[NUMBER_OF_TABS];
164 uint16_t g_max_row, g_max_col;
165 uint16_t g_data_win_size, g_max_data_rows;
166 uint32_t g_last_threads_count, g_last_pollers_count, g_last_cores_count;
167 uint8_t g_current_sort_col[NUMBER_OF_TABS] = {COL_THREADS_NAME, COL_POLLERS_NAME, COL_CORES_CORE};
168 uint8_t g_current_sort_col2[NUMBER_OF_TABS] = {COL_THREADS_NONE, COL_POLLERS_NONE, COL_CORES_NONE};
169 bool g_interval_data = true;
170 bool g_quit_app = false;
171 pthread_mutex_t g_thread_lock;
172 static struct col_desc g_col_desc[NUMBER_OF_TABS][TABS_COL_COUNT] = {
173 	{	{.name = "Thread name", .max_data_string = MAX_THREAD_NAME_LEN},
174 		{.name = "Core", .max_data_string = MAX_CORE_STR_LEN},
175 		{.name = "Active pollers", .max_data_string = MAX_POLLER_TYPE_COUNT_STR_LEN},
176 		{.name = "Timed pollers", .max_data_string = MAX_POLLER_TYPE_COUNT_STR_LEN},
177 		{.name = "Paused pollers", .max_data_string = MAX_POLLER_TYPE_COUNT_STR_LEN},
178 		{.name = "Idle [us]", .max_data_string = MAX_TIME_STR_LEN},
179 		{.name = "Busy [us]", .max_data_string = MAX_TIME_STR_LEN},
180 		{.name = "CPU %", .max_data_string = MAX_FLOAT_STR_LEN},
181 		{.name = "Status", .max_data_string = MAX_STATUS_IND_STR_LEN},
182 		{.name = (char *)NULL}
183 	},
184 	{	{.name = "Poller name", .max_data_string = MAX_POLLER_NAME_LEN},
185 		{.name = "Type", .max_data_string = MAX_POLLER_TYPE_STR_LEN},
186 		{.name = "On thread", .max_data_string = MAX_THREAD_NAME_LEN},
187 		{.name = "Run count", .max_data_string = MAX_POLLER_RUN_COUNT},
188 		{.name = "Period [us]", .max_data_string = MAX_PERIOD_STR_LEN},
189 		{.name = "Status (busy count)", .max_data_string = MAX_POLLER_IND_STR_LEN},
190 		{.name = (char *)NULL}
191 	},
192 	{	{.name = "Core", .max_data_string = MAX_CORE_STR_LEN},
193 		{.name = "Threads", .max_data_string = MAX_THREAD_COUNT_STR_LEN},
194 		{.name = "Pollers", .max_data_string = MAX_POLLER_COUNT_STR_LEN},
195 		{.name = "Idle [us]", .max_data_string = MAX_TIME_STR_LEN},
196 		{.name = "Busy [us]", .max_data_string = MAX_TIME_STR_LEN},
197 		{.name = "Busy %", .max_data_string = MAX_FLOAT_STR_LEN},
198 		{.name = "Status", .max_data_string = MAX_STATUS_IND_STR_LEN},
199 		{.name = "Intr", .max_data_string = MAX_INTR_LEN},
200 		{.name = "Sys %", .max_data_string = MAX_FLOAT_STR_LEN},
201 		{.name = "Irq %", .max_data_string = MAX_FLOAT_STR_LEN},
202 		{.name = "CPU %", .max_data_string = MAX_FLOAT_STR_LEN},
203 		{.name = "Freq [MHz]", .max_data_string = MAX_CORE_FREQ_STR_LEN},
204 		{.name = (char *)NULL}
205 	}
206 };
207 
208 struct rpc_thread_info {
209 	char *name;
210 	uint64_t id;
211 	int core_num;
212 	int core_idx;
213 	char *cpumask;
214 	uint64_t busy;
215 	uint64_t last_busy;
216 	uint64_t idle;
217 	uint64_t last_idle;
218 	uint64_t active_pollers_count;
219 	uint64_t timed_pollers_count;
220 	uint64_t paused_pollers_count;
221 };
222 
223 struct rpc_poller_info {
224 	char *name;
225 	char *state;
226 	uint64_t id;
227 	uint64_t run_count;
228 	uint64_t busy_count;
229 	uint64_t period_ticks;
230 	enum spdk_poller_type type;
231 	char thread_name[MAX_THREAD_NAME];
232 	uint64_t thread_id;
233 };
234 
235 struct rpc_core_thread_info {
236 	char *name;
237 	uint64_t id;
238 	char *cpumask;
239 	uint64_t elapsed;
240 };
241 
242 struct rpc_core_threads {
243 	uint64_t threads_count;
244 	struct rpc_core_thread_info *thread;
245 };
246 
247 struct rpc_core_info {
248 	uint32_t lcore;
249 	uint64_t pollers_count;
250 	uint64_t busy;
251 	uint64_t idle;
252 	uint64_t irq;
253 	uint64_t sys;
254 	uint64_t usr;
255 	uint32_t core_freq;
256 	uint64_t last_idle;
257 	uint64_t last_busy;
258 	uint64_t last_sys;
259 	uint64_t last_usr;
260 	uint64_t last_irq;
261 	bool in_interrupt;
262 	struct rpc_core_threads threads;
263 	uint64_t tid;
264 };
265 
266 struct rpc_scheduler {
267 	char *scheduler_name;
268 	char *governor_name;
269 	uint64_t scheduler_period;
270 };
271 
272 struct rpc_thread_info g_threads_info[RPC_MAX_THREADS];
273 struct rpc_poller_info g_pollers_info[RPC_MAX_POLLERS];
274 struct rpc_core_info g_cores_info[RPC_MAX_CORES];
275 struct rpc_scheduler g_scheduler_info;
276 
277 static void
init_str_len(void)278 init_str_len(void)
279 {
280 	int i, j;
281 
282 	for (i = 0; i < NUMBER_OF_TABS; i++) {
283 		for (j = 0; g_col_desc[i][j].name != NULL; j++) {
284 			g_col_desc[i][j].name_len = strlen(g_col_desc[i][j].name);
285 		}
286 	}
287 }
288 
289 static void
free_rpc_threads_stats(struct rpc_thread_info * req)290 free_rpc_threads_stats(struct rpc_thread_info *req)
291 {
292 	free(req->name);
293 	req->name = NULL;
294 	free(req->cpumask);
295 	req->cpumask = NULL;
296 }
297 
298 static const struct spdk_json_object_decoder rpc_thread_info_decoders[] = {
299 	{"name", offsetof(struct rpc_thread_info, name), spdk_json_decode_string},
300 	{"id", offsetof(struct rpc_thread_info, id), spdk_json_decode_uint64},
301 	{"cpumask", offsetof(struct rpc_thread_info, cpumask), spdk_json_decode_string},
302 	{"busy", offsetof(struct rpc_thread_info, busy), spdk_json_decode_uint64},
303 	{"idle", offsetof(struct rpc_thread_info, idle), spdk_json_decode_uint64},
304 	{"active_pollers_count", offsetof(struct rpc_thread_info, active_pollers_count), spdk_json_decode_uint64},
305 	{"timed_pollers_count", offsetof(struct rpc_thread_info, timed_pollers_count), spdk_json_decode_uint64},
306 	{"paused_pollers_count", offsetof(struct rpc_thread_info, paused_pollers_count), spdk_json_decode_uint64},
307 };
308 
309 static int
rpc_decode_threads_array(struct spdk_json_val * val,struct rpc_thread_info * out,uint64_t * current_threads_count)310 rpc_decode_threads_array(struct spdk_json_val *val, struct rpc_thread_info *out,
311 			 uint64_t *current_threads_count)
312 {
313 	struct spdk_json_val *thread = val;
314 	uint64_t i = 0;
315 	int rc;
316 
317 	/* Fetch the beginning of threads array */
318 	rc = spdk_json_find_array(thread, "threads", NULL, &thread);
319 	if (rc) {
320 		printf("Could not fetch threads array from JSON.\n");
321 		goto end;
322 	}
323 
324 	for (thread = spdk_json_array_first(thread); thread != NULL; thread = spdk_json_next(thread)) {
325 		rc = spdk_json_decode_object(thread, rpc_thread_info_decoders,
326 					     SPDK_COUNTOF(rpc_thread_info_decoders), &out[i]);
327 		if (rc) {
328 			printf("Could not decode thread object from JSON.\n");
329 			break;
330 		}
331 
332 		i++;
333 	}
334 
335 end:
336 
337 	*current_threads_count = i;
338 	return rc;
339 }
340 
341 static void
free_rpc_poller(struct rpc_poller_info * poller)342 free_rpc_poller(struct rpc_poller_info *poller)
343 {
344 	free(poller->name);
345 	poller->name = NULL;
346 	free(poller->state);
347 	poller->state = NULL;
348 }
349 
350 static void
free_rpc_core_info(struct rpc_core_info * core_info,size_t size)351 free_rpc_core_info(struct rpc_core_info *core_info, size_t size)
352 {
353 	struct rpc_core_threads *threads;
354 	struct rpc_core_thread_info *thread;
355 	uint64_t i, core_number;
356 
357 	for (core_number = 0; core_number < size; core_number++) {
358 		threads = &core_info[core_number].threads;
359 		for (i = 0; i < threads->threads_count; i++) {
360 			thread = &threads->thread[i];
361 			free(thread->name);
362 			free(thread->cpumask);
363 		}
364 		free(threads->thread);
365 	}
366 }
367 
368 static const struct spdk_json_object_decoder rpc_pollers_decoders[] = {
369 	{"name", offsetof(struct rpc_poller_info, name), spdk_json_decode_string},
370 	{"state", offsetof(struct rpc_poller_info, state), spdk_json_decode_string},
371 	{"id", offsetof(struct rpc_poller_info, id), spdk_json_decode_uint64},
372 	{"run_count", offsetof(struct rpc_poller_info, run_count), spdk_json_decode_uint64},
373 	{"busy_count", offsetof(struct rpc_poller_info, busy_count), spdk_json_decode_uint64},
374 	{"period_ticks", offsetof(struct rpc_poller_info, period_ticks), spdk_json_decode_uint64, true},
375 };
376 
377 static int
rpc_decode_pollers_array(struct spdk_json_val * poller,struct rpc_poller_info * out,uint64_t * poller_count,const char * thread_name,uint64_t thread_name_length,uint64_t thread_id,enum spdk_poller_type poller_type)378 rpc_decode_pollers_array(struct spdk_json_val *poller, struct rpc_poller_info *out,
379 			 uint64_t *poller_count,
380 			 const char *thread_name, uint64_t thread_name_length, uint64_t thread_id,
381 			 enum spdk_poller_type poller_type)
382 {
383 	int rc;
384 
385 	for (poller = spdk_json_array_first(poller); poller != NULL; poller = spdk_json_next(poller)) {
386 		out[*poller_count].thread_id = thread_id;
387 		memcpy(out[*poller_count].thread_name, thread_name, sizeof(char) * thread_name_length);
388 		out[*poller_count].type = poller_type;
389 
390 		rc = spdk_json_decode_object(poller, rpc_pollers_decoders,
391 					     SPDK_COUNTOF(rpc_pollers_decoders), &out[*poller_count]);
392 		if (rc) {
393 			printf("Could not decode poller object from JSON.\n");
394 			return rc;
395 		}
396 
397 		(*poller_count)++;
398 		if (*poller_count == RPC_MAX_POLLERS) {
399 			return -1;
400 		}
401 	}
402 
403 	return 0;
404 }
405 
406 static const struct spdk_json_object_decoder rpc_thread_pollers_decoders[] = {
407 	{"name", offsetof(struct rpc_thread_info, name), spdk_json_decode_string},
408 	{"id", offsetof(struct rpc_thread_info, id), spdk_json_decode_uint64},
409 };
410 
411 static int
rpc_decode_pollers_threads_array(struct spdk_json_val * val,struct rpc_poller_info * out,uint32_t * num_pollers)412 rpc_decode_pollers_threads_array(struct spdk_json_val *val, struct rpc_poller_info *out,
413 				 uint32_t *num_pollers)
414 {
415 	struct spdk_json_val *thread = val, *poller;
416 	/* This is a temporary poller structure to hold thread name and id.
417 	 * It is filled with data only once per thread change and then
418 	 * that memory is copied to each poller running on that thread. */
419 	struct rpc_thread_info thread_info = {};
420 	uint64_t poller_count = 0, i, thread_name_length;
421 	int rc;
422 	const char *poller_typenames[] = { "active_pollers", "timed_pollers", "paused_pollers" };
423 	enum spdk_poller_type poller_types[] = { SPDK_ACTIVE_POLLER, SPDK_TIMED_POLLER, SPDK_PAUSED_POLLER };
424 
425 	/* Fetch the beginning of threads array */
426 	rc = spdk_json_find_array(thread, "threads", NULL, &thread);
427 	if (rc) {
428 		printf("Could not fetch threads array from JSON.\n");
429 		goto end;
430 	}
431 
432 	for (thread = spdk_json_array_first(thread); thread != NULL; thread = spdk_json_next(thread)) {
433 		rc = spdk_json_decode_object_relaxed(thread, rpc_thread_pollers_decoders,
434 						     SPDK_COUNTOF(rpc_thread_pollers_decoders), &thread_info);
435 		if (rc) {
436 			printf("Could not decode thread info from JSON.\n");
437 			goto end;
438 		}
439 
440 		thread_name_length = strlen(thread_info.name);
441 
442 		for (i = 0; i < SPDK_COUNTOF(poller_types); i++) {
443 			/* Find poller array */
444 			rc = spdk_json_find(thread, poller_typenames[i], NULL, &poller,
445 					    SPDK_JSON_VAL_ARRAY_BEGIN);
446 			if (rc) {
447 				printf("Could not fetch pollers array from JSON.\n");
448 				goto end;
449 			}
450 
451 			rc = rpc_decode_pollers_array(poller, out, &poller_count, thread_info.name,
452 						      thread_name_length,
453 						      thread_info.id, poller_types[i]);
454 			if (rc) {
455 				printf("Could not decode the first object in pollers array.\n");
456 				goto end;
457 			}
458 		}
459 	}
460 
461 	*num_pollers = poller_count;
462 
463 end:
464 	/* Since we rely in spdk_json_object_decode() to free this value
465 	 * each time we rewrite it, we need to free the last allocation
466 	 * manually. */
467 	free(thread_info.name);
468 
469 	if (rc) {
470 		*num_pollers = 0;
471 		for (i = 0; i < poller_count; i++) {
472 			free_rpc_poller(&out[i]);
473 		}
474 	}
475 
476 	return rc;
477 }
478 
479 static const struct spdk_json_object_decoder rpc_core_thread_info_decoders[] = {
480 	{"name", offsetof(struct rpc_core_thread_info, name), spdk_json_decode_string},
481 	{"id", offsetof(struct rpc_core_thread_info, id), spdk_json_decode_uint64},
482 	{"cpumask", offsetof(struct rpc_core_thread_info, cpumask), spdk_json_decode_string},
483 	{"elapsed", offsetof(struct rpc_core_thread_info, elapsed), spdk_json_decode_uint64},
484 };
485 
486 static int
rpc_decode_core_threads_object(const struct spdk_json_val * val,void * out)487 rpc_decode_core_threads_object(const struct spdk_json_val *val, void *out)
488 {
489 	struct rpc_core_thread_info *info = out;
490 
491 	return spdk_json_decode_object(val, rpc_core_thread_info_decoders,
492 				       SPDK_COUNTOF(rpc_core_thread_info_decoders), info);
493 }
494 
495 #define RPC_THREAD_ENTRY_SIZE (SPDK_COUNTOF(rpc_core_thread_info_decoders) * 2)
496 
497 static int
rpc_decode_cores_lw_threads(const struct spdk_json_val * val,void * out)498 rpc_decode_cores_lw_threads(const struct spdk_json_val *val, void *out)
499 {
500 	struct rpc_core_threads *threads = out;
501 	/* The number of thread entries received from RPC can be calculated using
502 	 * above define value (each JSON line = key + value, hence '* 2' ) and JSON
503 	 * 'val' value (-2 is to subtract VAL_OBJECT_BEGIN/END). */
504 	uint16_t threads_count = (spdk_json_val_len(val) - 2) / RPC_THREAD_ENTRY_SIZE;
505 
506 	assert(threads != NULL);
507 	threads->thread = calloc(threads_count, sizeof(struct rpc_core_thread_info));
508 
509 	return spdk_json_decode_array(val, rpc_decode_core_threads_object, threads->thread, threads_count,
510 				      &threads->threads_count, sizeof(struct rpc_core_thread_info));
511 }
512 
513 static const struct spdk_json_object_decoder rpc_core_info_decoders[] = {
514 	{"lcore", offsetof(struct rpc_core_info, lcore), spdk_json_decode_uint32},
515 	{"busy", offsetof(struct rpc_core_info, busy), spdk_json_decode_uint64},
516 	{"idle", offsetof(struct rpc_core_info, idle), spdk_json_decode_uint64},
517 	{"irq", offsetof(struct rpc_core_info, irq), spdk_json_decode_uint64},
518 	{"sys", offsetof(struct rpc_core_info, sys), spdk_json_decode_uint64},
519 	{"usr", offsetof(struct rpc_core_info, usr), spdk_json_decode_uint64},
520 	{"core_freq", offsetof(struct rpc_core_info, core_freq), spdk_json_decode_uint32, true},
521 	{"in_interrupt", offsetof(struct rpc_core_info, in_interrupt), spdk_json_decode_bool},
522 	{"lw_threads", offsetof(struct rpc_core_info, threads), rpc_decode_cores_lw_threads},
523 	{"tid", offsetof(struct rpc_core_info, tid), spdk_json_decode_uint64},
524 };
525 
526 static int
rpc_decode_core_object(const struct spdk_json_val * val,void * out)527 rpc_decode_core_object(const struct spdk_json_val *val, void *out)
528 {
529 	struct rpc_core_info *info = out;
530 
531 	return spdk_json_decode_object(val, rpc_core_info_decoders,
532 				       SPDK_COUNTOF(rpc_core_info_decoders), info);
533 }
534 
535 static int
rpc_decode_cores_array(struct spdk_json_val * val,struct rpc_core_info * out,uint32_t * current_cores_count)536 rpc_decode_cores_array(struct spdk_json_val *val, struct rpc_core_info *out,
537 		       uint32_t *current_cores_count)
538 {
539 	struct spdk_json_val *core = val;
540 	size_t cores_count;
541 	int rc;
542 
543 	/* Fetch the beginning of reactors array. */
544 	rc = spdk_json_find_array(core, "reactors", NULL, &core);
545 	if (rc) {
546 		printf("Could not fetch cores array from JSON.");
547 		goto end;
548 	}
549 
550 	rc = spdk_json_decode_array(core, rpc_decode_core_object, out, RPC_MAX_CORES, &cores_count,
551 				    sizeof(struct rpc_core_info));
552 
553 	*current_cores_count = (uint32_t)cores_count;
554 
555 end:
556 	return rc;
557 }
558 
559 static void
free_rpc_scheduler(struct rpc_scheduler * req)560 free_rpc_scheduler(struct rpc_scheduler *req)
561 {
562 	free(req->scheduler_name);
563 	free(req->governor_name);
564 }
565 
566 static const struct spdk_json_object_decoder rpc_scheduler_decoders[] = {
567 	{"scheduler_name", offsetof(struct rpc_scheduler, scheduler_name), spdk_json_decode_string, true},
568 	{"governor_name", offsetof(struct rpc_scheduler, governor_name), spdk_json_decode_string, true},
569 	{"scheduler_period", offsetof(struct rpc_scheduler, scheduler_period), spdk_json_decode_uint64},
570 };
571 
572 static int
rpc_send_req(char * rpc_name,struct spdk_jsonrpc_client_response ** resp)573 rpc_send_req(char *rpc_name, struct spdk_jsonrpc_client_response **resp)
574 {
575 	struct spdk_jsonrpc_client_response *json_resp = NULL;
576 	struct spdk_json_write_ctx *w;
577 	struct spdk_jsonrpc_client_request *request;
578 	int rc;
579 
580 	request = spdk_jsonrpc_client_create_request();
581 	if (request == NULL) {
582 		return -ENOMEM;
583 	}
584 
585 	w = spdk_jsonrpc_begin_request(request, 1, rpc_name);
586 	spdk_jsonrpc_end_request(request, w);
587 	spdk_jsonrpc_client_send_request(g_rpc_client, request);
588 
589 	do {
590 		rc = spdk_jsonrpc_client_poll(g_rpc_client, 1);
591 	} while (rc == 0 || rc == -ENOTCONN);
592 
593 	if (rc <= 0) {
594 		return -1;
595 	}
596 
597 	json_resp = spdk_jsonrpc_client_get_response(g_rpc_client);
598 	if (json_resp == NULL) {
599 		return -1;
600 	}
601 
602 	/* Check for error response */
603 	if (json_resp->error != NULL) {
604 		spdk_jsonrpc_client_free_response(json_resp);
605 		return -1;
606 	}
607 
608 	assert(json_resp->result);
609 
610 	*resp = json_resp;
611 
612 	return 0;
613 }
614 
615 static uint64_t
get_cpu_usage(uint64_t busy_ticks,uint64_t idle_ticks)616 get_cpu_usage(uint64_t busy_ticks, uint64_t idle_ticks)
617 {
618 	if (busy_ticks + idle_ticks > 0) {
619 		/* Increase numerator to convert fraction into decimal with
620 		 * additional precision */
621 		return busy_ticks * 10000 / (busy_ticks + idle_ticks);
622 	}
623 
624 	return 0;
625 }
626 
627 static int
subsort_threads(enum column_threads_type sort_column,const void * p1,const void * p2)628 subsort_threads(enum column_threads_type sort_column, const void *p1, const void *p2)
629 {
630 	const struct rpc_thread_info thread_info1 = *(struct rpc_thread_info *)p1;
631 	const struct rpc_thread_info thread_info2 = *(struct rpc_thread_info *)p2;
632 	uint64_t count1, count2;
633 
634 	switch (sort_column) {
635 	case COL_THREADS_NAME:
636 		return strcmp(thread_info1.name, thread_info2.name);
637 	case COL_THREADS_CORE:
638 		count2 = thread_info1.core_num;
639 		count1 = thread_info2.core_num;
640 		break;
641 	case COL_THREADS_ACTIVE_POLLERS:
642 		count1 = thread_info1.active_pollers_count;
643 		count2 = thread_info2.active_pollers_count;
644 		break;
645 	case COL_THREADS_TIMED_POLLERS:
646 		count1 = thread_info1.timed_pollers_count;
647 		count2 = thread_info2.timed_pollers_count;
648 		break;
649 	case COL_THREADS_PAUSED_POLLERS:
650 		count1 = thread_info1.paused_pollers_count;
651 		count2 = thread_info2.paused_pollers_count;
652 		break;
653 	case COL_THREADS_IDLE_TIME:
654 		if (g_interval_data) {
655 			count1 = thread_info1.idle - thread_info1.last_idle;
656 			count2 = thread_info2.idle - thread_info2.last_idle;
657 		} else {
658 			count1 = thread_info1.idle;
659 			count2 = thread_info2.idle;
660 		}
661 		break;
662 	case COL_THREADS_BUSY_TIME:
663 		if (g_interval_data) {
664 			count1 = thread_info1.busy - thread_info1.last_busy;
665 			count2 = thread_info2.busy - thread_info2.last_busy;
666 		} else {
667 			count1 = thread_info1.busy;
668 			count2 = thread_info2.busy;
669 		}
670 		break;
671 	case COL_THREADS_CPU_USAGE:
672 		count1 = get_cpu_usage(thread_info1.busy - thread_info1.last_busy,
673 				       g_cores_info[thread_info1.core_idx].busy + g_cores_info[thread_info1.core_idx].idle);
674 		count2 = get_cpu_usage(thread_info2.busy - thread_info2.last_busy,
675 				       g_cores_info[thread_info2.core_idx].busy + g_cores_info[thread_info2.core_idx].idle);
676 		break;
677 	case COL_THREADS_NONE:
678 	default:
679 		return 0;
680 	}
681 
682 	if (count2 > count1) {
683 		return 1;
684 	} else if (count2 < count1) {
685 		return -1;
686 	} else {
687 		return 0;
688 	}
689 }
690 
691 static int
sort_threads(const void * p1,const void * p2)692 sort_threads(const void *p1, const void *p2)
693 {
694 	int res;
695 
696 	res = subsort_threads(g_current_sort_col[THREADS_TAB], p1, p2);
697 	if (res == 0) {
698 		res = subsort_threads(g_current_sort_col2[THREADS_TAB], p1, p2);
699 	}
700 	return res;
701 }
702 
703 static void
store_last_counters(uint64_t poller_id,uint64_t thread_id,uint64_t last_run_counter,uint64_t last_busy_counter)704 store_last_counters(uint64_t poller_id, uint64_t thread_id, uint64_t last_run_counter,
705 		    uint64_t last_busy_counter)
706 {
707 	struct run_counter_history *history;
708 
709 	TAILQ_FOREACH(history, &g_run_counter_history, link) {
710 		if ((history->poller_id == poller_id) && (history->thread_id == thread_id)) {
711 			history->last_run_counter = last_run_counter;
712 			history->last_busy_counter = last_busy_counter;
713 			return;
714 		}
715 	}
716 
717 	history = calloc(1, sizeof(*history));
718 	if (history == NULL) {
719 		fprintf(stderr, "Unable to allocate a history object in store_last_counters.\n");
720 		return;
721 	}
722 	history->poller_id = poller_id;
723 	history->thread_id = thread_id;
724 	history->last_run_counter = last_run_counter;
725 	history->last_busy_counter = last_busy_counter;
726 
727 	TAILQ_INSERT_TAIL(&g_run_counter_history, history, link);
728 }
729 
730 static int
get_thread_data(void)731 get_thread_data(void)
732 {
733 	struct spdk_jsonrpc_client_response *json_resp = NULL;
734 	struct rpc_thread_info thread_info[RPC_MAX_THREADS], *thread;
735 	struct rpc_core_info *core_info;
736 	uint64_t i, j, k, current_threads_count = 0;
737 	int rc = 0;
738 
739 	rc = rpc_send_req("thread_get_stats", &json_resp);
740 	if (rc) {
741 		return rc;
742 	}
743 
744 	/* Decode json */
745 	memset(thread_info, 0, sizeof(struct rpc_thread_info) * RPC_MAX_THREADS);
746 	if (rpc_decode_threads_array(json_resp->result, thread_info, &current_threads_count)) {
747 		rc = -EINVAL;
748 		for (i = 0; i < current_threads_count; i++) {
749 			free_rpc_threads_stats(&thread_info[i]);
750 		}
751 		goto end;
752 	}
753 
754 	pthread_mutex_lock(&g_thread_lock);
755 
756 	/* This is to free allocated char arrays with old thread names */
757 	for (i = 0; i < g_last_threads_count; i++) {
758 		free_rpc_threads_stats(&g_threads_info[i]);
759 	}
760 
761 	for (i = 0; i < current_threads_count; i++) {
762 		for (j = 0; j < g_last_threads_count; j++) {
763 			if (thread_info[i].id == g_threads_info[j].id) {
764 				thread_info[i].last_busy = g_threads_info[j].busy;
765 				thread_info[i].last_idle = g_threads_info[j].idle;
766 			}
767 		}
768 	}
769 	g_last_threads_count = current_threads_count;
770 
771 	memcpy(g_threads_info, thread_info, sizeof(struct rpc_thread_info) * RPC_MAX_THREADS);
772 
773 	for (i = 0; i < g_last_threads_count; i++) {
774 		g_threads_info[i].core_num = -1;
775 	}
776 
777 	for (i = 0; i < g_last_cores_count; i++) {
778 		core_info = &g_cores_info[i];
779 
780 		for (j = 0; j < core_info->threads.threads_count; j++) {
781 			for (k = 0; k < g_last_threads_count; k++) {
782 				/* For each thread on current core: check if it's ID also exists
783 				 * in g_thread_info data structure. If it does then assign current
784 				 * core's number to that thread, otherwise application state is inconsistent
785 				 * (e.g. scheduler is moving threads between cores). */
786 				thread = &g_threads_info[k];
787 				if (thread->id == core_info->threads.thread[j].id) {
788 					thread->core_num = core_info->lcore;
789 					thread->core_idx = i;
790 					break;
791 				}
792 			}
793 		}
794 	}
795 
796 	qsort(g_threads_info, g_last_threads_count, sizeof(struct rpc_thread_info), sort_threads);
797 
798 	pthread_mutex_unlock(&g_thread_lock);
799 
800 end:
801 	spdk_jsonrpc_client_free_response(json_resp);
802 	return rc;
803 }
804 
805 static uint64_t
get_last_run_counter(uint64_t poller_id,uint64_t thread_id)806 get_last_run_counter(uint64_t poller_id, uint64_t thread_id)
807 {
808 	struct run_counter_history *history;
809 
810 	TAILQ_FOREACH(history, &g_run_counter_history, link) {
811 		if ((history->poller_id == poller_id) && (history->thread_id == thread_id)) {
812 			return history->last_run_counter;
813 		}
814 	}
815 
816 	return 0;
817 }
818 
819 static uint64_t
get_last_busy_counter(uint64_t poller_id,uint64_t thread_id)820 get_last_busy_counter(uint64_t poller_id, uint64_t thread_id)
821 {
822 	struct run_counter_history *history;
823 
824 	TAILQ_FOREACH(history, &g_run_counter_history, link) {
825 		if ((history->poller_id == poller_id) && (history->thread_id == thread_id)) {
826 			return history->last_busy_counter;
827 		}
828 	}
829 
830 	return 0;
831 }
832 
833 static int
subsort_pollers(enum column_pollers_type sort_column,const void * p1,const void * p2)834 subsort_pollers(enum column_pollers_type sort_column, const void *p1, const void *p2)
835 {
836 	const struct rpc_poller_info *poller1 = (struct rpc_poller_info *)p1;
837 	const struct rpc_poller_info *poller2 = (struct rpc_poller_info *)p2;
838 	uint64_t count1, count2;
839 	uint64_t last_busy_counter1, last_busy_counter2;
840 
841 	switch (sort_column) {
842 	case COL_POLLERS_NAME:
843 		return strcmp(poller1->name, poller2->name);
844 	case COL_POLLERS_TYPE:
845 		return poller1->type - poller2->type;
846 	case COL_POLLERS_THREAD_NAME:
847 		return strcmp(poller1->thread_name, poller2->thread_name);
848 	case COL_POLLERS_RUN_COUNTER:
849 		if (g_interval_data) {
850 			count1 = poller1->run_count - get_last_run_counter(poller1->id, poller1->thread_id);
851 			count2 = poller2->run_count - get_last_run_counter(poller2->id, poller2->thread_id);
852 		} else {
853 			count1 = poller1->run_count;
854 			count2 = poller2->run_count;
855 		}
856 		break;
857 	case COL_POLLERS_PERIOD:
858 		count1 = poller1->period_ticks;
859 		count2 = poller2->period_ticks;
860 		break;
861 	case COL_POLLERS_BUSY_COUNT:
862 		count1 = poller1->busy_count;
863 		count2 = poller2->busy_count;
864 		if (g_interval_data) {
865 			last_busy_counter1 = get_last_busy_counter(poller1->id, poller1->thread_id);
866 			last_busy_counter2 = get_last_busy_counter(poller2->id, poller2->thread_id);
867 			if (count1 > last_busy_counter1) {
868 				count1 -= last_busy_counter1;
869 			}
870 			if (count2 > last_busy_counter2) {
871 				count2 -= last_busy_counter2;
872 			}
873 		}
874 		break;
875 	case COL_POLLERS_NONE:
876 	default:
877 		return 0;
878 	}
879 
880 	if (count2 > count1) {
881 		return 1;
882 	} else if (count2 < count1) {
883 		return -1;
884 	} else {
885 		return 0;
886 	}
887 }
888 
889 static int
sort_pollers(const void * p1,const void * p2)890 sort_pollers(const void *p1, const void *p2)
891 {
892 	int rc;
893 
894 	rc = subsort_pollers(g_current_sort_col[POLLERS_TAB], p1, p2);
895 	if (rc == 0) {
896 		rc = subsort_pollers(g_current_sort_col2[POLLERS_TAB], p1, p2);
897 	}
898 	return rc;
899 }
900 
901 static int
get_pollers_data(void)902 get_pollers_data(void)
903 {
904 	struct spdk_jsonrpc_client_response *json_resp = NULL;
905 	int rc = 0;
906 	uint64_t i = 0;
907 	uint32_t current_pollers_count;
908 	struct rpc_poller_info pollers_info[RPC_MAX_POLLERS];
909 
910 	rc = rpc_send_req("thread_get_pollers", &json_resp);
911 	if (rc) {
912 		return rc;
913 	}
914 
915 	/* Decode json */
916 	memset(&pollers_info, 0, sizeof(pollers_info));
917 	if (rpc_decode_pollers_threads_array(json_resp->result, pollers_info, &current_pollers_count)) {
918 		rc = -EINVAL;
919 		for (i = 0; i < current_pollers_count; i++) {
920 			free_rpc_poller(&pollers_info[i]);
921 		}
922 		goto end;
923 	}
924 
925 	pthread_mutex_lock(&g_thread_lock);
926 
927 	/* Save last run counter of each poller before updating g_pollers_stats. */
928 	for (i = 0; i < g_last_pollers_count; i++) {
929 		store_last_counters(g_pollers_info[i].id, g_pollers_info[i].thread_id,
930 				    g_pollers_info[i].run_count, g_pollers_info[i].busy_count);
931 	}
932 
933 	/* Free old pollers values before allocating memory for new ones */
934 	for (i = 0; i < g_last_pollers_count; i++) {
935 		free_rpc_poller(&g_pollers_info[i]);
936 	}
937 
938 	g_last_pollers_count = current_pollers_count;
939 
940 	qsort(&pollers_info, g_last_pollers_count, sizeof(struct rpc_poller_info), sort_pollers);
941 
942 	memcpy(&g_pollers_info, &pollers_info, sizeof(struct rpc_poller_info) * g_last_pollers_count);
943 
944 	pthread_mutex_unlock(&g_thread_lock);
945 
946 end:
947 	spdk_jsonrpc_client_free_response(json_resp);
948 	return rc;
949 }
950 
951 static int
subsort_cores(enum column_cores_type sort_column,const void * p1,const void * p2)952 subsort_cores(enum column_cores_type sort_column, const void *p1, const void *p2)
953 {
954 	const struct rpc_core_info core_info1 = *(struct rpc_core_info *)p1;
955 	const struct rpc_core_info core_info2 = *(struct rpc_core_info *)p2;
956 	uint64_t count1, count2;
957 
958 	switch (sort_column) {
959 	case COL_CORES_CORE:
960 		count1 = core_info2.lcore;
961 		count2 = core_info1.lcore;
962 		break;
963 	case COL_CORES_THREADS:
964 		count1 = core_info1.threads.threads_count;
965 		count2 = core_info2.threads.threads_count;
966 		break;
967 	case COL_CORES_POLLERS:
968 		count1 = core_info1.pollers_count;
969 		count2 = core_info2.pollers_count;
970 		break;
971 	case COL_CORES_IDLE_TIME:
972 		if (g_interval_data) {
973 			count1 = core_info1.last_idle - core_info1.idle;
974 			count2 = core_info2.last_idle - core_info2.idle;
975 		} else {
976 			count1 = core_info1.idle;
977 			count2 = core_info2.idle;
978 		}
979 		break;
980 	case COL_CORES_BUSY_TIME:
981 		if (g_interval_data) {
982 			count1 = core_info1.last_busy - core_info1.busy;
983 			count2 = core_info2.last_busy - core_info2.busy;
984 		} else {
985 			count1 = core_info1.busy;
986 			count2 = core_info2.busy;
987 		}
988 		break;
989 	case COL_CORES_CORE_FREQ:
990 		count1 = core_info1.core_freq;
991 		count2 = core_info2.core_freq;
992 		break;
993 	case COL_CORES_INTR:
994 		count1 = core_info1.in_interrupt;
995 		count2 = core_info2.in_interrupt;
996 		break;
997 	case COL_CORES_BUSY_PCT:
998 		count1 = get_cpu_usage(core_info1.last_busy - core_info1.busy,
999 				       core_info1.last_idle - core_info1.idle);
1000 		count2 = get_cpu_usage(core_info2.last_busy - core_info2.busy,
1001 				       core_info2.last_idle - core_info2.idle);
1002 		break;
1003 	case COL_CORES_NONE:
1004 	default:
1005 		return 0;
1006 	}
1007 
1008 	if (count2 > count1) {
1009 		return 1;
1010 	} else if (count2 < count1) {
1011 		return -1;
1012 	} else {
1013 		return 0;
1014 	}
1015 }
1016 
1017 static int
sort_cores(const void * p1,const void * p2)1018 sort_cores(const void *p1, const void *p2)
1019 {
1020 	int rc;
1021 
1022 	rc = subsort_cores(g_current_sort_col[CORES_TAB], p1, p2);
1023 	if (rc == 0) {
1024 		return subsort_cores(g_current_sort_col[CORES_TAB], p1, p2);
1025 	}
1026 	return rc;
1027 }
1028 
1029 static int
get_cores_data(void)1030 get_cores_data(void)
1031 {
1032 	struct spdk_jsonrpc_client_response *json_resp = NULL;
1033 	struct rpc_core_info *core_info;
1034 	uint64_t i, j, k;
1035 	uint32_t current_cores_count;
1036 	struct rpc_core_info cores_info[RPC_MAX_CORES];
1037 	int rc = 0;
1038 
1039 	rc = rpc_send_req("framework_get_reactors", &json_resp);
1040 	if (rc) {
1041 		return rc;
1042 	}
1043 
1044 	/* Decode json */
1045 	memset(cores_info, 0, sizeof(struct rpc_core_info) * RPC_MAX_CORES);
1046 	if (rpc_decode_cores_array(json_resp->result, cores_info, &current_cores_count)) {
1047 		rc = -EINVAL;
1048 		goto end;
1049 	}
1050 
1051 	pthread_mutex_lock(&g_thread_lock);
1052 	for (i = 0; i < current_cores_count; i++) {
1053 		for (j = 0; j < g_last_cores_count; j++) {
1054 			if (cores_info[i].lcore == g_cores_info[j].lcore) {
1055 				cores_info[i].last_busy = g_cores_info[j].busy;
1056 				cores_info[i].last_idle = g_cores_info[j].idle;
1057 				cores_info[i].last_irq = g_cores_info[j].irq;
1058 				cores_info[i].last_sys = g_cores_info[j].sys;
1059 				cores_info[i].last_usr = g_cores_info[j].usr;
1060 			}
1061 		}
1062 	}
1063 
1064 	/* Free old cores values before allocating memory for new ones */
1065 	free_rpc_core_info(g_cores_info, current_cores_count);
1066 	memcpy(g_cores_info, cores_info, sizeof(struct rpc_core_info) * current_cores_count);
1067 
1068 	for (i = 0; i < g_last_cores_count; i++) {
1069 		core_info = &g_cores_info[i];
1070 
1071 		core_info->threads.thread = cores_info[i].threads.thread;
1072 
1073 		for (j = 0; j < core_info->threads.threads_count; j++) {
1074 			memcpy(&core_info->threads.thread[j], &cores_info[i].threads.thread[j],
1075 			       sizeof(struct rpc_core_thread_info));
1076 			for (k = 0; k < g_last_threads_count; k++) {
1077 				if (core_info->threads.thread[j].id == g_threads_info[k].id) {
1078 					g_threads_info[k].core_num = core_info->lcore;
1079 					/* Do not consider threads which changed cores when issuing
1080 					 * RPCs to get_core_data and get_thread_data and threads
1081 					 * not currently assigned to this core. */
1082 					core_info->pollers_count += g_threads_info[k].active_pollers_count +
1083 								    g_threads_info[k].timed_pollers_count +
1084 								    g_threads_info[k].paused_pollers_count;
1085 				}
1086 			}
1087 		}
1088 	}
1089 
1090 	g_last_cores_count = current_cores_count;
1091 
1092 	qsort(&g_cores_info, g_last_cores_count, sizeof(struct rpc_core_info), sort_cores);
1093 
1094 end:
1095 	pthread_mutex_unlock(&g_thread_lock);
1096 	spdk_jsonrpc_client_free_response(json_resp);
1097 	return rc;
1098 }
1099 
1100 static int
get_scheduler_data(void)1101 get_scheduler_data(void)
1102 {
1103 	struct spdk_jsonrpc_client_response *json_resp = NULL;
1104 	struct rpc_scheduler scheduler_info;
1105 	int rc = 0;
1106 
1107 	rc = rpc_send_req("framework_get_scheduler", &json_resp);
1108 	if (rc) {
1109 		return rc;
1110 	}
1111 
1112 	memset(&scheduler_info, 0, sizeof(scheduler_info));
1113 	if (spdk_json_decode_object_relaxed(json_resp->result, rpc_scheduler_decoders,
1114 					    SPDK_COUNTOF(rpc_scheduler_decoders), &scheduler_info)) {
1115 		rc = -EINVAL;
1116 	} else {
1117 		pthread_mutex_lock(&g_thread_lock);
1118 
1119 		free_rpc_scheduler(&g_scheduler_info);
1120 
1121 		memcpy(&g_scheduler_info, &scheduler_info, sizeof(struct rpc_scheduler));
1122 		pthread_mutex_unlock(&g_thread_lock);
1123 	}
1124 
1125 	spdk_jsonrpc_client_free_response(json_resp);
1126 	return rc;
1127 }
1128 
1129 enum str_alignment {
1130 	ALIGN_LEFT,
1131 	ALIGN_RIGHT,
1132 };
1133 
1134 static void
print_max_len(WINDOW * win,int row,uint16_t col,uint16_t max_len,enum str_alignment alignment,const char * string)1135 print_max_len(WINDOW *win, int row, uint16_t col, uint16_t max_len, enum str_alignment alignment,
1136 	      const char *string)
1137 {
1138 	const char dots[] = "...";
1139 	int DOTS_STR_LEN = sizeof(dots) / sizeof(dots[0]);
1140 	char tmp_str[MAX_STRING_LEN];
1141 	int len, max_col, max_str, cmp_len;
1142 	int max_row;
1143 
1144 	len = strlen(string);
1145 	getmaxyx(win, max_row, max_col);
1146 
1147 	if (row > max_row) {
1148 		/* We are in a process of resizing and this may happen */
1149 		return;
1150 	}
1151 
1152 	if (max_len != 0 && col + max_len < max_col) {
1153 		max_col = col + max_len;
1154 	}
1155 
1156 	max_str = max_col - col;
1157 
1158 	if (max_str <= DOTS_STR_LEN + 1) {
1159 		/* No space to print anything, but we have to let a user know about it */
1160 		mvwprintw(win, row, max_col - DOTS_STR_LEN - 1, "...");
1161 		wnoutrefresh(win);
1162 		return;
1163 	}
1164 
1165 	if (max_len) {
1166 		if (alignment == ALIGN_LEFT) {
1167 			snprintf(tmp_str, max_str, "%s%*c", string, max_len - len - 1, ' ');
1168 		} else {
1169 			snprintf(tmp_str, max_str, "%*c%s", max_len - len - 1, ' ', string);
1170 		}
1171 		cmp_len = max_len - 1;
1172 	} else {
1173 		snprintf(tmp_str, max_str, "%s", string);
1174 		cmp_len = len;
1175 	}
1176 
1177 	if (col + cmp_len > max_col - 1) {
1178 		snprintf(&tmp_str[max_str - DOTS_STR_LEN - 2], DOTS_STR_LEN, "%s", dots);
1179 	}
1180 
1181 	mvwprintw(win, row, col, "%s", tmp_str);
1182 
1183 	wnoutrefresh(win);
1184 }
1185 
1186 static void
draw_menu_win(void)1187 draw_menu_win(void)
1188 {
1189 	wbkgd(g_menu_win, COLOR_PAIR(2));
1190 	box(g_menu_win, 0, 0);
1191 	print_max_len(g_menu_win, 1, 1, 0, ALIGN_LEFT,
1192 		      "  [q] Quit  |  [1-3][Tab] Switch tab  |  [PgUp] Previous page  |  [PgDown] Next page  |  [Enter] Item details  |  [h] Help");
1193 }
1194 
1195 static void
draw_tab_win(enum tabs tab)1196 draw_tab_win(enum tabs tab)
1197 {
1198 	uint16_t col;
1199 	uint8_t white_spaces = TABS_SPACING * NUMBER_OF_TABS;
1200 
1201 	wbkgd(g_tab_win[tab], COLOR_PAIR(10));
1202 	box(g_tab_win[tab], 0, 0);
1203 
1204 	col = ((g_max_col - white_spaces) / NUMBER_OF_TABS / 2) - (strlen(g_tab_title[tab]) / 2) -
1205 	      TABS_SPACING;
1206 	print_max_len(g_tab_win[tab], 1, col, 0, ALIGN_LEFT, g_tab_title[tab]);
1207 }
1208 
1209 static void
draw_tabs(enum tabs tab_index,uint8_t sort_col,uint8_t sort_col2)1210 draw_tabs(enum tabs tab_index, uint8_t sort_col, uint8_t sort_col2)
1211 {
1212 	struct col_desc *col_desc = g_col_desc[tab_index];
1213 	WINDOW *tab = g_tabs[tab_index];
1214 	int i, j;
1215 	uint16_t offset, draw_offset;
1216 	uint16_t tab_height = g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - 3;
1217 
1218 	for (i = 0; col_desc[i].name != NULL; i++) {
1219 		if (col_desc[i].disabled) {
1220 			continue;
1221 		}
1222 
1223 		offset = 1;
1224 		for (j = i; j != 0; j--) {
1225 			if (!col_desc[j - 1].disabled) {
1226 				offset += col_desc[j - 1].max_data_string;
1227 				offset += col_desc[j - 1].name_len % 2 + 1;
1228 			}
1229 		}
1230 
1231 		draw_offset = offset + (col_desc[i].max_data_string / 2) - (col_desc[i].name_len / 2);
1232 
1233 		if (i == sort_col) {
1234 			wattron(tab, COLOR_PAIR(4));
1235 			print_max_len(tab, 1, draw_offset, 0, ALIGN_LEFT, col_desc[i].name);
1236 			wattroff(tab, COLOR_PAIR(4));
1237 		} else if (i == sort_col2) {
1238 			wattron(tab, COLOR_PAIR(3));
1239 			print_max_len(tab, 1, draw_offset, 0, ALIGN_LEFT, col_desc[i].name);
1240 			wattroff(tab, COLOR_PAIR(3));
1241 		} else {
1242 			print_max_len(tab, 1, draw_offset, 0, ALIGN_LEFT, col_desc[i].name);
1243 		}
1244 
1245 		if (offset != 1) {
1246 			print_max_len(tab, 1, offset - 1, 0, ALIGN_LEFT, "|");
1247 		}
1248 	}
1249 
1250 	print_max_len(tab, 2, 1, 0, ALIGN_LEFT, ""); /* Move to next line */
1251 	whline(tab, ACS_HLINE, g_max_col - 2);
1252 
1253 	/* Border lines */
1254 	mvwhline(tab, 0, 1, ACS_HLINE, g_max_col - 2);
1255 	mvwhline(tab, tab_height, 1, ACS_HLINE, g_max_col - 2);
1256 
1257 	wrefresh(tab);
1258 }
1259 
1260 static void
resize_interface(enum tabs tab)1261 resize_interface(enum tabs tab)
1262 {
1263 	int i;
1264 
1265 	clear();
1266 	wclear(g_menu_win);
1267 	mvwin(g_menu_win, g_max_row - MENU_WIN_SPACING, MENU_WIN_LOCATION_COL);
1268 	wresize(g_menu_win, MENU_WIN_HEIGHT, g_max_col);
1269 	draw_menu_win();
1270 
1271 	for (i = 0; i < NUMBER_OF_TABS; i++) {
1272 		wclear(g_tabs[i]);
1273 		wresize(g_tabs[i], g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - 2, g_max_col);
1274 		mvwin(g_tabs[i], TABS_LOCATION_ROW, TABS_LOCATION_COL);
1275 	}
1276 
1277 	draw_tabs(tab, g_current_sort_col[tab], g_current_sort_col2[tab]);
1278 
1279 	for (i = 0; i < NUMBER_OF_TABS; i++) {
1280 		wclear(g_tab_win[i]);
1281 		wresize(g_tab_win[i], TAB_WIN_HEIGHT,
1282 			(g_max_col - (TABS_SPACING * NUMBER_OF_TABS)) / NUMBER_OF_TABS);
1283 		mvwin(g_tab_win[i], TAB_WIN_LOCATION_ROW, 1 + (g_max_col / NUMBER_OF_TABS) * i);
1284 		draw_tab_win(i);
1285 	}
1286 
1287 	update_panels();
1288 	doupdate();
1289 }
1290 
1291 static void
switch_tab(enum tabs tab)1292 switch_tab(enum tabs tab)
1293 {
1294 	wclear(g_tabs[tab]);
1295 	draw_tabs(tab, g_current_sort_col[tab], g_current_sort_col2[tab]);
1296 	top_panel(g_panels[tab]);
1297 	update_panels();
1298 	doupdate();
1299 }
1300 
1301 static void
get_time_str(uint64_t ticks,char * time_str)1302 get_time_str(uint64_t ticks, char *time_str)
1303 {
1304 	uint64_t time;
1305 
1306 	time = ticks * SPDK_SEC_TO_USEC / g_tick_rate;
1307 	snprintf(time_str, MAX_TIME_STR_LEN, "%" PRIu64, time);
1308 }
1309 
1310 static void
draw_row_background(uint8_t item_index,uint8_t tab)1311 draw_row_background(uint8_t item_index, uint8_t tab)
1312 {
1313 	int k;
1314 
1315 	if (item_index == g_selected_row) {
1316 		wattron(g_tabs[tab], COLOR_PAIR(2));
1317 	}
1318 	for (k = 1; k < g_max_col - 1; k++) {
1319 		mvwprintw(g_tabs[tab], TABS_DATA_START_ROW + item_index, k, " ");
1320 	}
1321 }
1322 
1323 static void
get_cpu_usage_str(uint64_t busy_ticks,uint64_t total_ticks,char * cpu_str)1324 get_cpu_usage_str(uint64_t busy_ticks, uint64_t total_ticks, char *cpu_str)
1325 {
1326 	if (total_ticks > 0) {
1327 		snprintf(cpu_str, MAX_FLOAT_STR_LEN, "%.2f",
1328 			 (double)(busy_ticks) * 100 / (double)(total_ticks));
1329 	} else {
1330 		cpu_str[0] = '\0';
1331 	}
1332 }
1333 
1334 static void
draw_thread_tab_row(uint64_t current_row,uint8_t item_index)1335 draw_thread_tab_row(uint64_t current_row, uint8_t item_index)
1336 {
1337 	struct col_desc *col_desc = g_col_desc[THREADS_TAB];
1338 	uint16_t col = TABS_DATA_START_COL;
1339 	int core_idx, color_attr = COLOR_PAIR(6);
1340 	char pollers_number[MAX_POLLER_TYPE_COUNT_STR_LEN], idle_time[MAX_TIME_STR_LEN],
1341 	     busy_time[MAX_TIME_STR_LEN], core_str[MAX_CORE_MASK_STR_LEN],
1342 	     cpu_usage[MAX_FLOAT_STR_LEN], *status_str;
1343 
1344 	if (!col_desc[COL_THREADS_NAME].disabled) {
1345 		print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col,
1346 			      col_desc[COL_THREADS_NAME].max_data_string, ALIGN_LEFT, g_threads_info[current_row].name);
1347 		col += col_desc[COL_THREADS_NAME].max_data_string;
1348 	}
1349 
1350 	if (!col_desc[COL_THREADS_CORE].disabled) {
1351 		snprintf(core_str, MAX_CORE_STR_LEN, "%d", g_threads_info[current_row].core_num);
1352 		print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index,
1353 			      col, col_desc[COL_THREADS_CORE].max_data_string, ALIGN_RIGHT, core_str);
1354 		col += col_desc[COL_THREADS_CORE].max_data_string + 2;
1355 	}
1356 
1357 	if (!col_desc[COL_THREADS_ACTIVE_POLLERS].disabled) {
1358 		snprintf(pollers_number, MAX_POLLER_TYPE_COUNT_STR_LEN, "%ld",
1359 			 g_threads_info[current_row].active_pollers_count);
1360 		print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index,
1361 			      col + (col_desc[COL_THREADS_ACTIVE_POLLERS].name_len / 2),
1362 			      col_desc[COL_THREADS_ACTIVE_POLLERS].max_data_string, ALIGN_LEFT, pollers_number);
1363 		col += col_desc[COL_THREADS_ACTIVE_POLLERS].max_data_string + 2;
1364 	}
1365 
1366 	if (!col_desc[COL_THREADS_TIMED_POLLERS].disabled) {
1367 		snprintf(pollers_number, MAX_POLLER_TYPE_COUNT_STR_LEN, "%ld",
1368 			 g_threads_info[current_row].timed_pollers_count);
1369 		print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index,
1370 			      col + (col_desc[COL_THREADS_TIMED_POLLERS].name_len / 2),
1371 			      col_desc[COL_THREADS_TIMED_POLLERS].max_data_string, ALIGN_LEFT, pollers_number);
1372 		col += col_desc[COL_THREADS_TIMED_POLLERS].max_data_string + 1;
1373 	}
1374 
1375 	if (!col_desc[COL_THREADS_PAUSED_POLLERS].disabled) {
1376 		snprintf(pollers_number, MAX_POLLER_TYPE_COUNT_STR_LEN, "%ld",
1377 			 g_threads_info[current_row].paused_pollers_count);
1378 		print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index,
1379 			      col + (col_desc[COL_THREADS_PAUSED_POLLERS].name_len / 2),
1380 			      col_desc[COL_THREADS_PAUSED_POLLERS].max_data_string, ALIGN_LEFT, pollers_number);
1381 		col += col_desc[COL_THREADS_PAUSED_POLLERS].max_data_string + 2;
1382 	}
1383 
1384 	uint64_t idle_period = g_threads_info[current_row].idle - g_threads_info[current_row].last_idle;
1385 	uint64_t busy_period = g_threads_info[current_row].busy - g_threads_info[current_row].last_busy;
1386 	if (!col_desc[COL_THREADS_IDLE_TIME].disabled) {
1387 		if (g_interval_data == true) {
1388 			get_time_str(idle_period, idle_time);
1389 		} else {
1390 			get_time_str(g_threads_info[current_row].idle, idle_time);
1391 		}
1392 		print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col,
1393 			      col_desc[COL_THREADS_IDLE_TIME].max_data_string, ALIGN_RIGHT, idle_time);
1394 		col += col_desc[COL_THREADS_IDLE_TIME].max_data_string;
1395 	}
1396 
1397 	if (!col_desc[COL_THREADS_BUSY_TIME].disabled) {
1398 		if (g_interval_data == true) {
1399 			get_time_str(busy_period, busy_time);
1400 		} else {
1401 			get_time_str(g_threads_info[current_row].busy, busy_time);
1402 		}
1403 		print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col,
1404 			      col_desc[COL_THREADS_BUSY_TIME].max_data_string, ALIGN_RIGHT, busy_time);
1405 		col += col_desc[COL_THREADS_BUSY_TIME].max_data_string + 3;
1406 	}
1407 
1408 	if (!col_desc[COL_THREADS_CPU_USAGE].disabled) {
1409 		core_idx = g_threads_info[current_row].core_idx;
1410 		if (core_idx >= 0 && core_idx < RPC_MAX_CORES) {
1411 			uint64_t core_busy_period = g_cores_info[core_idx].busy - g_cores_info[core_idx].last_busy;
1412 			uint64_t core_idle_period = g_cores_info[core_idx].idle - g_cores_info[core_idx].last_idle;
1413 			get_cpu_usage_str(busy_period, core_busy_period + core_idle_period, cpu_usage);
1414 		} else {
1415 			snprintf(cpu_usage, sizeof(cpu_usage), "n/a");
1416 		}
1417 
1418 		print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col,
1419 			      col_desc[COL_THREADS_CPU_USAGE].max_data_string, ALIGN_RIGHT, cpu_usage);
1420 		col += col_desc[COL_THREADS_CPU_USAGE].max_data_string + 2;
1421 	}
1422 
1423 	if (!col_desc[COL_THREADS_STATUS].disabled) {
1424 		if (busy_period > idle_period) {
1425 			if (item_index != g_selected_row) {
1426 				color_attr = COLOR_PAIR(6);
1427 			} else {
1428 				color_attr = COLOR_PAIR(8);
1429 			}
1430 			status_str = "Busy";
1431 		} else {
1432 			if (item_index != g_selected_row) {
1433 				color_attr = COLOR_PAIR(7);
1434 			} else {
1435 				color_attr = COLOR_PAIR(9);
1436 			}
1437 			status_str = "Idle";
1438 		}
1439 		wattron(g_tabs[THREADS_TAB], color_attr);
1440 		print_max_len(g_tabs[THREADS_TAB], TABS_DATA_START_ROW + item_index, col,
1441 			      col_desc[COL_THREADS_STATUS].max_data_string, ALIGN_RIGHT, status_str);
1442 		wattroff(g_tabs[THREADS_TAB], color_attr);
1443 	}
1444 }
1445 
1446 static uint8_t
refresh_threads_tab(uint8_t current_page)1447 refresh_threads_tab(uint8_t current_page)
1448 {
1449 	uint64_t i, j, threads_count;
1450 	uint16_t empty_col = 0;
1451 	uint8_t max_pages, item_index;
1452 
1453 	threads_count = g_last_threads_count;
1454 
1455 	max_pages = (threads_count + g_max_data_rows - 1) / g_max_data_rows;
1456 
1457 	for (i = current_page * g_max_data_rows;
1458 	     i < (uint64_t)((current_page + 1) * g_max_data_rows);
1459 	     i++) {
1460 		item_index = i - (current_page * g_max_data_rows);
1461 
1462 		/* When number of threads decreases, this will print spaces in places
1463 		 * where non existent threads were previously displayed. */
1464 		if (i >= threads_count) {
1465 			for (j = 1; j < (uint64_t)g_max_col - 1; j++) {
1466 				mvwprintw(g_tabs[THREADS_TAB], item_index + TABS_DATA_START_ROW, j, " ");
1467 			}
1468 
1469 			empty_col++;
1470 			continue;
1471 		}
1472 
1473 		draw_row_background(item_index, THREADS_TAB);
1474 		draw_thread_tab_row(i, item_index);
1475 
1476 		if (item_index == g_selected_row) {
1477 			wattroff(g_tabs[THREADS_TAB], COLOR_PAIR(2));
1478 		}
1479 	}
1480 
1481 	g_max_selected_row = i - current_page * g_max_data_rows - 1 - empty_col;
1482 
1483 	return max_pages;
1484 }
1485 
1486 static void
draw_poller_tab_row(uint64_t current_row,uint8_t item_index)1487 draw_poller_tab_row(uint64_t current_row, uint8_t item_index)
1488 {
1489 	struct col_desc *col_desc = g_col_desc[POLLERS_TAB];
1490 	uint64_t last_run_counter, last_busy_counter;
1491 	uint16_t col = TABS_DATA_START_COL;
1492 	char run_count[MAX_POLLER_RUN_COUNT], period_ticks[MAX_PERIOD_STR_LEN],
1493 	     status[MAX_POLLER_IND_STR_LEN];
1494 
1495 	last_busy_counter = get_last_busy_counter(g_pollers_info[current_row].id,
1496 			    g_pollers_info[current_row].thread_id);
1497 
1498 	if (!col_desc[COL_POLLERS_NAME].disabled) {
1499 		print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col + 1,
1500 			      col_desc[COL_POLLERS_NAME].max_data_string, ALIGN_LEFT, g_pollers_info[current_row].name);
1501 		col += col_desc[COL_POLLERS_NAME].max_data_string + 2;
1502 	}
1503 
1504 	if (!col_desc[COL_POLLERS_TYPE].disabled) {
1505 		print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1506 			      col_desc[COL_POLLERS_TYPE].max_data_string, ALIGN_LEFT,
1507 			      poller_type_str[g_pollers_info[current_row].type]);
1508 		col += col_desc[COL_POLLERS_TYPE].max_data_string + 2;
1509 	}
1510 
1511 	if (!col_desc[COL_POLLERS_THREAD_NAME].disabled) {
1512 		print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1513 			      col_desc[COL_POLLERS_THREAD_NAME].max_data_string, ALIGN_LEFT,
1514 			      g_pollers_info[current_row].thread_name);
1515 		col += col_desc[COL_POLLERS_THREAD_NAME].max_data_string + 1;
1516 	}
1517 
1518 	if (!col_desc[COL_POLLERS_RUN_COUNTER].disabled) {
1519 		last_run_counter = get_last_run_counter(g_pollers_info[current_row].id,
1520 							g_pollers_info[current_row].thread_id);
1521 		if (g_interval_data == true) {
1522 			snprintf(run_count, MAX_POLLER_RUN_COUNT, "%" PRIu64,
1523 				 g_pollers_info[current_row].run_count - last_run_counter);
1524 		} else {
1525 			snprintf(run_count, MAX_POLLER_RUN_COUNT, "%" PRIu64, g_pollers_info[current_row].run_count);
1526 		}
1527 		print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1528 			      col_desc[COL_POLLERS_RUN_COUNTER].max_data_string, ALIGN_RIGHT, run_count);
1529 		col += col_desc[COL_POLLERS_RUN_COUNTER].max_data_string;
1530 	}
1531 
1532 	if (!col_desc[COL_POLLERS_PERIOD].disabled) {
1533 		if (g_pollers_info[current_row].period_ticks != 0) {
1534 			get_time_str(g_pollers_info[current_row].period_ticks, period_ticks);
1535 			print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1536 				      col_desc[COL_POLLERS_PERIOD].max_data_string, ALIGN_RIGHT, period_ticks);
1537 		}
1538 		col += col_desc[COL_POLLERS_PERIOD].max_data_string + 7;
1539 	}
1540 
1541 	if (!col_desc[COL_POLLERS_BUSY_COUNT].disabled) {
1542 		if (g_pollers_info[current_row].busy_count > last_busy_counter) {
1543 			if (g_interval_data == true) {
1544 				snprintf(status, MAX_POLLER_IND_STR_LEN, "Busy (%" PRIu64 ")",
1545 					 g_pollers_info[current_row].busy_count - last_busy_counter);
1546 			} else {
1547 				snprintf(status, MAX_POLLER_IND_STR_LEN, "Busy (%" PRIu64 ")",
1548 					 g_pollers_info[current_row].busy_count);
1549 			}
1550 
1551 			if (item_index != g_selected_row) {
1552 				wattron(g_tabs[POLLERS_TAB], COLOR_PAIR(6));
1553 				print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1554 					      col_desc[COL_POLLERS_BUSY_COUNT].max_data_string, ALIGN_LEFT, status);
1555 				wattroff(g_tabs[POLLERS_TAB], COLOR_PAIR(6));
1556 			} else {
1557 				wattron(g_tabs[POLLERS_TAB], COLOR_PAIR(8));
1558 				print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1559 					      col_desc[COL_POLLERS_BUSY_COUNT].max_data_string, ALIGN_LEFT, status);
1560 				wattroff(g_tabs[POLLERS_TAB], COLOR_PAIR(8));
1561 			}
1562 		} else {
1563 			if (g_interval_data == true) {
1564 				snprintf(status, MAX_POLLER_IND_STR_LEN, "%s", "Idle");
1565 			} else {
1566 				snprintf(status, MAX_POLLER_IND_STR_LEN, "Idle (%" PRIu64 ")",
1567 					 g_pollers_info[current_row].busy_count);
1568 			}
1569 
1570 			if (item_index != g_selected_row) {
1571 				wattron(g_tabs[POLLERS_TAB], COLOR_PAIR(7));
1572 				print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1573 					      col_desc[COL_POLLERS_BUSY_COUNT].max_data_string, ALIGN_LEFT, status);
1574 				wattroff(g_tabs[POLLERS_TAB], COLOR_PAIR(7));
1575 			} else {
1576 				wattron(g_tabs[POLLERS_TAB], COLOR_PAIR(9));
1577 				print_max_len(g_tabs[POLLERS_TAB], TABS_DATA_START_ROW + item_index, col,
1578 					      col_desc[COL_POLLERS_BUSY_COUNT].max_data_string, ALIGN_LEFT, status);
1579 				wattroff(g_tabs[POLLERS_TAB], COLOR_PAIR(9));
1580 			}
1581 		}
1582 	}
1583 }
1584 
1585 static uint8_t
refresh_pollers_tab(uint8_t current_page)1586 refresh_pollers_tab(uint8_t current_page)
1587 {
1588 	uint64_t i, j;
1589 	uint16_t empty_col = 0;
1590 	uint8_t max_pages, item_index;
1591 
1592 	max_pages = (g_last_pollers_count + g_max_data_rows - 1) / g_max_data_rows;
1593 
1594 	/* Display info */
1595 	for (i = current_page * g_max_data_rows;
1596 	     i < (uint64_t)((current_page + 1) * g_max_data_rows);
1597 	     i++) {
1598 		item_index = i - (current_page * g_max_data_rows);
1599 
1600 		/* When number of pollers decreases, this will print spaces in places
1601 		 * where non existent pollers were previously displayed. */
1602 		if (i >= g_last_pollers_count) {
1603 			for (j = 1; j < (uint64_t)g_max_col - 1; j++) {
1604 				mvwprintw(g_tabs[POLLERS_TAB], item_index + TABS_DATA_START_ROW, j, " ");
1605 			}
1606 
1607 			empty_col++;
1608 			continue;
1609 		}
1610 
1611 		draw_row_background(item_index, POLLERS_TAB);
1612 		draw_poller_tab_row(i, item_index);
1613 
1614 		if (item_index == g_selected_row) {
1615 			wattroff(g_tabs[POLLERS_TAB], COLOR_PAIR(2));
1616 		}
1617 	}
1618 
1619 	g_max_selected_row = i - current_page * g_max_data_rows - 1 - empty_col;
1620 
1621 	return max_pages;
1622 }
1623 
1624 static void
draw_core_tab_row(uint64_t current_row,uint8_t item_index)1625 draw_core_tab_row(uint64_t current_row, uint8_t item_index)
1626 {
1627 	struct col_desc *col_desc = g_col_desc[CORES_TAB];
1628 	float res = 0.0;
1629 	uint16_t col = 1;
1630 	uint64_t irq_tmp, usr_tmp, sys_tmp, busy_tmp, idle_tmp;
1631 	int color_attr = COLOR_PAIR(6);
1632 	char core[MAX_CORE_STR_LEN], threads_number[MAX_THREAD_COUNT_STR_LEN], cpu_usage[MAX_FLOAT_STR_LEN],
1633 	     pollers_number[MAX_POLLER_COUNT_STR_LEN], idle_time[MAX_TIME_STR_LEN],
1634 	     busy_time[MAX_TIME_STR_LEN], core_freq[MAX_CORE_FREQ_STR_LEN],
1635 	     in_interrupt[MAX_INTR_LEN], *status_str, sys_str[MAX_FLOAT_STR_LEN],
1636 	     irq_str[MAX_FLOAT_STR_LEN], cpu_str[MAX_FLOAT_STR_LEN];
1637 
1638 	snprintf(threads_number, MAX_THREAD_COUNT_STR_LEN, "%ld",
1639 		 g_cores_info[current_row].threads.threads_count);
1640 	snprintf(pollers_number, MAX_POLLER_COUNT_STR_LEN, "%ld", g_cores_info[current_row].pollers_count);
1641 
1642 	if (!col_desc[COL_CORES_CORE].disabled) {
1643 		snprintf(core, MAX_CORE_STR_LEN, "%d", g_cores_info[current_row].lcore);
1644 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, col,
1645 			      col_desc[COL_CORES_CORE].max_data_string, ALIGN_RIGHT, core);
1646 		col += col_desc[COL_CORES_CORE].max_data_string + 2;
1647 	}
1648 
1649 	if (!col_desc[COL_CORES_THREADS].disabled) {
1650 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index,
1651 			      col + (col_desc[COL_CORES_THREADS].name_len / 2), col_desc[COL_CORES_THREADS].max_data_string,
1652 			      ALIGN_LEFT, threads_number);
1653 		col += col_desc[COL_CORES_THREADS].max_data_string + 2;
1654 	}
1655 
1656 	if (!col_desc[COL_CORES_POLLERS].disabled) {
1657 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index,
1658 			      col + (col_desc[COL_CORES_POLLERS].name_len / 2), col_desc[COL_CORES_POLLERS].max_data_string,
1659 			      ALIGN_LEFT, pollers_number);
1660 		col += col_desc[COL_CORES_POLLERS].max_data_string;
1661 	}
1662 
1663 	uint64_t idle_period = g_cores_info[current_row].idle - g_cores_info[current_row].last_idle;
1664 	uint64_t busy_period = g_cores_info[current_row].busy - g_cores_info[current_row].last_busy;
1665 	if (!col_desc[COL_CORES_IDLE_TIME].disabled) {
1666 		if (g_interval_data == true) {
1667 			get_time_str(idle_period, idle_time);
1668 		} else {
1669 			get_time_str(g_cores_info[current_row].idle, idle_time);
1670 		}
1671 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, col,
1672 			      col_desc[COL_CORES_IDLE_TIME].max_data_string, ALIGN_RIGHT, idle_time);
1673 		col += col_desc[COL_CORES_IDLE_TIME].max_data_string + 2;
1674 	}
1675 
1676 	if (!col_desc[COL_CORES_BUSY_TIME].disabled) {
1677 		if (g_interval_data == true) {
1678 			get_time_str(busy_period, busy_time);
1679 		} else {
1680 			get_time_str(g_cores_info[current_row].busy, busy_time);
1681 		}
1682 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, col,
1683 			      col_desc[COL_CORES_BUSY_TIME].max_data_string, ALIGN_RIGHT, busy_time);
1684 		col += col_desc[COL_CORES_BUSY_TIME].max_data_string + 2;
1685 	}
1686 
1687 	if (!col_desc[COL_CORES_BUSY_PCT].disabled) {
1688 		get_cpu_usage_str(busy_period, busy_period + idle_period, cpu_usage);
1689 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, col,
1690 			      col_desc[COL_CORES_BUSY_PCT].max_data_string, ALIGN_RIGHT, cpu_usage);
1691 		col += col_desc[COL_CORES_BUSY_PCT].max_data_string + 1;
1692 	}
1693 
1694 	if (!col_desc[COL_CORES_STATUS].disabled) {
1695 		if (busy_period > idle_period) {
1696 			if (item_index != g_selected_row) {
1697 				color_attr = COLOR_PAIR(6);
1698 			} else {
1699 				color_attr = COLOR_PAIR(8);
1700 			}
1701 			status_str = "Busy";
1702 		} else {
1703 			if (item_index != g_selected_row) {
1704 				color_attr = COLOR_PAIR(7);
1705 			} else {
1706 				color_attr = COLOR_PAIR(9);
1707 			}
1708 			status_str = "Idle";
1709 		}
1710 		wattron(g_tabs[CORES_TAB], color_attr);
1711 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, col,
1712 			      col_desc[COL_CORES_STATUS].max_data_string, ALIGN_RIGHT, status_str);
1713 		wattroff(g_tabs[CORES_TAB], color_attr);
1714 		if (item_index == g_selected_row) {
1715 			wattron(g_tabs[CORES_TAB], COLOR_PAIR(2));
1716 		} else {
1717 			wattron(g_tabs[CORES_TAB], COLOR_PAIR(0));
1718 		}
1719 		col += col_desc[COL_CORES_STATUS].max_data_string + 1;
1720 	}
1721 
1722 	if (!col_desc[COL_CORES_INTR].disabled) {
1723 		snprintf(in_interrupt, MAX_INTR_LEN, "%s",
1724 			 g_cores_info[current_row].in_interrupt ? "Yes" : "No");
1725 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index,
1726 			      col + (col_desc[COL_CORES_INTR].name_len / 2),
1727 			      col_desc[COL_CORES_INTR].max_data_string,
1728 			      ALIGN_LEFT, in_interrupt);
1729 		col += col_desc[COL_CORES_INTR].max_data_string + 1;
1730 	}
1731 
1732 	/* System stats */
1733 	col += 2;
1734 
1735 	if (g_interval_data == true) {
1736 		irq_tmp = g_cores_info[current_row].irq - g_cores_info[current_row].last_irq;
1737 		usr_tmp = g_cores_info[current_row].usr - g_cores_info[current_row].last_usr;
1738 		sys_tmp = g_cores_info[current_row].sys - g_cores_info[current_row].last_sys;
1739 		idle_tmp = idle_period;
1740 		busy_tmp = busy_period;
1741 	} else {
1742 		irq_tmp = g_cores_info[current_row].irq;
1743 		usr_tmp = g_cores_info[current_row].usr;
1744 		sys_tmp = g_cores_info[current_row].sys;
1745 		idle_tmp = g_cores_info[current_row].idle;
1746 		busy_tmp = g_cores_info[current_row].busy;
1747 	}
1748 
1749 	if (!col_desc[COL_CORES_SYS_PCT].disabled) {
1750 		if (usr_tmp + sys_tmp > 0) {
1751 			res = (((float)sys_tmp /
1752 				(usr_tmp + sys_tmp)) * 100);
1753 		}
1754 		snprintf(sys_str, sizeof(sys_str), "%.2f", res);
1755 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, col,
1756 			      col_desc[COL_CORES_SYS_PCT].max_data_string, ALIGN_RIGHT, sys_str);
1757 		col += col_desc[COL_CORES_SYS_PCT].max_data_string + 1;
1758 	}
1759 
1760 	if (!col_desc[COL_CORES_IRQ_PCT].disabled) {
1761 		res = 0.0;
1762 		if (usr_tmp + sys_tmp + irq_tmp > 0) {
1763 			res = (((float)irq_tmp / (usr_tmp + sys_tmp + irq_tmp)) * 100);
1764 		}
1765 		snprintf(irq_str, sizeof(irq_str), "%.2f", res);
1766 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, col,
1767 			      col_desc[COL_CORES_IRQ_PCT].max_data_string, ALIGN_RIGHT, irq_str);
1768 		col += col_desc[COL_CORES_IRQ_PCT].max_data_string + 1;
1769 	}
1770 
1771 	if (!col_desc[COL_CORES_CPU_PCT].disabled) {
1772 		res = 0.0;
1773 		if (busy_tmp + idle_tmp + sys_tmp + irq_tmp > 0) {
1774 			res = (((float)(busy_tmp + irq_tmp + sys_tmp) /
1775 				(busy_tmp + idle_tmp + sys_tmp + irq_tmp)) * 100);
1776 		}
1777 		snprintf(cpu_str, sizeof(cpu_str), "%.2f", res);
1778 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, col,
1779 			      col_desc[COL_CORES_CPU_PCT].max_data_string, ALIGN_RIGHT, cpu_str);
1780 		col += col_desc[COL_CORES_CPU_PCT].max_data_string + 1;
1781 	}
1782 
1783 	if (!col_desc[COL_CORES_CORE_FREQ].disabled) {
1784 		if (!g_cores_info[current_row].core_freq) {
1785 			snprintf(core_freq, MAX_CORE_FREQ_STR_LEN, "%s", "N/A");
1786 		} else {
1787 			snprintf(core_freq, MAX_CORE_FREQ_STR_LEN, "%" PRIu32,
1788 				 g_cores_info[current_row].core_freq);
1789 		}
1790 		print_max_len(g_tabs[CORES_TAB], TABS_DATA_START_ROW + item_index, col,
1791 			      col_desc[COL_CORES_CORE_FREQ].max_data_string, ALIGN_RIGHT, core_freq);
1792 	}
1793 }
1794 
1795 static uint8_t
refresh_cores_tab(uint8_t current_page)1796 refresh_cores_tab(uint8_t current_page)
1797 {
1798 	uint64_t i;
1799 	uint16_t count = 0;
1800 	uint8_t max_pages, item_index;
1801 
1802 	count = g_last_cores_count;
1803 
1804 	max_pages = (count + g_max_row - WINDOW_HEADER - 1) / (g_max_row - WINDOW_HEADER);
1805 
1806 	for (i = current_page * g_max_data_rows;
1807 	     i < spdk_min(count, (uint64_t)((current_page + 1) * g_max_data_rows));
1808 	     i++) {
1809 		item_index = i - (current_page * g_max_data_rows);
1810 
1811 		draw_row_background(item_index, CORES_TAB);
1812 		draw_core_tab_row(i, item_index);
1813 
1814 		if (item_index == g_selected_row) {
1815 			wattroff(g_tabs[CORES_TAB], COLOR_PAIR(2));
1816 		}
1817 	}
1818 
1819 	g_max_selected_row = i - current_page * g_max_data_rows - 1;
1820 
1821 	return max_pages;
1822 }
1823 
1824 static uint8_t
refresh_tab(enum tabs tab,uint8_t current_page)1825 refresh_tab(enum tabs tab, uint8_t current_page)
1826 {
1827 	uint8_t (*refresh_function[NUMBER_OF_TABS])(uint8_t current_page) = {refresh_threads_tab, refresh_pollers_tab, refresh_cores_tab};
1828 	int color_pair[NUMBER_OF_TABS] = {COLOR_PAIR(2), COLOR_PAIR(2), COLOR_PAIR(2)};
1829 	int i;
1830 	uint8_t max_pages = 0;
1831 
1832 	color_pair[tab] = COLOR_PAIR(1);
1833 
1834 	for (i = 0; i < NUMBER_OF_TABS; i++) {
1835 		wbkgd(g_tab_win[i], color_pair[i]);
1836 	}
1837 
1838 	max_pages = (*refresh_function[tab])(current_page);
1839 
1840 	for (i = 0; i < NUMBER_OF_TABS; i++) {
1841 		wnoutrefresh(g_tab_win[i]);
1842 	}
1843 	draw_menu_win();
1844 
1845 	return max_pages;
1846 }
1847 
1848 static void
print_in_middle(WINDOW * win,int starty,int startx,int width,char * string,chtype color)1849 print_in_middle(WINDOW *win, int starty, int startx, int width, char *string, chtype color)
1850 {
1851 	int length, temp;
1852 
1853 	length = strlen(string);
1854 	temp = (width - length) / 2;
1855 	wattron(win, color);
1856 	mvwprintw(win, starty, startx + temp, "%s", string);
1857 	wattroff(win, color);
1858 }
1859 
1860 static void
print_left(WINDOW * win,int starty,int startx,int width,const char * string,chtype color)1861 print_left(WINDOW *win, int starty, int startx, int width, const char *string, chtype color)
1862 {
1863 	wattron(win, color);
1864 	mvwprintw(win, starty, startx, "%s", string);
1865 	wattroff(win, color);
1866 }
1867 
1868 static void
apply_filters(enum tabs tab)1869 apply_filters(enum tabs tab)
1870 {
1871 	wclear(g_tabs[tab]);
1872 	draw_tabs(tab, g_current_sort_col[tab], g_current_sort_col2[tab]);
1873 }
1874 
1875 static ITEM **
draw_filtering_menu(uint8_t position,WINDOW * filter_win,uint8_t tab,MENU ** my_menu)1876 draw_filtering_menu(uint8_t position, WINDOW *filter_win, uint8_t tab, MENU **my_menu)
1877 {
1878 	const int ADDITIONAL_ELEMENTS = 3;
1879 	const int ROW_PADDING = 6;
1880 	const int WINDOW_START_X = 1;
1881 	const int WINDOW_START_Y = 3;
1882 	const int WINDOW_COLUMNS = 2;
1883 	struct col_desc *col_desc = g_col_desc[tab];
1884 	ITEM **my_items;
1885 	MENU *menu;
1886 	int i, elements;
1887 	uint8_t len = 0;
1888 
1889 	for (i = 0; col_desc[i].name != NULL; ++i) {
1890 		len = spdk_max(col_desc[i].name_len, len);
1891 	}
1892 
1893 	elements = i;
1894 
1895 	my_items = (ITEM **)calloc(elements * WINDOW_COLUMNS + ADDITIONAL_ELEMENTS, sizeof(ITEM *));
1896 	if (my_items == NULL) {
1897 		fprintf(stderr, "Unable to allocate an item list in draw_filtering_menu.\n");
1898 		return NULL;
1899 	}
1900 
1901 	for (i = 0; i < elements * 2; i++) {
1902 		my_items[i] = new_item(col_desc[i / WINDOW_COLUMNS].name, NULL);
1903 		i++;
1904 		my_items[i] = new_item(col_desc[i / WINDOW_COLUMNS].disabled ? "[ ]" : "[*]", NULL);
1905 	}
1906 
1907 	my_items[i] = new_item("     CLOSE", NULL);
1908 	set_item_userptr(my_items[i], apply_filters);
1909 
1910 	menu = new_menu((ITEM **)my_items);
1911 
1912 	menu_opts_off(menu, O_SHOWDESC);
1913 	set_menu_format(menu, elements + 1, WINDOW_COLUMNS);
1914 
1915 	set_menu_win(menu, filter_win);
1916 	set_menu_sub(menu, derwin(filter_win, elements + 1, len + ROW_PADDING, WINDOW_START_Y,
1917 				  WINDOW_START_X));
1918 
1919 	*my_menu = menu;
1920 
1921 	post_menu(menu);
1922 	refresh();
1923 	wrefresh(filter_win);
1924 
1925 	for (i = 0; i < position / WINDOW_COLUMNS; i++) {
1926 		menu_driver(menu, REQ_DOWN_ITEM);
1927 	}
1928 
1929 	return my_items;
1930 }
1931 
1932 static void
delete_filtering_menu(MENU * my_menu,ITEM ** my_items,uint8_t elements)1933 delete_filtering_menu(MENU *my_menu, ITEM **my_items, uint8_t elements)
1934 {
1935 	int i;
1936 
1937 	unpost_menu(my_menu);
1938 	free_menu(my_menu);
1939 	for (i = 0; i < elements * 2 + 2; ++i) {
1940 		free_item(my_items[i]);
1941 	}
1942 	free(my_items);
1943 }
1944 
1945 static ITEM **
refresh_filtering_menu(MENU ** my_menu,WINDOW * filter_win,uint8_t tab,ITEM ** my_items,uint8_t elements,uint8_t position)1946 refresh_filtering_menu(MENU **my_menu, WINDOW *filter_win, uint8_t tab, ITEM **my_items,
1947 		       uint8_t elements, uint8_t position)
1948 {
1949 	delete_filtering_menu(*my_menu, my_items, elements);
1950 	return draw_filtering_menu(position, filter_win, tab, my_menu);
1951 }
1952 
1953 static void
filter_columns(uint8_t tab)1954 filter_columns(uint8_t tab)
1955 {
1956 	const int WINDOW_HEADER_LEN = 5;
1957 	const int WINDOW_BORDER_LEN = 8;
1958 	const int WINDOW_HEADER_END_LINE = 2;
1959 	const int WINDOW_COLUMNS = 2;
1960 	struct col_desc *col_desc = g_col_desc[tab];
1961 	PANEL *filter_panel;
1962 	WINDOW *filter_win;
1963 	ITEM **my_items;
1964 	MENU *my_menu = NULL;
1965 	int i, c, elements;
1966 	bool stop_loop = false;
1967 	ITEM *cur;
1968 	void (*p)(enum tabs tab);
1969 	uint8_t current_index, len = 0;
1970 	bool disabled[TABS_COL_COUNT];
1971 
1972 	for (i = 0; col_desc[i].name != NULL; ++i) {
1973 		len = spdk_max(col_desc[i].name_len, len);
1974 	}
1975 
1976 	elements = i;
1977 
1978 	filter_win = newwin(elements + WINDOW_HEADER_LEN, len + WINDOW_BORDER_LEN,
1979 			    (g_max_row - elements - 1) / 2, (g_max_col - len) / 2);
1980 	assert(filter_win != NULL);
1981 	keypad(filter_win, TRUE);
1982 	filter_panel = new_panel(filter_win);
1983 	assert(filter_panel != NULL);
1984 
1985 	top_panel(filter_panel);
1986 	update_panels();
1987 	doupdate();
1988 
1989 	box(filter_win, 0, 0);
1990 
1991 	print_in_middle(filter_win, 1, 0, len + WINDOW_BORDER_LEN, "Filtering", COLOR_PAIR(3));
1992 	mvwaddch(filter_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE);
1993 	mvwhline(filter_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, len + WINDOW_BORDER_LEN - 2);
1994 	mvwaddch(filter_win, WINDOW_HEADER_END_LINE, len + WINDOW_BORDER_LEN - 1, ACS_RTEE);
1995 
1996 	my_items = draw_filtering_menu(0, filter_win, tab, &my_menu);
1997 	if (my_items == NULL || my_menu == NULL) {
1998 		goto fail;
1999 	}
2000 
2001 	for (int i = 0; i < TABS_COL_COUNT; i++) {
2002 		disabled[i] = col_desc[i].disabled;
2003 	}
2004 
2005 	while (!stop_loop) {
2006 		c = wgetch(filter_win);
2007 
2008 		switch (c) {
2009 		case KEY_DOWN:
2010 			menu_driver(my_menu, REQ_DOWN_ITEM);
2011 			break;
2012 		case KEY_UP:
2013 			menu_driver(my_menu, REQ_UP_ITEM);
2014 			break;
2015 		case 27: /* ESC */
2016 		case 'q':
2017 			for (int i = 0; i < TABS_COL_COUNT; i++) {
2018 				cur = current_item(my_menu);
2019 				col_desc[i].disabled = disabled[i];
2020 
2021 				my_items = refresh_filtering_menu(&my_menu, filter_win, tab, my_items, elements,
2022 								  item_index(cur) + 1);
2023 				if (my_items == NULL || my_menu == NULL) {
2024 					goto fail;
2025 				}
2026 			}
2027 
2028 			stop_loop = true;
2029 			break;
2030 		case ' ': /* Space */
2031 			cur = current_item(my_menu);
2032 			current_index = item_index(cur) / WINDOW_COLUMNS;
2033 			col_desc[current_index].disabled = !col_desc[current_index].disabled;
2034 			my_items = refresh_filtering_menu(&my_menu, filter_win, tab, my_items, elements,
2035 							  item_index(cur) + 1);
2036 			if (my_items == NULL || my_menu == NULL) {
2037 				goto fail;
2038 			}
2039 			break;
2040 		case 10: /* Enter */
2041 			cur = current_item(my_menu);
2042 			current_index = item_index(cur) / WINDOW_COLUMNS;
2043 			if (current_index == elements) {
2044 				stop_loop = true;
2045 				p = item_userptr(cur);
2046 				p(tab);
2047 			} else {
2048 				col_desc[current_index].disabled = !col_desc[current_index].disabled;
2049 				my_items = refresh_filtering_menu(&my_menu, filter_win, tab, my_items, elements,
2050 								  item_index(cur) + 1);
2051 				if (my_items == NULL || my_menu == NULL) {
2052 					goto fail;
2053 				}
2054 			}
2055 			break;
2056 		}
2057 		wrefresh(filter_win);
2058 	}
2059 
2060 	delete_filtering_menu(my_menu, my_items, elements);
2061 
2062 	del_panel(filter_panel);
2063 	delwin(filter_win);
2064 
2065 	wclear(g_menu_win);
2066 	draw_menu_win();
2067 	return;
2068 
2069 fail:
2070 	fprintf(stderr, "Unable to filter the columns due to allocation failure.\n");
2071 	assert(false);
2072 }
2073 
2074 static void
sort_type(enum tabs tab,int item_index)2075 sort_type(enum tabs tab, int item_index)
2076 {
2077 	g_current_sort_col[tab] = item_index;
2078 }
2079 
2080 static void
sort_type2(enum tabs tab,int item_index)2081 sort_type2(enum tabs tab, int item_index)
2082 {
2083 	g_current_sort_col2[tab] = item_index;
2084 }
2085 
2086 static void
change_sorting(uint8_t tab,int winnum,bool * pstop_loop)2087 change_sorting(uint8_t tab, int winnum, bool *pstop_loop)
2088 {
2089 	const int WINDOW_HEADER_LEN = 4;
2090 	const int WINDOW_BORDER_LEN = 3;
2091 	const int WINDOW_START_X = 1;
2092 	const int WINDOW_START_Y = 4;
2093 	const int WINDOW_HEADER_END_LINE = 3;
2094 	const int WINDOW_MIN_WIDTH = 31;
2095 	PANEL *sort_panel;
2096 	WINDOW *sort_win;
2097 	ITEM **my_items;
2098 	MENU *my_menu;
2099 	int i, c, elements;
2100 	bool stop_loop = false;
2101 	ITEM *cur;
2102 	char *name;
2103 	char *help;
2104 	void (*p)(enum tabs tab, int item_index);
2105 	uint8_t len = WINDOW_MIN_WIDTH;
2106 
2107 	for (i = 0; g_col_desc[tab][i].name != NULL; ++i) {
2108 		len = spdk_max(len, g_col_desc[tab][i].name_len);
2109 	}
2110 
2111 	elements = i;
2112 
2113 	my_items = (ITEM **)calloc(elements + 1, sizeof(ITEM *));
2114 	if (my_items == NULL) {
2115 		fprintf(stderr, "Unable to allocate an item list in change_sorting.\n");
2116 		return;
2117 	}
2118 
2119 	for (i = 0; i < elements; ++i) {
2120 		my_items[i] = new_item(g_col_desc[tab][i].name, NULL);
2121 		set_item_userptr(my_items[i], (winnum == 0) ? sort_type : sort_type2);
2122 	}
2123 
2124 	my_menu = new_menu((ITEM **)my_items);
2125 
2126 	menu_opts_off(my_menu, O_SHOWDESC);
2127 
2128 	sort_win = newwin(elements + WINDOW_HEADER_LEN + 1, len + WINDOW_BORDER_LEN,
2129 			  (g_max_row - elements) / 2,
2130 			  (g_max_col - len) / 2 - len + len * winnum);
2131 	assert(sort_win != NULL);
2132 	keypad(sort_win, TRUE);
2133 	sort_panel = new_panel(sort_win);
2134 	assert(sort_panel != NULL);
2135 
2136 	top_panel(sort_panel);
2137 	update_panels();
2138 	doupdate();
2139 
2140 	set_menu_win(my_menu, sort_win);
2141 	set_menu_sub(my_menu, derwin(sort_win, elements, len + 1, WINDOW_START_Y, WINDOW_START_X));
2142 	box(sort_win, 0, 0);
2143 
2144 	if (winnum == 0) {
2145 		name = "Sorting #1";
2146 		help = "Right key for second sorting";
2147 	} else {
2148 		name = "Sorting #2";
2149 		help = "Left key for first sorting";
2150 	}
2151 
2152 	print_in_middle(sort_win, 1, 0, len + WINDOW_BORDER_LEN, name, COLOR_PAIR(3));
2153 	print_in_middle(sort_win, 2, 0, len + WINDOW_BORDER_LEN, help, COLOR_PAIR(3));
2154 	mvwaddch(sort_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE);
2155 	mvwhline(sort_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, len + 1);
2156 	mvwaddch(sort_win, WINDOW_HEADER_END_LINE, len + WINDOW_BORDER_LEN - 1, ACS_RTEE);
2157 
2158 	post_menu(my_menu);
2159 	refresh();
2160 	wrefresh(sort_win);
2161 
2162 	while (!stop_loop) {
2163 		c = wgetch(sort_win);
2164 		/*
2165 		 * First sorting window:
2166 		 * Up/Down - select first sorting column;
2167 		 * Enter - apply current column;
2168 		 * Right - open second sorting window.
2169 		 * Second sorting window:
2170 		 * Up/Down - select second sorting column;
2171 		 * Enter - apply current column of both sorting windows;
2172 		 * Left - exit second window and reset second sorting key;
2173 		 * Right - do nothing.
2174 		 */
2175 		switch (c) {
2176 		case KEY_DOWN:
2177 			menu_driver(my_menu, REQ_DOWN_ITEM);
2178 			break;
2179 		case KEY_UP:
2180 			menu_driver(my_menu, REQ_UP_ITEM);
2181 			break;
2182 		case KEY_RIGHT:
2183 			if (winnum > 0) {
2184 				break;
2185 			}
2186 			change_sorting(tab, winnum + 1, &stop_loop);
2187 			/* Restore input. */
2188 			keypad(sort_win, TRUE);
2189 			post_menu(my_menu);
2190 			refresh();
2191 			wrefresh(sort_win);
2192 			redrawwin(sort_win);
2193 			if (winnum == 0) {
2194 				cur = current_item(my_menu);
2195 				p = item_userptr(cur);
2196 				p(tab, item_index(cur));
2197 			}
2198 			break;
2199 		case 27: /* ESC */
2200 			stop_loop = true;
2201 			break;
2202 		case KEY_LEFT:
2203 			if (winnum > 0) {
2204 				sort_type2(tab, COL_THREADS_NONE);
2205 			}
2206 		/* FALLTHROUGH */
2207 		case 10: /* Enter */
2208 			stop_loop = true;
2209 			if (winnum > 0 && c == 10) {
2210 				*pstop_loop = true;
2211 			}
2212 			if (c == 10) {
2213 				cur = current_item(my_menu);
2214 				p = item_userptr(cur);
2215 				p(tab, item_index(cur));
2216 			}
2217 			break;
2218 		}
2219 		wrefresh(sort_win);
2220 	}
2221 
2222 	if (winnum == 0) {
2223 		wclear(g_tabs[tab]);
2224 		draw_tabs(tab, g_current_sort_col[tab], g_current_sort_col2[tab]);
2225 	}
2226 
2227 	unpost_menu(my_menu);
2228 	free_menu(my_menu);
2229 
2230 	for (i = 0; i < elements; ++i) {
2231 		free_item(my_items[i]);
2232 	}
2233 
2234 	free(my_items);
2235 
2236 	del_panel(sort_panel);
2237 	delwin(sort_win);
2238 
2239 	if (winnum == 0) {
2240 		wclear(g_menu_win);
2241 		draw_menu_win();
2242 	}
2243 }
2244 
2245 static int
check_resize_interface(uint8_t active_tab,uint8_t * current_page)2246 check_resize_interface(uint8_t active_tab, uint8_t *current_page)
2247 {
2248 	int max_row, max_col;
2249 	uint16_t required_size = WINDOW_HEADER + 1;
2250 
2251 	/* Check if interface has to be resized (terminal size changed) */
2252 	getmaxyx(stdscr, max_row, max_col);
2253 
2254 	if (max_row != g_max_row || max_col != g_max_col) {
2255 		if (max_row != g_max_row) {
2256 			*current_page = 0;
2257 		}
2258 		g_max_row = spdk_max(max_row, required_size);
2259 		g_max_col = max_col;
2260 		g_data_win_size = g_max_row - required_size + 1;
2261 		g_max_data_rows = g_max_row - WINDOW_HEADER;
2262 		resize_interface(active_tab);
2263 
2264 		return 1;
2265 	}
2266 
2267 	return 0;
2268 }
2269 
2270 static void
change_refresh_rate(void)2271 change_refresh_rate(void)
2272 {
2273 	const int WINDOW_HEADER_END_LINE = 2;
2274 	PANEL *refresh_panel;
2275 	WINDOW *refresh_win;
2276 	int c;
2277 	bool stop_loop = false;
2278 	uint32_t rr_tmp, refresh_rate = 0;
2279 	char refresh_rate_str[MAX_STRING_LEN];
2280 
2281 	refresh_win = newwin(RR_WIN_HEIGHT, RR_WIN_WIDTH, (g_max_row - RR_WIN_HEIGHT - 1) / 2,
2282 			     (g_max_col - RR_WIN_WIDTH) / 2);
2283 	assert(refresh_win != NULL);
2284 	keypad(refresh_win, TRUE);
2285 	refresh_panel = new_panel(refresh_win);
2286 	assert(refresh_panel != NULL);
2287 
2288 	top_panel(refresh_panel);
2289 	update_panels();
2290 	doupdate();
2291 
2292 	box(refresh_win, 0, 0);
2293 
2294 	print_in_middle(refresh_win, 1, 0, RR_WIN_WIDTH + 1, "Enter refresh rate value [s]", COLOR_PAIR(3));
2295 	mvwaddch(refresh_win, WINDOW_HEADER_END_LINE, 0, ACS_LTEE);
2296 	mvwhline(refresh_win, WINDOW_HEADER_END_LINE, 1, ACS_HLINE, RR_WIN_WIDTH - 2);
2297 	mvwaddch(refresh_win, WINDOW_HEADER_END_LINE, RR_WIN_WIDTH, ACS_RTEE);
2298 	mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1, (RR_WIN_WIDTH - 1) / 2, "%d", refresh_rate);
2299 
2300 	refresh();
2301 	wrefresh(refresh_win);
2302 
2303 	while (!stop_loop) {
2304 		c = wgetch(refresh_win);
2305 
2306 		switch (c) {
2307 		case '0':
2308 		case '1':
2309 		case '2':
2310 		case '3':
2311 		case '4':
2312 		case '5':
2313 		case '6':
2314 		case '7':
2315 		case '8':
2316 		case '9':
2317 			rr_tmp = refresh_rate * 10 + c - '0';
2318 
2319 			if (rr_tmp <= RR_MAX_VALUE) {
2320 				refresh_rate = rr_tmp;
2321 				snprintf(refresh_rate_str, MAX_STRING_LEN - 1, "%d", refresh_rate);
2322 				mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1,
2323 					  (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str)) / 2, "%d", refresh_rate);
2324 				refresh();
2325 				wrefresh(refresh_win);
2326 			}
2327 			break;
2328 		case KEY_BACKSPACE:
2329 		case 127:
2330 		case '\b':
2331 			refresh_rate = refresh_rate / 10;
2332 			snprintf(refresh_rate_str, MAX_STRING_LEN - 1, "%d", refresh_rate);
2333 			mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1,
2334 				  (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str) - 2) / 2, "       ");
2335 			mvwprintw(refresh_win, WINDOW_HEADER_END_LINE + 1,
2336 				  (RR_WIN_WIDTH - 1 - strlen(refresh_rate_str)) / 2, "%d", refresh_rate);
2337 			refresh();
2338 			wrefresh(refresh_win);
2339 			break;
2340 		case 27: /* ESC */
2341 		case 'q':
2342 			stop_loop = true;
2343 			break;
2344 		case 10: /* Enter */
2345 			pthread_mutex_lock(&g_thread_lock);
2346 			g_sleep_time = refresh_rate;
2347 			pthread_mutex_unlock(&g_thread_lock);
2348 			stop_loop = true;
2349 			break;
2350 		}
2351 		wrefresh(refresh_win);
2352 	}
2353 
2354 	del_panel(refresh_panel);
2355 	delwin(refresh_win);
2356 }
2357 
2358 static void
free_poller_history(void)2359 free_poller_history(void)
2360 {
2361 	struct run_counter_history *history, *tmp;
2362 
2363 	TAILQ_FOREACH_SAFE(history, &g_run_counter_history, link, tmp) {
2364 		TAILQ_REMOVE(&g_run_counter_history, history, link);
2365 		free(history);
2366 	}
2367 }
2368 
2369 static uint64_t
get_position_for_window(uint64_t window_size,uint64_t max_size)2370 get_position_for_window(uint64_t window_size, uint64_t max_size)
2371 {
2372 	/* This function calculates position for pop-up detail window.
2373 	 * Since horizontal and vertical positions are calculated the same way
2374 	 * there is no need for separate functions. */
2375 	window_size = spdk_min(window_size, max_size);
2376 
2377 	return (max_size - window_size) / 2;
2378 }
2379 
2380 static void
print_bottom_message(char * msg)2381 print_bottom_message(char *msg)
2382 {
2383 	uint64_t i;
2384 
2385 	for (i = 1; i < (uint64_t)g_max_col - 1; i++) {
2386 		mvprintw(g_max_row - 1, i, " ");
2387 	}
2388 	mvprintw(g_max_row - 1, g_max_col - strlen(msg) - 2, "%s", msg);
2389 }
2390 
2391 static void
draw_thread_win_content(WINDOW * thread_win,struct rpc_thread_info * thread_info)2392 draw_thread_win_content(WINDOW *thread_win, struct rpc_thread_info *thread_info)
2393 {
2394 	uint64_t current_row, i, time;
2395 	char idle_time[MAX_TIME_STR_LEN], busy_time[MAX_TIME_STR_LEN];
2396 
2397 	box(thread_win, 0, 0);
2398 
2399 	print_in_middle(thread_win, 1, 0, THREAD_WIN_WIDTH, thread_info->name,
2400 			COLOR_PAIR(3));
2401 	mvwhline(thread_win, 2, 1, ACS_HLINE, THREAD_WIN_WIDTH - 2);
2402 	mvwaddch(thread_win, 2, THREAD_WIN_WIDTH, ACS_RTEE);
2403 
2404 	print_left(thread_win, 3, THREAD_WIN_FIRST_COL, THREAD_WIN_WIDTH,
2405 		   "Core:                Idle [us]:            Busy [us]:", COLOR_PAIR(5));
2406 	mvwprintw(thread_win, 3, THREAD_WIN_FIRST_COL + 6, "%d",
2407 		  thread_info->core_num);
2408 
2409 	if (g_interval_data) {
2410 		get_time_str(thread_info->idle - thread_info->last_idle, idle_time);
2411 		mvwprintw(thread_win, 3, THREAD_WIN_FIRST_COL + 32, "%s", idle_time);
2412 		get_time_str(thread_info->busy - thread_info->last_busy, busy_time);
2413 		mvwprintw(thread_win, 3, THREAD_WIN_FIRST_COL + 54, "%s", busy_time);
2414 	} else {
2415 		get_time_str(thread_info->idle, idle_time);
2416 		mvwprintw(thread_win, 3, THREAD_WIN_FIRST_COL + 32, "%s", idle_time);
2417 		get_time_str(thread_info->busy, busy_time);
2418 		mvwprintw(thread_win, 3, THREAD_WIN_FIRST_COL + 54, "%s", busy_time);
2419 	}
2420 
2421 	print_left(thread_win, 4, THREAD_WIN_FIRST_COL, THREAD_WIN_WIDTH,
2422 		   "Active pollers:      Timed pollers:        Paused pollers:", COLOR_PAIR(5));
2423 	mvwprintw(thread_win, 4, THREAD_WIN_FIRST_COL + 17, "%" PRIu64,
2424 		  thread_info->active_pollers_count);
2425 	mvwprintw(thread_win, 4, THREAD_WIN_FIRST_COL + 36, "%" PRIu64,
2426 		  thread_info->timed_pollers_count);
2427 	mvwprintw(thread_win, 4, THREAD_WIN_FIRST_COL + 59, "%" PRIu64,
2428 		  thread_info->paused_pollers_count);
2429 
2430 	mvwhline(thread_win, 5, 1, ACS_HLINE, THREAD_WIN_WIDTH - 2);
2431 
2432 	print_in_middle(thread_win, 6, 0, THREAD_WIN_WIDTH,
2433 			"Pollers                          Type    Total run count   Period", COLOR_PAIR(5));
2434 
2435 	mvwhline(thread_win, 7, 1, ACS_HLINE, THREAD_WIN_WIDTH - 2);
2436 
2437 	current_row = 8;
2438 
2439 	for (i = 0; i < g_last_pollers_count; i++) {
2440 		if (g_pollers_info[i].thread_id == thread_info->id) {
2441 			mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL, "%s", g_pollers_info[i].name);
2442 			if (g_pollers_info[i].type == SPDK_ACTIVE_POLLER) {
2443 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 33, "Active");
2444 			} else if (g_pollers_info[i].type == SPDK_TIMED_POLLER) {
2445 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 33, "Timed");
2446 			} else {
2447 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 33, "Paused");
2448 			}
2449 			mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 41, "%" PRIu64,
2450 				  g_pollers_info[i].run_count);
2451 			if (g_pollers_info[i].period_ticks) {
2452 				time = g_pollers_info[i].period_ticks * SPDK_SEC_TO_USEC / g_tick_rate;
2453 				mvwprintw(thread_win, current_row, THREAD_WIN_FIRST_COL + 59, "%" PRIu64, time);
2454 			}
2455 			current_row++;
2456 		}
2457 	}
2458 
2459 	wnoutrefresh(thread_win);
2460 }
2461 
2462 static int
get_single_thread_info(uint64_t thread_id,struct rpc_thread_info * thread_info)2463 get_single_thread_info(uint64_t thread_id, struct rpc_thread_info *thread_info)
2464 {
2465 	uint64_t i;
2466 
2467 	for (i = 0; i < g_last_threads_count; i++) {
2468 		if (g_threads_info[i].id == thread_id) {
2469 			memcpy(thread_info, &g_threads_info[i], sizeof(struct rpc_thread_info));
2470 			thread_info->name = strdup(g_threads_info[i].name);
2471 			thread_info->cpumask = strdup(g_threads_info[i].cpumask);
2472 
2473 			if (thread_info->name == NULL || thread_info->cpumask == NULL) {
2474 				print_bottom_message("Unable to allocate memory for thread name and cpumask. Exiting pop-up.");
2475 				return -1;
2476 			}
2477 
2478 			return 0;
2479 		}
2480 	}
2481 
2482 	print_bottom_message("Selected thread no longer exists. Exiting pop-up.");
2483 	return -1;
2484 }
2485 
2486 static void
draw_core_win_content(WINDOW * core_win,struct rpc_core_info * core_info)2487 draw_core_win_content(WINDOW *core_win, struct rpc_core_info *core_info)
2488 {
2489 	uint64_t i;
2490 	char core_win_title[25];
2491 	char idle_time[MAX_TIME_STR_LEN], busy_time[MAX_TIME_STR_LEN];
2492 
2493 	box(core_win, 0, 0);
2494 	snprintf(core_win_title, sizeof(core_win_title), "Core %" PRIu32 " details",
2495 		 core_info->lcore);
2496 	print_in_middle(core_win, 1, 0, CORE_WIN_WIDTH, core_win_title, COLOR_PAIR(3));
2497 
2498 	mvwhline(core_win, 2, 1, ACS_HLINE, CORE_WIN_WIDTH - 2);
2499 	print_left(core_win, 3, 1, CORE_WIN_WIDTH - (CORE_WIN_WIDTH / 3),
2500 		   "Frequency:             Intr:", COLOR_PAIR(5));
2501 	if (core_info->core_freq) {
2502 		mvwprintw(core_win, 3, CORE_WIN_FIRST_COL - 3, "%" PRIu32,
2503 			  core_info->core_freq);
2504 	} else {
2505 		mvwprintw(core_win, 3, CORE_WIN_FIRST_COL - 3, "%s", "N/A");
2506 	}
2507 
2508 	mvwprintw(core_win, 3, CORE_WIN_FIRST_COL + 15, "%s",
2509 		  core_info->in_interrupt ? "Yes" : "No");
2510 
2511 	mvwhline(core_win, 4, 1, ACS_HLINE, CORE_WIN_WIDTH - 2);
2512 	print_left(core_win, 5, 1, CORE_WIN_WIDTH, "Thread count:          Idle time:", COLOR_PAIR(5));
2513 
2514 	mvwprintw(core_win, 5, CORE_WIN_FIRST_COL, "%" PRIu64,
2515 		  core_info->threads.threads_count);
2516 
2517 	if (g_interval_data == true) {
2518 		get_time_str(core_info->idle - core_info->last_idle, idle_time);
2519 		get_time_str(core_info->busy - core_info->last_busy, busy_time);
2520 	} else {
2521 		get_time_str(core_info->idle, idle_time);
2522 		get_time_str(core_info->busy, busy_time);
2523 	}
2524 	mvwprintw(core_win, 5, CORE_WIN_FIRST_COL + 20, "%s", idle_time);
2525 	mvwhline(core_win, 6, 1, ACS_HLINE, CORE_WIN_WIDTH - 2);
2526 
2527 	print_left(core_win, 7, 1, CORE_WIN_WIDTH, "Poller count:          Busy time:", COLOR_PAIR(5));
2528 	mvwprintw(core_win, 7, CORE_WIN_FIRST_COL, "%" PRIu64,
2529 		  core_info->pollers_count);
2530 
2531 	mvwprintw(core_win, 7, CORE_WIN_FIRST_COL + 20, "%s", busy_time);
2532 
2533 	mvwhline(core_win, 8, 1, ACS_HLINE, CORE_WIN_WIDTH - 2);
2534 	print_left(core_win, 9, 1, CORE_WIN_WIDTH, "Threads on this core", COLOR_PAIR(5));
2535 
2536 	for (i = 0; i < core_info->threads.threads_count; i++) {
2537 		mvwprintw(core_win, i + CORE_WIN_HEIGHT - 1, 1, "%s", core_info->threads.thread[i].name);
2538 	}
2539 	pthread_mutex_unlock(&g_thread_lock);
2540 
2541 	wnoutrefresh(core_win);
2542 }
2543 
2544 static void
display_thread(uint64_t thread_id,uint8_t current_page,uint8_t active_tab,WINDOW * core_popup,struct rpc_core_info * core_info)2545 display_thread(uint64_t thread_id, uint8_t current_page, uint8_t active_tab,
2546 	       WINDOW *core_popup, struct rpc_core_info *core_info)
2547 {
2548 	PANEL *thread_panel = NULL;
2549 	WINDOW *thread_win = NULL;
2550 	struct rpc_thread_info thread_info;
2551 	uint64_t pollers_count, threads_count, last_pollers_count = 0;
2552 	int c;
2553 	bool stop_loop = false;
2554 	long int time_last, time_dif;
2555 	struct timespec time_now;
2556 
2557 	clock_gettime(CLOCK_MONOTONIC, &time_now);
2558 	time_last = time_now.tv_sec;
2559 
2560 	memset(&thread_info, 0, sizeof(thread_info));
2561 
2562 	while (!stop_loop) {
2563 		pthread_mutex_lock(&g_thread_lock);
2564 		if (get_single_thread_info(thread_id, &thread_info)) {
2565 			pthread_mutex_unlock(&g_thread_lock);
2566 			free(thread_info.name);
2567 			free(thread_info.cpumask);
2568 			thread_info.name = NULL;
2569 			thread_info.cpumask = NULL;
2570 			return;
2571 		}
2572 		pollers_count = thread_info.active_pollers_count +
2573 				thread_info.timed_pollers_count +
2574 				thread_info.paused_pollers_count;
2575 		if (pollers_count != last_pollers_count) {
2576 			if (thread_win != NULL) {
2577 				assert(thread_panel != NULL);
2578 				del_panel(thread_panel);
2579 				delwin(thread_win);
2580 			}
2581 
2582 			thread_win = newwin(pollers_count + THREAD_WIN_HEIGHT, THREAD_WIN_WIDTH,
2583 					    get_position_for_window(THREAD_WIN_HEIGHT + pollers_count, g_max_row),
2584 					    get_position_for_window(THREAD_WIN_WIDTH, g_max_col));
2585 			keypad(thread_win, TRUE);
2586 			thread_panel = new_panel(thread_win);
2587 
2588 			top_panel(thread_panel);
2589 			update_panels();
2590 			doupdate();
2591 			draw_thread_win_content(thread_win, &thread_info);
2592 			refresh();
2593 		}
2594 		pthread_mutex_unlock(&g_thread_lock);
2595 
2596 		if (check_resize_interface(active_tab, &current_page)) {
2597 			/* This clear is to avoid remaining artifacts after window has been moved */
2598 			wclear(thread_win);
2599 			wclear(core_popup);
2600 			resize_interface(active_tab);
2601 			draw_tabs(active_tab, g_current_sort_col[active_tab], g_current_sort_col2[active_tab]);
2602 			if (core_popup != NULL) {
2603 				pthread_mutex_lock(&g_thread_lock);
2604 				threads_count = g_cores_info[core_info->lcore].threads.threads_count;
2605 				pthread_mutex_unlock(&g_thread_lock);
2606 				mvwin(core_popup, get_position_for_window(CORE_WIN_HEIGHT + threads_count, g_max_row),
2607 				      get_position_for_window(CORE_WIN_WIDTH, g_max_col));
2608 			}
2609 			mvwin(thread_win, get_position_for_window(THREAD_WIN_HEIGHT + pollers_count, g_max_row),
2610 			      get_position_for_window(THREAD_WIN_WIDTH, g_max_col));
2611 		}
2612 
2613 		c = getch();
2614 
2615 		switch (c) {
2616 		case 27: /* ESC */
2617 			stop_loop = true;
2618 			break;
2619 		default:
2620 			break;
2621 		}
2622 
2623 		clock_gettime(CLOCK_MONOTONIC, &time_now);
2624 		time_dif = time_now.tv_sec - time_last;
2625 
2626 		if (time_dif >= g_sleep_time) {
2627 			time_last = time_now.tv_sec;
2628 			pthread_mutex_lock(&g_thread_lock);
2629 			refresh_tab(active_tab, current_page);
2630 			if (core_popup != NULL) {
2631 				draw_core_win_content(core_popup, core_info);
2632 			}
2633 			draw_thread_win_content(thread_win, &thread_info);
2634 			refresh();
2635 			pthread_mutex_unlock(&g_thread_lock);
2636 		}
2637 
2638 		last_pollers_count = pollers_count;
2639 		free(thread_info.name);
2640 		free(thread_info.cpumask);
2641 		thread_info.name = NULL;
2642 		thread_info.cpumask = NULL;
2643 	}
2644 
2645 	del_panel(thread_panel);
2646 	delwin(thread_win);
2647 }
2648 
2649 static void
show_thread(uint8_t current_page,uint8_t active_tab)2650 show_thread(uint8_t current_page, uint8_t active_tab)
2651 {
2652 	uint64_t thread_number = current_page * g_max_data_rows + g_selected_row;
2653 	uint64_t thread_id;
2654 
2655 	pthread_mutex_lock(&g_thread_lock);
2656 	assert(thread_number < g_last_threads_count);
2657 	thread_id = g_threads_info[thread_number].id;
2658 	pthread_mutex_unlock(&g_thread_lock);
2659 
2660 	display_thread(thread_id, current_page, active_tab, NULL, NULL);
2661 }
2662 
2663 static void
show_single_thread(uint64_t thread_id,uint8_t current_page,uint8_t active_tab,WINDOW * core_popup,struct rpc_core_info * core_info)2664 show_single_thread(uint64_t thread_id, uint8_t current_page, uint8_t active_tab, WINDOW *core_popup,
2665 		   struct rpc_core_info *core_info)
2666 {
2667 	uint64_t i;
2668 
2669 	pthread_mutex_lock(&g_thread_lock);
2670 	for (i = 0; i < g_last_threads_count; i++) {
2671 		if (g_threads_info[i].id == thread_id) {
2672 			pthread_mutex_unlock(&g_thread_lock);
2673 			display_thread(thread_id, current_page, active_tab, core_popup, core_info);
2674 			return;
2675 		}
2676 	}
2677 	pthread_mutex_unlock(&g_thread_lock);
2678 }
2679 
2680 static void
show_core(uint8_t current_page,uint8_t active_tab)2681 show_core(uint8_t current_page, uint8_t active_tab)
2682 {
2683 	PANEL *core_panel;
2684 	WINDOW *core_win;
2685 	uint64_t core_number = current_page * g_max_data_rows + g_selected_row;
2686 	struct rpc_core_info *core_info = &g_cores_info[core_number];
2687 	uint64_t threads_count, i;
2688 	uint64_t thread_id = 0;
2689 	uint16_t current_threads_row;
2690 	int c;
2691 	long int time_last, time_dif;
2692 	struct timespec time_now;
2693 
2694 	clock_gettime(CLOCK_MONOTONIC, &time_now);
2695 	time_last = time_now.tv_sec;
2696 
2697 	bool stop_loop = false;
2698 
2699 	pthread_mutex_lock(&g_thread_lock);
2700 	assert(core_number < g_last_cores_count);
2701 
2702 	threads_count = g_cores_info[core_number].threads.threads_count;
2703 
2704 	core_win = newwin(threads_count + CORE_WIN_HEIGHT, CORE_WIN_WIDTH,
2705 			  get_position_for_window(CORE_WIN_HEIGHT + threads_count, g_max_row),
2706 			  get_position_for_window(CORE_WIN_WIDTH, g_max_col));
2707 
2708 	keypad(core_win, TRUE);
2709 	core_panel = new_panel(core_win);
2710 
2711 	top_panel(core_panel);
2712 	update_panels();
2713 	doupdate();
2714 	draw_core_win_content(core_win, core_info);
2715 	refresh();
2716 
2717 	current_threads_row = 0;
2718 
2719 	while (!stop_loop) {
2720 		pthread_mutex_lock(&g_thread_lock);
2721 		for (i = 0; i < core_info->threads.threads_count; i++) {
2722 			if (i != current_threads_row) {
2723 				mvwprintw(core_win, i + CORE_WIN_HEIGHT - 1, 1,
2724 					  "%s", core_info->threads.thread[i].name);
2725 			} else {
2726 				print_left(core_win, i + CORE_WIN_HEIGHT - 1, 1, CORE_WIN_WIDTH - 2,
2727 					   core_info->threads.thread[i].name, COLOR_PAIR(2));
2728 			}
2729 		}
2730 		pthread_mutex_unlock(&g_thread_lock);
2731 
2732 		wrefresh(core_win);
2733 		if (check_resize_interface(active_tab, &current_page)) {
2734 			wclear(core_win);
2735 			resize_interface(active_tab);
2736 			draw_tab_win(active_tab);
2737 			mvwin(core_win, get_position_for_window(CORE_WIN_HEIGHT + threads_count, g_max_row),
2738 			      get_position_for_window(CORE_WIN_WIDTH, g_max_col));
2739 		}
2740 
2741 		c = getch();
2742 		switch (c) {
2743 		case 10: /* ENTER */
2744 			pthread_mutex_lock(&g_thread_lock);
2745 			if (core_info->threads.threads_count > 0) {
2746 				thread_id = core_info->threads.thread[current_threads_row].id;
2747 			}
2748 			pthread_mutex_unlock(&g_thread_lock);
2749 
2750 			if (thread_id != 0) {
2751 				show_single_thread(thread_id, current_page, active_tab, core_win, core_info);
2752 			}
2753 
2754 			/* This refreshes tab and core_pop-up after exiting threads pop-up. */
2755 			pthread_mutex_lock(&g_thread_lock);
2756 			refresh_tab(active_tab, current_page);
2757 			wnoutrefresh(core_win);
2758 			refresh();
2759 			pthread_mutex_unlock(&g_thread_lock);
2760 			break;
2761 		case 27: /* ESC */
2762 			stop_loop = true;
2763 			break;
2764 		case KEY_UP:
2765 			if (current_threads_row != 0) {
2766 				current_threads_row--;
2767 			}
2768 			break;
2769 		case KEY_DOWN:
2770 			pthread_mutex_lock(&g_thread_lock);
2771 			if (current_threads_row != core_info->threads.threads_count - 1) {
2772 				current_threads_row++;
2773 			}
2774 			pthread_mutex_unlock(&g_thread_lock);
2775 			break;
2776 		default:
2777 			break;
2778 		}
2779 
2780 		clock_gettime(CLOCK_MONOTONIC, &time_now);
2781 		time_dif = time_now.tv_sec - time_last;
2782 
2783 		if (time_dif >= g_sleep_time) {
2784 			time_last = time_now.tv_sec;
2785 			pthread_mutex_lock(&g_thread_lock);
2786 			refresh_tab(active_tab, current_page);
2787 			draw_core_win_content(core_win, core_info);
2788 			refresh();
2789 			pthread_mutex_unlock(&g_thread_lock);
2790 		}
2791 	}
2792 
2793 	del_panel(core_panel);
2794 	delwin(core_win);
2795 }
2796 
2797 static void
draw_poller_win_content(WINDOW * poller_win,struct rpc_poller_info * poller_info)2798 draw_poller_win_content(WINDOW *poller_win, struct rpc_poller_info *poller_info)
2799 {
2800 	uint64_t last_run_counter, last_busy_counter, busy_count;
2801 	char poller_period[MAX_TIME_STR_LEN];
2802 
2803 	box(poller_win, 0, 0);
2804 
2805 	print_in_middle(poller_win, 1, 0, POLLER_WIN_WIDTH, poller_info->name, COLOR_PAIR(3));
2806 	mvwhline(poller_win, 2, 1, ACS_HLINE, POLLER_WIN_WIDTH - 2);
2807 	mvwaddch(poller_win, 2, POLLER_WIN_WIDTH, ACS_RTEE);
2808 
2809 	print_left(poller_win, 3, 2, POLLER_WIN_WIDTH, "Type:                  On thread:", COLOR_PAIR(5));
2810 	mvwprintw(poller_win, 3, POLLER_WIN_FIRST_COL, "%s",
2811 		  poller_type_str[poller_info->type]);
2812 	mvwprintw(poller_win, 3, POLLER_WIN_FIRST_COL + 23, "%s", poller_info->thread_name);
2813 
2814 	print_left(poller_win, 4, 2, POLLER_WIN_WIDTH, "Run count:", COLOR_PAIR(5));
2815 
2816 	last_run_counter = get_last_run_counter(poller_info->id, poller_info->thread_id);
2817 	last_busy_counter = get_last_busy_counter(poller_info->id, poller_info->thread_id);
2818 	if (g_interval_data) {
2819 		mvwprintw(poller_win, 4, POLLER_WIN_FIRST_COL, "%" PRIu64,
2820 			  poller_info->run_count - last_run_counter);
2821 	} else {
2822 		mvwprintw(poller_win, 4, POLLER_WIN_FIRST_COL, "%" PRIu64, poller_info->run_count);
2823 	}
2824 
2825 	if (poller_info->period_ticks != 0) {
2826 		print_left(poller_win, 4, 28, POLLER_WIN_WIDTH, "Period:", COLOR_PAIR(5));
2827 		get_time_str(poller_info->period_ticks, poller_period);
2828 		mvwprintw(poller_win, 4, POLLER_WIN_FIRST_COL + 23, "%s", poller_period);
2829 	}
2830 	mvwhline(poller_win, 5, 1, ACS_HLINE, POLLER_WIN_WIDTH - 2);
2831 
2832 	busy_count = g_interval_data ? poller_info->busy_count - last_busy_counter :
2833 		     poller_info->busy_count;
2834 	if (busy_count != 0) {
2835 		print_left(poller_win, 6, 2, POLLER_WIN_WIDTH,  "Status:               Busy count:", COLOR_PAIR(5));
2836 
2837 		if (g_interval_data == false && poller_info->busy_count == last_busy_counter) {
2838 			print_left(poller_win, 6, POLLER_WIN_FIRST_COL, POLLER_WIN_WIDTH, "Idle", COLOR_PAIR(7));
2839 		} else {
2840 			print_left(poller_win, 6, POLLER_WIN_FIRST_COL, POLLER_WIN_WIDTH, "Busy", COLOR_PAIR(6));
2841 		}
2842 
2843 		mvwprintw(poller_win, 6, POLLER_WIN_FIRST_COL + 23, "%" PRIu64, busy_count);
2844 	} else {
2845 		print_in_middle(poller_win, 6, 1, POLLER_WIN_WIDTH - 7, "Status:", COLOR_PAIR(5));
2846 		print_in_middle(poller_win, 6, 1, POLLER_WIN_WIDTH + 6, "Idle", COLOR_PAIR(7));
2847 	}
2848 
2849 	wnoutrefresh(poller_win);
2850 }
2851 
2852 static void
show_poller(uint8_t current_page,uint8_t active_tab)2853 show_poller(uint8_t current_page, uint8_t active_tab)
2854 {
2855 	PANEL *poller_panel;
2856 	WINDOW *poller_win;
2857 	uint64_t poller_number = current_page * g_max_data_rows + g_selected_row;
2858 	struct rpc_poller_info *poller;
2859 	bool stop_loop = false;
2860 	int c;
2861 	long int time_last, time_dif;
2862 	struct timespec time_now;
2863 
2864 	clock_gettime(CLOCK_MONOTONIC, &time_now);
2865 	time_last = time_now.tv_sec;
2866 
2867 
2868 	pthread_mutex_lock(&g_thread_lock);
2869 
2870 	assert(poller_number < g_last_pollers_count);
2871 	poller = &g_pollers_info[poller_number];
2872 
2873 	poller_win = newwin(POLLER_WIN_HEIGHT, POLLER_WIN_WIDTH,
2874 			    get_position_for_window(POLLER_WIN_HEIGHT, g_max_row),
2875 			    get_position_for_window(POLLER_WIN_WIDTH, g_max_col));
2876 
2877 	keypad(poller_win, TRUE);
2878 	poller_panel = new_panel(poller_win);
2879 
2880 	top_panel(poller_panel);
2881 	update_panels();
2882 	doupdate();
2883 	draw_poller_win_content(poller_win, poller);
2884 	refresh();
2885 
2886 	pthread_mutex_unlock(&g_thread_lock);
2887 	while (!stop_loop) {
2888 		if (check_resize_interface(active_tab, &current_page)) {
2889 			/* This clear is to avoid remaining artifacts after window has been moved */
2890 			wclear(poller_win);
2891 			resize_interface(active_tab);
2892 			draw_tabs(active_tab, g_current_sort_col[active_tab], g_current_sort_col2[active_tab]);
2893 			mvwin(poller_win, get_position_for_window(POLLER_WIN_HEIGHT, g_max_row),
2894 			      get_position_for_window(POLLER_WIN_WIDTH, g_max_col));
2895 		}
2896 		c = getch();
2897 		switch (c) {
2898 		case 27: /* ESC */
2899 			stop_loop = true;
2900 			break;
2901 		default:
2902 			break;
2903 		}
2904 
2905 		clock_gettime(CLOCK_MONOTONIC, &time_now);
2906 		time_dif = time_now.tv_sec - time_last;
2907 
2908 		if (time_dif >= g_sleep_time) {
2909 			time_last = time_now.tv_sec;
2910 			pthread_mutex_lock(&g_thread_lock);
2911 			refresh_tab(active_tab, current_page);
2912 			draw_poller_win_content(poller_win, poller);
2913 			refresh();
2914 			pthread_mutex_unlock(&g_thread_lock);
2915 		}
2916 	}
2917 
2918 	del_panel(poller_panel);
2919 	delwin(poller_win);
2920 }
2921 
2922 static uint64_t
get_max_scheduler_win_width(uint8_t sched_name_label_len,uint8_t sched_period_label_len,uint8_t gov_name_label_len)2923 get_max_scheduler_win_width(uint8_t sched_name_label_len, uint8_t sched_period_label_len,
2924 			    uint8_t gov_name_label_len)
2925 {
2926 	uint8_t window_borders = 4;
2927 	uint64_t s_name, s_period, g_name, max_name_len;
2928 	char scheduler_period[MAX_SCHEDULER_PERIOD_STR_LEN];
2929 
2930 	snprintf(scheduler_period, MAX_SCHEDULER_PERIOD_STR_LEN, "%" PRIu64,
2931 		 g_scheduler_info.scheduler_period);
2932 
2933 	s_name = strlen(g_scheduler_info.scheduler_name) + sched_name_label_len;
2934 	if (g_scheduler_info.governor_name != NULL) {
2935 		g_name = strlen(g_scheduler_info.governor_name) + gov_name_label_len;
2936 	} else {
2937 		g_name = strlen("none") + gov_name_label_len;
2938 	}
2939 	s_period = strlen(scheduler_period) + sched_period_label_len;
2940 
2941 	max_name_len = spdk_max(s_name, g_name);
2942 	/* This function relies on the fact that scheduler/governor
2943 	 * names will not change during runtime. Otherwise the scheduler
2944 	 * pop-up would need dynamic resizing. */
2945 
2946 	return spdk_max(max_name_len, s_period) + window_borders;
2947 }
2948 
2949 static void
draw_scheduler_popup(WINDOW * scheduler_win,uint64_t scheduler_win_width,uint8_t active_tab,uint8_t current_page,const char * scheduler_name_label,const char * scheduler_period_label,const char * governor_name_label)2950 draw_scheduler_popup(WINDOW *scheduler_win, uint64_t scheduler_win_width, uint8_t active_tab,
2951 		     uint8_t current_page, const char *scheduler_name_label,
2952 		     const char *scheduler_period_label, const char *governor_name_label)
2953 {
2954 	char scheduler_period[MAX_SCHEDULER_PERIOD_STR_LEN];
2955 
2956 	box(scheduler_win, 0, 0);
2957 
2958 	print_left(scheduler_win, 1, SCHEDULER_WIN_FIRST_COL, scheduler_win_width, scheduler_name_label,
2959 		   COLOR_PAIR(5));
2960 	print_left(scheduler_win, 1, SCHEDULER_WIN_FIRST_COL + strlen(scheduler_name_label),
2961 		   scheduler_win_width,
2962 		   g_scheduler_info.scheduler_name, COLOR_PAIR(3));
2963 
2964 	mvwhline(scheduler_win, 2, 1, ACS_HLINE, scheduler_win_width - 2);
2965 	mvwaddch(scheduler_win, 2, scheduler_win_width, ACS_RTEE);
2966 
2967 	print_left(scheduler_win, 3, SCHEDULER_WIN_FIRST_COL, scheduler_win_width, scheduler_period_label,
2968 		   COLOR_PAIR(5));
2969 	snprintf(scheduler_period, MAX_SCHEDULER_PERIOD_STR_LEN, "%" PRIu64,
2970 		 g_scheduler_info.scheduler_period);
2971 	mvwprintw(scheduler_win, 3, SCHEDULER_WIN_FIRST_COL + strlen(scheduler_period_label), "%s",
2972 		  scheduler_period);
2973 
2974 	mvwhline(scheduler_win, 4, 1, ACS_HLINE, scheduler_win_width - 2);
2975 	mvwaddch(scheduler_win, 4, scheduler_win_width, ACS_RTEE);
2976 
2977 	print_left(scheduler_win, 5, SCHEDULER_WIN_FIRST_COL, scheduler_win_width, governor_name_label,
2978 		   COLOR_PAIR(5));
2979 
2980 	if (g_scheduler_info.governor_name != NULL) {
2981 		mvwprintw(scheduler_win, 5, SCHEDULER_WIN_FIRST_COL + strlen(governor_name_label), "%s",
2982 			  g_scheduler_info.governor_name);
2983 	} else {
2984 		mvwprintw(scheduler_win, 5, SCHEDULER_WIN_FIRST_COL + strlen(governor_name_label), "%s", "none");
2985 	}
2986 
2987 	refresh_tab(active_tab, current_page);
2988 	wnoutrefresh(scheduler_win);
2989 	refresh();
2990 }
2991 
2992 static void
show_scheduler(uint8_t active_tab,uint8_t current_page)2993 show_scheduler(uint8_t active_tab, uint8_t current_page)
2994 {
2995 	PANEL *scheduler_panel;
2996 	WINDOW *scheduler_win;
2997 	uint64_t scheduler_win_width;
2998 	bool stop_loop = false;
2999 	int c;
3000 	const char *scheduler_name_label = "Scheduler:  ";
3001 	const char *scheduler_period_label = "Period [us]:  ";
3002 	const char *governor_name_label = "Governor:  ";
3003 
3004 	pthread_mutex_lock(&g_thread_lock);
3005 	scheduler_win_width = get_max_scheduler_win_width(strlen(scheduler_name_label),
3006 			      strlen(scheduler_period_label),
3007 			      strlen(governor_name_label));
3008 
3009 	scheduler_win = newwin(SCHEDULER_WIN_HEIGHT, scheduler_win_width,
3010 			       get_position_for_window(SCHEDULER_WIN_HEIGHT, g_max_row),
3011 			       get_position_for_window(scheduler_win_width, g_max_col));
3012 
3013 	keypad(scheduler_win, TRUE);
3014 	scheduler_panel = new_panel(scheduler_win);
3015 
3016 	top_panel(scheduler_panel);
3017 	update_panels();
3018 	doupdate();
3019 
3020 	draw_scheduler_popup(scheduler_win, scheduler_win_width, active_tab, current_page,
3021 			     scheduler_name_label, scheduler_period_label, governor_name_label);
3022 	pthread_mutex_unlock(&g_thread_lock);
3023 
3024 	while (!stop_loop) {
3025 		c = wgetch(scheduler_win);
3026 
3027 		switch (c) {
3028 		case 27: /* ESC */
3029 			stop_loop = true;
3030 			break;
3031 		default:
3032 			break;
3033 		}
3034 	}
3035 
3036 	del_panel(scheduler_panel);
3037 	delwin(scheduler_win);
3038 }
3039 
3040 static void *
data_thread_routine(void * arg)3041 data_thread_routine(void *arg)
3042 {
3043 	int rc;
3044 	uint64_t refresh_rate;
3045 
3046 	while (1) {
3047 		pthread_mutex_lock(&g_thread_lock);
3048 		if (g_quit_app) {
3049 			pthread_mutex_unlock(&g_thread_lock);
3050 			break;
3051 		}
3052 
3053 		if (g_sleep_time == 0) {
3054 			/* Give display thread time to redraw all windows */
3055 			refresh_rate = SPDK_SEC_TO_USEC / 100;
3056 		} else {
3057 			refresh_rate = g_sleep_time * SPDK_SEC_TO_USEC;
3058 		}
3059 		pthread_mutex_unlock(&g_thread_lock);
3060 
3061 		/* Get data from RPC for each object type.
3062 		 * Start with cores since their number should not change. */
3063 		rc = get_cores_data();
3064 		if (rc) {
3065 			print_bottom_message("ERROR occurred while getting cores data");
3066 		}
3067 		rc = get_thread_data();
3068 		if (rc) {
3069 			print_bottom_message("ERROR occurred while getting threads data");
3070 		}
3071 
3072 		rc = get_pollers_data();
3073 		if (rc) {
3074 			print_bottom_message("ERROR occurred while getting pollers data");
3075 		}
3076 		rc = get_scheduler_data();
3077 		if (rc) {
3078 			print_bottom_message("ERROR occurred while getting scheduler data");
3079 		}
3080 
3081 		usleep(refresh_rate);
3082 	}
3083 
3084 	return NULL;
3085 }
3086 
3087 static void
help_window_display(void)3088 help_window_display(void)
3089 {
3090 	PANEL *help_panel;
3091 	WINDOW *help_win;
3092 	bool stop_loop = false;
3093 	int c;
3094 	uint64_t row = 1, col = 2, desc_second_row_col = 26, header_footer_col = 0;
3095 
3096 	help_win = newwin(HELP_WIN_HEIGHT, HELP_WIN_WIDTH,
3097 			  get_position_for_window(HELP_WIN_HEIGHT, g_max_row),
3098 			  get_position_for_window(HELP_WIN_WIDTH, g_max_col));
3099 	help_panel = new_panel(help_win);
3100 	top_panel(help_panel);
3101 	update_panels();
3102 	doupdate();
3103 
3104 	box(help_win, 0, 0);
3105 
3106 	/* Header */
3107 	print_in_middle(help_win, row, header_footer_col, HELP_WIN_WIDTH, "HELP", COLOR_PAIR(3));
3108 	mvwhline(help_win, 2, 1, ACS_HLINE, HELP_WIN_WIDTH - 2);
3109 	mvwaddch(help_win, 2, HELP_WIN_WIDTH, ACS_RTEE);
3110 	row = 3;
3111 
3112 	/* Content */
3113 	print_left(help_win, row, col, HELP_WIN_WIDTH, "MENU options", COLOR_PAIR(5));
3114 	print_left(help_win, ++row, ++col, HELP_WIN_WIDTH, "[q] Quit		- quit this application",
3115 		   COLOR_PAIR(10));
3116 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3117 		   "[Tab] Next tab	- switch to next tab", COLOR_PAIR(10));
3118 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3119 		   "[1-3] Select tab	- switch to THREADS, POLLERS or CORES tab", COLOR_PAIR(10));
3120 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3121 		   "[PgUp] Previous page	- scroll up to previous page", COLOR_PAIR(10));
3122 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3123 		   "[PgDown] Next page	- scroll down to next page", COLOR_PAIR(10));
3124 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3125 		   "[Up] Arrow key	- go to previous data row", COLOR_PAIR(10));
3126 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3127 		   "[Down] Arrow key	- go to next data row", COLOR_PAIR(10));
3128 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3129 		   "[Right] Arrow key	- go to second sorting window", COLOR_PAIR(10));
3130 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3131 		   "[Left] Arrow key	- close second sorting window", COLOR_PAIR(10));
3132 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3133 		   "[c] Columns		- choose data columns to display", COLOR_PAIR(10));
3134 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3135 		   "[s] Sorting		- change sorting by column", COLOR_PAIR(10));
3136 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3137 		   "[r] Refresh rate	- set refresh rate <0, 255> in seconds", COLOR_PAIR(10));
3138 	print_left(help_win, ++row, desc_second_row_col,  HELP_WIN_WIDTH, "that value in seconds",
3139 		   COLOR_PAIR(10));
3140 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3141 		   "[Enter] Item details	- show current data row details (Enter to open, Esc to close)",
3142 		   COLOR_PAIR(10));
3143 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3144 		   "[t] Total/Interval	- switch to display data measured from the start of SPDK", COLOR_PAIR(10));
3145 	print_left(help_win, ++row, desc_second_row_col,  HELP_WIN_WIDTH,
3146 		   "application or last refresh", COLOR_PAIR(10));
3147 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH,
3148 		   "[g] Scheduler pop-up - display current scheduler information", COLOR_PAIR(10));
3149 	print_left(help_win, ++row, col,  HELP_WIN_WIDTH, "[h] Help		- show this help window",
3150 		   COLOR_PAIR(10));
3151 
3152 	/* Footer */
3153 	mvwhline(help_win, HELP_WIN_HEIGHT - 3, 1, ACS_HLINE, HELP_WIN_WIDTH - 2);
3154 	mvwaddch(help_win, HELP_WIN_HEIGHT - 3, HELP_WIN_WIDTH, ACS_RTEE);
3155 
3156 	print_in_middle(help_win, HELP_WIN_HEIGHT - 2, header_footer_col, HELP_WIN_WIDTH,
3157 			"[Esc] Close this window", COLOR_PAIR(10));
3158 
3159 	refresh();
3160 	wrefresh(help_win);
3161 
3162 	while (!stop_loop) {
3163 		c = wgetch(help_win);
3164 
3165 		switch (c) {
3166 		case 27: /* ESC */
3167 			stop_loop = true;
3168 			break;
3169 		default:
3170 			break;
3171 		}
3172 	}
3173 
3174 	del_panel(help_panel);
3175 	delwin(help_win);
3176 
3177 }
3178 
3179 static void
refresh_after_popup(uint8_t active_tab,uint8_t * max_pages,uint8_t current_page)3180 refresh_after_popup(uint8_t active_tab, uint8_t *max_pages, uint8_t current_page)
3181 {
3182 	int i;
3183 
3184 	/* After closing pop-up there would be unrefreshed parts
3185 	 * of the tab, so this is to refresh them */
3186 	draw_tabs(active_tab, g_current_sort_col[active_tab], g_current_sort_col2[active_tab]);
3187 	pthread_mutex_lock(&g_thread_lock);
3188 	*max_pages = refresh_tab(active_tab, current_page);
3189 	pthread_mutex_unlock(&g_thread_lock);
3190 	top_panel(g_panels[active_tab]);
3191 
3192 	for (i = 0; i < NUMBER_OF_TABS; i++) {
3193 		wclear(g_tab_win[i]);
3194 		wresize(g_tab_win[i], TAB_WIN_HEIGHT,
3195 			(g_max_col - (TABS_SPACING * NUMBER_OF_TABS)) / NUMBER_OF_TABS);
3196 		mvwin(g_tab_win[i], TAB_WIN_LOCATION_ROW, 1 + (g_max_col / NUMBER_OF_TABS) * i);
3197 		draw_tab_win(i);
3198 	}
3199 
3200 	update_panels();
3201 	refresh();
3202 }
3203 
3204 static void
show_stats(pthread_t * data_thread)3205 show_stats(pthread_t *data_thread)
3206 {
3207 	const int CURRENT_PAGE_STR_LEN = 50;
3208 	long int time_last, time_dif;
3209 	struct timespec time_now;
3210 	int c;
3211 	uint8_t active_tab = THREADS_TAB;
3212 	uint8_t current_page = 0;
3213 	uint8_t max_pages = 1;
3214 	uint64_t i;
3215 	char current_page_str[CURRENT_PAGE_STR_LEN];
3216 	bool force_refresh = true;
3217 
3218 	clock_gettime(CLOCK_MONOTONIC, &time_now);
3219 	time_last = time_now.tv_sec;
3220 
3221 	switch_tab(THREADS_TAB);
3222 
3223 	while (1) {
3224 		check_resize_interface(active_tab, &current_page);
3225 
3226 		clock_gettime(CLOCK_MONOTONIC, &time_now);
3227 		time_dif = time_now.tv_sec - time_last;
3228 		if (time_dif < 0) {
3229 			time_dif = g_sleep_time;
3230 		}
3231 
3232 		if (time_dif >= g_sleep_time || force_refresh) {
3233 			time_last = time_now.tv_sec;
3234 			pthread_mutex_lock(&g_thread_lock);
3235 			max_pages = refresh_tab(active_tab, current_page);
3236 			pthread_mutex_unlock(&g_thread_lock);
3237 
3238 			snprintf(current_page_str, CURRENT_PAGE_STR_LEN - 1, "Page: %d/%d", current_page + 1, max_pages);
3239 			mvprintw(g_max_row - 1, 1, "%s", current_page_str);
3240 
3241 			refresh();
3242 		}
3243 
3244 		c = getch();
3245 		if (c == 'q') {
3246 			pthread_mutex_lock(&g_thread_lock);
3247 			g_quit_app = true;
3248 			pthread_mutex_unlock(&g_thread_lock);
3249 			break;
3250 		}
3251 
3252 		force_refresh = true;
3253 
3254 		switch (c) {
3255 		case '1':
3256 		case '2':
3257 		case '3':
3258 			active_tab = c - '1';
3259 			current_page = 0;
3260 			g_selected_row = 0;
3261 			switch_tab(active_tab);
3262 			break;
3263 		case '\t':
3264 			if (active_tab < NUMBER_OF_TABS - 1) {
3265 				active_tab++;
3266 			} else {
3267 				active_tab = THREADS_TAB;
3268 			}
3269 			g_selected_row = 0;
3270 			current_page = 0;
3271 			switch_tab(active_tab);
3272 			break;
3273 		case 's':
3274 			sort_type2(active_tab, COL_THREADS_NONE);
3275 			change_sorting(active_tab, 0, NULL);
3276 			break;
3277 		case 'c':
3278 			filter_columns(active_tab);
3279 			break;
3280 		case 'r':
3281 			change_refresh_rate();
3282 			break;
3283 		case 't':
3284 			g_interval_data = !g_interval_data;
3285 			break;
3286 		case 'g':
3287 			show_scheduler(active_tab, current_page);
3288 			break;
3289 		case KEY_NPAGE: /* PgDown */
3290 			if (current_page + 1 < max_pages) {
3291 				current_page++;
3292 			}
3293 			wclear(g_tabs[active_tab]);
3294 			g_selected_row = 0;
3295 			draw_tabs(active_tab, g_current_sort_col[active_tab], g_current_sort_col2[active_tab]);
3296 			break;
3297 		case KEY_PPAGE: /* PgUp */
3298 			if (current_page > 0) {
3299 				current_page--;
3300 			}
3301 			wclear(g_tabs[active_tab]);
3302 			g_selected_row = 0;
3303 			draw_tabs(active_tab, g_current_sort_col[active_tab], g_current_sort_col2[active_tab]);
3304 			break;
3305 		case KEY_UP: /* Arrow up */
3306 			if (g_selected_row > 0) {
3307 				g_selected_row--;
3308 			} else if (g_selected_row == 0) {
3309 				if (current_page > 0) {
3310 					current_page--;
3311 					g_selected_row = g_max_data_rows - 1;
3312 					wclear(g_tabs[active_tab]);
3313 					draw_tabs(active_tab, g_current_sort_col[active_tab], g_current_sort_col2[active_tab]);
3314 				}
3315 			}
3316 			break;
3317 		case KEY_DOWN: /* Arrow down */
3318 			if (g_selected_row < g_max_selected_row) {
3319 				g_selected_row++;
3320 			} else if (g_selected_row == g_max_selected_row) {
3321 				if (current_page + 1 < max_pages) {
3322 					current_page++;
3323 					g_selected_row = 0;
3324 					wclear(g_tabs[active_tab]);
3325 					draw_tabs(active_tab, g_current_sort_col[active_tab], g_current_sort_col2[active_tab]);
3326 				}
3327 			}
3328 			break;
3329 		case 10: /* Enter */
3330 			if (active_tab == THREADS_TAB) {
3331 				show_thread(current_page, active_tab);
3332 			} else if (active_tab == CORES_TAB) {
3333 				show_core(current_page, active_tab);
3334 			} else if (active_tab == POLLERS_TAB) {
3335 				show_poller(current_page, active_tab);
3336 			}
3337 			snprintf(current_page_str, CURRENT_PAGE_STR_LEN - 1, "Page: %d/%d", current_page + 1, max_pages);
3338 			mvprintw(g_max_row - 1, 1, "%s", current_page_str);
3339 			refresh_after_popup(active_tab, &max_pages, current_page);
3340 			break;
3341 		case 'h':
3342 			help_window_display();
3343 			refresh_after_popup(active_tab, &max_pages, current_page);
3344 			break;
3345 		default:
3346 			force_refresh = false;
3347 			break;
3348 		}
3349 	}
3350 
3351 	pthread_join(*data_thread, NULL);
3352 
3353 	free_poller_history();
3354 
3355 	/* Free memory holding current data states before quitting application */
3356 	for (i = 0; i < g_last_pollers_count; i++) {
3357 		free_rpc_poller(&g_pollers_info[i]);
3358 	}
3359 	for (i = 0; i < g_last_threads_count; i++) {
3360 		free_rpc_threads_stats(&g_threads_info[i]);
3361 	}
3362 	free_rpc_core_info(g_cores_info, g_last_cores_count);
3363 	free_rpc_scheduler(&g_scheduler_info);
3364 }
3365 
3366 static void
draw_interface(void)3367 draw_interface(void)
3368 {
3369 	int i;
3370 	uint16_t required_size =  WINDOW_HEADER + 1;
3371 
3372 	getmaxyx(stdscr, g_max_row, g_max_col);
3373 	g_max_row = spdk_max(g_max_row, required_size);
3374 	g_data_win_size = g_max_row - required_size;
3375 	g_max_data_rows = g_max_row - WINDOW_HEADER;
3376 
3377 	g_menu_win = newwin(MENU_WIN_HEIGHT, g_max_col, g_max_row - MENU_WIN_HEIGHT - 1,
3378 			    MENU_WIN_LOCATION_COL);
3379 	assert(g_menu_win != NULL);
3380 	draw_menu_win();
3381 
3382 	for (i = 0; i < NUMBER_OF_TABS; i++) {
3383 		g_tab_win[i] = newwin(TAB_WIN_HEIGHT, g_max_col / NUMBER_OF_TABS - TABS_SPACING,
3384 				      TAB_WIN_LOCATION_ROW, g_max_col / NUMBER_OF_TABS * i + 1);
3385 		assert(g_tab_win[i] != NULL);
3386 		draw_tab_win(i);
3387 
3388 		g_tabs[i] = newwin(g_max_row - MENU_WIN_HEIGHT - TAB_WIN_HEIGHT - 2, g_max_col, TABS_LOCATION_ROW,
3389 				   TABS_LOCATION_COL);
3390 		draw_tabs(i, g_current_sort_col[i], g_current_sort_col2[i]);
3391 		g_panels[i] = new_panel(g_tabs[i]);
3392 		assert(g_panels[i] != NULL);
3393 	}
3394 
3395 	update_panels();
3396 	doupdate();
3397 }
3398 
3399 static void
finish(int sig)3400 finish(int sig)
3401 {
3402 	/* End ncurses mode */
3403 	endwin();
3404 	spdk_jsonrpc_client_close(g_rpc_client);
3405 	exit(0);
3406 }
3407 
3408 static void
setup_ncurses(void)3409 setup_ncurses(void)
3410 {
3411 	clear();
3412 	noecho();
3413 	timeout(1);
3414 	curs_set(0);
3415 	keypad(stdscr, TRUE);
3416 	start_color();
3417 	init_pair(1, COLOR_BLACK, COLOR_GREEN);
3418 	init_pair(2, COLOR_BLACK, COLOR_WHITE);
3419 	init_pair(3, COLOR_YELLOW, COLOR_BLACK);
3420 	init_pair(4, COLOR_BLACK, COLOR_YELLOW);
3421 	init_pair(5, COLOR_GREEN, COLOR_BLACK);
3422 	init_pair(6, COLOR_RED, COLOR_BLACK);
3423 	init_pair(7, COLOR_BLUE, COLOR_BLACK);
3424 	init_pair(8, COLOR_RED, COLOR_WHITE);
3425 	init_pair(9, COLOR_BLUE, COLOR_WHITE);
3426 	init_pair(10, COLOR_WHITE, COLOR_BLACK);
3427 
3428 	if (has_colors() == FALSE) {
3429 		endwin();
3430 		printf("Your terminal does not support color\n");
3431 		exit(1);
3432 	}
3433 
3434 	/* Handle signals to exit gracefully cleaning up ncurses */
3435 	(void) signal(SIGINT, finish);
3436 	(void) signal(SIGPIPE, finish);
3437 	(void) signal(SIGABRT, finish);
3438 }
3439 
3440 static void
usage(const char * program_name)3441 usage(const char *program_name)
3442 {
3443 	printf("%s [options]", program_name);
3444 	printf("\n");
3445 	printf("options:\n");
3446 	printf(" -r <path>  RPC connect address (default: /var/tmp/spdk.sock)\n");
3447 	printf(" -h         show this usage\n");
3448 }
3449 
3450 static int
rpc_decode_tick_rate(struct spdk_json_val * val,uint64_t * tick_rate)3451 rpc_decode_tick_rate(struct spdk_json_val *val, uint64_t *tick_rate)
3452 {
3453 	struct t_rate {
3454 		uint64_t tr;
3455 	};
3456 
3457 	const struct spdk_json_object_decoder rpc_tick_rate_decoder[] = {
3458 		{"tick_rate", offsetof(struct t_rate, tr), spdk_json_decode_uint64}
3459 	};
3460 
3461 	int rc;
3462 	struct t_rate tmp;
3463 
3464 	rc = spdk_json_decode_object_relaxed(val, rpc_tick_rate_decoder,
3465 					     SPDK_COUNTOF(rpc_tick_rate_decoder), &tmp);
3466 
3467 	*tick_rate = tmp.tr;
3468 
3469 	return rc;
3470 }
3471 
3472 static int
wait_init(pthread_t * data_thread)3473 wait_init(pthread_t *data_thread)
3474 {
3475 	struct spdk_jsonrpc_client_response *json_resp = NULL;
3476 	char *uninit_log = "Waiting for SPDK target application to initialize...",
3477 	      *uninit_error = "Unable to read SPDK application state!";
3478 	int c, max_col, rc = 0;
3479 	uint64_t tick_rate;
3480 
3481 	max_col = getmaxx(stdscr);
3482 	print_in_middle(stdscr, FIRST_DATA_ROW, 1, max_col, uninit_log, COLOR_PAIR(5));
3483 	rc = rpc_send_req("framework_wait_init", &json_resp);
3484 	if (rc) {
3485 		while (1) {
3486 			print_in_middle(stdscr, FIRST_DATA_ROW, 1, max_col, uninit_error, COLOR_PAIR(8));
3487 			c = getch();
3488 			if (c == 'q') {
3489 				return -1;
3490 			}
3491 		}
3492 	}
3493 
3494 	spdk_jsonrpc_client_free_response(json_resp);
3495 
3496 	rc = pthread_mutex_init(&g_thread_lock, NULL);
3497 	if (rc) {
3498 		fprintf(stderr, "mutex lock failed to initialize: %d\n", errno);
3499 		return -1;
3500 	}
3501 
3502 	memset(&g_threads_info, 0, sizeof(struct rpc_thread_info) * RPC_MAX_THREADS);
3503 	memset(&g_cores_info, 0, sizeof(struct rpc_core_info) * RPC_MAX_CORES);
3504 
3505 	/* Decode tick rate */
3506 	rc = rpc_send_req("framework_get_reactors", &json_resp);
3507 	if (rc) {
3508 		return rc;
3509 	}
3510 
3511 	if (rpc_decode_tick_rate(json_resp->result, &tick_rate)) {
3512 		spdk_jsonrpc_client_free_response(json_resp);
3513 		return -EINVAL;
3514 	}
3515 
3516 	spdk_jsonrpc_client_free_response(json_resp);
3517 
3518 	g_tick_rate = tick_rate;
3519 
3520 	/* This is to get first batch of data for display functions.
3521 	 * Since data thread makes RPC calls that take more time than
3522 	 * startup of display functions on main thread, without these
3523 	 * calls both threads would be subject to a race condition. */
3524 	rc = get_thread_data();
3525 	if (rc) {
3526 		return -1;
3527 	}
3528 
3529 	rc = get_pollers_data();
3530 	if (rc) {
3531 		return -1;
3532 	}
3533 
3534 	rc = get_cores_data();
3535 	if (rc) {
3536 		return -1;
3537 	}
3538 
3539 	rc = pthread_create(data_thread, NULL, &data_thread_routine, NULL);
3540 	if (rc) {
3541 		fprintf(stderr, "data thread creation failed: %d\n", errno);
3542 		return -1;
3543 	}
3544 	return 0;
3545 }
3546 
3547 int
main(int argc,char ** argv)3548 main(int argc, char **argv)
3549 {
3550 	int op, rc;
3551 	char *socket = SPDK_DEFAULT_RPC_ADDR;
3552 	pthread_t data_thread;
3553 
3554 	while ((op = getopt(argc, argv, "r:h")) != -1) {
3555 		switch (op) {
3556 		case 'r':
3557 			socket = optarg;
3558 			break;
3559 		default:
3560 			usage(argv[0]);
3561 			return op == 'h' ? 0 : 1;
3562 		}
3563 	}
3564 
3565 	g_rpc_client = spdk_jsonrpc_client_connect(socket, socket[0] == '/' ? AF_UNIX : AF_INET);
3566 	if (!g_rpc_client) {
3567 		fprintf(stderr, "spdk_jsonrpc_client_connect() failed: %d\n", errno);
3568 		return 1;
3569 	}
3570 
3571 	initscr();
3572 	init_str_len();
3573 	setup_ncurses();
3574 	draw_interface();
3575 
3576 	rc = wait_init(&data_thread);
3577 	if (!rc) {
3578 		show_stats(&data_thread);
3579 	}
3580 
3581 	finish(0);
3582 
3583 	return (0);
3584 }
3585