xref: /freebsd/usr.sbin/pmc/cmd_pmc_frontend.cc (revision cc94d1bd7414b309a62a379a8e614e3a3f9cd6d9)
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/param.h>
33 #include <sys/cpuset.h>
34 #include <sys/event.h>
35 #include <sys/queue.h>
36 #include <sys/socket.h>
37 #include <sys/stat.h>
38 #include <sys/sysctl.h>
39 #include <sys/time.h>
40 #include <sys/ttycom.h>
41 #include <sys/user.h>
42 #include <sys/wait.h>
43 
44 #include <assert.h>
45 #include <curses.h>
46 #include <err.h>
47 #include <errno.h>
48 #include <fcntl.h>
49 #include <getopt.h>
50 #include <kvm.h>
51 #include <libgen.h>
52 #include <limits.h>
53 #include <locale.h>
54 #include <math.h>
55 #include <pmc.h>
56 #include <pmclog.h>
57 #include <regex.h>
58 #include <signal.h>
59 #include <stdarg.h>
60 #include <stdint.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <stddef.h>
64 #include <string.h>
65 #include <sysexits.h>
66 #include <unistd.h>
67 
68 #include <libpmcstat.h>
69 #include "cmd_pmc.h"
70 
71 #include <iostream>
72 #include <map>
73 #include <set>
74 #include <string>
75 #include <unordered_map>
76 #include <unordered_set>
77 #include <vector>
78 
79 #include <dev/hwpmc/hwpmc_ibs.h>
80 #include "display.hh"
81 #include "view.hh"
82 
83 struct frontend {
84 	syminfo		func;
85 	int64_t		ocmiss;
86 	int64_t		l2miss;
87 	int64_t		l3miss;
88 	int64_t		l1tlbmiss;
89 	int64_t		l2tlbmiss;
90 	int64_t		latency;
91 	int64_t		samples;
92 };
93 
94 static int sortcol = 2;
95 
96 class frontend_view : public pmcview
97 {
98 public:
frontend_view()99 	frontend_view() : pmcview(), samples() { }
~frontend_view()100 	~frontend_view() { }
101 
102 	virtual void
callchain(struct pmclog_ev_callchain & p,ibsfetchinfo & f,__unused uintfptr_t * cc,__unused int len)103 	callchain(struct pmclog_ev_callchain &p,
104 	    ibsfetchinfo &f, __unused uintfptr_t *cc, __unused int len)
105 	{
106 		int usermode = PMC_CALLCHAIN_CPUFLAGS_TO_USERMODE(p.pl_cpuflags);
107 
108 		/* Ignore zeros */
109 		if (IBS_FETCH_CTL_TO_LAT(f.ctl) == 0)
110 			return;
111 
112 		auto inst = samples.find(f.linaddr);
113 		if (inst == samples.end()) {
114 			syminfo sym = addrtosymbol(usermode ? p.pl_pid : 0, f.linaddr);
115 			if (sym.name == "")
116 				return;
117 
118 			samples[f.linaddr] = frontend();
119 			inst = samples.find(f.linaddr);
120 			inst->second.func = sym;
121 		}
122 
123 		/*
124 		 * Save fetch cache and TLB miss counts
125 		 */
126 		if (f.ctl & IBS_FETCH_CTL_OPCACHEMISS)
127 			inst->second.ocmiss += 1;
128 		if (f.ctl & IBS_FETCH_CTL_L2MISS)
129 			inst->second.l2miss += 1;
130 		if (f.ctl & IBS_FETCH_CTL_L3MISS)
131 			inst->second.l3miss += 1;
132 
133 		if (f.ctl & IBS_FETCH_CTL_L1TLBMISS)
134 			inst->second.l1tlbmiss += 1;
135 		if (f.ctl & IBS_FETCH_CTL_L2TLBMISS)
136 			inst->second.l2tlbmiss += 1;
137 
138 		inst->second.latency += IBS_FETCH_CTL_TO_LAT(f.ctl);
139 		inst->second.samples += 1;
140 	}
141 
142 	virtual void
print()143 	print()
144 	{
145 		title("IBS Frontend Analysis");
146 
147 		// Print header
148 		table t = table();
149 		t.addcolumn("Image", true);
150 		t.addcolumn("Function", true);
151 		t.addcolumn("Latency");
152 		t.addcolumn("Samples");
153 		t.addcolumn("OC Miss");
154 		t.addcolumn("L2 Miss");
155 		t.addcolumn("L3 Miss");
156 		t.addcolumn("L1 TLB Miss");
157 		t.addcolumn("L2 TLB Miss");
158 
159 		for (auto &kv : samples) {
160 			std::vector<field> r;
161 
162 			r.emplace_back(kv.second.func.binary);
163 			r.emplace_back(kv.second.func.to_string());
164 			r.emplace_back(kv.second.latency);
165 			r.emplace_back(kv.second.samples);
166 			r.emplace_back(kv.second.ocmiss, kv.second.samples, true);
167 			r.emplace_back(kv.second.l2miss, kv.second.samples, true);
168 			r.emplace_back(kv.second.l3miss, kv.second.samples, true);
169 			r.emplace_back(kv.second.l1tlbmiss, kv.second.samples, true);
170 			r.emplace_back(kv.second.l2tlbmiss, kv.second.samples, true);
171 
172 			t.addrow(r);
173 		}
174 
175 		t.sort(sortcol);
176 		t.print();
177 	}
178 protected:
179 	std::unordered_map<uint64_t, frontend> samples;
180 };
181 
182 
183 static struct option longopts[] = {
184 	PMCFILTER_LOPTS,
185 	{ "sort",	required_argument,	NULL,	's' },
186 	{ NULL,		0,			NULL,	0 }
187 };
188 
189 static void
usage(void)190 usage(void)
191 {
192 	printf("Usage: pmc frontend [options] [pmclog]\n\n");
193 	printf("Analyze frontend bottlenecks\n\n");
194 	printf("Options:\n");
195 	printf("\t-s,--sort         Sort by event\n\n");
196 	PMCFILTER_PRINTOPTS();
197 }
198 
199 int
cmd_pmc_frontend(int argc,char ** argv)200 cmd_pmc_frontend(int argc, char **argv)
201 {
202 	pmcfilter filter = pmcfilter();
203 	const char *logfile = "default.log";
204 	int option, logfd;
205 
206 	while ((option = getopt_long(argc, argv, PMCFILTER_SOPTS, longopts, NULL)) != -1) {
207 		switch (option) {
208 		PMCFILTER_CASE(filter)
209 		case 's':
210 			sortcol = atoi(optarg);
211 			break;
212 		case '?':
213 		default:
214 			usage();
215 			exit(EX_USAGE);
216 		}
217 	}
218 	argc -= optind;
219 	argv += optind;
220 	if (argc != 0 && argc != 1) {
221 		usage();
222 		exit(EX_USAGE);
223 	}
224 	if (argc == 1)
225 		logfile = argv[0];
226 
227 	setup_screen();
228 
229 	if ((logfd = open(logfile, O_RDONLY)) < 0) {
230 		errx(EX_OSERR, "ERROR: Cannot open \"%s\" for reading: %s.", logfile,
231 		    strerror(errno));
232 	}
233 
234 	frontend_view v = frontend_view();
235 	v.setfilter(filter);
236 	v.process(logfd);
237 	v.print();
238 
239 	close(logfd);
240 
241 	return (0);
242 }
243