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