Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mesa
Path: blob/21.2-virgl/src/freedreno/perfcntrs/fdperf.c
4565 views
1
/*
2
* Copyright (C) 2016 Rob Clark <[email protected]>
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
52
struct 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
77
static 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
92
static void config_save(void);
93
static void config_restore(void);
94
static void restore_counter_groups(void);
95
96
/*
97
* helpers
98
*/
99
100
static uint32_t
101
gettime_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
108
static uint32_t
109
delta(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
118
static void
119
find_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
168
static void
169
flush_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
191
static void
192
select_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
275
static void
276
resample_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: */
290
static void
291
resample(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
317
static int w, h;
318
static int ctr_width;
319
static int max_rows, current_cntr = 1;
320
321
static void
322
redraw_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
340
static void
341
redraw_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
352
static void
353
redraw_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
368
static void
369
redraw_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
403
static void
404
redraw_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
413
static void
414
redraw_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
439
static void
440
redraw(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
493
static struct counter_group *
494
current_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
530
static void
531
counter_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
613
out:
614
wborder(dialog, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ');
615
delwin(dialog);
616
}
617
618
static void
619
scroll_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
633
static void
634
main_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()??*/
695
out:
696
delwin(mainwin);
697
endwin();
698
refresh();
699
}
700
701
static void
702
restore_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
718
static void
719
setup_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
757
static config_t cfg;
758
static config_setting_t *setting;
759
760
static void
761
config_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
785
static void
786
config_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
839
int
840
main(int argc, char **argv)
841
{
842
find_device();
843
844
const struct fd_perfcntr_group *groups;
845
groups = fd_perfcntrs((dev.chipid >> 24) * 100, &dev.ngroups);
846
if (!groups) {
847
errx(1, "no perfcntr support");
848
}
849
850
dev.groups = calloc(dev.ngroups, sizeof(struct counter_group));
851
852
setlocale(LC_NUMERIC, "en_US.UTF-8");
853
854
setup_counter_groups(groups);
855
restore_counter_groups();
856
config_restore();
857
flush_ring();
858
859
main_ui();
860
861
return 0;
862
}
863
864