1 1.9 rillig /* $NetBSD: tprof_analyze.c,v 1.9 2024/11/03 10:43:27 rillig Exp $ */ 2 1.1 maxv 3 1.1 maxv /* 4 1.1 maxv * Copyright (c) 2010,2011,2012 YAMAMOTO Takashi, 5 1.1 maxv * All rights reserved. 6 1.1 maxv * 7 1.1 maxv * Redistribution and use in source and binary forms, with or without 8 1.1 maxv * modification, are permitted provided that the following conditions 9 1.1 maxv * are met: 10 1.1 maxv * 1. Redistributions of source code must retain the above copyright 11 1.1 maxv * notice, this list of conditions and the following disclaimer. 12 1.1 maxv * 2. Redistributions in binary form must reproduce the above copyright 13 1.1 maxv * notice, this list of conditions and the following disclaimer in the 14 1.1 maxv * documentation and/or other materials provided with the distribution. 15 1.1 maxv * 16 1.1 maxv * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 1.1 maxv * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 1.1 maxv * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 1.1 maxv * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 1.1 maxv * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 1.1 maxv * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 1.1 maxv * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 1.1 maxv * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 1.1 maxv * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 1.1 maxv * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 1.1 maxv * SUCH DAMAGE. 27 1.1 maxv */ 28 1.1 maxv 29 1.1 maxv #include <sys/cdefs.h> 30 1.1 maxv #ifndef lint 31 1.9 rillig __RCSID("$NetBSD: tprof_analyze.c,v 1.9 2024/11/03 10:43:27 rillig Exp $"); 32 1.1 maxv #endif /* not lint */ 33 1.1 maxv 34 1.1 maxv #include <assert.h> 35 1.1 maxv #include <err.h> 36 1.1 maxv #include <errno.h> 37 1.1 maxv #include <fcntl.h> 38 1.1 maxv #include <gelf.h> 39 1.1 maxv #include <inttypes.h> 40 1.1 maxv #include <libelf.h> 41 1.1 maxv #include <stdbool.h> 42 1.1 maxv #include <stdlib.h> 43 1.1 maxv #include <stdio.h> 44 1.1 maxv #include <unistd.h> 45 1.1 maxv #include <string.h> 46 1.1 maxv #include <util.h> 47 1.1 maxv #include <dev/tprof/tprof_ioctl.h> 48 1.1 maxv #include "tprof.h" 49 1.7 ryo #include "ksyms.h" 50 1.1 maxv 51 1.1 maxv #include <sys/rbtree.h> 52 1.1 maxv 53 1.1 maxv static bool filter_by_pid; 54 1.1 maxv static pid_t target_pid; 55 1.1 maxv static bool per_symbol; 56 1.1 maxv 57 1.1 maxv struct addr { 58 1.1 maxv struct rb_node node; 59 1.1 maxv uint64_t addr; /* address */ 60 1.1 maxv uint32_t pid; /* process id */ 61 1.1 maxv uint32_t lwpid; /* lwp id */ 62 1.1 maxv uint32_t cpuid; /* cpu id */ 63 1.1 maxv bool in_kernel; /* if addr is in the kernel address space */ 64 1.1 maxv unsigned int nsamples; /* number of samples taken for the address */ 65 1.6 ryo unsigned int ncount[TPROF_MAXCOUNTERS]; /* count per event */ 66 1.1 maxv }; 67 1.1 maxv 68 1.1 maxv static rb_tree_t addrtree; 69 1.1 maxv 70 1.1 maxv static signed int 71 1.1 maxv addrtree_compare_key(void *ctx, const void *n1, const void *keyp) 72 1.1 maxv { 73 1.1 maxv const struct addr *a1 = n1; 74 1.1 maxv const struct addr *a2 = (const struct addr *)keyp; 75 1.1 maxv 76 1.1 maxv if (a1->addr > a2->addr) { 77 1.1 maxv return 1; 78 1.1 maxv } else if (a1->addr < a2->addr) { 79 1.1 maxv return -1; 80 1.1 maxv } 81 1.1 maxv if (a1->pid > a2->pid) { 82 1.1 maxv return -1; 83 1.1 maxv } else if (a1->pid < a2->pid) { 84 1.1 maxv return 1; 85 1.1 maxv } 86 1.1 maxv if (a1->lwpid > a2->lwpid) { 87 1.1 maxv return -1; 88 1.1 maxv } else if (a1->lwpid < a2->lwpid) { 89 1.1 maxv return 1; 90 1.1 maxv } 91 1.1 maxv if (a1->cpuid > a2->cpuid) { 92 1.1 maxv return -1; 93 1.1 maxv } else if (a1->cpuid < a2->cpuid) { 94 1.1 maxv return 1; 95 1.1 maxv } 96 1.1 maxv if (a1->in_kernel > a2->in_kernel) { 97 1.1 maxv return -1; 98 1.1 maxv } else if (a1->in_kernel < a2->in_kernel) { 99 1.1 maxv return 1; 100 1.1 maxv } 101 1.1 maxv return 0; 102 1.1 maxv } 103 1.1 maxv 104 1.1 maxv static signed int 105 1.1 maxv addrtree_compare_nodes(void *ctx, const void *n1, const void *n2) 106 1.1 maxv { 107 1.1 maxv const struct addr *a2 = n2; 108 1.1 maxv 109 1.1 maxv return addrtree_compare_key(ctx, n1, a2); 110 1.1 maxv } 111 1.1 maxv 112 1.1 maxv static const rb_tree_ops_t addrtree_ops = { 113 1.1 maxv .rbto_compare_nodes = addrtree_compare_nodes, 114 1.1 maxv .rbto_compare_key = addrtree_compare_key, 115 1.1 maxv }; 116 1.1 maxv 117 1.1 maxv static int 118 1.1 maxv compare_nsamples(const void *p1, const void *p2) 119 1.1 maxv { 120 1.1 maxv const struct addr *a1 = *(const struct addr * const *)p1; 121 1.1 maxv const struct addr *a2 = *(const struct addr * const *)p2; 122 1.1 maxv 123 1.1 maxv if (a1->nsamples > a2->nsamples) { 124 1.1 maxv return -1; 125 1.1 maxv } else if (a1->nsamples < a2->nsamples) { 126 1.1 maxv return 1; 127 1.1 maxv } 128 1.1 maxv return 0; 129 1.1 maxv } 130 1.1 maxv 131 1.1 maxv void 132 1.1 maxv tprof_analyze(int argc, char **argv) 133 1.1 maxv { 134 1.1 maxv struct addr *a; 135 1.1 maxv struct addr **l; 136 1.1 maxv struct addr **p; 137 1.3 maxv size_t naddrs, nsamples, i; 138 1.3 maxv float perc; 139 1.1 maxv int ch; 140 1.6 ryo u_int c, maxevent = 0; 141 1.1 maxv bool distinguish_processes = true; 142 1.1 maxv bool distinguish_cpus = true; 143 1.1 maxv bool distinguish_lwps = true; 144 1.1 maxv bool kernel_only = false; 145 1.2 maxv FILE *f; 146 1.1 maxv 147 1.1 maxv while ((ch = getopt(argc, argv, "CkLPp:s")) != -1) { 148 1.1 maxv uintmax_t val; 149 1.1 maxv char *ep; 150 1.1 maxv 151 1.1 maxv switch (ch) { 152 1.1 maxv case 'C': /* don't distinguish cpus */ 153 1.1 maxv distinguish_cpus = false; 154 1.1 maxv break; 155 1.1 maxv case 'k': /* kernel only */ 156 1.1 maxv kernel_only = true; 157 1.1 maxv break; 158 1.1 maxv case 'L': /* don't distinguish lwps */ 159 1.1 maxv distinguish_lwps = false; 160 1.1 maxv break; 161 1.1 maxv case 'p': /* only for the process for the given pid */ 162 1.1 maxv errno = 0; 163 1.1 maxv val = strtoumax(optarg, &ep, 10); 164 1.1 maxv if (optarg[0] == 0 || *ep != 0 || 165 1.1 maxv val > INT32_MAX) { 166 1.1 maxv errx(EXIT_FAILURE, "invalid p option"); 167 1.1 maxv } 168 1.1 maxv target_pid = val; 169 1.1 maxv filter_by_pid = true; 170 1.1 maxv break; 171 1.1 maxv case 'P': /* don't distinguish processes */ 172 1.1 maxv distinguish_processes = false; 173 1.1 maxv break; 174 1.1 maxv case 's': /* per symbol */ 175 1.1 maxv per_symbol = true; 176 1.1 maxv break; 177 1.1 maxv default: 178 1.1 maxv exit(EXIT_FAILURE); 179 1.1 maxv } 180 1.1 maxv } 181 1.1 maxv argc -= optind; 182 1.1 maxv argv += optind; 183 1.1 maxv 184 1.2 maxv if (argc == 0) { 185 1.2 maxv errx(EXIT_FAILURE, "missing file name"); 186 1.2 maxv } 187 1.2 maxv 188 1.2 maxv f = fopen(argv[0], "rb"); 189 1.2 maxv if (f == NULL) { 190 1.2 maxv errx(EXIT_FAILURE, "fopen"); 191 1.2 maxv } 192 1.2 maxv 193 1.8 ryo ksymload(NULL); 194 1.1 maxv rb_tree_init(&addrtree, &addrtree_ops); 195 1.1 maxv 196 1.1 maxv /* 197 1.1 maxv * read and count samples. 198 1.1 maxv */ 199 1.1 maxv 200 1.1 maxv naddrs = 0; 201 1.3 maxv nsamples = 0; 202 1.1 maxv while (/*CONSTCOND*/true) { 203 1.1 maxv struct addr *o; 204 1.1 maxv tprof_sample_t sample; 205 1.2 maxv size_t n = fread(&sample, sizeof(sample), 1, f); 206 1.1 maxv bool in_kernel; 207 1.1 maxv 208 1.1 maxv if (n == 0) { 209 1.2 maxv if (feof(f)) { 210 1.1 maxv break; 211 1.1 maxv } 212 1.2 maxv if (ferror(f)) { 213 1.1 maxv err(EXIT_FAILURE, "fread"); 214 1.1 maxv } 215 1.1 maxv } 216 1.1 maxv if (filter_by_pid && (pid_t)sample.s_pid != target_pid) { 217 1.1 maxv continue; 218 1.1 maxv } 219 1.1 maxv in_kernel = (sample.s_flags & TPROF_SAMPLE_INKERNEL) != 0; 220 1.1 maxv if (kernel_only && !in_kernel) { 221 1.1 maxv continue; 222 1.1 maxv } 223 1.1 maxv a = emalloc(sizeof(*a)); 224 1.6 ryo memset(a, 0, sizeof(*a)); 225 1.1 maxv a->addr = (uint64_t)sample.s_pc; 226 1.1 maxv if (distinguish_processes) { 227 1.1 maxv a->pid = sample.s_pid; 228 1.1 maxv } else { 229 1.1 maxv a->pid = 0; 230 1.1 maxv } 231 1.1 maxv if (distinguish_lwps) { 232 1.1 maxv a->lwpid = sample.s_lwpid; 233 1.1 maxv } else { 234 1.1 maxv a->lwpid = 0; 235 1.1 maxv } 236 1.1 maxv if (distinguish_cpus) { 237 1.1 maxv a->cpuid = sample.s_cpuid; 238 1.1 maxv } else { 239 1.1 maxv a->cpuid = 0; 240 1.1 maxv } 241 1.1 maxv a->in_kernel = in_kernel; 242 1.1 maxv if (per_symbol) { 243 1.1 maxv const char *name; 244 1.1 maxv uint64_t offset; 245 1.1 maxv 246 1.8 ryo name = ksymlookup(a->addr, &offset, NULL); 247 1.1 maxv if (name != NULL) { 248 1.1 maxv a->addr -= offset; 249 1.1 maxv } 250 1.1 maxv } 251 1.6 ryo c = __SHIFTOUT(sample.s_flags, TPROF_SAMPLE_COUNTER_MASK); 252 1.6 ryo assert(c < TPROF_MAXCOUNTERS); 253 1.6 ryo if (maxevent < c) 254 1.6 ryo maxevent = c; 255 1.6 ryo 256 1.1 maxv a->nsamples = 1; 257 1.6 ryo a->ncount[c] = 1; 258 1.1 maxv o = rb_tree_insert_node(&addrtree, a); 259 1.1 maxv if (o != a) { 260 1.1 maxv assert(a->addr == o->addr); 261 1.1 maxv assert(a->pid == o->pid); 262 1.1 maxv assert(a->lwpid == o->lwpid); 263 1.1 maxv assert(a->cpuid == o->cpuid); 264 1.1 maxv assert(a->in_kernel == o->in_kernel); 265 1.1 maxv free(a); 266 1.6 ryo 267 1.1 maxv o->nsamples++; 268 1.6 ryo o->ncount[c]++; 269 1.1 maxv } else { 270 1.1 maxv naddrs++; 271 1.1 maxv } 272 1.3 maxv nsamples++; 273 1.1 maxv } 274 1.1 maxv 275 1.1 maxv /* 276 1.1 maxv * sort samples by addresses. 277 1.1 maxv */ 278 1.1 maxv 279 1.1 maxv l = emalloc(naddrs * sizeof(*l)); 280 1.1 maxv p = l; 281 1.1 maxv RB_TREE_FOREACH(a, &addrtree) { 282 1.1 maxv *p++ = a; 283 1.1 maxv } 284 1.1 maxv assert(l + naddrs == p); 285 1.1 maxv qsort(l, naddrs, sizeof(*l), compare_nsamples); 286 1.1 maxv 287 1.1 maxv /* 288 1.1 maxv * print addresses and number of samples, preferably with 289 1.1 maxv * resolved symbol names. 290 1.1 maxv */ 291 1.3 maxv printf("File: %s\n", argv[0]); 292 1.3 maxv printf("Number of samples: %zu\n\n", nsamples); 293 1.6 ryo 294 1.6 ryo printf("percentage nsamples "); 295 1.6 ryo for (c = 0; c <= maxevent; c++) 296 1.6 ryo printf("event#%02u ", c); 297 1.6 ryo printf("pid lwp cpu k address symbol\n"); 298 1.6 ryo 299 1.6 ryo printf("------------ -------- "); 300 1.6 ryo for (c = 0; c <= maxevent; c++) 301 1.6 ryo printf("-------- "); 302 1.6 ryo 303 1.6 ryo printf("------ ------ ---- - ---------------- ------\n"); 304 1.1 maxv for (i = 0; i < naddrs; i++) { 305 1.1 maxv const char *name; 306 1.1 maxv char buf[100]; 307 1.1 maxv uint64_t offset; 308 1.1 maxv 309 1.1 maxv a = l[i]; 310 1.1 maxv if (a->in_kernel) { 311 1.8 ryo name = ksymlookup(a->addr, &offset, NULL); 312 1.1 maxv } else { 313 1.1 maxv name = NULL; 314 1.1 maxv } 315 1.1 maxv if (name == NULL) { 316 1.1 maxv (void)snprintf(buf, sizeof(buf), "<%016" PRIx64 ">", 317 1.1 maxv a->addr); 318 1.1 maxv name = buf; 319 1.1 maxv } else if (offset != 0) { 320 1.1 maxv (void)snprintf(buf, sizeof(buf), "%s+0x%" PRIx64, name, 321 1.1 maxv offset); 322 1.1 maxv name = buf; 323 1.1 maxv } 324 1.3 maxv 325 1.3 maxv perc = ((float)a->nsamples / (float)nsamples) * 100.0; 326 1.3 maxv 327 1.6 ryo printf("%11f%% %8u", perc, a->nsamples); 328 1.6 ryo 329 1.6 ryo for (c = 0; c <= maxevent; c++) 330 1.6 ryo printf(" %8u", a->ncount[c]); 331 1.6 ryo 332 1.6 ryo printf(" %6" PRIu32 " %6" PRIu32 " %4" PRIu32 " %u %016" 333 1.6 ryo PRIx64" %s", 334 1.6 ryo a->pid, a->lwpid, a->cpuid, a->in_kernel, a->addr, name); 335 1.6 ryo 336 1.6 ryo 337 1.6 ryo printf("\n"); 338 1.1 maxv } 339 1.2 maxv 340 1.2 maxv fclose(f); 341 1.1 maxv } 342