xref: /netbsd-src/usr.sbin/tprof/tprof_analyze.c (revision 33b82b03392e2106db8728db8eea7f5301157325)
1*33b82b03Srillig /*	$NetBSD: tprof_analyze.c,v 1.9 2024/11/03 10:43:27 rillig Exp $	*/
296b21aedSmaxv 
396b21aedSmaxv /*
496b21aedSmaxv  * Copyright (c) 2010,2011,2012 YAMAMOTO Takashi,
596b21aedSmaxv  * All rights reserved.
696b21aedSmaxv  *
796b21aedSmaxv  * Redistribution and use in source and binary forms, with or without
896b21aedSmaxv  * modification, are permitted provided that the following conditions
996b21aedSmaxv  * are met:
1096b21aedSmaxv  * 1. Redistributions of source code must retain the above copyright
1196b21aedSmaxv  *    notice, this list of conditions and the following disclaimer.
1296b21aedSmaxv  * 2. Redistributions in binary form must reproduce the above copyright
1396b21aedSmaxv  *    notice, this list of conditions and the following disclaimer in the
1496b21aedSmaxv  *    documentation and/or other materials provided with the distribution.
1596b21aedSmaxv  *
1696b21aedSmaxv  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1796b21aedSmaxv  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1896b21aedSmaxv  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1996b21aedSmaxv  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2096b21aedSmaxv  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2196b21aedSmaxv  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2296b21aedSmaxv  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2396b21aedSmaxv  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2496b21aedSmaxv  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2596b21aedSmaxv  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2696b21aedSmaxv  * SUCH DAMAGE.
2796b21aedSmaxv  */
2896b21aedSmaxv 
2996b21aedSmaxv #include <sys/cdefs.h>
3096b21aedSmaxv #ifndef lint
31*33b82b03Srillig __RCSID("$NetBSD: tprof_analyze.c,v 1.9 2024/11/03 10:43:27 rillig Exp $");
3296b21aedSmaxv #endif /* not lint */
3396b21aedSmaxv 
3496b21aedSmaxv #include <assert.h>
3596b21aedSmaxv #include <err.h>
3696b21aedSmaxv #include <errno.h>
3796b21aedSmaxv #include <fcntl.h>
3896b21aedSmaxv #include <gelf.h>
3996b21aedSmaxv #include <inttypes.h>
4096b21aedSmaxv #include <libelf.h>
4196b21aedSmaxv #include <stdbool.h>
4296b21aedSmaxv #include <stdlib.h>
4396b21aedSmaxv #include <stdio.h>
4496b21aedSmaxv #include <unistd.h>
4596b21aedSmaxv #include <string.h>
4696b21aedSmaxv #include <util.h>
4796b21aedSmaxv #include <dev/tprof/tprof_ioctl.h>
4896b21aedSmaxv #include "tprof.h"
4914ff263eSryo #include "ksyms.h"
5096b21aedSmaxv 
5196b21aedSmaxv #include <sys/rbtree.h>
5296b21aedSmaxv 
5396b21aedSmaxv static bool filter_by_pid;
5496b21aedSmaxv static pid_t target_pid;
5596b21aedSmaxv static bool per_symbol;
5696b21aedSmaxv 
5796b21aedSmaxv struct addr {
5896b21aedSmaxv 	struct rb_node node;
5996b21aedSmaxv 	uint64_t addr;		/* address */
6096b21aedSmaxv 	uint32_t pid;		/* process id */
6196b21aedSmaxv 	uint32_t lwpid;		/* lwp id */
6296b21aedSmaxv 	uint32_t cpuid;		/* cpu id */
6396b21aedSmaxv 	bool in_kernel;		/* if addr is in the kernel address space */
6496b21aedSmaxv 	unsigned int nsamples;	/* number of samples taken for the address */
65caba1a6fSryo 	unsigned int ncount[TPROF_MAXCOUNTERS];	/* count per event */
6696b21aedSmaxv };
6796b21aedSmaxv 
6896b21aedSmaxv static rb_tree_t addrtree;
6996b21aedSmaxv 
7096b21aedSmaxv static signed int
7196b21aedSmaxv addrtree_compare_key(void *ctx, const void *n1, const void *keyp)
7296b21aedSmaxv {
7396b21aedSmaxv 	const struct addr *a1 = n1;
7496b21aedSmaxv 	const struct addr *a2 = (const struct addr *)keyp;
7596b21aedSmaxv 
7696b21aedSmaxv 	if (a1->addr > a2->addr) {
7796b21aedSmaxv 		return 1;
7896b21aedSmaxv 	} else if (a1->addr < a2->addr) {
7996b21aedSmaxv 		return -1;
8096b21aedSmaxv 	}
8196b21aedSmaxv 	if (a1->pid > a2->pid) {
8296b21aedSmaxv 		return -1;
8396b21aedSmaxv 	} else if (a1->pid < a2->pid) {
8496b21aedSmaxv 		return 1;
8596b21aedSmaxv 	}
8696b21aedSmaxv 	if (a1->lwpid > a2->lwpid) {
8796b21aedSmaxv 		return -1;
8896b21aedSmaxv 	} else if (a1->lwpid < a2->lwpid) {
8996b21aedSmaxv 		return 1;
9096b21aedSmaxv 	}
9196b21aedSmaxv 	if (a1->cpuid > a2->cpuid) {
9296b21aedSmaxv 		return -1;
9396b21aedSmaxv 	} else if (a1->cpuid < a2->cpuid) {
9496b21aedSmaxv 		return 1;
9596b21aedSmaxv 	}
9696b21aedSmaxv 	if (a1->in_kernel > a2->in_kernel) {
9796b21aedSmaxv 		return -1;
9896b21aedSmaxv 	} else if (a1->in_kernel < a2->in_kernel) {
9996b21aedSmaxv 		return 1;
10096b21aedSmaxv 	}
10196b21aedSmaxv 	return 0;
10296b21aedSmaxv }
10396b21aedSmaxv 
10496b21aedSmaxv static signed int
10596b21aedSmaxv addrtree_compare_nodes(void *ctx, const void *n1, const void *n2)
10696b21aedSmaxv {
10796b21aedSmaxv 	const struct addr *a2 = n2;
10896b21aedSmaxv 
10996b21aedSmaxv 	return addrtree_compare_key(ctx, n1, a2);
11096b21aedSmaxv }
11196b21aedSmaxv 
11296b21aedSmaxv static const rb_tree_ops_t addrtree_ops = {
11396b21aedSmaxv 	.rbto_compare_nodes = addrtree_compare_nodes,
11496b21aedSmaxv 	.rbto_compare_key = addrtree_compare_key,
11596b21aedSmaxv };
11696b21aedSmaxv 
11796b21aedSmaxv static int
11896b21aedSmaxv compare_nsamples(const void *p1, const void *p2)
11996b21aedSmaxv {
12096b21aedSmaxv 	const struct addr *a1 = *(const struct addr * const *)p1;
12196b21aedSmaxv 	const struct addr *a2 = *(const struct addr * const *)p2;
12296b21aedSmaxv 
12396b21aedSmaxv 	if (a1->nsamples > a2->nsamples) {
12496b21aedSmaxv 		return -1;
12596b21aedSmaxv 	} else if (a1->nsamples < a2->nsamples) {
12696b21aedSmaxv 		return 1;
12796b21aedSmaxv 	}
12896b21aedSmaxv 	return 0;
12996b21aedSmaxv }
13096b21aedSmaxv 
13196b21aedSmaxv void
13296b21aedSmaxv tprof_analyze(int argc, char **argv)
13396b21aedSmaxv {
13496b21aedSmaxv 	struct addr *a;
13596b21aedSmaxv 	struct addr **l;
13696b21aedSmaxv 	struct addr **p;
1379896bc73Smaxv 	size_t naddrs, nsamples, i;
1389896bc73Smaxv 	float perc;
13996b21aedSmaxv 	int ch;
140caba1a6fSryo 	u_int c, maxevent = 0;
14196b21aedSmaxv 	bool distinguish_processes = true;
14296b21aedSmaxv 	bool distinguish_cpus = true;
14396b21aedSmaxv 	bool distinguish_lwps = true;
14496b21aedSmaxv 	bool kernel_only = false;
145c52e6df8Smaxv 	FILE *f;
14696b21aedSmaxv 
14796b21aedSmaxv 	while ((ch = getopt(argc, argv, "CkLPp:s")) != -1) {
14896b21aedSmaxv 		uintmax_t val;
14996b21aedSmaxv 		char *ep;
15096b21aedSmaxv 
15196b21aedSmaxv 		switch (ch) {
15296b21aedSmaxv 		case 'C':	/* don't distinguish cpus */
15396b21aedSmaxv 			distinguish_cpus = false;
15496b21aedSmaxv 			break;
15596b21aedSmaxv 		case 'k':	/* kernel only */
15696b21aedSmaxv 			kernel_only = true;
15796b21aedSmaxv 			break;
15896b21aedSmaxv 		case 'L':	/* don't distinguish lwps */
15996b21aedSmaxv 			distinguish_lwps = false;
16096b21aedSmaxv 			break;
16196b21aedSmaxv 		case 'p':	/* only for the process for the given pid */
16296b21aedSmaxv 			errno = 0;
16396b21aedSmaxv 			val = strtoumax(optarg, &ep, 10);
16496b21aedSmaxv 			if (optarg[0] == 0 || *ep != 0 ||
16596b21aedSmaxv 			    val > INT32_MAX) {
16696b21aedSmaxv 				errx(EXIT_FAILURE, "invalid p option");
16796b21aedSmaxv 			}
16896b21aedSmaxv 			target_pid = val;
16996b21aedSmaxv 			filter_by_pid = true;
17096b21aedSmaxv 			break;
17196b21aedSmaxv 		case 'P':	/* don't distinguish processes */
17296b21aedSmaxv 			distinguish_processes = false;
17396b21aedSmaxv 			break;
17496b21aedSmaxv 		case 's':	/* per symbol */
17596b21aedSmaxv 			per_symbol = true;
17696b21aedSmaxv 			break;
17796b21aedSmaxv 		default:
17896b21aedSmaxv 			exit(EXIT_FAILURE);
17996b21aedSmaxv 		}
18096b21aedSmaxv 	}
18196b21aedSmaxv 	argc -= optind;
18296b21aedSmaxv 	argv += optind;
18396b21aedSmaxv 
184c52e6df8Smaxv 	if (argc == 0) {
185c52e6df8Smaxv 		errx(EXIT_FAILURE, "missing file name");
186c52e6df8Smaxv 	}
187c52e6df8Smaxv 
188c52e6df8Smaxv 	f = fopen(argv[0], "rb");
189c52e6df8Smaxv 	if (f == NULL) {
190c52e6df8Smaxv 		errx(EXIT_FAILURE, "fopen");
191c52e6df8Smaxv 	}
192c52e6df8Smaxv 
193e93233dbSryo 	ksymload(NULL);
19496b21aedSmaxv 	rb_tree_init(&addrtree, &addrtree_ops);
19596b21aedSmaxv 
19696b21aedSmaxv 	/*
19796b21aedSmaxv 	 * read and count samples.
19896b21aedSmaxv 	 */
19996b21aedSmaxv 
20096b21aedSmaxv 	naddrs = 0;
2019896bc73Smaxv 	nsamples = 0;
20296b21aedSmaxv 	while (/*CONSTCOND*/true) {
20396b21aedSmaxv 		struct addr *o;
20496b21aedSmaxv 		tprof_sample_t sample;
205c52e6df8Smaxv 		size_t n = fread(&sample, sizeof(sample), 1, f);
20696b21aedSmaxv 		bool in_kernel;
20796b21aedSmaxv 
20896b21aedSmaxv 		if (n == 0) {
209c52e6df8Smaxv 			if (feof(f)) {
21096b21aedSmaxv 				break;
21196b21aedSmaxv 			}
212c52e6df8Smaxv 			if (ferror(f)) {
21396b21aedSmaxv 				err(EXIT_FAILURE, "fread");
21496b21aedSmaxv 			}
21596b21aedSmaxv 		}
21696b21aedSmaxv 		if (filter_by_pid && (pid_t)sample.s_pid != target_pid) {
21796b21aedSmaxv 			continue;
21896b21aedSmaxv 		}
21996b21aedSmaxv 		in_kernel = (sample.s_flags & TPROF_SAMPLE_INKERNEL) != 0;
22096b21aedSmaxv 		if (kernel_only && !in_kernel) {
22196b21aedSmaxv 			continue;
22296b21aedSmaxv 		}
22396b21aedSmaxv 		a = emalloc(sizeof(*a));
224caba1a6fSryo 		memset(a, 0, sizeof(*a));
22596b21aedSmaxv 		a->addr = (uint64_t)sample.s_pc;
22696b21aedSmaxv 		if (distinguish_processes) {
22796b21aedSmaxv 			a->pid = sample.s_pid;
22896b21aedSmaxv 		} else {
22996b21aedSmaxv 			a->pid = 0;
23096b21aedSmaxv 		}
23196b21aedSmaxv 		if (distinguish_lwps) {
23296b21aedSmaxv 			a->lwpid = sample.s_lwpid;
23396b21aedSmaxv 		} else {
23496b21aedSmaxv 			a->lwpid = 0;
23596b21aedSmaxv 		}
23696b21aedSmaxv 		if (distinguish_cpus) {
23796b21aedSmaxv 			a->cpuid = sample.s_cpuid;
23896b21aedSmaxv 		} else {
23996b21aedSmaxv 			a->cpuid = 0;
24096b21aedSmaxv 		}
24196b21aedSmaxv 		a->in_kernel = in_kernel;
24296b21aedSmaxv 		if (per_symbol) {
24396b21aedSmaxv 			const char *name;
24496b21aedSmaxv 			uint64_t offset;
24596b21aedSmaxv 
246e93233dbSryo 			name = ksymlookup(a->addr, &offset, NULL);
24796b21aedSmaxv 			if (name != NULL) {
24896b21aedSmaxv 				a->addr -= offset;
24996b21aedSmaxv 			}
25096b21aedSmaxv 		}
251caba1a6fSryo 		c = __SHIFTOUT(sample.s_flags, TPROF_SAMPLE_COUNTER_MASK);
252caba1a6fSryo 		assert(c < TPROF_MAXCOUNTERS);
253caba1a6fSryo 		if (maxevent < c)
254caba1a6fSryo 			maxevent = c;
255caba1a6fSryo 
25696b21aedSmaxv 		a->nsamples = 1;
257caba1a6fSryo 		a->ncount[c] = 1;
25896b21aedSmaxv 		o = rb_tree_insert_node(&addrtree, a);
25996b21aedSmaxv 		if (o != a) {
26096b21aedSmaxv 			assert(a->addr == o->addr);
26196b21aedSmaxv 			assert(a->pid == o->pid);
26296b21aedSmaxv 			assert(a->lwpid == o->lwpid);
26396b21aedSmaxv 			assert(a->cpuid == o->cpuid);
26496b21aedSmaxv 			assert(a->in_kernel == o->in_kernel);
26596b21aedSmaxv 			free(a);
266caba1a6fSryo 
26796b21aedSmaxv 			o->nsamples++;
268caba1a6fSryo 			o->ncount[c]++;
26996b21aedSmaxv 		} else {
27096b21aedSmaxv 			naddrs++;
27196b21aedSmaxv 		}
2729896bc73Smaxv 		nsamples++;
27396b21aedSmaxv 	}
27496b21aedSmaxv 
27596b21aedSmaxv 	/*
27696b21aedSmaxv 	 * sort samples by addresses.
27796b21aedSmaxv 	 */
27896b21aedSmaxv 
27996b21aedSmaxv 	l = emalloc(naddrs * sizeof(*l));
28096b21aedSmaxv 	p = l;
28196b21aedSmaxv 	RB_TREE_FOREACH(a, &addrtree) {
28296b21aedSmaxv 		*p++ = a;
28396b21aedSmaxv 	}
28496b21aedSmaxv 	assert(l + naddrs == p);
28596b21aedSmaxv 	qsort(l, naddrs, sizeof(*l), compare_nsamples);
28696b21aedSmaxv 
28796b21aedSmaxv 	/*
28896b21aedSmaxv 	 * print addresses and number of samples, preferably with
28996b21aedSmaxv 	 * resolved symbol names.
29096b21aedSmaxv 	 */
2919896bc73Smaxv 	printf("File: %s\n", argv[0]);
2929896bc73Smaxv 	printf("Number of samples: %zu\n\n", nsamples);
293caba1a6fSryo 
294caba1a6fSryo 	printf("percentage   nsamples ");
295caba1a6fSryo 	for (c = 0; c <= maxevent; c++)
296caba1a6fSryo 		printf("event#%02u ", c);
297caba1a6fSryo 	printf("pid    lwp    cpu  k address          symbol\n");
298caba1a6fSryo 
299caba1a6fSryo 	printf("------------ -------- ");
300caba1a6fSryo 	for (c = 0; c <= maxevent; c++)
301caba1a6fSryo 		printf("-------- ");
302caba1a6fSryo 
303caba1a6fSryo 	printf("------ ------ ---- - ---------------- ------\n");
30496b21aedSmaxv 	for (i = 0; i < naddrs; i++) {
30596b21aedSmaxv 		const char *name;
30696b21aedSmaxv 		char buf[100];
30796b21aedSmaxv 		uint64_t offset;
30896b21aedSmaxv 
30996b21aedSmaxv 		a = l[i];
31096b21aedSmaxv 		if (a->in_kernel) {
311e93233dbSryo 			name = ksymlookup(a->addr, &offset, NULL);
31296b21aedSmaxv 		} else {
31396b21aedSmaxv 			name = NULL;
31496b21aedSmaxv 		}
31596b21aedSmaxv 		if (name == NULL) {
31696b21aedSmaxv 			(void)snprintf(buf, sizeof(buf), "<%016" PRIx64 ">",
31796b21aedSmaxv 			    a->addr);
31896b21aedSmaxv 			name = buf;
31996b21aedSmaxv 		} else if (offset != 0) {
32096b21aedSmaxv 			(void)snprintf(buf, sizeof(buf), "%s+0x%" PRIx64, name,
32196b21aedSmaxv 			    offset);
32296b21aedSmaxv 			name = buf;
32396b21aedSmaxv 		}
3249896bc73Smaxv 
3259896bc73Smaxv 		perc = ((float)a->nsamples / (float)nsamples) * 100.0;
3269896bc73Smaxv 
327caba1a6fSryo 		printf("%11f%% %8u", perc, a->nsamples);
328caba1a6fSryo 
329caba1a6fSryo 		for (c = 0; c <= maxevent; c++)
330caba1a6fSryo 			printf(" %8u", a->ncount[c]);
331caba1a6fSryo 
332caba1a6fSryo 		printf(" %6" PRIu32 " %6" PRIu32 " %4" PRIu32 " %u %016"
333caba1a6fSryo 		    PRIx64" %s",
334caba1a6fSryo 		    a->pid, a->lwpid, a->cpuid, a->in_kernel, a->addr, name);
335caba1a6fSryo 
336caba1a6fSryo 
337caba1a6fSryo 		printf("\n");
33896b21aedSmaxv 	}
339c52e6df8Smaxv 
340c52e6df8Smaxv 	fclose(f);
34196b21aedSmaxv }
342