1 /*
2 * Copyright © 2007-2019 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
24 #include <stdio.h>
25 #include <sys/types.h>
26 #include <dirent.h>
27 #include <stdint.h>
28 #include <assert.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 #include <inttypes.h>
36 #include <sys/ioctl.h>
37 #include <errno.h>
38 #include <math.h>
39 #include <locale.h>
40 #include <limits.h>
41 #include <signal.h>
42
43 #include "igt_perf.h"
44
45 struct pmu_pair {
46 uint64_t cur;
47 uint64_t prev;
48 };
49
50 struct pmu_counter {
51 bool present;
52 uint64_t config;
53 unsigned int idx;
54 struct pmu_pair val;
55 };
56
57 struct engine {
58 const char *name;
59 char *display_name;
60 char *short_name;
61
62 unsigned int class;
63 unsigned int instance;
64
65 unsigned int num_counters;
66
67 struct pmu_counter busy;
68 struct pmu_counter wait;
69 struct pmu_counter sema;
70 };
71
72 struct engines {
73 unsigned int num_engines;
74 unsigned int num_counters;
75 DIR *root;
76 int fd;
77 struct pmu_pair ts;
78
79 int rapl_fd;
80 double rapl_scale;
81 const char *rapl_unit;
82
83 int imc_fd;
84 double imc_reads_scale;
85 const char *imc_reads_unit;
86 double imc_writes_scale;
87 const char *imc_writes_unit;
88
89 struct pmu_counter freq_req;
90 struct pmu_counter freq_act;
91 struct pmu_counter irq;
92 struct pmu_counter rc6;
93 struct pmu_counter rapl;
94 struct pmu_counter imc_reads;
95 struct pmu_counter imc_writes;
96
97 struct engine engine;
98 };
99
100 static uint64_t
get_pmu_config(int dirfd,const char * name,const char * counter)101 get_pmu_config(int dirfd, const char *name, const char *counter)
102 {
103 char buf[128], *p;
104 int fd, ret;
105
106 ret = snprintf(buf, sizeof(buf), "%s-%s", name, counter);
107 if (ret < 0 || ret == sizeof(buf))
108 return -1;
109
110 fd = openat(dirfd, buf, O_RDONLY);
111 if (fd < 0)
112 return -1;
113
114 ret = read(fd, buf, sizeof(buf));
115 close(fd);
116 if (ret <= 0)
117 return -1;
118
119 p = index(buf, '0');
120 if (!p)
121 return -1;
122
123 return strtoul(p, NULL, 0);
124 }
125
126 #define engine_ptr(engines, n) (&engines->engine + (n))
127
class_display_name(unsigned int class)128 static const char *class_display_name(unsigned int class)
129 {
130 switch (class) {
131 case I915_ENGINE_CLASS_RENDER:
132 return "Render/3D";
133 case I915_ENGINE_CLASS_COPY:
134 return "Blitter";
135 case I915_ENGINE_CLASS_VIDEO:
136 return "Video";
137 case I915_ENGINE_CLASS_VIDEO_ENHANCE:
138 return "VideoEnhance";
139 default:
140 return "[unknown]";
141 }
142 }
143
class_short_name(unsigned int class)144 static const char *class_short_name(unsigned int class)
145 {
146 switch (class) {
147 case I915_ENGINE_CLASS_RENDER:
148 return "RCS";
149 case I915_ENGINE_CLASS_COPY:
150 return "BCS";
151 case I915_ENGINE_CLASS_VIDEO:
152 return "VCS";
153 case I915_ENGINE_CLASS_VIDEO_ENHANCE:
154 return "VECS";
155 default:
156 return "UNKN";
157 }
158 }
159
engine_cmp(const void * __a,const void * __b)160 static int engine_cmp(const void *__a, const void *__b)
161 {
162 const struct engine *a = (struct engine *)__a;
163 const struct engine *b = (struct engine *)__b;
164
165 if (a->class != b->class)
166 return a->class - b->class;
167 else
168 return a->instance - b->instance;
169 }
170
discover_engines(void)171 static struct engines *discover_engines(void)
172 {
173 const char *sysfs_root = "/sys/devices/i915/events";
174 struct engines *engines;
175 struct dirent *dent;
176 int ret = 0;
177 DIR *d;
178
179 engines = malloc(sizeof(struct engines));
180 if (!engines)
181 return NULL;
182
183 memset(engines, 0, sizeof(*engines));
184
185 engines->num_engines = 0;
186
187 d = opendir(sysfs_root);
188 if (!d)
189 return NULL;
190
191 while ((dent = readdir(d)) != NULL) {
192 const char *endswith = "-busy";
193 const unsigned int endlen = strlen(endswith);
194 struct engine *engine =
195 engine_ptr(engines, engines->num_engines);
196 char buf[256];
197
198 if (dent->d_type != DT_REG)
199 continue;
200
201 if (strlen(dent->d_name) >= sizeof(buf)) {
202 ret = ENAMETOOLONG;
203 break;
204 }
205
206 strcpy(buf, dent->d_name);
207
208 /* xxxN-busy */
209 if (strlen(buf) < (endlen + 4))
210 continue;
211 if (strcmp(&buf[strlen(buf) - endlen], endswith))
212 continue;
213
214 memset(engine, 0, sizeof(*engine));
215
216 buf[strlen(buf) - endlen] = 0;
217 engine->name = strdup(buf);
218 if (!engine->name) {
219 ret = errno;
220 break;
221 }
222
223 engine->busy.config = get_pmu_config(dirfd(d), engine->name,
224 "busy");
225 if (engine->busy.config == -1) {
226 ret = ENOENT;
227 break;
228 }
229
230 engine->class = (engine->busy.config &
231 (__I915_PMU_OTHER(0) - 1)) >>
232 I915_PMU_CLASS_SHIFT;
233
234 engine->instance = (engine->busy.config >>
235 I915_PMU_SAMPLE_BITS) &
236 ((1 << I915_PMU_SAMPLE_INSTANCE_BITS) - 1);
237
238 ret = asprintf(&engine->display_name, "%s/%u",
239 class_display_name(engine->class),
240 engine->instance);
241 if (ret <= 0) {
242 ret = errno;
243 break;
244 }
245
246 ret = asprintf(&engine->short_name, "%s/%u",
247 class_short_name(engine->class),
248 engine->instance);
249 if (ret <= 0) {
250 ret = errno;
251 break;
252 }
253
254 engines->num_engines++;
255 engines = realloc(engines, sizeof(struct engines) +
256 engines->num_engines * sizeof(struct engine));
257 if (!engines) {
258 ret = errno;
259 break;
260 }
261
262 ret = 0;
263 }
264
265 if (ret) {
266 free(engines);
267 errno = ret;
268
269 return NULL;
270 }
271
272 qsort(engine_ptr(engines, 0), engines->num_engines,
273 sizeof(struct engine), engine_cmp);
274
275 engines->root = d;
276
277 return engines;
278 }
279
280 static int
filename_to_buf(const char * filename,char * buf,unsigned int bufsize)281 filename_to_buf(const char *filename, char *buf, unsigned int bufsize)
282 {
283 int fd, err;
284 ssize_t ret;
285
286 fd = open(filename, O_RDONLY);
287 if (fd < 0)
288 return -1;
289
290 ret = read(fd, buf, bufsize - 1);
291 err = errno;
292 close(fd);
293 if (ret < 1) {
294 errno = ret < 0 ? err : ENOMSG;
295
296 return -1;
297 }
298
299 if (ret > 1 && buf[ret - 1] == '\n')
300 buf[ret - 1] = '\0';
301 else
302 buf[ret] = '\0';
303
304 return 0;
305 }
306
filename_to_u64(const char * filename,int base)307 static uint64_t filename_to_u64(const char *filename, int base)
308 {
309 char buf[64], *b;
310
311 if (filename_to_buf(filename, buf, sizeof(buf)))
312 return 0;
313
314 /*
315 * Handle both single integer and key=value formats by skipping
316 * leading non-digits.
317 */
318 b = buf;
319 while (*b && !isdigit(*b))
320 b++;
321
322 return strtoull(b, NULL, base);
323 }
324
filename_to_double(const char * filename)325 static double filename_to_double(const char *filename)
326 {
327 char *oldlocale;
328 char buf[80];
329 double v;
330
331 if (filename_to_buf(filename, buf, sizeof(buf)))
332 return 0;
333
334 oldlocale = setlocale(LC_ALL, "C");
335 v = strtod(buf, NULL);
336 setlocale(LC_ALL, oldlocale);
337
338 return v;
339 }
340
341 #define RAPL_ROOT "/sys/devices/power/"
342 #define RAPL_EVENT "/sys/devices/power/events/"
343
rapl_type_id(void)344 static uint64_t rapl_type_id(void)
345 {
346 return filename_to_u64(RAPL_ROOT "type", 10);
347 }
348
rapl_gpu_power(void)349 static uint64_t rapl_gpu_power(void)
350 {
351 return filename_to_u64(RAPL_EVENT "energy-gpu", 0);
352 }
353
rapl_gpu_power_scale(void)354 static double rapl_gpu_power_scale(void)
355 {
356 return filename_to_double(RAPL_EVENT "energy-gpu.scale");
357 }
358
rapl_gpu_power_unit(void)359 static const char *rapl_gpu_power_unit(void)
360 {
361 char buf[32];
362
363 if (filename_to_buf(RAPL_EVENT "energy-gpu.unit",
364 buf, sizeof(buf)) == 0)
365 if (!strcmp(buf, "Joules"))
366 return strdup("Watts");
367 else
368 return strdup(buf);
369 else
370 return NULL;
371 }
372
373 #define IMC_ROOT "/sys/devices/uncore_imc/"
374 #define IMC_EVENT "/sys/devices/uncore_imc/events/"
375
imc_type_id(void)376 static uint64_t imc_type_id(void)
377 {
378 return filename_to_u64(IMC_ROOT "type", 10);
379 }
380
imc_data_reads(void)381 static uint64_t imc_data_reads(void)
382 {
383 return filename_to_u64(IMC_EVENT "data_reads", 0);
384 }
385
imc_data_reads_scale(void)386 static double imc_data_reads_scale(void)
387 {
388 return filename_to_double(IMC_EVENT "data_reads.scale");
389 }
390
imc_data_reads_unit(void)391 static const char *imc_data_reads_unit(void)
392 {
393 char buf[32];
394
395 if (filename_to_buf(IMC_EVENT "data_reads.unit", buf, sizeof(buf)) == 0)
396 return strdup(buf);
397 else
398 return NULL;
399 }
400
imc_data_writes(void)401 static uint64_t imc_data_writes(void)
402 {
403 return filename_to_u64(IMC_EVENT "data_writes", 0);
404 }
405
imc_data_writes_scale(void)406 static double imc_data_writes_scale(void)
407 {
408 return filename_to_double(IMC_EVENT "data_writes.scale");
409 }
410
imc_data_writes_unit(void)411 static const char *imc_data_writes_unit(void)
412 {
413 char buf[32];
414
415 if (filename_to_buf(IMC_EVENT "data_writes.unit",
416 buf, sizeof(buf)) == 0)
417 return strdup(buf);
418 else
419 return NULL;
420 }
421
422 #define _open_pmu(cnt, pmu, fd) \
423 ({ \
424 int fd__; \
425 \
426 fd__ = perf_i915_open_group((pmu)->config, (fd)); \
427 if (fd__ >= 0) { \
428 if ((fd) == -1) \
429 (fd) = fd__; \
430 (pmu)->present = true; \
431 (pmu)->idx = (cnt)++; \
432 } \
433 \
434 fd__; \
435 })
436
437 #define _open_imc(cnt, pmu, fd) \
438 ({ \
439 int fd__; \
440 \
441 fd__ = igt_perf_open_group(imc_type_id(), (pmu)->config, (fd)); \
442 if (fd__ >= 0) { \
443 if ((fd) == -1) \
444 (fd) = fd__; \
445 (pmu)->present = true; \
446 (pmu)->idx = (cnt)++; \
447 } \
448 \
449 fd__; \
450 })
451
pmu_init(struct engines * engines)452 static int pmu_init(struct engines *engines)
453 {
454 unsigned int i;
455 int fd;
456
457 engines->fd = -1;
458 engines->num_counters = 0;
459
460 engines->irq.config = I915_PMU_INTERRUPTS;
461 fd = _open_pmu(engines->num_counters, &engines->irq, engines->fd);
462 if (fd < 0)
463 return -1;
464
465 engines->freq_req.config = I915_PMU_REQUESTED_FREQUENCY;
466 _open_pmu(engines->num_counters, &engines->freq_req, engines->fd);
467
468 engines->freq_act.config = I915_PMU_ACTUAL_FREQUENCY;
469 _open_pmu(engines->num_counters, &engines->freq_act, engines->fd);
470
471 engines->rc6.config = I915_PMU_RC6_RESIDENCY;
472 _open_pmu(engines->num_counters, &engines->rc6, engines->fd);
473
474 for (i = 0; i < engines->num_engines; i++) {
475 struct engine *engine = engine_ptr(engines, i);
476 struct {
477 struct pmu_counter *pmu;
478 const char *counter;
479 } *cnt, counters[] = {
480 { .pmu = &engine->busy, .counter = "busy" },
481 { .pmu = &engine->wait, .counter = "wait" },
482 { .pmu = &engine->sema, .counter = "sema" },
483 { .pmu = NULL, .counter = NULL },
484 };
485
486 for (cnt = counters; cnt->pmu; cnt++) {
487 if (!cnt->pmu->config)
488 cnt->pmu->config =
489 get_pmu_config(dirfd(engines->root),
490 engine->name,
491 cnt->counter);
492 fd = _open_pmu(engines->num_counters, cnt->pmu,
493 engines->fd);
494 if (fd >= 0)
495 engine->num_counters++;
496 }
497 }
498
499 engines->rapl_fd = -1;
500 if (rapl_type_id()) {
501 engines->rapl_scale = rapl_gpu_power_scale();
502 engines->rapl_unit = rapl_gpu_power_unit();
503 if (!engines->rapl_unit)
504 return -1;
505
506 engines->rapl.config = rapl_gpu_power();
507 if (!engines->rapl.config)
508 return -1;
509
510 engines->rapl_fd = igt_perf_open(rapl_type_id(),
511 engines->rapl.config);
512 if (engines->rapl_fd < 0)
513 return -1;
514
515 engines->rapl.present = true;
516 }
517
518 engines->imc_fd = -1;
519 if (imc_type_id()) {
520 unsigned int num = 0;
521
522 engines->imc_reads_scale = imc_data_reads_scale();
523 engines->imc_writes_scale = imc_data_writes_scale();
524
525 engines->imc_reads_unit = imc_data_reads_unit();
526 if (!engines->imc_reads_unit)
527 return -1;
528
529 engines->imc_writes_unit = imc_data_writes_unit();
530 if (!engines->imc_writes_unit)
531 return -1;
532
533 engines->imc_reads.config = imc_data_reads();
534 if (!engines->imc_reads.config)
535 return -1;
536
537 engines->imc_writes.config = imc_data_writes();
538 if (!engines->imc_writes.config)
539 return -1;
540
541 fd = _open_imc(num, &engines->imc_reads, engines->imc_fd);
542 if (fd < 0)
543 return -1;
544 fd = _open_imc(num, &engines->imc_writes, engines->imc_fd);
545 if (fd < 0)
546 return -1;
547
548 engines->imc_reads.present = true;
549 engines->imc_writes.present = true;
550 }
551
552 return 0;
553 }
554
pmu_read_multi(int fd,unsigned int num,uint64_t * val)555 static uint64_t pmu_read_multi(int fd, unsigned int num, uint64_t *val)
556 {
557 uint64_t buf[2 + num];
558 unsigned int i;
559 ssize_t len;
560
561 memset(buf, 0, sizeof(buf));
562
563 len = read(fd, buf, sizeof(buf));
564 assert(len == sizeof(buf));
565
566 for (i = 0; i < num; i++)
567 val[i] = buf[2 + i];
568
569 return buf[1];
570 }
571
pmu_calc(struct pmu_pair * p,double d,double t,double s)572 static double pmu_calc(struct pmu_pair *p, double d, double t, double s)
573 {
574 double v;
575
576 v = p->cur - p->prev;
577 v /= d;
578 v /= t;
579 v *= s;
580
581 if (s == 100.0 && v > 100.0)
582 v = 100.0;
583
584 return v;
585 }
586
fill_str(char * buf,unsigned int bufsz,char c,unsigned int num)587 static void fill_str(char *buf, unsigned int bufsz, char c, unsigned int num)
588 {
589 unsigned int i;
590
591 for (i = 0; i < num && i < (bufsz - 1); i++)
592 *buf++ = c;
593
594 *buf = 0;
595 }
596
__pmu_read_single(int fd,uint64_t * ts)597 static uint64_t __pmu_read_single(int fd, uint64_t *ts)
598 {
599 uint64_t data[2] = { };
600 ssize_t len;
601
602 len = read(fd, data, sizeof(data));
603 assert(len == sizeof(data));
604
605 if (ts)
606 *ts = data[1];
607
608 return data[0];
609 }
610
pmu_read_single(int fd)611 static uint64_t pmu_read_single(int fd)
612 {
613 return __pmu_read_single(fd, NULL);
614 }
615
__update_sample(struct pmu_counter * counter,uint64_t val)616 static void __update_sample(struct pmu_counter *counter, uint64_t val)
617 {
618 counter->val.prev = counter->val.cur;
619 counter->val.cur = val;
620 }
621
update_sample(struct pmu_counter * counter,uint64_t * val)622 static void update_sample(struct pmu_counter *counter, uint64_t *val)
623 {
624 if (counter->present)
625 __update_sample(counter, val[counter->idx]);
626 }
627
pmu_sample(struct engines * engines)628 static void pmu_sample(struct engines *engines)
629 {
630 const int num_val = engines->num_counters;
631 uint64_t val[2 + num_val];
632 unsigned int i;
633
634 engines->ts.prev = engines->ts.cur;
635
636 if (engines->rapl_fd >= 0)
637 __update_sample(&engines->rapl,
638 pmu_read_single(engines->rapl_fd));
639
640 if (engines->imc_fd >= 0) {
641 pmu_read_multi(engines->imc_fd, 2, val);
642 update_sample(&engines->imc_reads, val);
643 update_sample(&engines->imc_writes, val);
644 }
645
646 engines->ts.cur = pmu_read_multi(engines->fd, num_val, val);
647
648 update_sample(&engines->freq_req, val);
649 update_sample(&engines->freq_act, val);
650 update_sample(&engines->irq, val);
651 update_sample(&engines->rc6, val);
652
653 for (i = 0; i < engines->num_engines; i++) {
654 struct engine *engine = engine_ptr(engines, i);
655
656 update_sample(&engine->busy, val);
657 update_sample(&engine->sema, val);
658 update_sample(&engine->wait, val);
659 }
660 }
661
662 static const char *bars[] = { " ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█" };
663
664 static void
print_percentage_bar(double percent,int max_len)665 print_percentage_bar(double percent, int max_len)
666 {
667 int bar_len = percent * (8 * (max_len - 2)) / 100.0;
668 int i;
669
670 putchar('|');
671
672 for (i = bar_len; i >= 8; i -= 8)
673 printf("%s", bars[8]);
674 if (i)
675 printf("%s", bars[i]);
676
677 for (i = 0; i < (max_len - 2 - (bar_len + 7) / 8); i++)
678 putchar(' ');
679
680 putchar('|');
681 }
682
683 #define DEFAULT_PERIOD_MS (1000)
684
685 static void
usage(const char * appname)686 usage(const char *appname)
687 {
688 printf("intel_gpu_top - Display a top-like summary of Intel GPU usage\n"
689 "\n"
690 "Usage: %s [parameters]\n"
691 "\n"
692 "\tThe following parameters are optional:\n\n"
693 "\t[-h] Show this help text.\n"
694 "\t[-J] Output JSON formatted data.\n"
695 "\t[-l] List plain text data.\n"
696 "\t[-o <file|->] Output to specified file or '-' for standard out.\n"
697 "\t[-s <ms>] Refresh period in milliseconds (default %ums).\n"
698 "\n",
699 appname, DEFAULT_PERIOD_MS);
700 }
701
702 static enum {
703 INTERACTIVE,
704 STDOUT,
705 JSON
706 } output_mode;
707
708 struct cnt_item {
709 struct pmu_counter *pmu;
710 unsigned int fmt_width;
711 unsigned int fmt_precision;
712 double d;
713 double t;
714 double s;
715 const char *name;
716 const char *unit;
717
718 /* Internal fields. */
719 char buf[16];
720 };
721
722 struct cnt_group {
723 const char *name;
724 const char *display_name;
725 struct cnt_item *items;
726 };
727
728 static unsigned int json_indent_level;
729
730 static const char *json_indent[] = {
731 "",
732 "\t",
733 "\t\t",
734 "\t\t\t",
735 "\t\t\t\t",
736 "\t\t\t\t\t",
737 };
738
739 #define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(arr[0]))
740
741 static unsigned int json_prev_struct_members;
742 static unsigned int json_struct_members;
743
744 FILE *out;
745
746 static void
json_open_struct(const char * name)747 json_open_struct(const char *name)
748 {
749 assert(json_indent_level < ARRAY_SIZE(json_indent));
750
751 json_prev_struct_members = json_struct_members;
752 json_struct_members = 0;
753
754 if (name)
755 fprintf(out, "%s%s\"%s\": {\n",
756 json_prev_struct_members ? ",\n" : "",
757 json_indent[json_indent_level],
758 name);
759 else
760 fprintf(out, "%s\n%s{\n",
761 json_prev_struct_members ? "," : "",
762 json_indent[json_indent_level]);
763
764 json_indent_level++;
765 }
766
767 static void
json_close_struct(void)768 json_close_struct(void)
769 {
770 assert(json_indent_level > 0);
771
772 fprintf(out, "\n%s}", json_indent[--json_indent_level]);
773
774 if (json_indent_level == 0)
775 fflush(stdout);
776 }
777
778 static unsigned int
json_add_member(const struct cnt_group * parent,struct cnt_item * item,unsigned int headers)779 json_add_member(const struct cnt_group *parent, struct cnt_item *item,
780 unsigned int headers)
781 {
782 assert(json_indent_level < ARRAY_SIZE(json_indent));
783
784 fprintf(out, "%s%s\"%s\": ",
785 json_struct_members ? ",\n" : "",
786 json_indent[json_indent_level], item->name);
787
788 json_struct_members++;
789
790 if (!strcmp(item->name, "unit"))
791 fprintf(out, "\"%s\"", item->unit);
792 else
793 fprintf(out, "%f",
794 pmu_calc(&item->pmu->val, item->d, item->t, item->s));
795
796 return 1;
797 }
798
799 static unsigned int stdout_level;
800
801 #define STDOUT_HEADER_REPEAT 20
802 static unsigned int stdout_lines = STDOUT_HEADER_REPEAT;
803
804 static void
stdout_open_struct(const char * name)805 stdout_open_struct(const char *name)
806 {
807 stdout_level++;
808 assert(stdout_level > 0);
809 }
810
811 static void
stdout_close_struct(void)812 stdout_close_struct(void)
813 {
814 assert(stdout_level > 0);
815 if (--stdout_level == 0) {
816 stdout_lines++;
817 fputs("\n", out);
818 fflush(out);
819 }
820 }
821
822 static unsigned int
stdout_add_member(const struct cnt_group * parent,struct cnt_item * item,unsigned int headers)823 stdout_add_member(const struct cnt_group *parent, struct cnt_item *item,
824 unsigned int headers)
825 {
826 unsigned int fmt_tot = item->fmt_width + (item->fmt_precision ? 1 : 0);
827 char buf[fmt_tot + 1];
828 double val;
829 int len;
830
831 if (!item->pmu)
832 return 0;
833 else if (!item->pmu->present)
834 return 0;
835
836 if (headers == 1) {
837 unsigned int grp_tot = 0;
838 struct cnt_item *it;
839
840 if (item != parent->items)
841 return 0;
842
843 for (it = parent->items; it->pmu; it++) {
844 if (!it->pmu->present)
845 continue;
846
847 grp_tot += 1 + it->fmt_width +
848 (it->fmt_precision ? 1 : 0);
849 }
850
851 fprintf(out, "%*s ", grp_tot - 1, parent->display_name);
852 return 0;
853 } else if (headers == 2) {
854 fprintf(out, "%*s ", fmt_tot, item->unit ?: item->name);
855 return 0;
856 }
857
858 val = pmu_calc(&item->pmu->val, item->d, item->t, item->s);
859
860 len = snprintf(buf, sizeof(buf), "%*.*f",
861 fmt_tot, item->fmt_precision, val);
862 if (len < 0 || len == sizeof(buf))
863 fill_str(buf, sizeof(buf), 'X', fmt_tot);
864
865 len = fprintf(out, "%s ", buf);
866
867 return len > 0 ? len : 0;
868 }
869
870 static void
term_open_struct(const char * name)871 term_open_struct(const char *name)
872 {
873 }
874
875 static void
term_close_struct(void)876 term_close_struct(void)
877 {
878 }
879
880 static unsigned int
term_add_member(const struct cnt_group * parent,struct cnt_item * item,unsigned int headers)881 term_add_member(const struct cnt_group *parent, struct cnt_item *item,
882 unsigned int headers)
883 {
884 unsigned int fmt_tot = item->fmt_width + (item->fmt_precision ? 1 : 0);
885 double val;
886 int len;
887
888 if (!item->pmu)
889 return 0;
890
891 assert(fmt_tot <= sizeof(item->buf));
892
893 if (!item->pmu->present) {
894 fill_str(item->buf, sizeof(item->buf), '-', fmt_tot);
895 return 1;
896 }
897
898 val = pmu_calc(&item->pmu->val, item->d, item->t, item->s);
899 len = snprintf(item->buf, sizeof(item->buf),
900 "%*.*f",
901 fmt_tot, item->fmt_precision, val);
902
903 if (len < 0 || len == sizeof(item->buf))
904 fill_str(item->buf, sizeof(item->buf), 'X', fmt_tot);
905
906 return 1;
907 }
908
909 struct print_operations {
910 void (*open_struct)(const char *name);
911 void (*close_struct)(void);
912 unsigned int (*add_member)(const struct cnt_group *parent,
913 struct cnt_item *item,
914 unsigned int headers);
915 bool (*print_group)(struct cnt_group *group, unsigned int headers);
916 };
917
918 static const struct print_operations *pops;
919
920 static unsigned int
present_in_group(const struct cnt_group * grp)921 present_in_group(const struct cnt_group *grp)
922 {
923 unsigned int present = 0;
924 struct cnt_item *item;
925
926 for (item = grp->items; item->name; item++) {
927 if (item->pmu && item->pmu->present)
928 present++;
929 }
930
931 return present;
932 }
933
934 static bool
print_group(struct cnt_group * grp,unsigned int headers)935 print_group(struct cnt_group *grp, unsigned int headers)
936 {
937 unsigned int consumed = 0;
938 struct cnt_item *item;
939
940 if (!present_in_group(grp))
941 return false;
942
943 pops->open_struct(grp->name);
944
945 for (item = grp->items; item->name; item++)
946 consumed += pops->add_member(grp, item, headers);
947
948 pops->close_struct();
949
950 return consumed;
951 }
952
953 static bool
term_print_group(struct cnt_group * grp,unsigned int headers)954 term_print_group(struct cnt_group *grp, unsigned int headers)
955 {
956 unsigned int consumed = 0;
957 struct cnt_item *item;
958
959 pops->open_struct(grp->name);
960
961 for (item = grp->items; item->name; item++)
962 consumed += pops->add_member(grp, item, headers);
963
964 pops->close_struct();
965
966 return consumed;
967 }
968
969 static const struct print_operations json_pops = {
970 .open_struct = json_open_struct,
971 .close_struct = json_close_struct,
972 .add_member = json_add_member,
973 .print_group = print_group,
974 };
975
976 static const struct print_operations stdout_pops = {
977 .open_struct = stdout_open_struct,
978 .close_struct = stdout_close_struct,
979 .add_member = stdout_add_member,
980 .print_group = print_group,
981 };
982
983 static const struct print_operations term_pops = {
984 .open_struct = term_open_struct,
985 .close_struct = term_close_struct,
986 .add_member = term_add_member,
987 .print_group = term_print_group,
988 };
989
print_groups(struct cnt_group ** groups)990 static bool print_groups(struct cnt_group **groups)
991 {
992 unsigned int headers = stdout_lines % STDOUT_HEADER_REPEAT + 1;
993 bool print_data = true;
994
995 if (output_mode == STDOUT && (headers == 1 || headers == 2)) {
996 for (struct cnt_group **grp = groups; *grp; grp++)
997 print_data = pops->print_group(*grp, headers);
998 }
999
1000 for (struct cnt_group **grp = groups; print_data && *grp; grp++)
1001 pops->print_group(*grp, false);
1002
1003 return print_data;
1004 }
1005
1006 static int
print_header(struct engines * engines,double t,int lines,int con_w,int con_h,bool * consumed)1007 print_header(struct engines *engines, double t,
1008 int lines, int con_w, int con_h, bool *consumed)
1009 {
1010 struct pmu_counter fake_pmu = {
1011 .present = true,
1012 .val.cur = 1,
1013 };
1014 struct cnt_item period_items[] = {
1015 { &fake_pmu, 0, 0, 1.0, 1.0, t * 1e3, "duration" },
1016 { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "ms" },
1017 { },
1018 };
1019 struct cnt_group period_group = {
1020 .name = "period",
1021 .items = period_items,
1022 };
1023 struct cnt_item freq_items[] = {
1024 { &engines->freq_req, 4, 0, 1.0, t, 1, "requested", "req" },
1025 { &engines->freq_act, 4, 0, 1.0, t, 1, "actual", "act" },
1026 { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "MHz" },
1027 { },
1028 };
1029 struct cnt_group freq_group = {
1030 .name = "frequency",
1031 .display_name = "Freq MHz",
1032 .items = freq_items,
1033 };
1034 struct cnt_item irq_items[] = {
1035 { &engines->irq, 8, 0, 1.0, t, 1, "count", "/s" },
1036 { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "irq/s" },
1037 { },
1038 };
1039 struct cnt_group irq_group = {
1040 .name = "interrupts",
1041 .display_name = "IRQ",
1042 .items = irq_items,
1043 };
1044 struct cnt_item rc6_items[] = {
1045 { &engines->rc6, 3, 0, 1e9, t, 100, "value", "%" },
1046 { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "%" },
1047 { },
1048 };
1049 struct cnt_group rc6_group = {
1050 .name = "rc6",
1051 .display_name = "RC6",
1052 .items = rc6_items,
1053 };
1054 struct cnt_item power_items[] = {
1055 { &engines->rapl, 4, 2, 1.0, t, engines->rapl_scale, "value",
1056 "W" },
1057 { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "W" },
1058 { },
1059 };
1060 struct cnt_group power_group = {
1061 .name = "power",
1062 .display_name = "Power",
1063 .items = power_items,
1064 };
1065 struct cnt_group *groups[] = {
1066 &period_group,
1067 &freq_group,
1068 &irq_group,
1069 &rc6_group,
1070 &power_group,
1071 NULL
1072 };
1073
1074 if (output_mode != JSON)
1075 memmove(&groups[0], &groups[1],
1076 sizeof(groups) - sizeof(groups[0]));
1077
1078 pops->open_struct(NULL);
1079
1080 *consumed = print_groups(groups);
1081
1082 if (output_mode == INTERACTIVE) {
1083 printf("\033[H\033[J");
1084
1085 if (lines++ < con_h)
1086 printf("intel-gpu-top - %s/%s MHz; %s%% RC6; %s %s; %s irqs/s\n",
1087 freq_items[1].buf, freq_items[0].buf,
1088 rc6_items[0].buf, power_items[0].buf,
1089 engines->rapl_unit,
1090 irq_items[0].buf);
1091
1092 if (lines++ < con_h)
1093 printf("\n");
1094 }
1095
1096 return lines;
1097 }
1098
1099 static int
print_imc(struct engines * engines,double t,int lines,int con_w,int con_h)1100 print_imc(struct engines *engines, double t, int lines, int con_w, int con_h)
1101 {
1102 struct cnt_item imc_items[] = {
1103 { &engines->imc_reads, 6, 0, 1.0, t, engines->imc_reads_scale,
1104 "reads", "rd" },
1105 { &engines->imc_writes, 6, 0, 1.0, t, engines->imc_writes_scale,
1106 "writes", "wr" },
1107 { NULL, 0, 0, 0.0, 0.0, 0.0, "unit" },
1108 { },
1109 };
1110 struct cnt_group imc_group = {
1111 .name = "imc-bandwidth",
1112 .items = imc_items,
1113 };
1114 struct cnt_group *groups[] = {
1115 &imc_group,
1116 NULL
1117 };
1118 int ret;
1119
1120 ret = asprintf((char **)&imc_group.display_name, "IMC %s/s",
1121 engines->imc_reads_unit);
1122 assert(ret >= 0);
1123
1124 ret = asprintf((char **)&imc_items[2].unit, "%s/s",
1125 engines->imc_reads_unit);
1126 assert(ret >= 0);
1127
1128 print_groups(groups);
1129
1130 free((void *)imc_group.display_name);
1131 free((void *)imc_items[2].unit);
1132
1133 if (output_mode == INTERACTIVE) {
1134 if (lines++ < con_h)
1135 printf(" IMC reads: %s %s/s\n",
1136 imc_items[0].buf, engines->imc_reads_unit);
1137
1138 if (lines++ < con_h)
1139 printf(" IMC writes: %s %s/s\n",
1140 imc_items[1].buf, engines->imc_writes_unit);
1141
1142 if (lines++ < con_h)
1143 printf("\n");
1144 }
1145
1146 return lines;
1147 }
1148
1149 static int
print_engines_header(struct engines * engines,double t,int lines,int con_w,int con_h)1150 print_engines_header(struct engines *engines, double t,
1151 int lines, int con_w, int con_h)
1152 {
1153 for (unsigned int i = 0;
1154 i < engines->num_engines && lines < con_h;
1155 i++) {
1156 struct engine *engine = engine_ptr(engines, i);
1157
1158 if (!engine->num_counters)
1159 continue;
1160
1161 pops->open_struct("engines");
1162
1163 if (output_mode == INTERACTIVE) {
1164 const char *a = " ENGINE BUSY ";
1165 const char *b = " MI_SEMA MI_WAIT";
1166
1167 printf("\033[7m%s%*s%s\033[0m\n",
1168 a, (int)(con_w - 1 - strlen(a) - strlen(b)),
1169 " ", b);
1170
1171 lines++;
1172 }
1173
1174 break;
1175 }
1176
1177 return lines;
1178 }
1179
1180 static int
print_engine(struct engines * engines,unsigned int i,double t,int lines,int con_w,int con_h)1181 print_engine(struct engines *engines, unsigned int i, double t,
1182 int lines, int con_w, int con_h)
1183 {
1184 struct engine *engine = engine_ptr(engines, i);
1185 struct cnt_item engine_items[] = {
1186 { &engine->busy, 6, 2, 1e9, t, 100, "busy", "%" },
1187 { &engine->sema, 3, 0, 1e9, t, 100, "sema", "se" },
1188 { &engine->wait, 3, 0, 1e9, t, 100, "wait", "wa" },
1189 { NULL, 0, 0, 0.0, 0.0, 0.0, "unit", "%" },
1190 { },
1191 };
1192 struct cnt_group engine_group = {
1193 .name = engine->display_name,
1194 .display_name = engine->short_name,
1195 .items = engine_items,
1196 };
1197 struct cnt_group *groups[] = {
1198 &engine_group,
1199 NULL
1200 };
1201
1202 if (!engine->num_counters)
1203 return lines;
1204
1205 print_groups(groups);
1206
1207 if (output_mode == INTERACTIVE) {
1208 unsigned int max_w = con_w - 1;
1209 unsigned int len;
1210 char buf[128];
1211 double val;
1212
1213 len = snprintf(buf, sizeof(buf), " %s%% %s%%",
1214 engine_items[1].buf, engine_items[2].buf);
1215
1216 len += printf("%16s %s%% ",
1217 engine->display_name, engine_items[0].buf);
1218
1219 val = pmu_calc(&engine->busy.val, 1e9, t, 100);
1220 print_percentage_bar(val, max_w - len);
1221
1222 printf("%s\n", buf);
1223
1224 lines++;
1225 }
1226
1227 return lines;
1228 }
1229
1230 static int
print_engines_footer(struct engines * engines,double t,int lines,int con_w,int con_h)1231 print_engines_footer(struct engines *engines, double t,
1232 int lines, int con_w, int con_h)
1233 {
1234 pops->close_struct();
1235 pops->close_struct();
1236
1237 if (output_mode == INTERACTIVE) {
1238 if (lines++ < con_h)
1239 printf("\n");
1240 }
1241
1242 return lines;
1243 }
1244
1245 static bool stop_top;
1246
sigint_handler(int sig)1247 static void sigint_handler(int sig)
1248 {
1249 stop_top = true;
1250 }
1251
main(int argc,char ** argv)1252 int main(int argc, char **argv)
1253 {
1254 unsigned int period_us = DEFAULT_PERIOD_MS * 1000;
1255 int con_w = -1, con_h = -1;
1256 char *output_path = NULL;
1257 struct engines *engines;
1258 unsigned int i;
1259 int ret, ch;
1260
1261 /* Parse options */
1262 while ((ch = getopt(argc, argv, "o:s:Jlh")) != -1) {
1263 switch (ch) {
1264 case 'o':
1265 output_path = optarg;
1266 break;
1267 case 's':
1268 period_us = atoi(optarg) * 1000;
1269 break;
1270 case 'J':
1271 output_mode = JSON;
1272 break;
1273 case 'l':
1274 output_mode = STDOUT;
1275 break;
1276 case 'h':
1277 usage(argv[0]);
1278 exit(0);
1279 default:
1280 fprintf(stderr, "Invalid option %c!\n", (char)optopt);
1281 usage(argv[0]);
1282 exit(1);
1283 }
1284 }
1285
1286 if (output_mode == INTERACTIVE && (output_path || isatty(1) != 1))
1287 output_mode = STDOUT;
1288
1289 if (output_path && strcmp(output_path, "-")) {
1290 out = fopen(output_path, "w");
1291
1292 if (!out) {
1293 fprintf(stderr, "Failed to open output file - '%s'!\n",
1294 strerror(errno));
1295 exit(1);
1296 }
1297 } else {
1298 out = stdout;
1299 }
1300
1301 if (output_mode != INTERACTIVE) {
1302 sighandler_t sig = signal(SIGINT, sigint_handler);
1303
1304 if (sig == SIG_ERR)
1305 fprintf(stderr, "Failed to install signal handler!\n");
1306 }
1307
1308 switch (output_mode) {
1309 case INTERACTIVE:
1310 pops = &term_pops;
1311 break;
1312 case STDOUT:
1313 pops = &stdout_pops;
1314 break;
1315 case JSON:
1316 pops = &json_pops;
1317 break;
1318 default:
1319 assert(0);
1320 break;
1321 };
1322
1323 engines = discover_engines();
1324 if (!engines) {
1325 fprintf(stderr,
1326 "Failed to detect engines! (%s)\n(Kernel 4.16 or newer is required for i915 PMU support.)\n",
1327 strerror(errno));
1328 return 1;
1329 }
1330
1331 ret = pmu_init(engines);
1332 if (ret) {
1333 fprintf(stderr,
1334 "Failed to initialize PMU! (%s)\n", strerror(errno));
1335 return 1;
1336 }
1337
1338 pmu_sample(engines);
1339
1340 while (!stop_top) {
1341 bool consumed = false;
1342 int lines = 0;
1343 struct winsize ws;
1344 double t;
1345
1346 /* Update terminal size. */
1347 if (output_mode != INTERACTIVE) {
1348 con_w = con_h = INT_MAX;
1349 } else if (ioctl(0, TIOCGWINSZ, &ws) != -1) {
1350 con_w = ws.ws_col;
1351 con_h = ws.ws_row;
1352 }
1353
1354 pmu_sample(engines);
1355 t = (double)(engines->ts.cur - engines->ts.prev) / 1e9;
1356
1357 if (stop_top)
1358 break;
1359
1360 while (!consumed) {
1361 lines = print_header(engines, t, lines, con_w, con_h,
1362 &consumed);
1363
1364 if (engines->imc_fd)
1365 lines = print_imc(engines, t, lines, con_w,
1366 con_h);
1367
1368 lines = print_engines_header(engines, t, lines, con_w,
1369 con_h);
1370
1371 for (i = 0;
1372 i < engines->num_engines && lines < con_h;
1373 i++)
1374 lines = print_engine(engines, i, t, lines,
1375 con_w, con_h);
1376
1377 lines = print_engines_footer(engines, t, lines, con_w,
1378 con_h);
1379 }
1380
1381 if (stop_top)
1382 break;
1383
1384 usleep(period_us);
1385 }
1386
1387 return 0;
1388 }
1389