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