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
usage(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
getncpu(void)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 *
process_samples(void * dummy)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
show_counters(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
output_delay(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
siginfo_nothing(int signo)227 siginfo_nothing(int signo)
228 {
229           __nothing;
230 }
231 
232 static void
siginfo_showcount(int signo)233 siginfo_showcount(int signo)
234 {
235           output_delay();
236           show_counters();
237 }
238 
239 static void *
process_stat(void * arg)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
tprof_list(int argc,char ** argv)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
tprof_parse_event(tprof_param_t * param,const char * str,uint32_t flags,const char ** eventnamep,char ** errmsgp)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 *
tprof_cycle_event_name(void)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
tprof_monitor_common(bool do_profile,int argc,char ** argv)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
tprof_monitor(int argc,char ** argv)544 tprof_monitor(int argc, char **argv)
545 {
546           tprof_monitor_common(true, argc, argv);
547 }
548 
549 static void
tprof_count(int argc,char ** argv)550 tprof_count(int argc, char **argv)
551 {
552           tprof_monitor_common(false, argc, argv);
553 }
554 
555 int
main(int argc,char * argv[])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