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