4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright (c) 2008-2009, Intel Corporation.
23 * All Rights Reserved.
30 #include <sys/types.h>
40 #include "latencytop.h"
42 #define LT_WINDOW_X 80
43 #define LT_WINDOW_Y 24
45 #define LT_COLOR_DEFAULT 1
46 #define LT_COLOR_HEADER 2
48 /* Windows created by libcurses */
49 static WINDOW
*titlebar
= NULL
;
50 static WINDOW
*captionbar
= NULL
;
51 static WINDOW
*sysglobal_window
= NULL
;
52 static WINDOW
*taskbar
= NULL
;
53 static WINDOW
*process_window
= NULL
;
54 static WINDOW
*hintbar
= NULL
;
55 /* Screen dimension */
56 static int screen_width
= 1, screen_height
= 1;
57 /* Is display initialized, i.e. are window pointers set up. */
58 static int display_initialized
= FALSE
;
59 /* Is initscr() called */
60 static int curses_inited
= FALSE
;
62 /* To handle user key presses */
63 static pid_t selected_pid
= INVALID_PID
;
64 static id_t selected_tid
= INVALID_TID
;
65 static lt_sort_t sort_type
= LT_SORT_TOTAL
;
66 static int thread_mode
= FALSE
;
67 /* Type of list being displayed */
68 static int current_list_type
= LT_LIST_CAUSE
;
69 static int show_help
= FALSE
;
71 /* Help functions that append/prepend a blank to the given string */
72 #define fill_space_right(a, b, c) fill_space((a), (b), (c), TRUE)
73 #define fill_space_left(a, b, c) fill_space((a), (b), (c), FALSE)
76 fill_space(char *buffer
, int len
, int buffer_limit
, int is_right
)
81 if (len
>= buffer_limit
) {
82 len
= buffer_limit
- 1;
94 (void) memset(&buffer
[i
], ' ', tofill
);
97 (void) memmove(&buffer
[tofill
], buffer
, i
+1);
98 (void) memset(buffer
, ' ', tofill
);
102 /* Convert the nanosecond value to a human readable string */
104 get_time_string(double nanoseconds
, char *buffer
, int len
, int fill_width
)
106 const double ONE_USEC
= 1000.0;
107 const double ONE_MSEC
= 1000000.0;
108 const double ONE_SEC
= 1000000000.0;
110 if (nanoseconds
< (ONE_USEC
- .5)) {
111 (void) snprintf(buffer
, len
, "%3.1f nsec", nanoseconds
);
112 } else if (nanoseconds
< (ONE_MSEC
- .5 * ONE_USEC
)) {
113 (void) snprintf(buffer
, len
,
114 "%3.1f usec", nanoseconds
/ ONE_USEC
);
115 } else if (nanoseconds
< (ONE_SEC
- .5 * ONE_MSEC
)) {
116 (void) snprintf(buffer
, len
,
117 "%3.1f msec", nanoseconds
/ ONE_MSEC
);
118 } else if (nanoseconds
< 999.5 * ONE_SEC
) {
119 (void) snprintf(buffer
, len
,
120 "%3.1f sec", nanoseconds
/ ONE_SEC
);
122 (void) snprintf(buffer
, len
,
123 "%.0e sec", nanoseconds
/ ONE_SEC
);
126 fill_space_left(buffer
, fill_width
, len
);
130 /* Used in print_statistics below */
131 #define WIDTH_REASON_STRING 36
132 #define WIDTH_COUNT 12
136 #define BEGIN_COUNT WIDTH_REASON_STRING
137 #define BEGIN_AVG (BEGIN_COUNT + WIDTH_COUNT)
138 #define BEGIN_MAX (BEGIN_AVG + WIDTH_AVG)
139 #define BEGIN_PCT (BEGIN_MAX + WIDTH_MAX)
142 * Print statistics in global/process pane. Called by print_sysglobal
146 * window - the global or process statistics window.
147 * begin_line - where to start printing.
148 * count - how many lines should be printed.
149 * list - a stat_list.
152 print_statistics(WINDOW
* window
, int begin_line
, int nlines
, void *list
)
157 if (!display_initialized
) {
161 total
= lt_stat_list_get_gtotal(list
);
167 while (i
< nlines
&& lt_stat_list_has_item(list
, i
)) {
169 char tmp
[WIDTH_REASON_STRING
];
170 const char *reason
= lt_stat_list_get_reason(list
, i
);
171 uint64_t count
= lt_stat_list_get_count(list
, i
);
177 (void) snprintf(tmp
, sizeof (tmp
), "%s", reason
);
178 (void) mvwprintw(window
, i
+ begin_line
, 0, "%s", tmp
);
180 (void) snprintf(tmp
, sizeof (tmp
), "%llu", count
);
181 fill_space_left(tmp
, WIDTH_COUNT
, sizeof (tmp
));
182 (void) mvwprintw(window
, i
+ begin_line
, BEGIN_COUNT
,
185 (void) mvwprintw(window
, i
+ begin_line
, BEGIN_AVG
,
186 "%s", get_time_string(
187 (double)lt_stat_list_get_sum(list
, i
) / count
,
188 tmp
, sizeof (tmp
), WIDTH_AVG
));
190 (void) mvwprintw(window
, i
+ begin_line
, BEGIN_MAX
,
191 "%s", get_time_string(
192 (double)lt_stat_list_get_max(list
, i
),
193 tmp
, sizeof (tmp
), WIDTH_MAX
));
195 if (LT_LIST_SPECIALS
!= current_list_type
) {
196 (void) snprintf(tmp
, sizeof (tmp
), "%.1f %%",
197 (double)lt_stat_list_get_sum(list
, i
)
200 (void) snprintf(tmp
, sizeof (tmp
), "--- ");
203 fill_space_left(tmp
, WIDTH_PCT
, sizeof (tmp
));
205 (void) mvwprintw(window
, i
+ begin_line
, BEGIN_PCT
,
212 * Print statistics in global pane.
215 print_sysglobal(void)
220 if (!display_initialized
) {
224 (void) werase(sysglobal_window
);
226 (void) wattron(sysglobal_window
, A_REVERSE
);
227 (void) snprintf(header
, sizeof (header
),
228 "%s", "System wide latencies");
229 fill_space_right(header
, screen_width
, sizeof (header
));
230 (void) mvwprintw(sysglobal_window
, 0, 0, "%s", header
);
231 (void) wattroff(sysglobal_window
, A_REVERSE
);
233 list
= lt_stat_list_create(current_list_type
,
234 LT_LEVEL_GLOBAL
, 0, 0, 10, sort_type
);
235 print_statistics(sysglobal_window
, 1, 10, list
);
236 lt_stat_list_free(list
);
238 (void) wrefresh(sysglobal_window
);
242 * Prints current operation mode. Mode is combination of:
244 * "Process or Thread", and "1 or 2 or 3".
251 if (!display_initialized
) {
255 switch (current_list_type
) {
259 case LT_LIST_SPECIALS
:
270 (void) mvwprintw(process_window
, 0, screen_width
- 8, "View: %c%c",
271 type
, thread_mode
? 'T' : 'P');
275 * Print process window bar when the list is empty.
278 print_empty_process_bar()
282 if (!display_initialized
) {
286 (void) werase(process_window
);
287 (void) wattron(process_window
, A_REVERSE
);
288 (void) snprintf(header
, sizeof (header
),
289 "No process/thread data is available");
290 fill_space_right(header
, screen_width
, sizeof (header
));
291 (void) mvwprintw(process_window
, 0, 0, "%s", header
);
293 print_current_mode();
294 (void) wattroff(process_window
, A_REVERSE
);
296 (void) wrefresh(process_window
);
300 * Print per-process statistics in process pane.
301 * This is called when mode of operation is process.
304 print_process(unsigned int pid
)
310 if (!display_initialized
) {
314 list
= lt_stat_list_create(current_list_type
, LT_LEVEL_PROCESS
,
315 pid
, 0, 8, sort_type
);
317 (void) werase(process_window
);
318 (void) wattron(process_window
, A_REVERSE
);
319 (void) snprintf(header
, sizeof (header
), "Process %s (%i), %d threads",
320 lt_stat_proc_get_name(pid
), pid
, lt_stat_proc_get_nthreads(pid
));
321 fill_space_right(header
, screen_width
, sizeof (header
));
322 (void) mvwprintw(process_window
, 0, 0, "%s", header
);
324 if (current_list_type
!= LT_LIST_SPECIALS
) {
325 (void) mvwprintw(process_window
, 0, 48, "Total: %s",
326 get_time_string((double)lt_stat_list_get_gtotal(list
),
327 tmp
, sizeof (tmp
), 12));
330 print_current_mode();
331 (void) wattroff(process_window
, A_REVERSE
);
332 print_statistics(process_window
, 1, 8, list
);
333 lt_stat_list_free(list
);
335 (void) wrefresh(process_window
);
339 * Display the list of processes that are tracked, in task bar.
340 * This one is called when mode of operation is process.
343 print_taskbar_process(pid_t
*pidlist
, int pidlist_len
, int pidlist_index
)
345 const int ITEM_WIDTH
= 8;
351 if (!display_initialized
) {
355 number_item
= (screen_width
/ ITEM_WIDTH
) - 1;
356 i
= pidlist_index
- (pidlist_index
% number_item
);
358 (void) werase(taskbar
);
361 (void) mvwprintw(taskbar
, 0, xpos
, "<-");
364 xpos
= ITEM_WIDTH
/ 2;
366 while (xpos
+ ITEM_WIDTH
<= screen_width
&& i
< pidlist_len
) {
367 char str
[ITEM_WIDTH
+1];
369 const char *pname
= lt_stat_proc_get_name(pidlist
[i
]);
371 if (pname
&& pname
[0]) {
372 (void) snprintf(str
, sizeof (str
) - 1, "%s", pname
);
374 (void) snprintf(str
, sizeof (str
) - 1,
380 if (slen
< ITEM_WIDTH
) {
381 (void) memset(&str
[slen
], ' ', ITEM_WIDTH
- slen
);
384 str
[sizeof (str
) - 1] = '\0';
386 if (i
== pidlist_index
) {
387 (void) wattron(taskbar
, A_REVERSE
);
390 (void) mvwprintw(taskbar
, 0, xpos
, "%s", str
);
392 if (i
== pidlist_index
) {
393 (void) wattroff(taskbar
, A_REVERSE
);
400 if (i
!= pidlist_len
) {
401 (void) mvwprintw(taskbar
, 0, screen_width
- 2, "->");
404 (void) wrefresh(taskbar
);
408 * Display the list of processes that are tracked, in task bar.
409 * This one is called when mode of operation is thread.
412 print_taskbar_thread(pid_t
*pidlist
, id_t
*tidlist
, int list_len
,
415 const int ITEM_WIDTH
= 12;
420 const char *pname
= NULL
;
421 pid_t last_pid
= INVALID_PID
;
424 if (!display_initialized
) {
428 number_item
= (screen_width
- 8) / ITEM_WIDTH
;
429 i
= list_index
- (list_index
% number_item
);
431 (void) werase(taskbar
);
434 (void) mvwprintw(taskbar
, 0, xpos
, "<-");
439 while (xpos
+ ITEM_WIDTH
<= screen_width
&& i
< list_len
) {
440 char str
[ITEM_WIDTH
+1];
443 if (pidlist
[i
] != last_pid
) {
444 pname
= lt_stat_proc_get_name(pidlist
[i
]);
445 last_pid
= pidlist
[i
];
449 * Calculate length of thread's ID; use shorter process name
450 * in order to save space on the screen.
452 tlen
= snprintf(NULL
, 0, "_%d", tidlist
[i
]);
454 if (pname
&& pname
[0]) {
455 (void) snprintf(str
, sizeof (str
) - tlen
- 1,
458 (void) snprintf(str
, sizeof (str
) - tlen
- 1,
464 (void) snprintf(&str
[slen
], sizeof (str
) - slen
,
469 if (slen
< ITEM_WIDTH
) {
470 (void) memset(&str
[slen
], ' ', ITEM_WIDTH
- slen
);
473 str
[sizeof (str
) - 1] = '\0';
475 if (i
== list_index
) {
476 (void) wattron(taskbar
, A_REVERSE
);
479 (void) mvwprintw(taskbar
, 0, xpos
, "%s", str
);
481 if (i
== list_index
) {
482 (void) wattroff(taskbar
, A_REVERSE
);
490 (void) mvwprintw(taskbar
, 0, screen_width
- 2, "->");
493 (void) wrefresh(taskbar
);
497 * Print per-thread statistics in process pane.
498 * This is called when mode of operation is thread.
501 print_thread(pid_t pid
, id_t tid
)
507 if (!display_initialized
) {
511 list
= lt_stat_list_create(current_list_type
, LT_LEVEL_THREAD
,
512 pid
, tid
, 8, sort_type
);
514 (void) werase(process_window
);
515 (void) wattron(process_window
, A_REVERSE
);
516 (void) snprintf(header
, sizeof (header
),
517 "Process %s (%i), LWP %d",
518 lt_stat_proc_get_name(pid
), pid
, tid
);
519 fill_space_right(header
, screen_width
, sizeof (header
));
520 (void) mvwprintw(process_window
, 0, 0, "%s", header
);
522 if (current_list_type
!= LT_LIST_SPECIALS
) {
523 (void) mvwprintw(process_window
, 0, 48, "Total: %s",
525 (double)lt_stat_list_get_gtotal(list
),
526 tmp
, sizeof (tmp
), 12));
529 print_current_mode();
530 (void) wattroff(process_window
, A_REVERSE
);
531 print_statistics(process_window
, 1, 8, list
);
532 lt_stat_list_free(list
);
533 (void) wrefresh(process_window
);
537 * Update hint string at the bottom line. The message to print is stored in
538 * hint. If hint is NULL, the function will display its own message.
541 print_hint(const char *hint
)
543 const char *HINTS
[] = {
544 "Press '<' or '>' to switch between processes.",
545 "Press 'q' to exit.",
546 "Press 'r' to refresh immediately.",
547 "Press 't' to toggle Process/Thread display mode.",
548 "Press 'h' for help.",
549 "Use 'c', 'a', 'm', 'p' to change sort criteria.",
550 "Use '1', '2', '3' to switch between windows."
552 const uint64_t update_interval
= 5000; /* 5 seconds */
554 static int index
= 0;
555 static uint64_t next_hint
= 0;
556 uint64_t now
= lt_millisecond();
558 if (!display_initialized
) {
563 if (now
< next_hint
) {
568 index
= (index
+ 1) % (sizeof (HINTS
) / sizeof (HINTS
[0]));
569 next_hint
= now
+ update_interval
;
572 * Important messages are displayed at least every 2 cycles.
574 next_hint
= now
+ update_interval
* 2;
577 (void) werase(hintbar
);
578 (void) mvwprintw(hintbar
, 0, (screen_width
- strlen(hint
)) / 2,
580 (void) wrefresh(hintbar
);
584 * Create a PID list or a PID/TID list (if operation mode is thread) from
585 * available statistics.
588 get_plist(pid_t
**plist
, id_t
**tlist
, int *list_len
, int *list_index
)
591 /* Per-process mode */
592 *list_len
= lt_stat_proc_list_create(plist
, NULL
);
593 /* Search for previously selected PID */
594 for (*list_index
= 0; *list_index
< *list_len
&&
595 (*plist
)[*list_index
] != selected_pid
;
599 if (*list_index
>= *list_len
) {
601 * The previously selected pid is gone.
602 * Select the first one.
607 /* Per-thread mode */
608 *list_len
= lt_stat_proc_list_create(plist
, tlist
);
610 /* Search for previously selected PID & TID */
611 for (*list_index
= 0; *list_index
< *list_len
;
613 if ((*plist
)[*list_index
] == selected_pid
&&
614 (*tlist
)[*list_index
] == selected_tid
) {
619 if (*list_index
>= *list_len
) {
621 * The previously selected pid/tid is gone.
622 * Select the first one.
624 for (*list_index
= 0;
625 *list_index
< *list_len
&&
626 (*plist
)[*list_index
] != selected_pid
;
631 if (*list_index
>= *list_len
) {
633 * The previously selected pid is gone.
634 * Select the first one
641 /* Print help message when user presses 'h' hot key */
645 const char *HELP
[] = {
649 "These single-character commands are available:",
650 "< - Move to previous process/thread.",
651 "> - Move to next process/thread.",
654 "t - Toggle process/thread mode.",
655 "c - Sort by count.",
656 "a - Sort by average.",
657 "m - Sort by maximum.",
658 "p - Sort by percent.",
659 "1 - Show list by causes.",
660 "2 - Show list of special entries.",
661 "3 - Show list by synchronization objects.",
662 "h - Show this help.",
664 "Press any key to continue..."
668 if (!display_initialized
) {
672 for (i
= 0; i
< sizeof (HELP
) / sizeof (HELP
[0]); ++i
) {
673 (void) mvwprintw(stdscr
, i
, 0, "%s", HELP
[i
]);
680 * Print title on screen
685 if (!display_initialized
) {
689 (void) wattrset(titlebar
, COLOR_PAIR(LT_COLOR_HEADER
));
690 (void) wbkgd(titlebar
, COLOR_PAIR(LT_COLOR_HEADER
));
691 (void) werase(titlebar
);
693 (void) mvwprintw(titlebar
, 0, (screen_width
- strlen(TITLE
)) / 2,
695 (void) wrefresh(titlebar
);
697 (void) werase(captionbar
);
698 (void) mvwprintw(captionbar
, 0, 0, "%s",
700 "Count Average Maximum Percent");
701 (void) wrefresh(captionbar
);
703 (void) wattrset(hintbar
, COLOR_PAIR(LT_COLOR_HEADER
));
704 (void) wbkgd(hintbar
, COLOR_PAIR(LT_COLOR_HEADER
));
708 * Handle signal from terminal resize
718 * Initialize display. Display will be cleared when this function returns.
721 lt_display_init(void)
723 if (display_initialized
) {
727 /* Window resize signal */
728 (void) signal(SIGWINCH
, on_resize
);
730 /* Initialize curses library */
732 (void) start_color();
733 (void) keypad(stdscr
, TRUE
);
739 /* Set up color pairs */
740 (void) init_pair(LT_COLOR_DEFAULT
, COLOR_WHITE
, COLOR_BLACK
);
741 (void) init_pair(LT_COLOR_HEADER
, COLOR_BLACK
, COLOR_WHITE
);
743 curses_inited
= TRUE
;
744 getmaxyx(stdscr
, screen_height
, screen_width
);
746 if (screen_width
< LT_WINDOW_X
|| screen_height
< LT_WINDOW_Y
) {
747 (void) mvwprintw(stdscr
, 0, 0, "Terminal size is too small.");
748 (void) mvwprintw(stdscr
, 1, 0,
749 "Please resize it to 80x24 or larger.");
750 (void) mvwprintw(stdscr
, 2, 0, "Press q to quit.");
755 /* Set up all window panes */
756 titlebar
= subwin(stdscr
, 1, screen_width
, 0, 0);
757 captionbar
= subwin(stdscr
, 1, screen_width
, 1, 0);
758 sysglobal_window
= subwin(stdscr
, screen_height
/ 2 - 1,
760 process_window
= subwin(stdscr
, screen_height
/ 2 - 3,
761 screen_width
, screen_height
/ 2 + 1, 0);
762 taskbar
= subwin(stdscr
, 1, screen_width
, screen_height
- 2, 0);
763 hintbar
= subwin(stdscr
, 1, screen_width
, screen_height
- 1, 0);
764 (void) werase(stdscr
);
767 display_initialized
= TRUE
;
773 * The event loop for display. It displays data on screen and handles hotkey
777 * duration - returns after 'duration'
779 * The function also returns if user presses 'q', 'Ctrl+C' or 'r'.
783 * 1 - main() calls it again
786 lt_display_loop(int duration
)
790 struct timeval timeout
;
792 int need_refresh
= TRUE
;
801 start
= lt_millisecond();
802 gpipe
= lt_gpipe_readfd();
809 get_plist(&plist
, &tlist
, &list_len
, &list_index
);
812 if (need_refresh
&& !show_help
) {
815 print_taskbar_process(plist
, list_len
,
817 print_process(plist
[list_index
]);
819 print_taskbar_thread(plist
, tlist
,
820 list_len
, list_index
);
821 print_thread(plist
[list_index
],
825 print_empty_process_bar();
829 need_refresh
= TRUE
; /* Usually we need refresh. */
830 remaining
= duration
- (int)(lt_millisecond() - start
);
832 if (remaining
<= 0) {
836 /* Embedded dtrace snap action here. */
837 next_snap
= lt_dtrace_work(0);
839 if (next_snap
== 0) {
841 * Just did a snap, check time for the next one.
843 next_snap
= lt_dtrace_work(0);
846 if (next_snap
> 0 && remaining
> next_snap
) {
847 remaining
= next_snap
;
850 timeout
.tv_sec
= remaining
/ 1000;
851 timeout
.tv_usec
= (remaining
% 1000) * 1000;
855 FD_SET(gpipe
, &read_fd
);
857 /* Wait for keyboard input, or signal from gpipe */
858 if (select(gpipe
+ 1, &read_fd
, NULL
, NULL
, &timeout
) > 0) {
861 if (FD_ISSET(gpipe
, &read_fd
)) {
862 /* Data from pipe has priority */
864 (void) read(gpipe
, &ch
, 1);
865 k
= ch
; /* Need this for big-endianness */
871 * Check if we need to update the hint line whenever we
873 * NOTE: current implementation depends on
874 * g_config.lt_cfg_snap_interval, but it's OK because it
875 * doesn't have to be precise.
879 * If help is on display right now, and a key press
880 * happens, we need to clear the help and continue.
883 (void) werase(stdscr
);
888 /* Drop this key and continue */
905 (void) werase(stdscr
);
914 if (list_index
< 0) {
924 if (list_index
>= list_len
) {
925 list_index
= list_len
- 1;
931 sort_type
= LT_SORT_AVG
;
936 sort_type
= LT_SORT_TOTAL
;
941 sort_type
= LT_SORT_MAX
;
946 sort_type
= LT_SORT_COUNT
;
952 selected_pid
= plist
[list_index
];
955 selected_tid
= INVALID_TID
;
956 thread_mode
= !thread_mode
;
957 get_plist(&plist
, &tlist
,
958 &list_len
, &list_index
);
962 current_list_type
= LT_LIST_CAUSE
;
967 if (g_config
.lt_cfg_low_overhead_mode
) {
968 lt_display_error("Switching mode is "
969 "not available for '-f low'.");
971 current_list_type
= LT_LIST_SPECIALS
;
978 if (g_config
.lt_cfg_trace_syncobj
) {
979 current_list_type
= LT_LIST_SOBJ
;
981 } else if (g_config
.lt_cfg_low_overhead_mode
) {
982 lt_display_error("Switching mode is "
983 "not available for '-f low'.");
985 lt_display_error("Tracing "
986 "synchronization objects is "
992 /* Wake up for nothing; no refresh is needed */
993 need_refresh
= FALSE
;
997 need_refresh
= FALSE
;
1002 if (plist
!= NULL
) {
1003 selected_pid
= plist
[list_index
];
1006 if (tlist
!= NULL
) {
1007 selected_tid
= tlist
[list_index
];
1010 lt_stat_proc_list_free(plist
, tlist
);
1019 lt_display_deinit(void)
1021 if (curses_inited
) {
1029 sysglobal_window
= NULL
;
1031 process_window
= NULL
;
1036 display_initialized
= FALSE
;
1037 curses_inited
= FALSE
;
1041 * Print message when display error happens.
1045 lt_display_error(const char *fmt
, ...)
1052 (void) vsnprintf(tmp
, sizeof (tmp
), fmt
, vl
);
1057 while (l
> 0 && (tmp
[l
- 1] == '\n' || tmp
[l
- 1] == '\r')) {
1062 if (!display_initialized
) {
1063 (void) fprintf(stderr
, "%s\n", tmp
);
1064 } else if (!show_help
) {