Home | History | Annotate | Line # | Download | only in tprof
      1 /*	$NetBSD: tprof.c,v 1.21 2023/04/17 08:37:24 msaitoh Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2018 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * This code is derived from software contributed to The NetBSD Foundation
      8  * by Maxime Villard.
      9  *
     10  * Redistribution and use in source and binary forms, with or without
     11  * modification, are permitted provided that the following conditions
     12  * are met:
     13  * 1. Redistributions of source code must retain the above copyright
     14  *    notice, this list of conditions and the following disclaimer.
     15  * 2. Redistributions in binary form must reproduce the above copyright
     16  *    notice, this list of conditions and the following disclaimer in the
     17  *    documentation and/or other materials provided with the distribution.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     29  * POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /*
     33  * Copyright (c)2008 YAMAMOTO Takashi,
     34  * All rights reserved.
     35  *
     36  * Redistribution and use in source and binary forms, with or without
     37  * modification, are permitted provided that the following conditions
     38  * are met:
     39  * 1. Redistributions of source code must retain the above copyright
     40  *    notice, this list of conditions and the following disclaimer.
     41  * 2. Redistributions in binary form must reproduce the above copyright
     42  *    notice, this list of conditions and the following disclaimer in the
     43  *    documentation and/or other materials provided with the distribution.
     44  *
     45  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     46  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     47  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     48  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     49  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     50  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     51  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     52  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     53  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     54  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     55  * SUCH DAMAGE.
     56  */
     57 
     58 #include <sys/cdefs.h>
     59 #ifndef lint
     60 __RCSID("$NetBSD: tprof.c,v 1.21 2023/04/17 08:37:24 msaitoh Exp $");
     61 #endif /* not lint */
     62 
     63 #include <sys/atomic.h>
     64 #include <sys/ioctl.h>
     65 #include <sys/sysctl.h>
     66 #include <sys/wait.h>
     67 
     68 #include <dev/tprof/tprof_ioctl.h>
     69 
     70 #include <err.h>
     71 #include <errno.h>
     72 #include <fcntl.h>
     73 #include <inttypes.h>
     74 #include <math.h>
     75 #include <pthread.h>
     76 #include <signal.h>
     77 #include <stdbool.h>
     78 #include <stdio.h>
     79 #include <stdlib.h>
     80 #include <string.h>
     81 #include <time.h>
     82 #include <unistd.h>
     83 #include <util.h>
     84 #include "tprof.h"
     85 
     86 #define	_PATH_TPROF	"/dev/tprof"
     87 
     88 struct tprof_info tprof_info;
     89 u_int ncounters;
     90 int devfd;
     91 int outfd;
     92 int ncpu;
     93 u_int nevent;
     94 double interval = 0xffffffff;	/* XXX */
     95 const char *eventname[TPROF_MAXCOUNTERS];
     96 u_int eventnamewidth[TPROF_MAXCOUNTERS];
     97 #define	COUNTER_COLUMNS_WIDTH	11
     98 
     99 static void tprof_list(int, char **);
    100 static void tprof_monitor_common(bool, int, char **) __dead;
    101 static void tprof_monitor(int, char **) __dead;
    102 static void tprof_count(int, char **) __dead;
    103 
    104 static struct cmdtab {
    105 	const char *label;
    106 	bool takesargs;
    107 	bool argsoptional;
    108 	void (*func)(int, char **);
    109 } const tprof_cmdtab[] = {
    110 	{ "list",	false, false, tprof_list },
    111 	{ "monitor",	true,  false, tprof_monitor },
    112 	{ "count",	true,  false, tprof_count },
    113 	{ "analyze",	true,  true,  tprof_analyze },
    114 	{ "top",	true,  true,  tprof_top },
    115 	{ NULL,		false, false, NULL },
    116 };
    117 
    118 __dead static void
    119 usage(void)
    120 {
    121 
    122 	fprintf(stderr, "%s op [arguments]\n", getprogname());
    123 	fprintf(stderr, "\n");
    124 	fprintf(stderr, "\tlist\n");
    125 	fprintf(stderr, "\t\tList the available events.\n");
    126 	fprintf(stderr, "\tmonitor -e name[:option] [-e ...] [-o outfile]"
    127 	    " command\n");
    128 	fprintf(stderr, "\t\tMonitor the event 'name' with option 'option'\n"
    129 	    "\t\tcounted during the execution of 'command'.\n");
    130 	fprintf(stderr, "\tcount -e name[:option] [-e ...] [-i interval]"
    131 	    " command\n");
    132 	fprintf(stderr, "\t\tSame as monitor, but does not profile,"
    133 	    " only outputs a counter.\n");
    134 	fprintf(stderr, "\tanalyze [-CkLPs] [-p pid] file\n");
    135 	fprintf(stderr, "\t\tAnalyze the samples of the file 'file'.\n");
    136 	fprintf(stderr, "\ttop [-e name [-e ...]] [-i interval] [-acu]\n");
    137 	fprintf(stderr, "\t\tDisplay profiling results in real-time.\n");
    138 	exit(EXIT_FAILURE);
    139 }
    140 
    141 static int
    142 getncpu(void)
    143 {
    144 	size_t size;
    145 	int mib[2];
    146 
    147 	mib[0] = CTL_HW;
    148 	mib[1] = HW_NCPU;
    149 	size = sizeof(ncpu);
    150 	if (sysctl(mib, 2, &ncpu, &size, NULL, 0) == -1)
    151 		ncpu = 1;
    152 	return ncpu;
    153 }
    154 
    155 static void *
    156 process_samples(void *dummy)
    157 {
    158 
    159 	for (;;) {
    160 		char buf[4096];
    161 		const char *cp;
    162 		ssize_t ssz;
    163 
    164 		ssz = read(devfd, buf, sizeof(buf));
    165 		if (ssz == -1) {
    166 			err(EXIT_FAILURE, "read");
    167 		}
    168 		if (ssz == 0) {
    169 			break;
    170 		}
    171 		cp = buf;
    172 		while (ssz) {
    173 			ssize_t wsz;
    174 
    175 			wsz = write(outfd, cp, ssz);
    176 			if (wsz == -1) {
    177 				err(EXIT_FAILURE, "write");
    178 			}
    179 			ssz -= wsz;
    180 			cp += wsz;
    181 		}
    182 	}
    183 	return NULL;
    184 }
    185 
    186 static void
    187 show_counters(void)
    188 {
    189 	unsigned int i;
    190 	int n, ret;
    191 
    192 	fprintf(stderr, "      ");
    193 	for (i = 0; i < nevent; i++)
    194 		fprintf(stderr, " %*s", eventnamewidth[i], eventname[i]);
    195 	fprintf(stderr, "\n");
    196 
    197 	for (n = 0; n < ncpu; n++) {
    198 		tprof_counts_t counts;
    199 
    200 		memset(&counts, 0, sizeof(counts));
    201 		counts.c_cpu = n;
    202 		ret = ioctl(devfd, TPROF_IOC_GETCOUNTS, &counts);
    203 		if (ret == -1)
    204 			err(EXIT_FAILURE, "TPROF_IOC_GETCOUNTS");
    205 
    206 		fprintf(stderr, "CPU%-3d", n);
    207 		for (i = 0; i < nevent; i++) {
    208 			fprintf(stderr, " %*"PRIu64,
    209 			    eventnamewidth[i], counts.c_count[i]);
    210 		}
    211 		fprintf(stderr, "\n");
    212 	}
    213 }
    214 
    215 /* XXX: avoid mixing with the output of the child process SIGINFO handler... */
    216 static void
    217 output_delay(void)
    218 {
    219 	struct timespec delay_ts;
    220 
    221 	delay_ts.tv_sec = 0;
    222 	delay_ts.tv_nsec = 100000000;
    223 	nanosleep(&delay_ts, NULL);
    224 }
    225 
    226 static void
    227 siginfo_nothing(int signo)
    228 {
    229 	__nothing;
    230 }
    231 
    232 static void
    233 siginfo_showcount(int signo)
    234 {
    235 	output_delay();
    236 	show_counters();
    237 }
    238 
    239 static void *
    240 process_stat(void *arg)
    241 {
    242 	unsigned int *done = arg;
    243 	double ival, fval;
    244 	struct timespec ts;
    245 
    246 	ival = floor(interval);
    247 	fval = (1000000000 * (interval - ival));
    248 	ts.tv_sec = ival;
    249 	ts.tv_nsec = fval;
    250 
    251 	while (atomic_add_int_nv(done, 0) == 0) {
    252 		show_counters();
    253 		nanosleep(&ts, NULL);
    254 		if (errno == EINTR)	/* interrupted by SIGINFO? */
    255 			output_delay();
    256 	}
    257 	return NULL;
    258 }
    259 
    260 static void
    261 tprof_list(int argc, char **argv)
    262 {
    263 	const char *defaultevent = tprof_cycle_event_name();
    264 
    265 	printf("%u events can be counted at the same time.\n", ncounters);
    266 	if (defaultevent != NULL)
    267 		printf("The default counter for monitor and top command is "
    268 		    "\"%s\".\n", defaultevent);
    269 	tprof_event_list();
    270 }
    271 
    272 int
    273 tprof_parse_event(tprof_param_t *param, const char *str, uint32_t flags,
    274     const char **eventnamep, char **errmsgp)
    275 {
    276 	double d;
    277 	uint64_t n;
    278 	int error = 0;
    279 	char *p, *event = NULL, *opt = NULL, *scale = NULL;
    280 	bool allow_option, allow_scale;
    281 	static char errmsgbuf[128];
    282 
    283 	allow_option = flags & TPROF_PARSE_EVENT_F_ALLOWOPTION;
    284 	allow_scale = flags & TPROF_PARSE_EVENT_F_ALLOWSCALE;
    285 
    286 	p = estrdup(str);
    287 	event = p;
    288 	if (allow_option) {
    289 		opt = strchr(p, ':');
    290 		if (opt != NULL) {
    291 			*opt++ = '\0';
    292 			p = opt;
    293 		}
    294 	}
    295 	if (allow_scale) {
    296 		scale = strchr(p, ',');
    297 		if (scale != NULL)
    298 			*scale++ = '\0';
    299 	}
    300 
    301 	tprof_event_lookup(event, param);
    302 
    303 	if (opt != NULL) {
    304 		while (*opt != '\0') {
    305 			switch (*opt) {
    306 			case 'u':
    307 				param->p_flags |= TPROF_PARAM_USER;
    308 				break;
    309 			case 'k':
    310 				param->p_flags |= TPROF_PARAM_KERN;
    311 				break;
    312 			default:
    313 				error = -1;
    314 				snprintf(errmsgbuf, sizeof(errmsgbuf),
    315 				    "invalid option: '%c'", *opt);
    316 				goto done;
    317 			}
    318 			opt++;
    319 		}
    320 	} else if (allow_option) {
    321 		param->p_flags |= TPROF_PARAM_USER;
    322 		param->p_flags |= TPROF_PARAM_KERN;
    323 	}
    324 
    325 	if (scale != NULL) {
    326 		if (*scale == '=') {
    327 			scale++;
    328 			n = strtoull(scale, &p, 0);
    329 			if (*p != '\0') {
    330 				error = -1;
    331 			} else {
    332 				param->p_value2 = n;
    333 				param->p_flags |=
    334 				    TPROF_PARAM_VALUE2_TRIGGERCOUNT;
    335 			}
    336 		} else {
    337 			if (strncasecmp("0x", scale, 2) == 0)
    338 				d = strtol(scale, &p, 0);
    339 			else
    340 				d = strtod(scale, &p);
    341 			if (*p != '\0' || d <= 0) {
    342 				error = -1;
    343 			} else {
    344 				param->p_value2 = 0x100000000ULL / d;
    345 				param->p_flags |= TPROF_PARAM_VALUE2_SCALE;
    346 			}
    347 		}
    348 
    349 		if (error != 0) {
    350 			snprintf(errmsgbuf, sizeof(errmsgbuf),
    351 			    "invalid scale: %s", scale);
    352 			goto done;
    353 		}
    354 	}
    355 
    356  done:
    357 	if (eventnamep != NULL)
    358 		*eventnamep = event;
    359 	if (error != 0 && errmsgp != NULL)
    360 		*errmsgp = errmsgbuf;
    361 	return error;
    362 }
    363 
    364 const char *
    365 tprof_cycle_event_name(void)
    366 {
    367 	const char *cycleevent;
    368 
    369 	switch (tprof_info.ti_ident) {
    370 	case TPROF_IDENT_INTEL_GENERIC:
    371 		cycleevent = "unhalted-core-cycles";
    372 		break;
    373 	case TPROF_IDENT_AMD_GENERIC:
    374 		cycleevent = "LsNotHaltedCyc";
    375 		break;
    376 	case TPROF_IDENT_ARMV8_GENERIC:
    377 	case TPROF_IDENT_ARMV7_GENERIC:
    378 		cycleevent = "CPU_CYCLES";
    379 		break;
    380 	default:
    381 		cycleevent = NULL;
    382 		break;
    383 	}
    384 	return cycleevent;
    385 }
    386 
    387 static void
    388 tprof_monitor_common(bool do_profile, int argc, char **argv)
    389 {
    390 	const char *outfile = "tprof.out";
    391 	struct tprof_stat ts;
    392 	tprof_param_t params[TPROF_MAXCOUNTERS];
    393 	pid_t pid;
    394 	pthread_t pt;
    395 	int ret, ch, i;
    396 	char *p, *errmsg;
    397 	tprof_countermask_t mask = TPROF_COUNTERMASK_ALL;
    398 
    399 	memset(params, 0, sizeof(params));
    400 
    401 	while ((ch = getopt(argc, argv, do_profile ? "o:e:" : "e:i:")) != -1) {
    402 		switch (ch) {
    403 		case 'o':
    404 			outfile = optarg;
    405 			break;
    406 		case 'i':
    407 			interval = strtod(optarg, &p);
    408 			if (*p != '\0' || interval <= 0)
    409 				errx(EXIT_FAILURE, "Bad/invalid interval: %s",
    410 				    optarg);
    411 			break;
    412 		case 'e':
    413 			if (tprof_parse_event(&params[nevent], optarg,
    414 			    TPROF_PARSE_EVENT_F_ALLOWOPTION |
    415 			    (do_profile ? TPROF_PARSE_EVENT_F_ALLOWSCALE : 0),
    416 			    &eventname[nevent], &errmsg) != 0) {
    417 				errx(EXIT_FAILURE, "%s", errmsg);
    418 			}
    419 			eventnamewidth[nevent] = strlen(eventname[nevent]);
    420 			if (eventnamewidth[nevent] < COUNTER_COLUMNS_WIDTH)
    421 				eventnamewidth[nevent] = COUNTER_COLUMNS_WIDTH;
    422 			nevent++;
    423 			if (nevent > __arraycount(params) ||
    424 			    nevent > ncounters)
    425 				errx(EXIT_FAILURE, "Too many events. Only a"
    426 				    " maximum of %d counters can be used.",
    427 				    ncounters);
    428 			break;
    429 		default:
    430 			usage();
    431 		}
    432 	}
    433 	argc -= optind;
    434 	argv += optind;
    435 	if (argc == 0)
    436 		usage();
    437 	if (nevent == 0) {
    438 		const char *defaultevent = tprof_cycle_event_name();
    439 		if (defaultevent == NULL)
    440 			errx(EXIT_FAILURE, "cpu not supported");
    441 
    442 		tprof_event_lookup(defaultevent, &params[nevent]);
    443 		eventname[nevent] = defaultevent;
    444 		params[nevent].p_flags |= TPROF_PARAM_KERN;
    445 		nevent++;
    446 	}
    447 
    448 	if (do_profile) {
    449 		outfd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    450 		if (outfd == -1) {
    451 			err(EXIT_FAILURE, "%s", outfile);
    452 		}
    453 	}
    454 
    455 	for (i = 0; i < (int)nevent; i++) {
    456 		params[i].p_counter = i;
    457 		if (do_profile)
    458 			params[i].p_flags |= TPROF_PARAM_PROFILE;
    459 		ret = ioctl(devfd, TPROF_IOC_CONFIGURE_EVENT, &params[i]);
    460 		if (ret == -1) {
    461 			err(EXIT_FAILURE, "TPROF_IOC_CONFIGURE_EVENT: %s",
    462 			    eventname[i]);
    463 		}
    464 	}
    465 
    466 	ret = ioctl(devfd, TPROF_IOC_START, &mask);
    467 	if (ret == -1) {
    468 		err(EXIT_FAILURE, "TPROF_IOC_START");
    469 	}
    470 
    471 	pid = fork();
    472 	switch (pid) {
    473 	case -1:
    474 		err(EXIT_FAILURE, "fork");
    475 	case 0:
    476 		close(devfd);
    477 		execvp(argv[0], argv);
    478 		_Exit(EXIT_FAILURE);
    479 	}
    480 
    481 	signal(SIGINT, SIG_IGN);
    482 	if (do_profile)
    483 		signal(SIGINFO, siginfo_showcount);
    484 	else
    485 		signal(SIGINFO, siginfo_nothing);
    486 
    487 	unsigned int done = 0;
    488 	if (do_profile)
    489 		ret = pthread_create(&pt, NULL, process_samples, NULL);
    490 	else
    491 		ret = pthread_create(&pt, NULL, process_stat, &done);
    492 	if (ret != 0)
    493 		errx(1, "pthread_create: %s", strerror(ret));
    494 
    495 	for (;;) {
    496 		int status;
    497 
    498 		pid = wait4(-1, &status, 0, NULL);
    499 		if (pid == -1) {
    500 			if (errno == ECHILD) {
    501 				break;
    502 			}
    503 			err(EXIT_FAILURE, "wait4");
    504 		}
    505 		if (pid != 0 && WIFEXITED(status)) {
    506 			break;
    507 		}
    508 	}
    509 
    510 	ret = ioctl(devfd, TPROF_IOC_STOP, &mask);
    511 	if (ret == -1) {
    512 		err(EXIT_FAILURE, "TPROF_IOC_STOP");
    513 	}
    514 
    515 	if (!do_profile) {
    516 		atomic_add_int(&done, 1);	/* terminate thread */
    517 		kill(0, SIGINFO);
    518 	}
    519 
    520 	pthread_join(pt, NULL);
    521 
    522 	if (do_profile) {
    523 		ret = ioctl(devfd, TPROF_IOC_GETSTAT, &ts);
    524 		if (ret == -1)
    525 			err(EXIT_FAILURE, "TPROF_IOC_GETSTAT");
    526 
    527 		fprintf(stderr, "\n%s statistics:\n", getprogname());
    528 		fprintf(stderr, "\tsample %" PRIu64 "\n", ts.ts_sample);
    529 		fprintf(stderr, "\toverflow %" PRIu64 "\n", ts.ts_overflow);
    530 		fprintf(stderr, "\tbuf %" PRIu64 "\n", ts.ts_buf);
    531 		fprintf(stderr, "\temptybuf %" PRIu64 "\n", ts.ts_emptybuf);
    532 		fprintf(stderr, "\tdropbuf %" PRIu64 "\n", ts.ts_dropbuf);
    533 		fprintf(stderr, "\tdropbuf_sample %" PRIu64 "\n",
    534 		    ts.ts_dropbuf_sample);
    535 
    536 		fprintf(stderr, "\n");
    537 	}
    538 	show_counters();
    539 
    540 	exit(EXIT_SUCCESS);
    541 }
    542 
    543 static void
    544 tprof_monitor(int argc, char **argv)
    545 {
    546 	tprof_monitor_common(true, argc, argv);
    547 }
    548 
    549 static void
    550 tprof_count(int argc, char **argv)
    551 {
    552 	tprof_monitor_common(false, argc, argv);
    553 }
    554 
    555 int
    556 main(int argc, char *argv[])
    557 {
    558 	const struct cmdtab *ct;
    559 	int ret;
    560 
    561 	getncpu();
    562 	setprogname(argv[0]);
    563 	argv += 1, argc -= 1;
    564 
    565 	devfd = open(_PATH_TPROF, O_RDWR);
    566 	if (devfd == -1) {
    567 		err(EXIT_FAILURE, "%s", _PATH_TPROF);
    568 	}
    569 
    570 	ret = ioctl(devfd, TPROF_IOC_GETINFO, &tprof_info);
    571 	if (ret == -1) {
    572 		err(EXIT_FAILURE, "TPROF_IOC_GETINFO");
    573 	}
    574 	if (tprof_info.ti_version != TPROF_VERSION) {
    575 		errx(EXIT_FAILURE, "version mismatch: version=%d, expected=%d",
    576 		    tprof_info.ti_version, TPROF_VERSION);
    577 	}
    578 	if (tprof_event_init(tprof_info.ti_ident) == -1) {
    579 		errx(EXIT_FAILURE, "cpu not supported");
    580 	}
    581 
    582 	ret = ioctl(devfd, TPROF_IOC_GETNCOUNTERS, &ncounters);
    583 	if (ret == -1) {
    584 		err(EXIT_FAILURE, "TPROF_IOC_GETNCOUNTERS");
    585 	}
    586 	if (ncounters == 0) {
    587 		errx(EXIT_FAILURE, "no available counters");
    588 	}
    589 
    590 	if (argc == 0)
    591 		usage();
    592 
    593 	for (ct = tprof_cmdtab; ct->label != NULL; ct++) {
    594 		if (strcmp(argv[0], ct->label) == 0) {
    595 			if (!ct->argsoptional &&
    596 			    ((ct->takesargs == 0) ^ (argv[1] == NULL)))
    597 			{
    598 				usage();
    599 			}
    600 			(*ct->func)(argc, argv);
    601 			break;
    602 		}
    603 	}
    604 	if (ct->label == NULL) {
    605 		usage();
    606 	}
    607 }
    608