1 /*
2 * Copyright (C) 2016 Rob Clark <robclark@freedesktop.org>
3 * All Rights Reserved.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a
6 * copy of this software and associated documentation files (the "Software"),
7 * to deal in the Software without restriction, including without limitation
8 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 * and/or sell copies of the Software, and to permit persons to whom the
10 * Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 * OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25 #include <assert.h>
26 #include <curses.h>
27 #include <err.h>
28 #include <inttypes.h>
29 #include <libconfig.h>
30 #include <locale.h>
31 #include <stdint.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
37 #include <xf86drm.h>
38
39 #include "drm/freedreno_drmif.h"
40 #include "drm/freedreno_ringbuffer.h"
41
42 #include "util/os_file.h"
43
44 #include "freedreno_dt.h"
45 #include "freedreno_perfcntr.h"
46
47 #define MAX_CNTR_PER_GROUP 24
48 #define REFRESH_MS 500
49
50 static struct {
51 int refresh_ms;
52 bool dump;
53 } options = {
54 .refresh_ms = REFRESH_MS,
55 .dump = false,
56 };
57
58 /* NOTE first counter group should always be CP, since we unconditionally
59 * use CP counter to measure the gpu freq.
60 */
61
62 struct counter_group {
63 const struct fd_perfcntr_group *group;
64
65 struct {
66 const struct fd_perfcntr_counter *counter;
67 uint16_t select_val;
68 volatile uint32_t *val_hi;
69 volatile uint32_t *val_lo;
70 } counter[MAX_CNTR_PER_GROUP];
71
72 /* last sample time: */
73 uint32_t stime[MAX_CNTR_PER_GROUP];
74 /* for now just care about the low 32b value.. at least then we don't
75 * have to really care that we can't sample both hi and lo regs at the
76 * same time:
77 */
78 uint32_t last[MAX_CNTR_PER_GROUP];
79 /* current value, ie. by how many did the counter increase in last
80 * sampling period divided by the sampling period:
81 */
82 float current[MAX_CNTR_PER_GROUP];
83 /* name of currently selected counters (for UI): */
84 const char *label[MAX_CNTR_PER_GROUP];
85 };
86
87 static struct {
88 void *io;
89 uint32_t min_freq;
90 uint32_t max_freq;
91 /* per-generation table of counters: */
92 unsigned ngroups;
93 struct counter_group *groups;
94 /* drm device (for writing select regs via ring): */
95 struct fd_device *dev;
96 struct fd_pipe *pipe;
97 const struct fd_dev_id *dev_id;
98 struct fd_submit *submit;
99 struct fd_ringbuffer *ring;
100 } dev;
101
102 static void config_save(void);
103 static void config_restore(void);
104 static void restore_counter_groups(void);
105
106 /*
107 * helpers
108 */
109
110 static uint32_t
gettime_us(void)111 gettime_us(void)
112 {
113 struct timespec ts;
114 clock_gettime(CLOCK_MONOTONIC, &ts);
115 return (ts.tv_sec * 1000000) + (ts.tv_nsec / 1000);
116 }
117
118 static void
sleep_us(uint32_t us)119 sleep_us(uint32_t us)
120 {
121 const struct timespec ts = {
122 .tv_sec = us / 1000000,
123 .tv_nsec = (us % 1000000) * 1000,
124 };
125 clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL);
126 }
127
128 static uint32_t
delta(uint32_t a,uint32_t b)129 delta(uint32_t a, uint32_t b)
130 {
131 /* deal with rollover: */
132 if (a > b)
133 return 0xffffffff - a + b;
134 else
135 return b - a;
136 }
137
138 static void
find_device(void)139 find_device(void)
140 {
141 int ret;
142
143 dev.dev = fd_device_open();
144 if (!dev.dev)
145 err(1, "could not open drm device");
146
147 dev.pipe = fd_pipe_new(dev.dev, FD_PIPE_3D);
148
149 dev.dev_id = fd_pipe_dev_id(dev.pipe);
150 if (!fd_dev_info_raw(dev.dev_id))
151 err(1, "unknown device");
152
153 printf("device: %s\n", fd_dev_name(dev.dev_id));
154
155 /* try MAX_FREQ first as that will work regardless of old dt
156 * dt bindings vs upstream bindings:
157 */
158 uint64_t val;
159 ret = fd_pipe_get_param(dev.pipe, FD_MAX_FREQ, &val);
160 if (ret) {
161 printf("falling back to parsing DT bindings for freq\n");
162 if (!fd_dt_find_freqs(&dev.min_freq, &dev.max_freq))
163 err(1, "could not find GPU freqs");
164 } else {
165 dev.min_freq = 0;
166 dev.max_freq = val;
167 }
168
169 printf("min_freq=%u, max_freq=%u\n", dev.min_freq, dev.max_freq);
170
171 dev.io = fd_dt_find_io();
172 if (!dev.io) {
173 err(1, "could not map device");
174 }
175
176 fd_pipe_set_param(dev.pipe, FD_SYSPROF, 1);
177 }
178
179 /*
180 * perf-monitor
181 */
182
183 static void
flush_ring(void)184 flush_ring(void)
185 {
186 if (!dev.submit)
187 return;
188
189 struct fd_fence *fence = fd_submit_flush(dev.submit, -1, false);
190
191 if (!fence)
192 errx(1, "submit failed");
193
194 fd_fence_flush(fence);
195 fd_fence_del(fence);
196 fd_ringbuffer_del(dev.ring);
197 fd_submit_del(dev.submit);
198
199 dev.ring = NULL;
200 dev.submit = NULL;
201 }
202
203 static void
select_counter(struct counter_group * group,int ctr,int n)204 select_counter(struct counter_group *group, int ctr, int n)
205 {
206 assert(n < group->group->num_countables);
207 assert(ctr < group->group->num_counters);
208
209 group->label[ctr] = group->group->countables[n].name;
210 group->counter[ctr].select_val = n;
211
212 if (!dev.submit) {
213 dev.submit = fd_submit_new(dev.pipe);
214 dev.ring = fd_submit_new_ringbuffer(
215 dev.submit, 0x1000, FD_RINGBUFFER_PRIMARY | FD_RINGBUFFER_GROWABLE);
216 }
217
218 /* bashing select register directly while gpu is active will end
219 * in tears.. so we need to write it via the ring:
220 *
221 * TODO it would help startup time, if gpu is loaded, to batch
222 * all the initial writes and do a single flush.. although that
223 * makes things more complicated for capturing inital sample value
224 */
225 struct fd_ringbuffer *ring = dev.ring;
226 switch (fd_dev_gen(dev.dev_id)) {
227 case 2:
228 case 3:
229 case 4:
230 OUT_PKT3(ring, CP_WAIT_FOR_IDLE, 1);
231 OUT_RING(ring, 0x00000000);
232
233 if (group->group->counters[ctr].enable) {
234 OUT_PKT0(ring, group->group->counters[ctr].enable, 1);
235 OUT_RING(ring, 0);
236 }
237
238 if (group->group->counters[ctr].clear) {
239 OUT_PKT0(ring, group->group->counters[ctr].clear, 1);
240 OUT_RING(ring, 1);
241
242 OUT_PKT0(ring, group->group->counters[ctr].clear, 1);
243 OUT_RING(ring, 0);
244 }
245
246 OUT_PKT0(ring, group->group->counters[ctr].select_reg, 1);
247 OUT_RING(ring, n);
248
249 if (group->group->counters[ctr].enable) {
250 OUT_PKT0(ring, group->group->counters[ctr].enable, 1);
251 OUT_RING(ring, 1);
252 }
253
254 break;
255 case 5:
256 case 6:
257 OUT_PKT7(ring, CP_WAIT_FOR_IDLE, 0);
258
259 if (group->group->counters[ctr].enable) {
260 OUT_PKT4(ring, group->group->counters[ctr].enable, 1);
261 OUT_RING(ring, 0);
262 }
263
264 if (group->group->counters[ctr].clear) {
265 OUT_PKT4(ring, group->group->counters[ctr].clear, 1);
266 OUT_RING(ring, 1);
267
268 OUT_PKT4(ring, group->group->counters[ctr].clear, 1);
269 OUT_RING(ring, 0);
270 }
271
272 OUT_PKT4(ring, group->group->counters[ctr].select_reg, 1);
273 OUT_RING(ring, n);
274
275 if (group->group->counters[ctr].enable) {
276 OUT_PKT4(ring, group->group->counters[ctr].enable, 1);
277 OUT_RING(ring, 1);
278 }
279
280 break;
281 }
282
283 group->last[ctr] = *group->counter[ctr].val_lo;
284 group->stime[ctr] = gettime_us();
285 }
286
287 static void
resample_counter(struct counter_group * group,int ctr)288 resample_counter(struct counter_group *group, int ctr)
289 {
290 uint32_t val = *group->counter[ctr].val_lo;
291 uint32_t t = gettime_us();
292 uint32_t dt = delta(group->stime[ctr], t);
293 uint32_t dval = delta(group->last[ctr], val);
294 group->current[ctr] = (float)dval * 1000000.0 / (float)dt;
295 group->last[ctr] = val;
296 group->stime[ctr] = t;
297 }
298
299 /* sample all the counters: */
300 static void
resample(void)301 resample(void)
302 {
303 static uint64_t last_time;
304 uint64_t current_time = gettime_us();
305
306 if ((current_time - last_time) < (options.refresh_ms * 1000 / 2))
307 return;
308
309 last_time = current_time;
310
311 for (unsigned i = 0; i < dev.ngroups; i++) {
312 struct counter_group *group = &dev.groups[i];
313 for (unsigned j = 0; j < group->group->num_counters; j++) {
314 resample_counter(group, j);
315 }
316 }
317 }
318
319 /*
320 * The UI
321 */
322
323 #define COLOR_GROUP_HEADER 1
324 #define COLOR_FOOTER 2
325 #define COLOR_INVERSE 3
326
327 static int w, h;
328 static int ctr_width;
329 static int max_rows, current_cntr = 1;
330
331 static void
redraw_footer(WINDOW * win)332 redraw_footer(WINDOW *win)
333 {
334 char *footer;
335 int n;
336
337 n = asprintf(&footer, " fdperf: %s (%.2fMHz..%.2fMHz)",
338 fd_dev_name(dev.dev_id), ((float)dev.min_freq) / 1000000.0,
339 ((float)dev.max_freq) / 1000000.0);
340
341 wmove(win, h - 1, 0);
342 wattron(win, COLOR_PAIR(COLOR_FOOTER));
343 waddstr(win, footer);
344 whline(win, ' ', w - n);
345 wattroff(win, COLOR_PAIR(COLOR_FOOTER));
346
347 free(footer);
348 }
349
350 static void
redraw_group_header(WINDOW * win,int row,const char * name)351 redraw_group_header(WINDOW *win, int row, const char *name)
352 {
353 wmove(win, row, 0);
354 wattron(win, A_BOLD);
355 wattron(win, COLOR_PAIR(COLOR_GROUP_HEADER));
356 waddstr(win, name);
357 whline(win, ' ', w - strlen(name));
358 wattroff(win, COLOR_PAIR(COLOR_GROUP_HEADER));
359 wattroff(win, A_BOLD);
360 }
361
362 static void
redraw_counter_label(WINDOW * win,int row,const char * name,bool selected)363 redraw_counter_label(WINDOW *win, int row, const char *name, bool selected)
364 {
365 int n = strlen(name);
366 assert(n <= ctr_width);
367 wmove(win, row, 0);
368 whline(win, ' ', ctr_width - n);
369 wmove(win, row, ctr_width - n);
370 if (selected)
371 wattron(win, COLOR_PAIR(COLOR_INVERSE));
372 waddstr(win, name);
373 if (selected)
374 wattroff(win, COLOR_PAIR(COLOR_INVERSE));
375 waddstr(win, ": ");
376 }
377
378 static void
redraw_counter_value_cycles(WINDOW * win,float val)379 redraw_counter_value_cycles(WINDOW *win, float val)
380 {
381 char *str;
382 int x = getcurx(win);
383 int valwidth = w - x;
384 int barwidth, n;
385
386 /* convert to fraction of max freq: */
387 val = val / (float)dev.max_freq;
388
389 /* figure out percentage-bar width: */
390 barwidth = (int)(val * valwidth);
391
392 /* sometimes things go over 100%.. idk why, could be
393 * things running faster than base clock, or counter
394 * summing up cycles in multiple cores?
395 */
396 barwidth = MIN2(barwidth, valwidth - 1);
397
398 n = asprintf(&str, "%.2f%%", 100.0 * val);
399 wattron(win, COLOR_PAIR(COLOR_INVERSE));
400 waddnstr(win, str, barwidth);
401 if (barwidth > n) {
402 whline(win, ' ', barwidth - n);
403 wmove(win, getcury(win), x + barwidth);
404 }
405 wattroff(win, COLOR_PAIR(COLOR_INVERSE));
406 if (barwidth < n)
407 waddstr(win, str + barwidth);
408 whline(win, ' ', w - getcurx(win));
409
410 free(str);
411 }
412
413 static void
redraw_counter_value_raw(WINDOW * win,float val)414 redraw_counter_value_raw(WINDOW *win, float val)
415 {
416 char *str;
417 (void)asprintf(&str, "%'.2f", val);
418 waddstr(win, str);
419 whline(win, ' ', w - getcurx(win));
420 free(str);
421 }
422
423 static void
redraw_counter(WINDOW * win,int row,struct counter_group * group,int ctr,bool selected)424 redraw_counter(WINDOW *win, int row, struct counter_group *group, int ctr,
425 bool selected)
426 {
427 redraw_counter_label(win, row, group->label[ctr], selected);
428
429 /* quick hack, if the label has "CYCLE" in the name, it is
430 * probably a cycle counter ;-)
431 * Perhaps add more info in rnndb schema to know how to
432 * treat individual counters (ie. which are cycles, and
433 * for those we want to present as a percentage do we
434 * need to scale the result.. ie. is it running at some
435 * multiple or divisor of core clk, etc)
436 *
437 * TODO it would be much more clever to get this from xml
438 * Also.. in some cases I think we want to know how many
439 * units the counter is counting for, ie. if a320 has 2x
440 * shader as a306 we might need to scale the result..
441 */
442 if (strstr(group->label[ctr], "CYCLE") ||
443 strstr(group->label[ctr], "BUSY") || strstr(group->label[ctr], "IDLE"))
444 redraw_counter_value_cycles(win, group->current[ctr]);
445 else
446 redraw_counter_value_raw(win, group->current[ctr]);
447 }
448
449 static void
redraw(WINDOW * win)450 redraw(WINDOW *win)
451 {
452 static int scroll = 0;
453 int max, row = 0;
454
455 w = getmaxx(win);
456 h = getmaxy(win);
457
458 max = h - 3;
459
460 if ((current_cntr - scroll) > (max - 1)) {
461 scroll = current_cntr - (max - 1);
462 } else if ((current_cntr - 1) < scroll) {
463 scroll = current_cntr - 1;
464 }
465
466 for (unsigned i = 0; i < dev.ngroups; i++) {
467 struct counter_group *group = &dev.groups[i];
468 unsigned j = 0;
469
470 /* NOTE skip CP the first CP counter */
471 if (i == 0)
472 j++;
473
474 if (j < group->group->num_counters) {
475 if ((scroll <= row) && ((row - scroll) < max))
476 redraw_group_header(win, row - scroll, group->group->name);
477 row++;
478 }
479
480 for (; j < group->group->num_counters; j++) {
481 if ((scroll <= row) && ((row - scroll) < max))
482 redraw_counter(win, row - scroll, group, j, row == current_cntr);
483 row++;
484 }
485 }
486
487 /* convert back to physical (unscrolled) offset: */
488 row = max;
489
490 redraw_group_header(win, row, "Status");
491 row++;
492
493 /* Draw GPU freq row: */
494 redraw_counter_label(win, row, "Freq (MHz)", false);
495 redraw_counter_value_raw(win, dev.groups[0].current[0] / 1000000.0);
496 row++;
497
498 redraw_footer(win);
499
500 refresh();
501 }
502
503 static struct counter_group *
current_counter(int * ctr)504 current_counter(int *ctr)
505 {
506 int n = 0;
507
508 for (unsigned i = 0; i < dev.ngroups; i++) {
509 struct counter_group *group = &dev.groups[i];
510 unsigned j = 0;
511
512 /* NOTE skip the first CP counter (CP_ALWAYS_COUNT) */
513 if (i == 0)
514 j++;
515
516 /* account for group header: */
517 if (j < group->group->num_counters) {
518 /* cannot select group header.. return null to indicate this
519 * main_ui():
520 */
521 if (n == current_cntr)
522 return NULL;
523 n++;
524 }
525
526 for (; j < group->group->num_counters; j++) {
527 if (n == current_cntr) {
528 if (ctr)
529 *ctr = j;
530 return group;
531 }
532 n++;
533 }
534 }
535
536 assert(0);
537 return NULL;
538 }
539
540 static void
counter_dialog(void)541 counter_dialog(void)
542 {
543 WINDOW *dialog;
544 struct counter_group *group;
545 int cnt = 0, current = 0, scroll;
546
547 /* figure out dialog size: */
548 int dh = h / 2;
549 int dw = ctr_width + 2;
550
551 group = current_counter(&cnt);
552
553 /* find currently selected idx (note there can be discontinuities
554 * so the selected value does not map 1:1 to current idx)
555 */
556 uint32_t selected = group->counter[cnt].select_val;
557 for (int i = 0; i < group->group->num_countables; i++) {
558 if (group->group->countables[i].selector == selected) {
559 current = i;
560 break;
561 }
562 }
563
564 /* scrolling offset, if dialog is too small for all the choices: */
565 scroll = 0;
566
567 dialog = newwin(dh, dw, (h - dh) / 2, (w - dw) / 2);
568 box(dialog, 0, 0);
569 wrefresh(dialog);
570 keypad(dialog, true);
571
572 while (true) {
573 int max = MIN2(dh - 2, group->group->num_countables);
574 int selector = -1;
575
576 if ((current - scroll) >= (dh - 3)) {
577 scroll = current - (dh - 3);
578 } else if (current < scroll) {
579 scroll = current;
580 }
581
582 for (int i = 0; i < max; i++) {
583 int n = scroll + i;
584 wmove(dialog, i + 1, 1);
585 if (n == current) {
586 assert(n < group->group->num_countables);
587 selector = group->group->countables[n].selector;
588 wattron(dialog, COLOR_PAIR(COLOR_INVERSE));
589 }
590 if (n < group->group->num_countables)
591 waddstr(dialog, group->group->countables[n].name);
592 whline(dialog, ' ', dw - getcurx(dialog) - 1);
593 if (n == current)
594 wattroff(dialog, COLOR_PAIR(COLOR_INVERSE));
595 }
596
597 assert(selector >= 0);
598
599 switch (wgetch(dialog)) {
600 case KEY_UP:
601 current = MAX2(0, current - 1);
602 break;
603 case KEY_DOWN:
604 current = MIN2(group->group->num_countables - 1, current + 1);
605 break;
606 case KEY_LEFT:
607 case KEY_ENTER:
608 /* select new sampler */
609 select_counter(group, cnt, selector);
610 flush_ring();
611 config_save();
612 goto out;
613 case 'q':
614 goto out;
615 default:
616 /* ignore */
617 break;
618 }
619
620 resample();
621 }
622
623 out:
624 wborder(dialog, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
625 delwin(dialog);
626 }
627
628 static void
scroll_cntr(int amount)629 scroll_cntr(int amount)
630 {
631 if (amount < 0) {
632 current_cntr = MAX2(1, current_cntr + amount);
633 if (current_counter(NULL) == NULL) {
634 current_cntr = MAX2(1, current_cntr - 1);
635 }
636 } else {
637 current_cntr = MIN2(max_rows - 1, current_cntr + amount);
638 if (current_counter(NULL) == NULL)
639 current_cntr = MIN2(max_rows - 1, current_cntr + 1);
640 }
641 }
642
643 static void
main_ui(void)644 main_ui(void)
645 {
646 WINDOW *mainwin;
647 uint32_t last_time = gettime_us();
648
649 /* curses setup: */
650 mainwin = initscr();
651 if (!mainwin)
652 goto out;
653
654 cbreak();
655 wtimeout(mainwin, options.refresh_ms);
656 noecho();
657 keypad(mainwin, true);
658 curs_set(0);
659 start_color();
660 init_pair(COLOR_GROUP_HEADER, COLOR_WHITE, COLOR_GREEN);
661 init_pair(COLOR_FOOTER, COLOR_WHITE, COLOR_BLUE);
662 init_pair(COLOR_INVERSE, COLOR_BLACK, COLOR_WHITE);
663
664 while (true) {
665 switch (wgetch(mainwin)) {
666 case KEY_UP:
667 scroll_cntr(-1);
668 break;
669 case KEY_DOWN:
670 scroll_cntr(+1);
671 break;
672 case KEY_NPAGE: /* page-down */
673 /* TODO figure out # of rows visible? */
674 scroll_cntr(+15);
675 break;
676 case KEY_PPAGE: /* page-up */
677 /* TODO figure out # of rows visible? */
678 scroll_cntr(-15);
679 break;
680 case KEY_RIGHT:
681 counter_dialog();
682 break;
683 case 'q':
684 goto out;
685 break;
686 default:
687 /* ignore */
688 break;
689 }
690 resample();
691 redraw(mainwin);
692
693 /* restore the counters every 0.5s in case the GPU has suspended,
694 * in which case the current selected countables will have reset:
695 */
696 uint32_t t = gettime_us();
697 if (delta(last_time, t) > 500000) {
698 restore_counter_groups();
699 flush_ring();
700 last_time = t;
701 }
702 }
703
704 /* restore settings.. maybe we need an atexit()??*/
705 out:
706 delwin(mainwin);
707 endwin();
708 refresh();
709 }
710
711 static void
dump_counters(void)712 dump_counters(void)
713 {
714 resample();
715 sleep_us(options.refresh_ms * 1000);
716 resample();
717
718 for (unsigned i = 0; i < dev.ngroups; i++) {
719 const struct counter_group *group = &dev.groups[i];
720 for (unsigned j = 0; j < group->group->num_counters; j++) {
721 const char *label = group->label[j];
722 float val = group->current[j];
723
724 /* we did not config the first CP counter */
725 if (i == 0 && j == 0)
726 label = group->group->countables[0].name;
727
728 int n = printf("%s: ", label) - 2;
729 while (n++ < ctr_width)
730 fputc(' ', stdout);
731
732 if (strstr(label, "CYCLE") ||
733 strstr(label, "BUSY") ||
734 strstr(label, "IDLE")) {
735 val = val / dev.max_freq * 100.0f;
736 printf("%.2f%%\n", val);
737 } else {
738 printf("%'.2f\n", val);
739 }
740 }
741 }
742 }
743
744 static void
restore_counter_groups(void)745 restore_counter_groups(void)
746 {
747 for (unsigned i = 0; i < dev.ngroups; i++) {
748 struct counter_group *group = &dev.groups[i];
749 unsigned j = 0;
750
751 /* NOTE skip CP the first CP counter */
752 if (i == 0)
753 j++;
754
755 for (; j < group->group->num_counters; j++) {
756 select_counter(group, j, group->counter[j].select_val);
757 }
758 }
759 }
760
761 static void
setup_counter_groups(const struct fd_perfcntr_group * groups)762 setup_counter_groups(const struct fd_perfcntr_group *groups)
763 {
764 for (unsigned i = 0; i < dev.ngroups; i++) {
765 struct counter_group *group = &dev.groups[i];
766
767 group->group = &groups[i];
768
769 max_rows += group->group->num_counters + 1;
770
771 /* the first CP counter is hidden: */
772 if (i == 0) {
773 max_rows--;
774 if (group->group->num_counters <= 1)
775 max_rows--;
776 }
777
778 for (unsigned j = 0; j < group->group->num_counters; j++) {
779 group->counter[j].counter = &group->group->counters[j];
780
781 group->counter[j].val_hi =
782 dev.io + (group->counter[j].counter->counter_reg_hi * 4);
783 group->counter[j].val_lo =
784 dev.io + (group->counter[j].counter->counter_reg_lo * 4);
785
786 group->counter[j].select_val = j;
787 }
788
789 for (unsigned j = 0; j < group->group->num_countables; j++) {
790 ctr_width =
791 MAX2(ctr_width, strlen(group->group->countables[j].name) + 1);
792 }
793 }
794 }
795
796 /*
797 * configuration / persistence
798 */
799
800 static config_t cfg;
801 static config_setting_t *setting;
802
803 static void
config_save(void)804 config_save(void)
805 {
806 for (unsigned i = 0; i < dev.ngroups; i++) {
807 struct counter_group *group = &dev.groups[i];
808 unsigned j = 0;
809
810 /* NOTE skip CP the first CP counter */
811 if (i == 0)
812 j++;
813
814 config_setting_t *sect =
815 config_setting_get_member(setting, group->group->name);
816
817 for (; j < group->group->num_counters; j++) {
818 char name[] = "counter0000";
819 sprintf(name, "counter%d", j);
820 config_setting_t *s = config_setting_lookup(sect, name);
821 config_setting_set_int(s, group->counter[j].select_val);
822 }
823 }
824
825 config_write_file(&cfg, "fdperf.cfg");
826 }
827
828 static void
config_restore(void)829 config_restore(void)
830 {
831 char *str;
832
833 config_init(&cfg);
834
835 /* Read the file. If there is an error, report it and exit. */
836 if (!config_read_file(&cfg, "fdperf.cfg")) {
837 warn("could not restore settings");
838 }
839
840 config_setting_t *root = config_root_setting(&cfg);
841
842 /* per device settings: */
843 (void)asprintf(&str, "%s", fd_dev_name(dev.dev_id));
844 setting = config_setting_get_member(root, str);
845 if (!setting)
846 setting = config_setting_add(root, str, CONFIG_TYPE_GROUP);
847 free(str);
848 if (!setting)
849 return;
850
851 for (unsigned i = 0; i < dev.ngroups; i++) {
852 struct counter_group *group = &dev.groups[i];
853 unsigned j = 0;
854
855 /* NOTE skip CP the first CP counter */
856 if (i == 0)
857 j++;
858
859 config_setting_t *sect =
860 config_setting_get_member(setting, group->group->name);
861
862 if (!sect) {
863 sect =
864 config_setting_add(setting, group->group->name, CONFIG_TYPE_GROUP);
865 }
866
867 for (; j < group->group->num_counters; j++) {
868 char name[] = "counter0000";
869 sprintf(name, "counter%d", j);
870 config_setting_t *s = config_setting_lookup(sect, name);
871 if (!s) {
872 config_setting_add(sect, name, CONFIG_TYPE_INT);
873 continue;
874 }
875 select_counter(group, j, config_setting_get_int(s));
876 }
877 }
878 }
879
880 static void
print_usage(const char * argv0)881 print_usage(const char *argv0)
882 {
883 fprintf(stderr,
884 "Usage: %s [OPTION]...\n"
885 "\n"
886 " -r <N> refresh every N milliseconds\n"
887 " -d dump counters and exit\n"
888 " -h show this message\n",
889 argv0);
890 exit(2);
891 }
892
893 static void
parse_options(int argc,char ** argv)894 parse_options(int argc, char **argv)
895 {
896 int c;
897
898 while ((c = getopt(argc, argv, "r:d")) != -1) {
899 switch (c) {
900 case 'r':
901 options.refresh_ms = atoi(optarg);
902 break;
903 case 'd':
904 options.dump = true;
905 break;
906 default:
907 print_usage(argv[0]);
908 break;
909 }
910 }
911 }
912
913 /*
914 * main
915 */
916
917 int
main(int argc,char ** argv)918 main(int argc, char **argv)
919 {
920 parse_options(argc, argv);
921
922 find_device();
923
924 const struct fd_perfcntr_group *groups;
925 groups = fd_perfcntrs(dev.dev_id, &dev.ngroups);
926 if (!groups) {
927 errx(1, "no perfcntr support");
928 }
929
930 dev.groups = calloc(dev.ngroups, sizeof(struct counter_group));
931
932 setlocale(LC_NUMERIC, "en_US.UTF-8");
933
934 setup_counter_groups(groups);
935 restore_counter_groups();
936 config_restore();
937 flush_ring();
938
939 if (options.dump)
940 dump_counters();
941 else
942 main_ui();
943
944 return 0;
945 }
946