• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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