tprof_analyze.c revision 1.2 1 /* $NetBSD: tprof_analyze.c,v 1.2 2018/07/13 12:04:50 maxv Exp $ */
2
3 /*
4 * Copyright (c) 2010,2011,2012 YAMAMOTO Takashi,
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30 #ifndef lint
31 __RCSID("$NetBSD: tprof_analyze.c,v 1.2 2018/07/13 12:04:50 maxv Exp $");
32 #endif /* not lint */
33
34 #include <assert.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <gelf.h>
39 #include <inttypes.h>
40 #include <libelf.h>
41 #include <stdbool.h>
42 #include <stdlib.h>
43 #include <stdio.h>
44 #include <unistd.h>
45 #include <string.h>
46 #include <util.h>
47 #include <dev/tprof/tprof_ioctl.h>
48 #include "tprof.h"
49
50 #define _PATH_KSYMS "/dev/ksyms"
51
52 #include <sys/rbtree.h>
53
54 static bool filter_by_pid;
55 static pid_t target_pid;
56 static bool per_symbol;
57
58 struct addr {
59 struct rb_node node;
60 uint64_t addr; /* address */
61 uint32_t pid; /* process id */
62 uint32_t lwpid; /* lwp id */
63 uint32_t cpuid; /* cpu id */
64 bool in_kernel; /* if addr is in the kernel address space */
65 unsigned int nsamples; /* number of samples taken for the address */
66 };
67
68 static rb_tree_t addrtree;
69
70 struct sym {
71 char *name;
72 uint64_t value;
73 uint64_t size;
74 };
75
76 static struct sym **syms = NULL;
77 static size_t nsyms = 0;
78
79 static int
80 compare_value(const void *p1, const void *p2)
81 {
82 const struct sym *s1 = *(const struct sym * const *)p1;
83 const struct sym *s2 = *(const struct sym * const *)p2;
84
85 if (s1->value > s2->value) {
86 return -1;
87 } else if (s1->value < s2->value) {
88 return 1;
89 }
90 /*
91 * to produce a stable result, it's better not to return 0
92 * even for __strong_alias.
93 */
94 if (s1->size > s2->size) {
95 return -1;
96 } else if (s1->size < s2->size) {
97 return 1;
98 }
99 return strcmp(s1->name, s2->name);
100 }
101
102 static void
103 ksymload(void)
104 {
105 Elf *e;
106 Elf_Scn *s;
107 GElf_Shdr sh_store;
108 GElf_Shdr *sh;
109 Elf_Data *d;
110 int fd;
111 size_t size, i;
112
113 fd = open(_PATH_KSYMS, O_RDONLY);
114 if (fd == -1) {
115 err(EXIT_FAILURE, "open");
116 }
117 if (elf_version(EV_CURRENT) == EV_NONE) {
118 goto elffail;
119 }
120 e = elf_begin(fd, ELF_C_READ, NULL);
121 if (e == NULL) {
122 goto elffail;
123 }
124 for (s = elf_nextscn(e, NULL); s != NULL; s = elf_nextscn(e, s)) {
125 sh = gelf_getshdr(s, &sh_store);
126 if (sh == NULL) {
127 goto elffail;
128 }
129 if (sh->sh_type == SHT_SYMTAB) {
130 break;
131 }
132 }
133 if (s == NULL) {
134 errx(EXIT_FAILURE, "no symtab");
135 }
136 d = elf_getdata(s, NULL);
137 if (d == NULL) {
138 goto elffail;
139 }
140 assert(sh->sh_size == d->d_size);
141 size = sh->sh_size / sh->sh_entsize;
142 for (i = 1; i < size; i++) {
143 GElf_Sym st_store;
144 GElf_Sym *st;
145 struct sym *sym;
146
147 st = gelf_getsym(d, (int)i, &st_store);
148 if (st == NULL) {
149 goto elffail;
150 }
151 if (ELF_ST_TYPE(st->st_info) != STT_FUNC) {
152 continue;
153 }
154 sym = emalloc(sizeof(*sym));
155 sym->name = estrdup(elf_strptr(e, sh->sh_link, st->st_name));
156 sym->value = (uint64_t)st->st_value;
157 sym->size = st->st_size;
158 nsyms++;
159 syms = erealloc(syms, sizeof(*syms) * nsyms);
160 syms[nsyms - 1] = sym;
161 }
162 qsort(syms, nsyms, sizeof(*syms), compare_value);
163 return;
164 elffail:
165 errx(EXIT_FAILURE, "libelf: %s", elf_errmsg(elf_errno()));
166 }
167
168 static const char *
169 ksymlookup(uint64_t value, uint64_t *offset)
170 {
171 size_t hi;
172 size_t lo;
173 size_t i;
174
175 /*
176 * try to find the smallest i for which syms[i]->value <= value.
177 * syms[] is ordered by syms[]->value in the descending order.
178 */
179
180 hi = nsyms - 1;
181 lo = 0;
182 while (lo < hi) {
183 const size_t mid = (lo + hi) / 2;
184 const struct sym *sym = syms[mid];
185
186 assert(syms[lo]->value >= sym->value);
187 assert(sym->value >= syms[hi]->value);
188 if (sym->value <= value) {
189 hi = mid;
190 continue;
191 }
192 lo = mid + 1;
193 }
194 assert(lo == nsyms - 1 || syms[lo]->value <= value);
195 assert(lo == 0 || syms[lo - 1]->value > value);
196 for (i = lo; i < nsyms; i++) {
197 const struct sym *sym = syms[i];
198
199 if (sym->value <= value &&
200 (sym->size == 0 || value - sym->value <= sym->size )) {
201 *offset = value - sym->value;
202 return sym->name;
203 }
204 if (sym->size != 0 && sym->value + sym->size < value) {
205 break;
206 }
207 }
208 return NULL;
209 }
210
211 static signed int
212 addrtree_compare_key(void *ctx, const void *n1, const void *keyp)
213 {
214 const struct addr *a1 = n1;
215 const struct addr *a2 = (const struct addr *)keyp;
216
217 if (a1->addr > a2->addr) {
218 return 1;
219 } else if (a1->addr < a2->addr) {
220 return -1;
221 }
222 if (a1->pid > a2->pid) {
223 return -1;
224 } else if (a1->pid < a2->pid) {
225 return 1;
226 }
227 if (a1->lwpid > a2->lwpid) {
228 return -1;
229 } else if (a1->lwpid < a2->lwpid) {
230 return 1;
231 }
232 if (a1->cpuid > a2->cpuid) {
233 return -1;
234 } else if (a1->cpuid < a2->cpuid) {
235 return 1;
236 }
237 if (a1->in_kernel > a2->in_kernel) {
238 return -1;
239 } else if (a1->in_kernel < a2->in_kernel) {
240 return 1;
241 }
242 return 0;
243 }
244
245 static signed int
246 addrtree_compare_nodes(void *ctx, const void *n1, const void *n2)
247 {
248 const struct addr *a2 = n2;
249
250 return addrtree_compare_key(ctx, n1, a2);
251 }
252
253 static const rb_tree_ops_t addrtree_ops = {
254 .rbto_compare_nodes = addrtree_compare_nodes,
255 .rbto_compare_key = addrtree_compare_key,
256 };
257
258 static int
259 compare_nsamples(const void *p1, const void *p2)
260 {
261 const struct addr *a1 = *(const struct addr * const *)p1;
262 const struct addr *a2 = *(const struct addr * const *)p2;
263
264 if (a1->nsamples > a2->nsamples) {
265 return -1;
266 } else if (a1->nsamples < a2->nsamples) {
267 return 1;
268 }
269 return 0;
270 }
271
272 void
273 tprof_analyze(int argc, char **argv)
274 {
275 struct addr *a;
276 struct addr **l;
277 struct addr **p;
278 size_t naddrs, i;
279 int ch;
280 bool distinguish_processes = true;
281 bool distinguish_cpus = true;
282 bool distinguish_lwps = true;
283 bool kernel_only = false;
284 extern char *optarg;
285 extern int optind;
286 FILE *f;
287
288 while ((ch = getopt(argc, argv, "CkLPp:s")) != -1) {
289 uintmax_t val;
290 char *ep;
291
292 switch (ch) {
293 case 'C': /* don't distinguish cpus */
294 distinguish_cpus = false;
295 break;
296 case 'k': /* kernel only */
297 kernel_only = true;
298 break;
299 case 'L': /* don't distinguish lwps */
300 distinguish_lwps = false;
301 break;
302 case 'p': /* only for the process for the given pid */
303 errno = 0;
304 val = strtoumax(optarg, &ep, 10);
305 if (optarg[0] == 0 || *ep != 0 ||
306 val > INT32_MAX) {
307 errx(EXIT_FAILURE, "invalid p option");
308 }
309 target_pid = val;
310 filter_by_pid = true;
311 break;
312 case 'P': /* don't distinguish processes */
313 distinguish_processes = false;
314 break;
315 case 's': /* per symbol */
316 per_symbol = true;
317 break;
318 default:
319 exit(EXIT_FAILURE);
320 }
321 }
322 argc -= optind;
323 argv += optind;
324
325 if (argc == 0) {
326 errx(EXIT_FAILURE, "missing file name");
327 }
328
329 f = fopen(argv[0], "rb");
330 if (f == NULL) {
331 errx(EXIT_FAILURE, "fopen");
332 }
333
334 ksymload();
335 rb_tree_init(&addrtree, &addrtree_ops);
336
337 /*
338 * read and count samples.
339 */
340
341 naddrs = 0;
342 while (/*CONSTCOND*/true) {
343 struct addr *o;
344 tprof_sample_t sample;
345 size_t n = fread(&sample, sizeof(sample), 1, f);
346 bool in_kernel;
347
348 if (n == 0) {
349 if (feof(f)) {
350 break;
351 }
352 if (ferror(f)) {
353 err(EXIT_FAILURE, "fread");
354 }
355 }
356 if (filter_by_pid && (pid_t)sample.s_pid != target_pid) {
357 continue;
358 }
359 in_kernel = (sample.s_flags & TPROF_SAMPLE_INKERNEL) != 0;
360 if (kernel_only && !in_kernel) {
361 continue;
362 }
363 a = emalloc(sizeof(*a));
364 a->addr = (uint64_t)sample.s_pc;
365 if (distinguish_processes) {
366 a->pid = sample.s_pid;
367 } else {
368 a->pid = 0;
369 }
370 if (distinguish_lwps) {
371 a->lwpid = sample.s_lwpid;
372 } else {
373 a->lwpid = 0;
374 }
375 if (distinguish_cpus) {
376 a->cpuid = sample.s_cpuid;
377 } else {
378 a->cpuid = 0;
379 }
380 a->in_kernel = in_kernel;
381 if (per_symbol) {
382 const char *name;
383 uint64_t offset;
384
385 name = ksymlookup(a->addr, &offset);
386 if (name != NULL) {
387 a->addr -= offset;
388 }
389 }
390 a->nsamples = 1;
391 o = rb_tree_insert_node(&addrtree, a);
392 if (o != a) {
393 assert(a->addr == o->addr);
394 assert(a->pid == o->pid);
395 assert(a->lwpid == o->lwpid);
396 assert(a->cpuid == o->cpuid);
397 assert(a->in_kernel == o->in_kernel);
398 free(a);
399 o->nsamples++;
400 } else {
401 naddrs++;
402 }
403 }
404
405 /*
406 * sort samples by addresses.
407 */
408
409 l = emalloc(naddrs * sizeof(*l));
410 p = l;
411 RB_TREE_FOREACH(a, &addrtree) {
412 *p++ = a;
413 }
414 assert(l + naddrs == p);
415 qsort(l, naddrs, sizeof(*l), compare_nsamples);
416
417 /*
418 * print addresses and number of samples, preferably with
419 * resolved symbol names.
420 */
421
422 for (i = 0; i < naddrs; i++) {
423 const char *name;
424 char buf[100];
425 uint64_t offset;
426
427 a = l[i];
428 if (a->in_kernel) {
429 name = ksymlookup(a->addr, &offset);
430 } else {
431 name = NULL;
432 }
433 if (name == NULL) {
434 (void)snprintf(buf, sizeof(buf), "<%016" PRIx64 ">",
435 a->addr);
436 name = buf;
437 } else if (offset != 0) {
438 (void)snprintf(buf, sizeof(buf), "%s+0x%" PRIx64, name,
439 offset);
440 name = buf;
441 }
442 printf("%8u %6" PRIu32 " %4" PRIu32 " %2" PRIu32 " %u %016"
443 PRIx64 " %s\n",
444 a->nsamples, a->pid, a->lwpid, a->cpuid, a->in_kernel,
445 a->addr, name);
446 }
447
448 fclose(f);
449 }
450