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