]>
Commit | Line | Data |
---|---|---|
f9224c5c ACM |
1 | #define _GNU_SOURCE |
2 | #include <stdio.h> | |
3 | #undef _GNU_SOURCE | |
32ec6acf ACM |
4 | /* |
5 | * slang versions <= 2.0.6 have a "#if HAVE_LONG_LONG" that breaks | |
6 | * the build if it isn't defined. Use the equivalent one that glibc | |
7 | * has on features.h. | |
8 | */ | |
9 | #include <features.h> | |
10 | #ifndef HAVE_LONG_LONG | |
11 | #define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG | |
12 | #endif | |
ef7b93a1 | 13 | #include <slang.h> |
73ae8f85 | 14 | #include <signal.h> |
f9224c5c ACM |
15 | #include <stdlib.h> |
16 | #include <newt.h> | |
7081e087 | 17 | #include <sys/ttydefaults.h> |
f9224c5c ACM |
18 | |
19 | #include "cache.h" | |
20 | #include "hist.h" | |
3e1bbdc3 | 21 | #include "pstack.h" |
f9224c5c ACM |
22 | #include "session.h" |
23 | #include "sort.h" | |
24 | #include "symbol.h" | |
25 | ||
dc4ff193 ACM |
26 | #if SLANG_VERSION < 20104 |
27 | #define slsmg_printf(msg, args...) SLsmg_printf((char *)msg, ##args) | |
28 | #define slsmg_write_nstring(msg, len) SLsmg_write_nstring((char *)msg, len) | |
29 | #define sltt_set_color(obj, name, fg, bg) SLtt_set_color(obj,(char *)name,\ | |
30 | (char *)fg, (char *)bg) | |
31 | #else | |
32 | #define slsmg_printf SLsmg_printf | |
33 | #define slsmg_write_nstring SLsmg_write_nstring | |
34 | #define sltt_set_color SLtt_set_color | |
35 | #endif | |
36 | ||
5f4d3f88 ACM |
37 | struct ui_progress { |
38 | newtComponent form, scale; | |
39 | }; | |
40 | ||
41 | struct ui_progress *ui_progress__new(const char *title, u64 total) | |
42 | { | |
43 | struct ui_progress *self = malloc(sizeof(*self)); | |
44 | ||
45 | if (self != NULL) { | |
46 | int cols; | |
1d90f2e7 ACM |
47 | |
48 | if (use_browser <= 0) | |
49 | return self; | |
5f4d3f88 ACM |
50 | newtGetScreenSize(&cols, NULL); |
51 | cols -= 4; | |
52 | newtCenteredWindow(cols, 1, title); | |
53 | self->form = newtForm(NULL, NULL, 0); | |
54 | if (self->form == NULL) | |
55 | goto out_free_self; | |
56 | self->scale = newtScale(0, 0, cols, total); | |
57 | if (self->scale == NULL) | |
58 | goto out_free_form; | |
7f826453 | 59 | newtFormAddComponent(self->form, self->scale); |
5f4d3f88 ACM |
60 | newtRefresh(); |
61 | } | |
62 | ||
63 | return self; | |
64 | ||
65 | out_free_form: | |
66 | newtFormDestroy(self->form); | |
67 | out_free_self: | |
68 | free(self); | |
69 | return NULL; | |
70 | } | |
71 | ||
72 | void ui_progress__update(struct ui_progress *self, u64 curr) | |
73 | { | |
1d90f2e7 ACM |
74 | /* |
75 | * FIXME: We should have a per UI backend way of showing progress, | |
76 | * stdio will just show a percentage as NN%, etc. | |
77 | */ | |
78 | if (use_browser <= 0) | |
79 | return; | |
5f4d3f88 ACM |
80 | newtScaleSet(self->scale, curr); |
81 | newtRefresh(); | |
82 | } | |
83 | ||
84 | void ui_progress__delete(struct ui_progress *self) | |
85 | { | |
1d90f2e7 ACM |
86 | if (use_browser > 0) { |
87 | newtFormDestroy(self->form); | |
88 | newtPopWindow(); | |
89 | } | |
5f4d3f88 ACM |
90 | free(self); |
91 | } | |
92 | ||
3798ed7b ACM |
93 | static void ui_helpline__pop(void) |
94 | { | |
95 | newtPopHelpLine(); | |
96 | } | |
97 | ||
98 | static void ui_helpline__push(const char *msg) | |
99 | { | |
100 | newtPushHelpLine(msg); | |
101 | } | |
102 | ||
103 | static void ui_helpline__vpush(const char *fmt, va_list ap) | |
104 | { | |
105 | char *s; | |
106 | ||
107 | if (vasprintf(&s, fmt, ap) < 0) | |
108 | vfprintf(stderr, fmt, ap); | |
109 | else { | |
110 | ui_helpline__push(s); | |
111 | free(s); | |
112 | } | |
113 | } | |
114 | ||
115 | static void ui_helpline__fpush(const char *fmt, ...) | |
116 | { | |
117 | va_list ap; | |
118 | ||
119 | va_start(ap, fmt); | |
120 | ui_helpline__vpush(fmt, ap); | |
121 | va_end(ap); | |
122 | } | |
123 | ||
124 | static void ui_helpline__puts(const char *msg) | |
125 | { | |
126 | ui_helpline__pop(); | |
127 | ui_helpline__push(msg); | |
128 | } | |
129 | ||
5f4d3f88 ACM |
130 | static char browser__last_msg[1024]; |
131 | ||
132 | int browser__show_help(const char *format, va_list ap) | |
133 | { | |
134 | int ret; | |
135 | static int backlog; | |
136 | ||
137 | ret = vsnprintf(browser__last_msg + backlog, | |
138 | sizeof(browser__last_msg) - backlog, format, ap); | |
139 | backlog += ret; | |
140 | ||
141 | if (browser__last_msg[backlog - 1] == '\n') { | |
3798ed7b | 142 | ui_helpline__puts(browser__last_msg); |
5f4d3f88 ACM |
143 | newtRefresh(); |
144 | backlog = 0; | |
145 | } | |
146 | ||
147 | return ret; | |
148 | } | |
149 | ||
7081e087 ACM |
150 | static void newt_form__set_exit_keys(newtComponent self) |
151 | { | |
a308f3a8 | 152 | newtFormAddHotKey(self, NEWT_KEY_LEFT); |
7081e087 ACM |
153 | newtFormAddHotKey(self, NEWT_KEY_ESCAPE); |
154 | newtFormAddHotKey(self, 'Q'); | |
155 | newtFormAddHotKey(self, 'q'); | |
156 | newtFormAddHotKey(self, CTRL('c')); | |
157 | } | |
158 | ||
159 | static newtComponent newt_form__new(void) | |
160 | { | |
161 | newtComponent self = newtForm(NULL, NULL, 0); | |
162 | if (self) | |
163 | newt_form__set_exit_keys(self); | |
164 | return self; | |
165 | } | |
166 | ||
83753190 | 167 | static int popup_menu(int argc, char * const argv[]) |
53c54019 ACM |
168 | { |
169 | struct newtExitStruct es; | |
170 | int i, rc = -1, max_len = 5; | |
171 | newtComponent listbox, form = newt_form__new(); | |
172 | ||
173 | if (form == NULL) | |
174 | return -1; | |
175 | ||
176 | listbox = newtListbox(0, 0, argc, NEWT_FLAG_RETURNEXIT); | |
177 | if (listbox == NULL) | |
178 | goto out_destroy_form; | |
179 | ||
7f826453 | 180 | newtFormAddComponent(form, listbox); |
53c54019 ACM |
181 | |
182 | for (i = 0; i < argc; ++i) { | |
183 | int len = strlen(argv[i]); | |
184 | if (len > max_len) | |
185 | max_len = len; | |
186 | if (newtListboxAddEntry(listbox, argv[i], (void *)(long)i)) | |
187 | goto out_destroy_form; | |
188 | } | |
189 | ||
190 | newtCenteredWindow(max_len, argc, NULL); | |
191 | newtFormRun(form, &es); | |
192 | rc = newtListboxGetCurrent(listbox) - NULL; | |
193 | if (es.reason == NEWT_EXIT_HOTKEY) | |
194 | rc = -1; | |
195 | newtPopWindow(); | |
196 | out_destroy_form: | |
197 | newtFormDestroy(form); | |
198 | return rc; | |
199 | } | |
200 | ||
a9a4ab74 ACM |
201 | static int ui__help_window(const char *text) |
202 | { | |
203 | struct newtExitStruct es; | |
204 | newtComponent tb, form = newt_form__new(); | |
205 | int rc = -1; | |
206 | int max_len = 0, nr_lines = 0; | |
207 | const char *t; | |
208 | ||
209 | if (form == NULL) | |
210 | return -1; | |
211 | ||
212 | t = text; | |
213 | while (1) { | |
214 | const char *sep = strchr(t, '\n'); | |
215 | int len; | |
216 | ||
217 | if (sep == NULL) | |
218 | sep = strchr(t, '\0'); | |
219 | len = sep - t; | |
220 | if (max_len < len) | |
221 | max_len = len; | |
222 | ++nr_lines; | |
223 | if (*sep == '\0') | |
224 | break; | |
225 | t = sep + 1; | |
226 | } | |
227 | ||
228 | tb = newtTextbox(0, 0, max_len, nr_lines, 0); | |
229 | if (tb == NULL) | |
230 | goto out_destroy_form; | |
231 | ||
232 | newtTextboxSetText(tb, text); | |
233 | newtFormAddComponent(form, tb); | |
234 | newtCenteredWindow(max_len, nr_lines, NULL); | |
235 | newtFormRun(form, &es); | |
236 | newtPopWindow(); | |
237 | rc = 0; | |
238 | out_destroy_form: | |
239 | newtFormDestroy(form); | |
240 | return rc; | |
241 | } | |
242 | ||
53c54019 ACM |
243 | static bool dialog_yesno(const char *msg) |
244 | { | |
245 | /* newtWinChoice should really be accepting const char pointers... */ | |
246 | char yes[] = "Yes", no[] = "No"; | |
c0ed55d2 | 247 | return newtWinChoice(NULL, yes, no, (char *)msg) == 1; |
53c54019 ACM |
248 | } |
249 | ||
46e3e055 ACM |
250 | static void ui__error_window(const char *fmt, ...) |
251 | { | |
252 | va_list ap; | |
253 | ||
254 | va_start(ap, fmt); | |
255 | newtWinMessagev((char *)"Error", (char *)"Ok", (char *)fmt, ap); | |
256 | va_end(ap); | |
257 | } | |
258 | ||
ef7b93a1 ACM |
259 | #define HE_COLORSET_TOP 50 |
260 | #define HE_COLORSET_MEDIUM 51 | |
261 | #define HE_COLORSET_NORMAL 52 | |
262 | #define HE_COLORSET_SELECTED 53 | |
263 | #define HE_COLORSET_CODE 54 | |
264 | ||
265 | static int ui_browser__percent_color(double percent, bool current) | |
266 | { | |
267 | if (current) | |
268 | return HE_COLORSET_SELECTED; | |
269 | if (percent >= MIN_RED) | |
270 | return HE_COLORSET_TOP; | |
271 | if (percent >= MIN_GREEN) | |
272 | return HE_COLORSET_MEDIUM; | |
273 | return HE_COLORSET_NORMAL; | |
274 | } | |
275 | ||
276 | struct ui_browser { | |
277 | newtComponent form, sb; | |
278 | u64 index, first_visible_entry_idx; | |
279 | void *first_visible_entry, *entries; | |
280 | u16 top, left, width, height; | |
281 | void *priv; | |
9f61d85f | 282 | unsigned int (*refresh_entries)(struct ui_browser *self); |
46b0a07a ACM |
283 | void (*seek)(struct ui_browser *self, |
284 | off_t offset, int whence); | |
ef7b93a1 ACM |
285 | u32 nr_entries; |
286 | }; | |
287 | ||
46b0a07a ACM |
288 | static void ui_browser__list_head_seek(struct ui_browser *self, |
289 | off_t offset, int whence) | |
290 | { | |
291 | struct list_head *head = self->entries; | |
292 | struct list_head *pos; | |
293 | ||
294 | switch (whence) { | |
295 | case SEEK_SET: | |
296 | pos = head->next; | |
297 | break; | |
298 | case SEEK_CUR: | |
299 | pos = self->first_visible_entry; | |
300 | break; | |
301 | case SEEK_END: | |
302 | pos = head->prev; | |
303 | break; | |
304 | default: | |
305 | return; | |
306 | } | |
307 | ||
308 | if (offset > 0) { | |
309 | while (offset-- != 0) | |
310 | pos = pos->next; | |
311 | } else { | |
312 | while (offset++ != 0) | |
313 | pos = pos->prev; | |
314 | } | |
315 | ||
316 | self->first_visible_entry = pos; | |
317 | } | |
318 | ||
8c694d25 ACM |
319 | static bool ui_browser__is_current_entry(struct ui_browser *self, unsigned row) |
320 | { | |
321 | return (self->first_visible_entry_idx + row) == self->index; | |
322 | } | |
323 | ||
ef7b93a1 ACM |
324 | static void ui_browser__refresh_dimensions(struct ui_browser *self) |
325 | { | |
326 | int cols, rows; | |
327 | newtGetScreenSize(&cols, &rows); | |
328 | ||
329 | if (self->width > cols - 4) | |
330 | self->width = cols - 4; | |
331 | self->height = rows - 5; | |
332 | if (self->height > self->nr_entries) | |
333 | self->height = self->nr_entries; | |
334 | self->top = (rows - self->height) / 2; | |
335 | self->left = (cols - self->width) / 2; | |
336 | } | |
337 | ||
338 | static void ui_browser__reset_index(struct ui_browser *self) | |
339 | { | |
8c694d25 | 340 | self->index = self->first_visible_entry_idx = 0; |
46b0a07a | 341 | self->seek(self, 0, SEEK_SET); |
ef7b93a1 ACM |
342 | } |
343 | ||
13f499f0 ACM |
344 | static int ui_browser__show(struct ui_browser *self, const char *title) |
345 | { | |
63160f73 ACM |
346 | if (self->form != NULL) { |
347 | newtFormDestroy(self->form); | |
348 | newtPopWindow(); | |
349 | } | |
13f499f0 | 350 | ui_browser__refresh_dimensions(self); |
8d8c369f | 351 | newtCenteredWindow(self->width, self->height, title); |
13f499f0 ACM |
352 | self->form = newt_form__new(); |
353 | if (self->form == NULL) | |
354 | return -1; | |
355 | ||
8d8c369f | 356 | self->sb = newtVerticalScrollbar(self->width, 0, self->height, |
13f499f0 ACM |
357 | HE_COLORSET_NORMAL, |
358 | HE_COLORSET_SELECTED); | |
359 | if (self->sb == NULL) | |
360 | return -1; | |
361 | ||
362 | newtFormAddHotKey(self->form, NEWT_KEY_UP); | |
363 | newtFormAddHotKey(self->form, NEWT_KEY_DOWN); | |
364 | newtFormAddHotKey(self->form, NEWT_KEY_PGUP); | |
365 | newtFormAddHotKey(self->form, NEWT_KEY_PGDN); | |
366 | newtFormAddHotKey(self->form, NEWT_KEY_HOME); | |
367 | newtFormAddHotKey(self->form, NEWT_KEY_END); | |
368 | newtFormAddComponent(self->form, self->sb); | |
369 | return 0; | |
370 | } | |
371 | ||
ef7b93a1 ACM |
372 | static int objdump_line__show(struct objdump_line *self, struct list_head *head, |
373 | int width, struct hist_entry *he, int len, | |
374 | bool current_entry) | |
375 | { | |
376 | if (self->offset != -1) { | |
377 | struct symbol *sym = he->ms.sym; | |
378 | unsigned int hits = 0; | |
379 | double percent = 0.0; | |
380 | int color; | |
381 | struct sym_priv *priv = symbol__priv(sym); | |
382 | struct sym_ext *sym_ext = priv->ext; | |
383 | struct sym_hist *h = priv->hist; | |
384 | s64 offset = self->offset; | |
385 | struct objdump_line *next = objdump__get_next_ip_line(head, self); | |
386 | ||
387 | while (offset < (s64)len && | |
388 | (next == NULL || offset < next->offset)) { | |
389 | if (sym_ext) { | |
390 | percent += sym_ext[offset].percent; | |
391 | } else | |
392 | hits += h->ip[offset]; | |
393 | ||
394 | ++offset; | |
395 | } | |
396 | ||
397 | if (sym_ext == NULL && h->sum) | |
398 | percent = 100.0 * hits / h->sum; | |
399 | ||
400 | color = ui_browser__percent_color(percent, current_entry); | |
401 | SLsmg_set_color(color); | |
dc4ff193 | 402 | slsmg_printf(" %7.2f ", percent); |
ef7b93a1 ACM |
403 | if (!current_entry) |
404 | SLsmg_set_color(HE_COLORSET_CODE); | |
405 | } else { | |
406 | int color = ui_browser__percent_color(0, current_entry); | |
407 | SLsmg_set_color(color); | |
dc4ff193 | 408 | slsmg_write_nstring(" ", 9); |
ef7b93a1 ACM |
409 | } |
410 | ||
411 | SLsmg_write_char(':'); | |
dc4ff193 | 412 | slsmg_write_nstring(" ", 8); |
ef7b93a1 | 413 | if (!*self->line) |
dc4ff193 | 414 | slsmg_write_nstring(" ", width - 18); |
ef7b93a1 | 415 | else |
dc4ff193 | 416 | slsmg_write_nstring(self->line, width - 18); |
ef7b93a1 ACM |
417 | |
418 | return 0; | |
419 | } | |
420 | ||
421 | static int ui_browser__refresh_entries(struct ui_browser *self) | |
422 | { | |
9f61d85f | 423 | int row; |
ef7b93a1 | 424 | |
9f61d85f ACM |
425 | newtScrollbarSet(self->sb, self->index, self->nr_entries - 1); |
426 | row = self->refresh_entries(self); | |
ef7b93a1 ACM |
427 | SLsmg_set_color(HE_COLORSET_NORMAL); |
428 | SLsmg_fill_region(self->top + row, self->left, | |
429 | self->height - row, self->width, ' '); | |
430 | ||
431 | return 0; | |
432 | } | |
433 | ||
13f499f0 | 434 | static int ui_browser__run(struct ui_browser *self, struct newtExitStruct *es) |
ef7b93a1 | 435 | { |
ef7b93a1 ACM |
436 | if (ui_browser__refresh_entries(self) < 0) |
437 | return -1; | |
ef7b93a1 ACM |
438 | |
439 | while (1) { | |
46b0a07a | 440 | off_t offset; |
ef7b93a1 ACM |
441 | |
442 | newtFormRun(self->form, es); | |
443 | ||
444 | if (es->reason != NEWT_EXIT_HOTKEY) | |
445 | break; | |
46e3e055 ACM |
446 | if (is_exit_key(es->u.key)) |
447 | return es->u.key; | |
ef7b93a1 ACM |
448 | switch (es->u.key) { |
449 | case NEWT_KEY_DOWN: | |
450 | if (self->index == self->nr_entries - 1) | |
451 | break; | |
452 | ++self->index; | |
453 | if (self->index == self->first_visible_entry_idx + self->height) { | |
ef7b93a1 | 454 | ++self->first_visible_entry_idx; |
46b0a07a | 455 | self->seek(self, +1, SEEK_CUR); |
ef7b93a1 ACM |
456 | } |
457 | break; | |
458 | case NEWT_KEY_UP: | |
459 | if (self->index == 0) | |
460 | break; | |
461 | --self->index; | |
462 | if (self->index < self->first_visible_entry_idx) { | |
ef7b93a1 | 463 | --self->first_visible_entry_idx; |
46b0a07a | 464 | self->seek(self, -1, SEEK_CUR); |
ef7b93a1 ACM |
465 | } |
466 | break; | |
467 | case NEWT_KEY_PGDN: | |
17930b40 | 468 | case ' ': |
ef7b93a1 ACM |
469 | if (self->first_visible_entry_idx + self->height > self->nr_entries - 1) |
470 | break; | |
471 | ||
472 | offset = self->height; | |
473 | if (self->index + offset > self->nr_entries - 1) | |
474 | offset = self->nr_entries - 1 - self->index; | |
475 | self->index += offset; | |
476 | self->first_visible_entry_idx += offset; | |
46b0a07a | 477 | self->seek(self, +offset, SEEK_CUR); |
ef7b93a1 ACM |
478 | break; |
479 | case NEWT_KEY_PGUP: | |
480 | if (self->first_visible_entry_idx == 0) | |
481 | break; | |
482 | ||
483 | if (self->first_visible_entry_idx < self->height) | |
484 | offset = self->first_visible_entry_idx; | |
485 | else | |
486 | offset = self->height; | |
487 | ||
488 | self->index -= offset; | |
489 | self->first_visible_entry_idx -= offset; | |
46b0a07a | 490 | self->seek(self, -offset, SEEK_CUR); |
ef7b93a1 ACM |
491 | break; |
492 | case NEWT_KEY_HOME: | |
493 | ui_browser__reset_index(self); | |
494 | break; | |
46b0a07a | 495 | case NEWT_KEY_END: |
ef7b93a1 | 496 | offset = self->height - 1; |
63f20e74 ACM |
497 | if (offset >= self->nr_entries) |
498 | offset = self->nr_entries - 1; | |
ef7b93a1 | 499 | |
63f20e74 ACM |
500 | self->index = self->nr_entries - 1; |
501 | self->first_visible_entry_idx = self->index - offset; | |
46b0a07a | 502 | self->seek(self, -offset, SEEK_END); |
ef7b93a1 | 503 | break; |
ef7b93a1 | 504 | default: |
b66ecd97 | 505 | return es->u.key; |
ef7b93a1 ACM |
506 | } |
507 | if (ui_browser__refresh_entries(self) < 0) | |
508 | return -1; | |
509 | } | |
510 | return 0; | |
511 | } | |
512 | ||
4ded2b25 ACM |
513 | static char *callchain_list__sym_name(struct callchain_list *self, |
514 | char *bf, size_t bfsize) | |
515 | { | |
b3c9ac08 ACM |
516 | if (self->ms.sym) |
517 | return self->ms.sym->name; | |
4ded2b25 ACM |
518 | |
519 | snprintf(bf, bfsize, "%#Lx", self->ip); | |
520 | return bf; | |
521 | } | |
522 | ||
9f61d85f ACM |
523 | static unsigned int hist_entry__annotate_browser_refresh(struct ui_browser *self) |
524 | { | |
525 | struct objdump_line *pos; | |
526 | struct list_head *head = self->entries; | |
527 | struct hist_entry *he = self->priv; | |
528 | int row = 0; | |
529 | int len = he->ms.sym->end - he->ms.sym->start; | |
530 | ||
531 | if (self->first_visible_entry == NULL || self->first_visible_entry == self->entries) | |
532 | self->first_visible_entry = head->next; | |
533 | ||
534 | pos = list_entry(self->first_visible_entry, struct objdump_line, node); | |
535 | ||
536 | list_for_each_entry_from(pos, head, node) { | |
537 | bool current_entry = ui_browser__is_current_entry(self, row); | |
538 | SLsmg_gotorc(self->top + row, self->left); | |
539 | objdump_line__show(pos, head, self->width, | |
540 | he, len, current_entry); | |
541 | if (++row == self->height) | |
542 | break; | |
543 | } | |
544 | ||
545 | return row; | |
546 | } | |
547 | ||
46e3e055 | 548 | int hist_entry__tui_annotate(struct hist_entry *self) |
f9224c5c | 549 | { |
ef7b93a1 | 550 | struct ui_browser browser; |
f9224c5c | 551 | struct newtExitStruct es; |
ef7b93a1 ACM |
552 | struct objdump_line *pos, *n; |
553 | LIST_HEAD(head); | |
46e3e055 | 554 | int ret; |
f9224c5c | 555 | |
ef7b93a1 | 556 | if (self->ms.sym == NULL) |
46e3e055 | 557 | return -1; |
f9224c5c | 558 | |
46e3e055 ACM |
559 | if (self->ms.map->dso->annotate_warned) |
560 | return -1; | |
561 | ||
562 | if (hist_entry__annotate(self, &head) < 0) { | |
563 | ui__error_window(browser__last_msg); | |
564 | return -1; | |
565 | } | |
f9224c5c | 566 | |
60553903 | 567 | ui_helpline__push("Press <- or ESC to exit"); |
f9224c5c | 568 | |
ef7b93a1 | 569 | memset(&browser, 0, sizeof(browser)); |
46b0a07a | 570 | browser.entries = &head; |
9f61d85f | 571 | browser.refresh_entries = hist_entry__annotate_browser_refresh; |
46b0a07a | 572 | browser.seek = ui_browser__list_head_seek; |
ef7b93a1 ACM |
573 | browser.priv = self; |
574 | list_for_each_entry(pos, &head, node) { | |
575 | size_t line_len = strlen(pos->line); | |
576 | if (browser.width < line_len) | |
577 | browser.width = line_len; | |
578 | ++browser.nr_entries; | |
f9224c5c | 579 | } |
f9224c5c | 580 | |
ef7b93a1 | 581 | browser.width += 18; /* Percentage */ |
13f499f0 | 582 | ui_browser__show(&browser, self->ms.sym->name); |
b61b55ed | 583 | newtFormAddHotKey(browser.form, ' '); |
0879b100 | 584 | ret = ui_browser__run(&browser, &es); |
ef7b93a1 | 585 | newtFormDestroy(browser.form); |
f9224c5c | 586 | newtPopWindow(); |
ef7b93a1 ACM |
587 | list_for_each_entry_safe(pos, n, &head, node) { |
588 | list_del(&pos->node); | |
589 | objdump_line__free(pos); | |
590 | } | |
3798ed7b | 591 | ui_helpline__pop(); |
46e3e055 | 592 | return ret; |
f9224c5c ACM |
593 | } |
594 | ||
e65713ea | 595 | struct hist_browser { |
0f0cbf7a ACM |
596 | struct ui_browser b; |
597 | struct hists *hists; | |
598 | struct hist_entry *he_selection; | |
599 | struct map_symbol *selection; | |
e65713ea ACM |
600 | }; |
601 | ||
0f0cbf7a ACM |
602 | static void hist_browser__reset(struct hist_browser *self); |
603 | static int hist_browser__run(struct hist_browser *self, const char *title, | |
604 | struct newtExitStruct *es); | |
605 | static unsigned int hist_browser__refresh_entries(struct ui_browser *self); | |
606 | static void ui_browser__hists_seek(struct ui_browser *self, | |
607 | off_t offset, int whence); | |
608 | ||
609 | static struct hist_browser *hist_browser__new(struct hists *hists) | |
e65713ea | 610 | { |
0f0cbf7a | 611 | struct hist_browser *self = zalloc(sizeof(*self)); |
e65713ea | 612 | |
0f0cbf7a ACM |
613 | if (self) { |
614 | self->hists = hists; | |
615 | self->b.refresh_entries = hist_browser__refresh_entries; | |
616 | self->b.seek = ui_browser__hists_seek; | |
617 | } | |
e65713ea ACM |
618 | |
619 | return self; | |
620 | } | |
621 | ||
622 | static void hist_browser__delete(struct hist_browser *self) | |
623 | { | |
0f0cbf7a | 624 | newtFormDestroy(self->b.form); |
e65713ea ACM |
625 | newtPopWindow(); |
626 | free(self); | |
627 | } | |
628 | ||
ef7b93a1 | 629 | static struct hist_entry *hist_browser__selected_entry(struct hist_browser *self) |
a5e29aca | 630 | { |
0f0cbf7a | 631 | return self->he_selection; |
ef7b93a1 ACM |
632 | } |
633 | ||
634 | static struct thread *hist_browser__selected_thread(struct hist_browser *self) | |
635 | { | |
0f0cbf7a | 636 | return self->he_selection->thread; |
a5e29aca ACM |
637 | } |
638 | ||
d67f088e | 639 | static int hist_browser__title(char *bf, size_t size, const char *ev_name, |
6e7ab4c6 ACM |
640 | const struct dso *dso, const struct thread *thread) |
641 | { | |
642 | int printed = 0; | |
643 | ||
644 | if (thread) | |
645 | printed += snprintf(bf + printed, size - printed, | |
646 | "Thread: %s(%d)", | |
647 | (thread->comm_set ? thread->comm : ""), | |
648 | thread->pid); | |
649 | if (dso) | |
650 | printed += snprintf(bf + printed, size - printed, | |
651 | "%sDSO: %s", thread ? " " : "", | |
652 | dso->short_name); | |
d67f088e | 653 | return printed ?: snprintf(bf, size, "Event: %s", ev_name); |
6e7ab4c6 ACM |
654 | } |
655 | ||
d67f088e | 656 | int hists__browse(struct hists *self, const char *helpline, const char *ev_name) |
e65713ea | 657 | { |
0f0cbf7a | 658 | struct hist_browser *browser = hist_browser__new(self); |
d67f088e | 659 | struct pstack *fstack; |
6e7ab4c6 ACM |
660 | const struct thread *thread_filter = NULL; |
661 | const struct dso *dso_filter = NULL; | |
e65713ea | 662 | struct newtExitStruct es; |
6e7ab4c6 | 663 | char msg[160]; |
d67f088e | 664 | int key = -1; |
e65713ea ACM |
665 | |
666 | if (browser == NULL) | |
667 | return -1; | |
668 | ||
3e1bbdc3 ACM |
669 | fstack = pstack__new(2); |
670 | if (fstack == NULL) | |
671 | goto out; | |
672 | ||
3798ed7b | 673 | ui_helpline__push(helpline); |
e65713ea | 674 | |
d67f088e | 675 | hist_browser__title(msg, sizeof(msg), ev_name, |
6e7ab4c6 | 676 | dso_filter, thread_filter); |
f9224c5c ACM |
677 | |
678 | while (1) { | |
a5e29aca | 679 | const struct thread *thread; |
6e7ab4c6 | 680 | const struct dso *dso; |
83753190 ACM |
681 | char *options[16]; |
682 | int nr_options = 0, choice = 0, i, | |
a5e29aca | 683 | annotate = -2, zoom_dso = -2, zoom_thread = -2; |
f9224c5c | 684 | |
0f0cbf7a ACM |
685 | if (hist_browser__run(browser, msg, &es)) |
686 | break; | |
9d192e11 ACM |
687 | |
688 | thread = hist_browser__selected_thread(browser); | |
689 | dso = browser->selection->map ? browser->selection->map->dso : NULL; | |
690 | ||
53c54019 | 691 | if (es.reason == NEWT_EXIT_HOTKEY) { |
d67f088e ACM |
692 | key = es.u.key; |
693 | ||
694 | switch (key) { | |
695 | case NEWT_KEY_F1: | |
a9a4ab74 | 696 | goto do_help; |
d67f088e ACM |
697 | case NEWT_KEY_TAB: |
698 | case NEWT_KEY_UNTAB: | |
699 | /* | |
700 | * Exit the browser, let hists__browser_tree | |
701 | * go to the next or previous | |
702 | */ | |
703 | goto out_free_stack; | |
704 | default:; | |
705 | } | |
a9a4ab74 | 706 | |
d67f088e ACM |
707 | key = toupper(key); |
708 | switch (key) { | |
9d192e11 | 709 | case 'A': |
6e78c9fd ACM |
710 | if (browser->selection->map == NULL && |
711 | browser->selection->map->dso->annotate_warned) | |
712 | continue; | |
d5679ae4 | 713 | goto do_annotate; |
9d192e11 ACM |
714 | case 'D': |
715 | goto zoom_dso; | |
716 | case 'T': | |
717 | goto zoom_thread; | |
a9a4ab74 ACM |
718 | case 'H': |
719 | case '?': | |
720 | do_help: | |
721 | ui__help_window("-> Zoom into DSO/Threads & Annotate current symbol\n" | |
722 | "<- Zoom out\n" | |
723 | "a Annotate current symbol\n" | |
724 | "h/?/F1 Show this window\n" | |
725 | "d Zoom into current DSO\n" | |
726 | "t Zoom into current Thread\n" | |
727 | "q/CTRL+C Exit browser"); | |
728 | continue; | |
9d192e11 ACM |
729 | default:; |
730 | } | |
d67f088e ACM |
731 | if (is_exit_key(key)) { |
732 | if (key == NEWT_KEY_ESCAPE) { | |
46e3e055 ACM |
733 | if (dialog_yesno("Do you really want to exit?")) |
734 | break; | |
735 | else | |
736 | continue; | |
737 | } else | |
53c54019 | 738 | break; |
53c54019 | 739 | } |
3e1bbdc3 ACM |
740 | |
741 | if (es.u.key == NEWT_KEY_LEFT) { | |
742 | const void *top; | |
743 | ||
744 | if (pstack__empty(fstack)) | |
745 | continue; | |
746 | top = pstack__pop(fstack); | |
747 | if (top == &dso_filter) | |
748 | goto zoom_out_dso; | |
749 | if (top == &thread_filter) | |
750 | goto zoom_out_thread; | |
751 | continue; | |
752 | } | |
53c54019 ACM |
753 | } |
754 | ||
83753190 | 755 | if (browser->selection->sym != NULL && |
6e78c9fd | 756 | !browser->selection->map->dso->annotate_warned && |
83753190 ACM |
757 | asprintf(&options[nr_options], "Annotate %s", |
758 | browser->selection->sym->name) > 0) | |
759 | annotate = nr_options++; | |
760 | ||
a5e29aca ACM |
761 | if (thread != NULL && |
762 | asprintf(&options[nr_options], "Zoom %s %s(%d) thread", | |
6e7ab4c6 ACM |
763 | (thread_filter ? "out of" : "into"), |
764 | (thread->comm_set ? thread->comm : ""), | |
765 | thread->pid) > 0) | |
a5e29aca ACM |
766 | zoom_thread = nr_options++; |
767 | ||
6e7ab4c6 ACM |
768 | if (dso != NULL && |
769 | asprintf(&options[nr_options], "Zoom %s %s DSO", | |
770 | (dso_filter ? "out of" : "into"), | |
771 | (dso->kernel ? "the Kernel" : dso->short_name)) > 0) | |
772 | zoom_dso = nr_options++; | |
773 | ||
83753190 | 774 | options[nr_options++] = (char *)"Exit"; |
53c54019 | 775 | |
53c54019 | 776 | choice = popup_menu(nr_options, options); |
83753190 ACM |
777 | |
778 | for (i = 0; i < nr_options - 1; ++i) | |
779 | free(options[i]); | |
780 | ||
53c54019 | 781 | if (choice == nr_options - 1) |
f9224c5c | 782 | break; |
a5e29aca ACM |
783 | |
784 | if (choice == -1) | |
785 | continue; | |
c1ec5fef | 786 | |
83753190 | 787 | if (choice == annotate) { |
ef7b93a1 | 788 | struct hist_entry *he; |
c1ec5fef | 789 | do_annotate: |
e65713ea | 790 | if (browser->selection->map->dso->origin == DSO__ORIG_KERNEL) { |
6e78c9fd | 791 | browser->selection->map->dso->annotate_warned = 1; |
3798ed7b | 792 | ui_helpline__puts("No vmlinux file found, can't " |
d5679ae4 ACM |
793 | "annotate with just a " |
794 | "kallsyms file"); | |
795 | continue; | |
796 | } | |
ef7b93a1 ACM |
797 | |
798 | he = hist_browser__selected_entry(browser); | |
799 | if (he == NULL) | |
800 | continue; | |
801 | ||
46e3e055 | 802 | hist_entry__tui_annotate(he); |
a5e29aca | 803 | } else if (choice == zoom_dso) { |
9d192e11 | 804 | zoom_dso: |
6e7ab4c6 | 805 | if (dso_filter) { |
3e1bbdc3 ACM |
806 | pstack__remove(fstack, &dso_filter); |
807 | zoom_out_dso: | |
3798ed7b | 808 | ui_helpline__pop(); |
6e7ab4c6 ACM |
809 | dso_filter = NULL; |
810 | } else { | |
9d192e11 ACM |
811 | if (dso == NULL) |
812 | continue; | |
3e1bbdc3 | 813 | ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"", |
3798ed7b | 814 | dso->kernel ? "the Kernel" : dso->short_name); |
6e7ab4c6 | 815 | dso_filter = dso; |
3e1bbdc3 | 816 | pstack__push(fstack, &dso_filter); |
6e7ab4c6 | 817 | } |
b09e0190 | 818 | hists__filter_by_dso(self, dso_filter); |
d67f088e | 819 | hist_browser__title(msg, sizeof(msg), ev_name, |
6e7ab4c6 | 820 | dso_filter, thread_filter); |
0f0cbf7a | 821 | hist_browser__reset(browser); |
a5e29aca | 822 | } else if (choice == zoom_thread) { |
9d192e11 | 823 | zoom_thread: |
6e7ab4c6 | 824 | if (thread_filter) { |
3e1bbdc3 ACM |
825 | pstack__remove(fstack, &thread_filter); |
826 | zoom_out_thread: | |
3798ed7b | 827 | ui_helpline__pop(); |
6e7ab4c6 ACM |
828 | thread_filter = NULL; |
829 | } else { | |
3e1bbdc3 | 830 | ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", |
3798ed7b ACM |
831 | thread->comm_set ? thread->comm : "", |
832 | thread->pid); | |
6e7ab4c6 | 833 | thread_filter = thread; |
3e1bbdc3 | 834 | pstack__push(fstack, &thread_filter); |
6e7ab4c6 | 835 | } |
b09e0190 | 836 | hists__filter_by_thread(self, thread_filter); |
d67f088e | 837 | hist_browser__title(msg, sizeof(msg), ev_name, |
6e7ab4c6 | 838 | dso_filter, thread_filter); |
0f0cbf7a | 839 | hist_browser__reset(browser); |
d5679ae4 | 840 | } |
f9224c5c | 841 | } |
3e1bbdc3 ACM |
842 | out_free_stack: |
843 | pstack__delete(fstack); | |
e65713ea ACM |
844 | out: |
845 | hist_browser__delete(browser); | |
d67f088e ACM |
846 | return key; |
847 | } | |
848 | ||
849 | int hists__tui_browse_tree(struct rb_root *self, const char *help) | |
850 | { | |
851 | struct rb_node *first = rb_first(self), *nd = first, *next; | |
852 | int key = 0; | |
853 | ||
854 | while (nd) { | |
855 | struct hists *hists = rb_entry(nd, struct hists, rb_node); | |
856 | const char *ev_name = __event_name(hists->type, hists->config); | |
857 | ||
858 | key = hists__browse(hists, help, ev_name); | |
859 | ||
860 | if (is_exit_key(key)) | |
861 | break; | |
862 | ||
863 | switch (key) { | |
864 | case NEWT_KEY_TAB: | |
865 | next = rb_next(nd); | |
866 | if (next) | |
867 | nd = next; | |
868 | break; | |
869 | case NEWT_KEY_UNTAB: | |
870 | if (nd == first) | |
871 | continue; | |
872 | nd = rb_prev(nd); | |
873 | default: | |
874 | break; | |
875 | } | |
876 | } | |
877 | ||
878 | return key; | |
f9224c5c ACM |
879 | } |
880 | ||
ef7b93a1 ACM |
881 | static struct newtPercentTreeColors { |
882 | const char *topColorFg, *topColorBg; | |
883 | const char *mediumColorFg, *mediumColorBg; | |
884 | const char *normalColorFg, *normalColorBg; | |
885 | const char *selColorFg, *selColorBg; | |
886 | const char *codeColorFg, *codeColorBg; | |
887 | } defaultPercentTreeColors = { | |
888 | "red", "lightgray", | |
889 | "green", "lightgray", | |
890 | "black", "lightgray", | |
891 | "lightgray", "magenta", | |
892 | "blue", "lightgray", | |
893 | }; | |
894 | ||
73ae8f85 ACM |
895 | static void newt_suspend(void *d __used) |
896 | { | |
897 | newtSuspend(); | |
898 | raise(SIGTSTP); | |
899 | newtResume(); | |
900 | } | |
901 | ||
f9224c5c ACM |
902 | void setup_browser(void) |
903 | { | |
ef7b93a1 | 904 | struct newtPercentTreeColors *c = &defaultPercentTreeColors; |
5d06e691 | 905 | |
46e3e055 | 906 | if (!isatty(1) || !use_browser || dump_trace) { |
62e3436b | 907 | use_browser = 0; |
5d06e691 | 908 | setup_pager(); |
f9224c5c | 909 | return; |
5d06e691 | 910 | } |
f9224c5c | 911 | |
5d06e691 | 912 | use_browser = 1; |
f9224c5c ACM |
913 | newtInit(); |
914 | newtCls(); | |
73ae8f85 | 915 | newtSetSuspendCallback(newt_suspend, NULL); |
3798ed7b | 916 | ui_helpline__puts(" "); |
dc4ff193 ACM |
917 | sltt_set_color(HE_COLORSET_TOP, NULL, c->topColorFg, c->topColorBg); |
918 | sltt_set_color(HE_COLORSET_MEDIUM, NULL, c->mediumColorFg, c->mediumColorBg); | |
919 | sltt_set_color(HE_COLORSET_NORMAL, NULL, c->normalColorFg, c->normalColorBg); | |
920 | sltt_set_color(HE_COLORSET_SELECTED, NULL, c->selColorFg, c->selColorBg); | |
921 | sltt_set_color(HE_COLORSET_CODE, NULL, c->codeColorFg, c->codeColorBg); | |
f9224c5c ACM |
922 | } |
923 | ||
f3a1f0ea | 924 | void exit_browser(bool wait_for_ok) |
f9224c5c | 925 | { |
5d06e691 | 926 | if (use_browser > 0) { |
f3a1f0ea ACM |
927 | if (wait_for_ok) { |
928 | char title[] = "Fatal Error", ok[] = "Ok"; | |
929 | newtWinMessage(title, ok, browser__last_msg); | |
930 | } | |
f9224c5c | 931 | newtFinished(); |
f3a1f0ea | 932 | } |
f9224c5c | 933 | } |
0f0cbf7a ACM |
934 | |
935 | static void hist_browser__refresh_dimensions(struct hist_browser *self) | |
936 | { | |
937 | /* 3 == +/- toggle symbol before actual hist_entry rendering */ | |
938 | self->b.width = 3 + (hists__sort_list_width(self->hists) + | |
939 | sizeof("[k]")); | |
940 | } | |
941 | ||
942 | static void hist_browser__reset(struct hist_browser *self) | |
943 | { | |
944 | self->b.nr_entries = self->hists->nr_entries; | |
945 | hist_browser__refresh_dimensions(self); | |
946 | ui_browser__reset_index(&self->b); | |
947 | } | |
948 | ||
949 | static char tree__folded_sign(bool unfolded) | |
950 | { | |
951 | return unfolded ? '-' : '+'; | |
952 | } | |
953 | ||
954 | static char map_symbol__folded(const struct map_symbol *self) | |
955 | { | |
956 | return self->has_children ? tree__folded_sign(self->unfolded) : ' '; | |
957 | } | |
958 | ||
959 | static char hist_entry__folded(const struct hist_entry *self) | |
960 | { | |
961 | return map_symbol__folded(&self->ms); | |
962 | } | |
963 | ||
964 | static char callchain_list__folded(const struct callchain_list *self) | |
965 | { | |
966 | return map_symbol__folded(&self->ms); | |
967 | } | |
968 | ||
969 | static bool map_symbol__toggle_fold(struct map_symbol *self) | |
970 | { | |
971 | if (!self->has_children) | |
972 | return false; | |
973 | ||
974 | self->unfolded = !self->unfolded; | |
975 | return true; | |
976 | } | |
977 | ||
978 | #define LEVEL_OFFSET_STEP 3 | |
979 | ||
980 | static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *self, | |
981 | struct callchain_node *chain_node, | |
982 | u64 total, int level, | |
983 | unsigned short row, | |
984 | off_t *row_offset, | |
985 | bool *is_current_entry) | |
986 | { | |
987 | struct rb_node *node; | |
988 | int first_row = row, width, offset = level * LEVEL_OFFSET_STEP; | |
989 | u64 new_total, remaining; | |
990 | ||
991 | if (callchain_param.mode == CHAIN_GRAPH_REL) | |
992 | new_total = chain_node->children_hit; | |
993 | else | |
994 | new_total = total; | |
995 | ||
996 | remaining = new_total; | |
997 | node = rb_first(&chain_node->rb_root); | |
998 | while (node) { | |
999 | struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node); | |
1000 | struct rb_node *next = rb_next(node); | |
1001 | u64 cumul = cumul_hits(child); | |
1002 | struct callchain_list *chain; | |
1003 | char folded_sign = ' '; | |
1004 | int first = true; | |
1005 | int extra_offset = 0; | |
1006 | ||
1007 | remaining -= cumul; | |
1008 | ||
1009 | list_for_each_entry(chain, &child->val, list) { | |
1010 | char ipstr[BITS_PER_LONG / 4 + 1], *alloc_str; | |
1011 | const char *str; | |
1012 | int color; | |
1013 | bool was_first = first; | |
1014 | ||
1015 | if (first) { | |
1016 | first = false; | |
1017 | chain->ms.has_children = chain->list.next != &child->val || | |
1018 | rb_first(&child->rb_root) != NULL; | |
1019 | } else { | |
1020 | extra_offset = LEVEL_OFFSET_STEP; | |
1021 | chain->ms.has_children = chain->list.next == &child->val && | |
1022 | rb_first(&child->rb_root) != NULL; | |
1023 | } | |
1024 | ||
1025 | folded_sign = callchain_list__folded(chain); | |
1026 | if (*row_offset != 0) { | |
1027 | --*row_offset; | |
1028 | goto do_next; | |
1029 | } | |
1030 | ||
1031 | alloc_str = NULL; | |
1032 | str = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | |
1033 | if (was_first) { | |
1034 | double percent = cumul * 100.0 / new_total; | |
1035 | ||
1036 | if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0) | |
1037 | str = "Not enough memory!"; | |
1038 | else | |
1039 | str = alloc_str; | |
1040 | } | |
1041 | ||
1042 | color = HE_COLORSET_NORMAL; | |
1043 | width = self->b.width - (offset + extra_offset + 2); | |
1044 | if (ui_browser__is_current_entry(&self->b, row)) { | |
1045 | self->selection = &chain->ms; | |
1046 | color = HE_COLORSET_SELECTED; | |
1047 | *is_current_entry = true; | |
1048 | } | |
1049 | ||
1050 | SLsmg_set_color(color); | |
1051 | SLsmg_gotorc(self->b.top + row, self->b.left); | |
1052 | slsmg_write_nstring(" ", offset + extra_offset); | |
1053 | slsmg_printf("%c ", folded_sign); | |
1054 | slsmg_write_nstring(str, width); | |
1055 | free(alloc_str); | |
1056 | ||
1057 | if (++row == self->b.height) | |
1058 | goto out; | |
1059 | do_next: | |
1060 | if (folded_sign == '+') | |
1061 | break; | |
1062 | } | |
1063 | ||
1064 | if (folded_sign == '-') { | |
1065 | const int new_level = level + (extra_offset ? 2 : 1); | |
1066 | row += hist_browser__show_callchain_node_rb_tree(self, child, new_total, | |
1067 | new_level, row, row_offset, | |
1068 | is_current_entry); | |
1069 | } | |
1070 | if (row == self->b.height) | |
1071 | goto out; | |
1072 | node = next; | |
1073 | } | |
1074 | out: | |
1075 | return row - first_row; | |
1076 | } | |
1077 | ||
1078 | static int hist_browser__show_callchain_node(struct hist_browser *self, | |
1079 | struct callchain_node *node, | |
1080 | int level, unsigned short row, | |
1081 | off_t *row_offset, | |
1082 | bool *is_current_entry) | |
1083 | { | |
1084 | struct callchain_list *chain; | |
1085 | int first_row = row, | |
1086 | offset = level * LEVEL_OFFSET_STEP, | |
1087 | width = self->b.width - offset; | |
1088 | char folded_sign = ' '; | |
1089 | ||
1090 | list_for_each_entry(chain, &node->val, list) { | |
1091 | char ipstr[BITS_PER_LONG / 4 + 1], *s; | |
1092 | int color; | |
1093 | /* | |
1094 | * FIXME: This should be moved to somewhere else, | |
1095 | * probably when the callchain is created, so as not to | |
1096 | * traverse it all over again | |
1097 | */ | |
1098 | chain->ms.has_children = rb_first(&node->rb_root) != NULL; | |
1099 | folded_sign = callchain_list__folded(chain); | |
1100 | ||
1101 | if (*row_offset != 0) { | |
1102 | --*row_offset; | |
1103 | continue; | |
1104 | } | |
1105 | ||
1106 | color = HE_COLORSET_NORMAL; | |
1107 | if (ui_browser__is_current_entry(&self->b, row)) { | |
1108 | self->selection = &chain->ms; | |
1109 | color = HE_COLORSET_SELECTED; | |
1110 | *is_current_entry = true; | |
1111 | } | |
1112 | ||
1113 | s = callchain_list__sym_name(chain, ipstr, sizeof(ipstr)); | |
1114 | SLsmg_gotorc(self->b.top + row, self->b.left); | |
1115 | SLsmg_set_color(color); | |
1116 | slsmg_write_nstring(" ", offset); | |
1117 | slsmg_printf("%c ", folded_sign); | |
1118 | slsmg_write_nstring(s, width - 2); | |
1119 | ||
1120 | if (++row == self->b.height) | |
1121 | goto out; | |
1122 | } | |
1123 | ||
1124 | if (folded_sign == '-') | |
1125 | row += hist_browser__show_callchain_node_rb_tree(self, node, | |
1126 | self->hists->stats.total_period, | |
1127 | level + 1, row, | |
1128 | row_offset, | |
1129 | is_current_entry); | |
1130 | out: | |
1131 | return row - first_row; | |
1132 | } | |
1133 | ||
1134 | static int hist_browser__show_callchain(struct hist_browser *self, | |
1135 | struct rb_root *chain, | |
1136 | int level, unsigned short row, | |
1137 | off_t *row_offset, | |
1138 | bool *is_current_entry) | |
1139 | { | |
1140 | struct rb_node *nd; | |
1141 | int first_row = row; | |
1142 | ||
1143 | for (nd = rb_first(chain); nd; nd = rb_next(nd)) { | |
1144 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | |
1145 | ||
1146 | row += hist_browser__show_callchain_node(self, node, level, | |
1147 | row, row_offset, | |
1148 | is_current_entry); | |
1149 | if (row == self->b.height) | |
1150 | break; | |
1151 | } | |
1152 | ||
1153 | return row - first_row; | |
1154 | } | |
1155 | ||
1156 | static int hist_browser__show_entry(struct hist_browser *self, | |
1157 | struct hist_entry *entry, | |
1158 | unsigned short row) | |
1159 | { | |
1160 | char s[256]; | |
1161 | double percent; | |
1162 | int printed = 0; | |
1163 | int color, width = self->b.width; | |
1164 | char folded_sign = ' '; | |
1165 | bool current_entry = ui_browser__is_current_entry(&self->b, row); | |
1166 | off_t row_offset = entry->row_offset; | |
1167 | ||
1168 | if (current_entry) { | |
1169 | self->he_selection = entry; | |
1170 | self->selection = &entry->ms; | |
1171 | } | |
1172 | ||
1173 | if (symbol_conf.use_callchain) { | |
1174 | entry->ms.has_children = !RB_EMPTY_ROOT(&entry->sorted_chain); | |
1175 | folded_sign = hist_entry__folded(entry); | |
1176 | } | |
1177 | ||
1178 | if (row_offset == 0) { | |
1179 | hist_entry__snprintf(entry, s, sizeof(s), self->hists, NULL, false, | |
1180 | 0, false, self->hists->stats.total_period); | |
1181 | percent = (entry->period * 100.0) / self->hists->stats.total_period; | |
1182 | ||
1183 | color = HE_COLORSET_SELECTED; | |
1184 | if (!current_entry) { | |
1185 | if (percent >= MIN_RED) | |
1186 | color = HE_COLORSET_TOP; | |
1187 | else if (percent >= MIN_GREEN) | |
1188 | color = HE_COLORSET_MEDIUM; | |
1189 | else | |
1190 | color = HE_COLORSET_NORMAL; | |
1191 | } | |
1192 | ||
1193 | SLsmg_set_color(color); | |
1194 | SLsmg_gotorc(self->b.top + row, self->b.left); | |
1195 | if (symbol_conf.use_callchain) { | |
1196 | slsmg_printf("%c ", folded_sign); | |
1197 | width -= 2; | |
1198 | } | |
1199 | slsmg_write_nstring(s, width); | |
1200 | ++row; | |
1201 | ++printed; | |
1202 | } else | |
1203 | --row_offset; | |
1204 | ||
1205 | if (folded_sign == '-' && row != self->b.height) { | |
1206 | printed += hist_browser__show_callchain(self, &entry->sorted_chain, | |
1207 | 1, row, &row_offset, | |
1208 | ¤t_entry); | |
1209 | if (current_entry) | |
1210 | self->he_selection = entry; | |
1211 | } | |
1212 | ||
1213 | return printed; | |
1214 | } | |
1215 | ||
1216 | static unsigned int hist_browser__refresh_entries(struct ui_browser *self) | |
1217 | { | |
1218 | unsigned row = 0; | |
1219 | struct rb_node *nd; | |
1220 | struct hist_browser *hb = container_of(self, struct hist_browser, b); | |
1221 | ||
1222 | if (self->first_visible_entry == NULL) | |
1223 | self->first_visible_entry = rb_first(&hb->hists->entries); | |
1224 | ||
1225 | for (nd = self->first_visible_entry; nd; nd = rb_next(nd)) { | |
1226 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | |
1227 | ||
1228 | if (h->filtered) | |
1229 | continue; | |
1230 | ||
1231 | row += hist_browser__show_entry(hb, h, row); | |
1232 | if (row == self->height) | |
1233 | break; | |
1234 | } | |
1235 | ||
1236 | return row; | |
1237 | } | |
1238 | ||
1239 | static void callchain_node__init_have_children_rb_tree(struct callchain_node *self) | |
1240 | { | |
1241 | struct rb_node *nd = rb_first(&self->rb_root); | |
1242 | ||
1243 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | |
1244 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | |
1245 | struct callchain_list *chain; | |
1246 | int first = true; | |
1247 | ||
1248 | list_for_each_entry(chain, &child->val, list) { | |
1249 | if (first) { | |
1250 | first = false; | |
1251 | chain->ms.has_children = chain->list.next != &child->val || | |
1252 | rb_first(&child->rb_root) != NULL; | |
1253 | } else | |
1254 | chain->ms.has_children = chain->list.next == &child->val && | |
1255 | rb_first(&child->rb_root) != NULL; | |
1256 | } | |
1257 | ||
1258 | callchain_node__init_have_children_rb_tree(child); | |
1259 | } | |
1260 | } | |
1261 | ||
1262 | static void callchain_node__init_have_children(struct callchain_node *self) | |
1263 | { | |
1264 | struct callchain_list *chain; | |
1265 | ||
1266 | list_for_each_entry(chain, &self->val, list) | |
1267 | chain->ms.has_children = rb_first(&self->rb_root) != NULL; | |
1268 | ||
1269 | callchain_node__init_have_children_rb_tree(self); | |
1270 | } | |
1271 | ||
1272 | static void callchain__init_have_children(struct rb_root *self) | |
1273 | { | |
1274 | struct rb_node *nd; | |
1275 | ||
1276 | for (nd = rb_first(self); nd; nd = rb_next(nd)) { | |
1277 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | |
1278 | callchain_node__init_have_children(node); | |
1279 | } | |
1280 | } | |
1281 | ||
1282 | static void hist_entry__init_have_children(struct hist_entry *self) | |
1283 | { | |
1284 | if (!self->init_have_children) { | |
1285 | callchain__init_have_children(&self->sorted_chain); | |
1286 | self->init_have_children = true; | |
1287 | } | |
1288 | } | |
1289 | ||
1290 | static struct rb_node *hists__filter_entries(struct rb_node *nd) | |
1291 | { | |
1292 | while (nd != NULL) { | |
1293 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | |
1294 | if (!h->filtered) | |
1295 | return nd; | |
1296 | ||
1297 | nd = rb_next(nd); | |
1298 | } | |
1299 | ||
1300 | return NULL; | |
1301 | } | |
1302 | ||
1303 | static struct rb_node *hists__filter_prev_entries(struct rb_node *nd) | |
1304 | { | |
1305 | while (nd != NULL) { | |
1306 | struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node); | |
1307 | if (!h->filtered) | |
1308 | return nd; | |
1309 | ||
1310 | nd = rb_prev(nd); | |
1311 | } | |
1312 | ||
1313 | return NULL; | |
1314 | } | |
1315 | ||
1316 | static void ui_browser__hists_seek(struct ui_browser *self, | |
1317 | off_t offset, int whence) | |
1318 | { | |
1319 | struct hist_entry *h; | |
1320 | struct rb_node *nd; | |
1321 | bool first = true; | |
1322 | ||
1323 | switch (whence) { | |
1324 | case SEEK_SET: | |
1325 | nd = hists__filter_entries(rb_first(self->entries)); | |
1326 | break; | |
1327 | case SEEK_CUR: | |
1328 | nd = self->first_visible_entry; | |
1329 | goto do_offset; | |
1330 | case SEEK_END: | |
1331 | nd = hists__filter_prev_entries(rb_last(self->entries)); | |
1332 | first = false; | |
1333 | break; | |
1334 | default: | |
1335 | return; | |
1336 | } | |
1337 | ||
1338 | /* | |
1339 | * Moves not relative to the first visible entry invalidates its | |
1340 | * row_offset: | |
1341 | */ | |
1342 | h = rb_entry(self->first_visible_entry, struct hist_entry, rb_node); | |
1343 | h->row_offset = 0; | |
1344 | ||
1345 | /* | |
1346 | * Here we have to check if nd is expanded (+), if it is we can't go | |
1347 | * the next top level hist_entry, instead we must compute an offset of | |
1348 | * what _not_ to show and not change the first visible entry. | |
1349 | * | |
1350 | * This offset increments when we are going from top to bottom and | |
1351 | * decreases when we're going from bottom to top. | |
1352 | * | |
1353 | * As we don't have backpointers to the top level in the callchains | |
1354 | * structure, we need to always print the whole hist_entry callchain, | |
1355 | * skipping the first ones that are before the first visible entry | |
1356 | * and stop when we printed enough lines to fill the screen. | |
1357 | */ | |
1358 | do_offset: | |
1359 | if (offset > 0) { | |
1360 | do { | |
1361 | h = rb_entry(nd, struct hist_entry, rb_node); | |
1362 | if (h->ms.unfolded) { | |
1363 | u16 remaining = h->nr_rows - h->row_offset; | |
1364 | if (offset > remaining) { | |
1365 | offset -= remaining; | |
1366 | h->row_offset = 0; | |
1367 | } else { | |
1368 | h->row_offset += offset; | |
1369 | offset = 0; | |
1370 | self->first_visible_entry = nd; | |
1371 | break; | |
1372 | } | |
1373 | } | |
1374 | nd = hists__filter_entries(rb_next(nd)); | |
1375 | if (nd == NULL) | |
1376 | break; | |
1377 | --offset; | |
1378 | self->first_visible_entry = nd; | |
1379 | } while (offset != 0); | |
1380 | } else if (offset < 0) { | |
1381 | while (1) { | |
1382 | h = rb_entry(nd, struct hist_entry, rb_node); | |
1383 | if (h->ms.unfolded) { | |
1384 | if (first) { | |
1385 | if (-offset > h->row_offset) { | |
1386 | offset += h->row_offset; | |
1387 | h->row_offset = 0; | |
1388 | } else { | |
1389 | h->row_offset += offset; | |
1390 | offset = 0; | |
1391 | self->first_visible_entry = nd; | |
1392 | break; | |
1393 | } | |
1394 | } else { | |
1395 | if (-offset > h->nr_rows) { | |
1396 | offset += h->nr_rows; | |
1397 | h->row_offset = 0; | |
1398 | } else { | |
1399 | h->row_offset = h->nr_rows + offset; | |
1400 | offset = 0; | |
1401 | self->first_visible_entry = nd; | |
1402 | break; | |
1403 | } | |
1404 | } | |
1405 | } | |
1406 | ||
1407 | nd = hists__filter_prev_entries(rb_prev(nd)); | |
1408 | if (nd == NULL) | |
1409 | break; | |
1410 | ++offset; | |
1411 | self->first_visible_entry = nd; | |
1412 | if (offset == 0) { | |
1413 | /* | |
1414 | * Last unfiltered hist_entry, check if it is | |
1415 | * unfolded, if it is then we should have | |
1416 | * row_offset at its last entry. | |
1417 | */ | |
1418 | h = rb_entry(nd, struct hist_entry, rb_node); | |
1419 | if (h->ms.unfolded) | |
1420 | h->row_offset = h->nr_rows; | |
1421 | break; | |
1422 | } | |
1423 | first = false; | |
1424 | } | |
1425 | } else { | |
1426 | self->first_visible_entry = nd; | |
1427 | h = rb_entry(nd, struct hist_entry, rb_node); | |
1428 | h->row_offset = 0; | |
1429 | } | |
1430 | } | |
1431 | ||
1432 | static int callchain_node__count_rows_rb_tree(struct callchain_node *self) | |
1433 | { | |
1434 | int n = 0; | |
1435 | struct rb_node *nd; | |
1436 | ||
1437 | for (nd = rb_first(&self->rb_root); nd; nd = rb_next(nd)) { | |
1438 | struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node); | |
1439 | struct callchain_list *chain; | |
1440 | char folded_sign = ' '; /* No children */ | |
1441 | ||
1442 | list_for_each_entry(chain, &child->val, list) { | |
1443 | ++n; | |
1444 | /* We need this because we may not have children */ | |
1445 | folded_sign = callchain_list__folded(chain); | |
1446 | if (folded_sign == '+') | |
1447 | break; | |
1448 | } | |
1449 | ||
1450 | if (folded_sign == '-') /* Have children and they're unfolded */ | |
1451 | n += callchain_node__count_rows_rb_tree(child); | |
1452 | } | |
1453 | ||
1454 | return n; | |
1455 | } | |
1456 | ||
1457 | static int callchain_node__count_rows(struct callchain_node *node) | |
1458 | { | |
1459 | struct callchain_list *chain; | |
1460 | bool unfolded = false; | |
1461 | int n = 0; | |
1462 | ||
1463 | list_for_each_entry(chain, &node->val, list) { | |
1464 | ++n; | |
1465 | unfolded = chain->ms.unfolded; | |
1466 | } | |
1467 | ||
1468 | if (unfolded) | |
1469 | n += callchain_node__count_rows_rb_tree(node); | |
1470 | ||
1471 | return n; | |
1472 | } | |
1473 | ||
1474 | static int callchain__count_rows(struct rb_root *chain) | |
1475 | { | |
1476 | struct rb_node *nd; | |
1477 | int n = 0; | |
1478 | ||
1479 | for (nd = rb_first(chain); nd; nd = rb_next(nd)) { | |
1480 | struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node); | |
1481 | n += callchain_node__count_rows(node); | |
1482 | } | |
1483 | ||
1484 | return n; | |
1485 | } | |
1486 | ||
1487 | static bool hist_browser__toggle_fold(struct hist_browser *self) | |
1488 | { | |
1489 | if (map_symbol__toggle_fold(self->selection)) { | |
1490 | struct hist_entry *he = self->he_selection; | |
1491 | ||
1492 | hist_entry__init_have_children(he); | |
1493 | self->hists->nr_entries -= he->nr_rows; | |
1494 | ||
1495 | if (he->ms.unfolded) | |
1496 | he->nr_rows = callchain__count_rows(&he->sorted_chain); | |
1497 | else | |
1498 | he->nr_rows = 0; | |
1499 | self->hists->nr_entries += he->nr_rows; | |
1500 | self->b.nr_entries = self->hists->nr_entries; | |
1501 | ||
1502 | return true; | |
1503 | } | |
1504 | ||
1505 | /* If it doesn't have children, no toggling performed */ | |
1506 | return false; | |
1507 | } | |
1508 | ||
1509 | static int hist_browser__run(struct hist_browser *self, const char *title, | |
1510 | struct newtExitStruct *es) | |
1511 | { | |
1512 | char str[256], unit; | |
1513 | unsigned long nr_events = self->hists->stats.nr_events[PERF_RECORD_SAMPLE]; | |
1514 | ||
1515 | self->b.entries = &self->hists->entries; | |
1516 | self->b.nr_entries = self->hists->nr_entries; | |
1517 | ||
1518 | hist_browser__refresh_dimensions(self); | |
1519 | ||
1520 | nr_events = convert_unit(nr_events, &unit); | |
1521 | snprintf(str, sizeof(str), "Events: %lu%c ", | |
1522 | nr_events, unit); | |
1523 | newtDrawRootText(0, 0, str); | |
1524 | ||
1525 | if (ui_browser__show(&self->b, title) < 0) | |
1526 | return -1; | |
1527 | ||
1528 | newtFormAddHotKey(self->b.form, 'A'); | |
1529 | newtFormAddHotKey(self->b.form, 'a'); | |
1530 | newtFormAddHotKey(self->b.form, '?'); | |
1531 | newtFormAddHotKey(self->b.form, 'h'); | |
1532 | newtFormAddHotKey(self->b.form, 'H'); | |
1533 | newtFormAddHotKey(self->b.form, 'd'); | |
1534 | ||
1535 | newtFormAddHotKey(self->b.form, NEWT_KEY_LEFT); | |
1536 | newtFormAddHotKey(self->b.form, NEWT_KEY_RIGHT); | |
1537 | newtFormAddHotKey(self->b.form, NEWT_KEY_ENTER); | |
1538 | ||
1539 | while (1) { | |
1540 | ui_browser__run(&self->b, es); | |
1541 | ||
1542 | if (es->reason != NEWT_EXIT_HOTKEY) | |
1543 | break; | |
1544 | switch (es->u.key) { | |
1545 | case 'd': { /* Debug */ | |
1546 | static int seq; | |
1547 | struct hist_entry *h = rb_entry(self->b.first_visible_entry, | |
1548 | struct hist_entry, rb_node); | |
1549 | ui_helpline__pop(); | |
1550 | ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d", | |
1551 | seq++, self->b.nr_entries, | |
1552 | self->hists->nr_entries, | |
1553 | self->b.height, | |
1554 | self->b.index, | |
1555 | self->b.first_visible_entry_idx, | |
1556 | h->row_offset, h->nr_rows); | |
1557 | } | |
1558 | continue; | |
1559 | case NEWT_KEY_ENTER: | |
1560 | if (hist_browser__toggle_fold(self)) | |
1561 | break; | |
1562 | /* fall thru */ | |
1563 | default: | |
1564 | return 0; | |
1565 | } | |
1566 | } | |
1567 | return 0; | |
1568 | } |