xref: /freebsd/usr.sbin/pmc/display.cc (revision 503f16182e08d58ce17843cf5a71c7dbc27af6b5)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2026, Netflix, Inc.
5  *
6  * This software was developed by Ali Mashtizadeh under the sponsorship from
7  * Netflix, Inc.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  *
30  */
31 
32 #include <sys/ioctl.h>
33 
34 #include <inttypes.h>
35 #include <stdbool.h>
36 #include <stdint.h>
37 #include <stdio.h>
38 #include <term.h>
39 #include <math.h>
40 
41 #include <curses.h>
42 #include <unistd.h>
43 
44 #include <cxxabi.h>
45 #include <cstring>
46 #include <iomanip>
47 #include <ios>
48 #include <iostream>
49 #include <sstream>
50 #include <string>
51 #include <vector>
52 
53 #include "display.hh"
54 
55 enum termmode {
56     TERMMODE_BASIC,
57     TERMMODE_VT,
58     TERMMODE_XTERM
59 };
60 
61 static termmode tmode;
62 static int disp_height;
63 static int disp_width;
64 
65 #define TITLE_TITLE		0
66 #define TITLE_SUBTITLE		1
67 
68 static const char **title_char;
69 
70 static const char *title_vt[] = {
71     "\xe2\x95\x90", "\xe2\x94\x80"
72 };
73 static const char *title_basic[] = { "=", "-" };
74 
75 #define ARROW_DOWN		0
76 #define ARROW_UP		1
77 
78 static const char **arrow_char;
79 
80 static const char *arrow_vt[] = {
81     "\xe2\x96\xbc", "\xe2\x96\xb2"
82 };
83 static const char *arrow_basic[] = { "D", "U" };
84 
85 #define BORDER_HBAR		0
86 #define BORDER_VBAR		1
87 #define BORDER_TOPRIGHT		2
88 #define BORDER_TOPLEFT		3
89 #define BORDER_BOTTOMLEFT	4
90 #define BORDER_BOTTOMRIGHT	5
91 #define BORDER_TOPSPLIT		6
92 #define BORDER_BOTTOMSPLIT	7
93 #define BORDER_RIGHTSPLIT	8
94 #define BORDER_LEFTSPLIT	9
95 #define BORDER_CROSS		10
96 
97 static const char **border_char;
98 
99 static const char *border_vt[] = {
100     "\xe2\x94\x80", "\xe2\x94\x82",
101     "\xe2\x94\x8c", "\xe2\x94\x90", "\xe2\x94\x94", "\xe2\x94\x98",
102     "\xe2\x94\xac", "\xe2\x94\xb4", "\xe2\x94\xa4", "\xe2\x94\x9c",
103     "\xe2\x94\xbc"
104 };
105 static const char *border_basic[] = { "-", "|", "+", "+", "+", "+", "+", "+", "+", "+" };
106 
107 static char tc_boldbuf[16];
108 static char tc_sgr0buf[16];
109 static char tc_afbuf[16];
110 static char tc_green[16];
111 static char tc_yellow[16];
112 static char tc_red[16];
113 
114 static const char *TC_BOLD;
115 static const char *TC_SGR0;
116 static const char *TC_SETAF;
117 
118 void
tcemit(const char * c)119 tcemit(const char *c)
120 {
121 	if (c)
122 		tputs(c, 1, putchar);
123 }
124 
125 void
tcfg(int n)126 tcfg(int n)
127 {
128 	if (TC_SETAF)
129 		tputs(tgoto(TC_SETAF, 0, n), 1, putchar);
130 }
131 
132 static void
setup_termcap(const char * term)133 setup_termcap(const char *term)
134 {
135 	bool xcolor;
136 	char *buf;
137 	char *clicolor;
138 	char termcapbuf[4096];
139 
140 	if (tgetent(termcapbuf, "xterm-256color") != 1) {
141 		TC_BOLD = NULL;
142 		TC_SGR0 = NULL;
143 		TC_SETAF = NULL;
144 		return;
145 	}
146 
147 	buf = tc_boldbuf;
148 	TC_BOLD = tgetstr("md", &buf);
149 	buf = tc_sgr0buf;
150 	TC_SGR0 = tgetstr("me", &buf);
151 	buf = tc_afbuf;
152 	TC_SETAF = tgetstr("AF", &buf);
153 
154 	clicolor = getenv("CLICOLOR");
155 	if (clicolor == NULL || strcmp(clicolor, "1") != 0) {
156 		return;
157 	}
158 
159 	if (strcmp(term, "xterm-256color") == 0) {
160 		xcolor = true;
161 	} else {
162 		xcolor = false;
163 	}
164 
165 	buf = tgoto(TC_SETAF, 1, xcolor ? COLOR256_GREEN : COLOR_GREEN);
166 	if (buf) {
167 		strlcpy(tc_green, buf, sizeof(tc_green));
168 	} else {
169 		tc_green[0] = '\0';
170 	}
171 
172 	buf = tgoto(TC_SETAF, 1, xcolor ? COLOR256_YELLOW : COLOR_YELLOW);
173 	if (buf) {
174 		strlcpy(tc_yellow, buf, sizeof(tc_yellow));
175 	} else {
176 		tc_yellow[0] = '\0';
177 	}
178 
179 	buf = tgoto(TC_SETAF, 1, xcolor ? COLOR256_RED : COLOR_RED);
180 	if (buf) {
181 		strlcpy(tc_red, buf, sizeof(tc_red));
182 	} else {
183 		tc_red[0] = '\0';
184 	}
185 }
186 
187 void
setup_screen()188 setup_screen()
189 {
190 	int status;
191 	const char *term;
192 	struct winsize wsz;
193 
194 	/* Sane defaults */
195 	tmode = TERMMODE_BASIC;
196 	title_char = title_basic;
197 	arrow_char = arrow_basic;
198 	border_char = border_basic;
199 	disp_height = 25;
200 	disp_width = 80;
201 
202 	if (isatty(STDOUT_FILENO) == 0) {
203 		tmode = TERMMODE_XTERM;
204 		title_char = title_vt;
205 		arrow_char = arrow_vt;
206 		border_char = border_vt;
207 		return;
208 	}
209 
210 	status = ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz);
211 	if (status < 0) {
212 		perror("ioctl(TIOCGWINSZ)");
213 		return;
214 	}
215 
216 	disp_height = wsz.ws_row;
217 	disp_width = wsz.ws_col;
218 
219 	term = getenv("TERM");
220 	if (term == NULL) {
221 		tmode = TERMMODE_BASIC;
222 		title_char = title_basic;
223 		arrow_char = arrow_basic;
224 		border_char = border_basic;
225 		return;
226 	}
227 
228 	if (strncmp(term, "xterm", 5) == 0) {
229 		tmode = TERMMODE_XTERM;
230 		title_char = title_vt;
231 		arrow_char = arrow_vt;
232 		border_char = border_vt;
233 	} else if (strncmp(term, "vt", 2) == 0) {
234 		tmode = TERMMODE_VT;
235 		title_char = title_vt;
236 		arrow_char = arrow_vt;
237 		border_char = border_vt;
238 	}
239 
240 	setup_termcap(term);
241 }
242 
243 void
title(const std::string & t)244 title(const std::string &t)
245 {
246 	size_t i;
247 
248 	if (tmode != TERMMODE_BASIC)
249 		tcemit(TC_BOLD);
250 	printf("%s\n", t.c_str());
251 	if (tmode != TERMMODE_BASIC)
252 		tcemit(TC_SGR0);
253 	for (i = 0; i < t.size(); i++)
254 		printf("%s", title_char[TITLE_TITLE]);
255 	printf("\n\n");
256 }
257 
258 void
header(const std::string & h)259 header(const std::string &h)
260 {
261 	size_t i;
262 
263 	if (tmode != TERMMODE_BASIC)
264 		tcemit(TC_BOLD);
265 	printf("%s\n", h.c_str());
266 	if (tmode != TERMMODE_BASIC)
267 		tcemit(TC_SGR0);
268 	for (i = 0; i < h.size(); i++)
269 		printf("%s", title_char[TITLE_SUBTITLE]);
270 	printf("\n");
271 }
272 
273 void
printval(const std::string & msg,uint64_t val,siunit ui)274 printval(const std::string &msg, uint64_t val, siunit ui)
275 {
276 	switch (ui) {
277 	case siunit::seconds:
278 		printf("%s: %" PRIu64 " s\n", msg.c_str(), val);
279 		break;
280 	case siunit::percent:
281 		printf("%s: %" PRIu64 "%%\n", msg.c_str(), val);
282 		break;
283 	case siunit::cycles:
284 		printf("%s: %" PRIu64 " cycles\n", msg.c_str(), val);
285 		break;
286 	}
287 }
288 
289 void
printval(const std::string & msg,float val,siunit ui)290 printval(const std::string &msg, float val, siunit ui)
291 {
292 	switch (ui) {
293 	case siunit::seconds:
294 		printf("%s: %.2f s\n", msg.c_str(), val);
295 		break;
296 	case siunit::percent:
297 		printf("%s: %6.2f%%\n", msg.c_str(), val);
298 		break;
299 	case siunit::cycles:
300 		printf("%s: %.2f cycles\n", msg.c_str(), val);
301 		break;
302 	}
303 }
304 
305 std::string
format_siprefix(uint64_t count)306 format_siprefix(uint64_t count)
307 {
308 	unsigned long index = 0;
309 	char prefix[] = " kMGTP";
310 	char buf[10];
311 
312 	while (count > 10000 && index < sizeof(prefix)) {
313 		index++;
314 		count /= 1000;
315 	}
316 
317 	if (index == 0) {
318 		snprintf(buf, sizeof(buf), "%" PRIu64, count);
319 	} else {
320 		snprintf(buf, sizeof(buf), "%" PRIu64 "%c", count, prefix[index]);
321 	}
322 
323 	return buf;
324 }
325 
326 std::string
format_binprefix(uint64_t count)327 format_binprefix(uint64_t count)
328 {
329 	unsigned long index = 0;
330 	char prefix[] = " KMGTP";
331 	char buf[10];
332 
333 	while (count > 10000 && index < sizeof(prefix)) {
334 		index++;
335 		count /= 1000;
336 	}
337 
338 	if (index == 0) {
339 		snprintf(buf, sizeof(buf), "%" PRIu64 " B", count);
340 	} else {
341 		snprintf(buf, sizeof(buf), "%" PRIu64 " %ciB", count, prefix[index]);
342 	}
343 
344 	return buf;
345 }
346 
347 std::string
format_sample(uint64_t count,uint64_t total)348 format_sample(uint64_t count, uint64_t total)
349 {
350 	char buf[20];
351 	std::string fmt = format_siprefix(count);
352 
353 	snprintf(buf, sizeof(buf), "%5s (%4.1f%%)", fmt.c_str(),
354 	    100.0 * (float)count / (float)total);
355 
356 	return buf;
357 }
358 
359 std::string
format_percent(uint64_t count,uint64_t total)360 format_percent(uint64_t count, uint64_t total)
361 {
362 	char buf[20];
363 
364 	if (total == 0) {
365 		snprintf(buf, sizeof(buf), "%6s", "");
366 	} else {
367 		snprintf(buf, sizeof(buf), "%5.1f%%",
368 		    100.0 * (float)count / (float)total);
369 	}
370 
371 	return buf;
372 }
373 
374 /* fields */
375 
field(int64_t v)376 field::field(int64_t v)
377 {
378 	type = FIELD::INTEGER;
379 	value[0] = v;
380 }
381 
field(float v)382 field::field(float v)
383 {
384 	type = FIELD::FLOAT;
385 	fvalue = v;
386 }
387 
field(const std::string & s)388 field::field(const std::string &s)
389 {
390 	type = FIELD::STRING;
391 	svalue = s;
392 }
393 
field(int64_t c,int64_t t,bool percent)394 field::field(int64_t c, int64_t t, bool percent)
395 {
396 	if (percent)
397 		type = FIELD::PERCENT;
398 	else
399 		type = FIELD::SAMPLE;
400 	value[0] = c;
401 	value[1] = t;
402 }
403 
field(int64_t uc,int64_t ut,int64_t ku,int64_t kt)404 field::field(int64_t uc, int64_t ut, int64_t ku, int64_t kt)
405 {
406 	type = FIELD::DSAMPLE;
407 	value[0] = uc;
408 	value[1] = ut;
409 	value[2] = ku;
410 	value[3] = kt;
411 }
412 
413 bool
operator >(const field & b) const414 field::operator>(const field &b) const
415 {
416 	switch (type) {
417 	case FIELD::STRING:
418 		return (svalue > b.svalue);
419 	case FIELD::FLOAT:
420 		return (fvalue > b.fvalue);
421 	case FIELD::INTEGER: [[fallthrough]];
422 	case FIELD::SAMPLE:
423 		return (value[0] > b.value[0]);
424 	case FIELD::DSAMPLE:
425 		return ((value[0] + value[2]) > (b.value[0] + b.value[2]));
426 	case FIELD::PERCENT:
427 		return (((float)value[0] / (float)value[1]) >
428 		    ((float)b.value[0] / (float)b.value[1]));
429 	}
430 }
431 
432 bool
operator <(const field & b) const433 field::operator<(const field &b) const
434 {
435 	switch (type) {
436 	case FIELD::STRING:
437 		return (svalue < b.svalue);
438 	case FIELD::FLOAT:
439 		return (fvalue < b.fvalue);
440 	case FIELD::INTEGER: [[fallthrough]];
441 	case FIELD::SAMPLE:
442 		return (value[0] < b.value[0]);
443 	case FIELD::DSAMPLE:
444 		return ((value[0] + value[2]) < (b.value[0] + b.value[2]));
445 	case FIELD::PERCENT:
446 		return (((float)value[0] / (float)value[1]) <
447 		    ((float)b.value[0] / (float)b.value[1]));
448 	}
449 }
450 
451 size_t
length()452 field::length()
453 {
454 	return (to_string().length());
455 }
456 
457 std::string
to_string()458 field::to_string()
459 {
460 	switch (type) {
461 	case FIELD::STRING:
462 		return (svalue);
463 	case FIELD::FLOAT:
464 		return (std::to_string(fvalue));
465 	case FIELD::INTEGER:
466 		return (std::to_string(value[0]));
467 	case FIELD::SAMPLE:
468 		return (format_sample(value[0], value[1]));
469 	case FIELD::DSAMPLE:
470 		return (format_sample(value[0], value[1]) + " / " +
471 		    format_sample(value[2], value[3]));
472 	case FIELD::PERCENT:
473 		return (format_percent(value[0], value[1]));
474 	}
475 }
476 
477 /* table class */
478 
table()479 table::table() : sortcol(-1), sortdir(true), cols(), align(), rows()
480 {
481 }
482 
~table()483 table::~table()
484 {
485 }
486 
487 void
addcolumn(const std::string & c,bool alignleft)488 table::addcolumn(const std::string &c, bool alignleft)
489 {
490 	cols.push_back(c);
491 	align.push_back(alignleft);
492 }
493 
494 void
addrow(std::vector<field> r)495 table::addrow(std::vector<field> r)
496 {
497 	rows.push_back(std::move(r));
498 }
499 
500 void
sort(int col,bool descending)501 table::sort(int col, bool descending)
502 {
503 	sortcol = col;
504 	sortdir = descending;
505 	if (descending)
506 		std::sort(rows.begin(), rows.end(), [col](auto &a, auto &b)
507 		    { return (a[col] > b[col]); });
508 	else
509 		std::sort(rows.begin(), rows.end(), [col](auto &a, auto &b)
510 		    { return (a[col] < b[col]); });
511 }
512 
513 void
print()514 table::print()
515 {
516 	unsigned long c;
517 	std::vector<unsigned long> width;
518 
519 	// Compute column width
520 	for (auto h : cols)
521 		width.push_back(h.length() + 1);
522 	if (sortcol != -1)
523 		width[sortcol] += 1;
524 	for (auto &r : rows) {
525 		for (c = 0; c < r.size(); c++) {
526 			if (width[c] < r[c].length())
527 				width[c] = r[c].length();
528 		}
529 	}
530 
531 	for (c = 0; c < width.size(); c++) {
532 		if (align[c])
533 			std::cout << std::left;
534 		std::cout << std::setw(width[c]);
535 		tcemit(TC_BOLD);
536 		if (c == (unsigned long)sortcol)
537 		    std::cout << cols[c] + " " + arrow_char[sortdir ? ARROW_DOWN : ARROW_UP];
538 		else
539 		    std::cout << cols[c];
540 		tcemit(TC_SGR0);
541 		if (c != (width.size() - 1))
542 			std::cout << " " << border_char[BORDER_VBAR] << " ";
543 		if (align[c])
544 			std::cout << std::right;
545 	}
546 	std::cout << std::endl;
547 
548 	for (c = 0; c < width.size(); c++) {
549 		for (unsigned long i = 0; i < width[c]; i++)
550 		    std::cout << border_char[BORDER_HBAR];
551 		if (c != (width.size() - 1))
552 			std::cout << border_char[BORDER_HBAR] <<
553 			    border_char[BORDER_CROSS] << border_char[BORDER_HBAR];
554 	}
555 	std::cout << std::setfill(' ') << std::endl;
556 
557 	for (auto &r : rows) {
558 		for (c = 0; c < width.size(); c++) {
559 			if (align[c])
560 				std::cout << std::left;
561 			std::cout << std::setw(width[c]) << r[c].to_string();
562 			if (c != (width.size() - 1))
563 				std::cout << " " << border_char[BORDER_VBAR] << " ";
564 			if (align[c])
565 				std::cout << std::right;
566 		}
567 		std::cout << std::endl;
568 	}
569 
570 	std::cout << std::endl;
571 }
572 
573