Module Name: src
Committed By: maxv
Date: Fri Jul 13 11:03:36 UTC 2018
Modified Files:
src/usr.sbin/tprof: Makefile tprof.8 tprof.c tprof.h
Added Files:
src/usr.sbin/tprof: tprof_analyze.c
Log Message:
Merge tpfmt(1) into tprof(8). We want to have access to everything with
only one tool. The code is copied mostly as-is, and the functionality is
available via the "analyze" command.
Eg:
tprof monitor -e llc-misses:k -o myfile.out sleep 20
tprof analyze < myfile.out
Will move soon, I don't like the reading via stdin.
To generate a diff of this commit:
cvs rdiff -u -r1.4 -r1.5 src/usr.sbin/tprof/Makefile
cvs rdiff -u -r1.5 -r1.6 src/usr.sbin/tprof/tprof.8
cvs rdiff -u -r1.7 -r1.8 src/usr.sbin/tprof/tprof.c
cvs rdiff -u -r1.1 -r1.2 src/usr.sbin/tprof/tprof.h
cvs rdiff -u -r0 -r1.1 src/usr.sbin/tprof/tprof_analyze.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/usr.sbin/tprof/Makefile
diff -u src/usr.sbin/tprof/Makefile:1.4 src/usr.sbin/tprof/Makefile:1.5
--- src/usr.sbin/tprof/Makefile:1.4 Fri Jul 13 07:56:29 2018
+++ src/usr.sbin/tprof/Makefile Fri Jul 13 11:03:36 2018
@@ -1,10 +1,10 @@
-# $NetBSD: Makefile,v 1.4 2018/07/13 07:56:29 maxv Exp $
+# $NetBSD: Makefile,v 1.5 2018/07/13 11:03:36 maxv Exp $
.PATH: ${.CURDIR}/arch
PROG= tprof
MAN= tprof.8
-SRCS= tprof.c
+SRCS= tprof.c tprof_analyze.c
.if ${MACHINE_ARCH} == "i386" || ${MACHINE_ARCH} == "x86_64"
SRCS+= tprof_x86.c
@@ -15,7 +15,11 @@ SRCS+= tprof_noarch.c
CPPFLAGS+= -I${NETBSDSRCDIR}/sys/
LDADD+= -lpthread
+LDADD+= -lelf
+LDADD+= -lutil
DPADD+= ${LIBPTHREAD}
+DPADD+= ${LIBELF}
+DPADD+= ${LIBUTIL}
.include <bsd.own.mk>
.include <bsd.prog.mk>
Index: src/usr.sbin/tprof/tprof.8
diff -u src/usr.sbin/tprof/tprof.8:1.5 src/usr.sbin/tprof/tprof.8:1.6
--- src/usr.sbin/tprof/tprof.8:1.5 Fri Jul 13 09:04:31 2018
+++ src/usr.sbin/tprof/tprof.8 Fri Jul 13 11:03:36 2018
@@ -1,4 +1,4 @@
-.\" $NetBSD: tprof.8,v 1.5 2018/07/13 09:04:31 maxv Exp $
+.\" $NetBSD: tprof.8,v 1.6 2018/07/13 11:03:36 maxv Exp $
.\"
.\" Copyright (c)2011 YAMAMOTO Takashi,
.\" All rights reserved.
@@ -83,11 +83,43 @@ The collected samples are written into t
if specified.
The default is
.Dq Pa tprof.out .
+.It analyze Xo
+.Op Fl C
+.Op Fl k
+.Op Fl L
+.Op Fl P
+.Op Fl p Ar pid
+.Op Fl s
+.Xc
+Analyze the samples produced by a previous run of
+.Nm tprof ,
+and generate a plain text
+representation.
+.It Fl C
+Don't distinguish CPUs.
+All samples are treated as its CPU number is 0.
+.It Fl k
+Kernel only.
+Ignore samples for userland code.
+.It Fl L
+Don't distinguish LWPs.
+All samples are treated as its LWP ID is 0.
+.It Fl P
+Don't distinguish processes.
+All samples are treated as its PID is 0.
+.It Fl p Ar pid
+Process only samples for the process with PID
+.Ar pid
+and ignore the rest.
+.It Fl s
+Per symbol.
.El
.Sh EXAMPLES
The following command profiles the system during 20 seconds and writes the
samples into the file myfile.out.
.Dl # tprof monitor -e llc-misses:k -o myfile.out sleep 20
+The following command displays the results of the sampling.
+.Dl # tprof analyze < myfile.out
.Ed
.Sh DIAGNOSTICS
The
Index: src/usr.sbin/tprof/tprof.c
diff -u src/usr.sbin/tprof/tprof.c:1.7 src/usr.sbin/tprof/tprof.c:1.8
--- src/usr.sbin/tprof/tprof.c:1.7 Fri Jul 13 09:04:31 2018
+++ src/usr.sbin/tprof/tprof.c Fri Jul 13 11:03:36 2018
@@ -1,4 +1,4 @@
-/* $NetBSD: tprof.c,v 1.7 2018/07/13 09:04:31 maxv Exp $ */
+/* $NetBSD: tprof.c,v 1.8 2018/07/13 11:03:36 maxv Exp $ */
/*
* Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -57,7 +57,7 @@
#include <sys/cdefs.h>
#ifndef lint
-__RCSID("$NetBSD: tprof.c,v 1.7 2018/07/13 09:04:31 maxv Exp $");
+__RCSID("$NetBSD: tprof.c,v 1.8 2018/07/13 11:03:36 maxv Exp $");
#endif /* not lint */
#include <sys/ioctl.h>
@@ -94,6 +94,7 @@ static struct cmdtab {
} const tprof_cmdtab[] = {
{ "list", false, false, tprof_list },
{ "monitor", true, false, tprof_monitor },
+ { "analyze", true, true, tprof_analyze },
{ NULL, false, false, NULL },
};
@@ -101,13 +102,15 @@ __dead static void
usage(void)
{
- fprintf(stderr, "%s [op] [options] [command]\n", getprogname());
+ fprintf(stderr, "%s op [arguments]\n", getprogname());
fprintf(stderr, "\n");
fprintf(stderr, "\tlist\n");
fprintf(stderr, "\t\tList the available events.\n");
fprintf(stderr, "\tmonitor -e name:option [-o outfile] command\n");
fprintf(stderr, "\t\tMonitor the event 'name' with option 'option'\n"
"\t\tcounted during the execution of 'command'.\n");
+ fprintf(stderr, "\tanalyze [-C] [-k] [-L] [-P] [-p pid] [-s]\n");
+ fprintf(stderr, "\t\tAnalyze the samples from stdin.\n");
exit(EXIT_FAILURE);
}
Index: src/usr.sbin/tprof/tprof.h
diff -u src/usr.sbin/tprof/tprof.h:1.1 src/usr.sbin/tprof/tprof.h:1.2
--- src/usr.sbin/tprof/tprof.h:1.1 Fri Jul 13 07:56:29 2018
+++ src/usr.sbin/tprof/tprof.h Fri Jul 13 11:03:36 2018
@@ -1,4 +1,4 @@
-/* $NetBSD: tprof.h,v 1.1 2018/07/13 07:56:29 maxv Exp $ */
+/* $NetBSD: tprof.h,v 1.2 2018/07/13 11:03:36 maxv Exp $ */
/*
* Copyright (c) 2018 The NetBSD Foundation, Inc.
@@ -33,3 +33,4 @@ int tprof_event_init(uint32_t);
void tprof_event_list(void);
void tprof_event_lookup(const char *, struct tprof_param *);
+void tprof_analyze(int, char **);
Added files:
Index: src/usr.sbin/tprof/tprof_analyze.c
diff -u /dev/null src/usr.sbin/tprof/tprof_analyze.c:1.1
--- /dev/null Fri Jul 13 11:03:36 2018
+++ src/usr.sbin/tprof/tprof_analyze.c Fri Jul 13 11:03:36 2018
@@ -0,0 +1,437 @@
+/* $NetBSD: tprof_analyze.c,v 1.1 2018/07/13 11:03:36 maxv Exp $ */
+
+/*
+ * Copyright (c) 2010,2011,2012 YAMAMOTO Takashi,
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__RCSID("$NetBSD: tprof_analyze.c,v 1.1 2018/07/13 11:03:36 maxv Exp $");
+#endif /* not lint */
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <inttypes.h>
+#include <libelf.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <util.h>
+#include <dev/tprof/tprof_ioctl.h>
+#include "tprof.h"
+
+#define _PATH_KSYMS "/dev/ksyms"
+
+#include <sys/rbtree.h>
+
+static bool filter_by_pid;
+static pid_t target_pid;
+static bool per_symbol;
+
+struct addr {
+ struct rb_node node;
+ uint64_t addr; /* address */
+ uint32_t pid; /* process id */
+ uint32_t lwpid; /* lwp id */
+ uint32_t cpuid; /* cpu id */
+ bool in_kernel; /* if addr is in the kernel address space */
+ unsigned int nsamples; /* number of samples taken for the address */
+};
+
+static rb_tree_t addrtree;
+
+struct sym {
+ char *name;
+ uint64_t value;
+ uint64_t size;
+};
+
+static struct sym **syms = NULL;
+static size_t nsyms = 0;
+
+static int
+compare_value(const void *p1, const void *p2)
+{
+ const struct sym *s1 = *(const struct sym * const *)p1;
+ const struct sym *s2 = *(const struct sym * const *)p2;
+
+ if (s1->value > s2->value) {
+ return -1;
+ } else if (s1->value < s2->value) {
+ return 1;
+ }
+ /*
+ * to produce a stable result, it's better not to return 0
+ * even for __strong_alias.
+ */
+ if (s1->size > s2->size) {
+ return -1;
+ } else if (s1->size < s2->size) {
+ return 1;
+ }
+ return strcmp(s1->name, s2->name);
+}
+
+static void
+ksymload(void)
+{
+ Elf *e;
+ Elf_Scn *s;
+ GElf_Shdr sh_store;
+ GElf_Shdr *sh;
+ Elf_Data *d;
+ int fd;
+ size_t size, i;
+
+ fd = open(_PATH_KSYMS, O_RDONLY);
+ if (fd == -1) {
+ err(EXIT_FAILURE, "open");
+ }
+ if (elf_version(EV_CURRENT) == EV_NONE) {
+ goto elffail;
+ }
+ e = elf_begin(fd, ELF_C_READ, NULL);
+ if (e == NULL) {
+ goto elffail;
+ }
+ for (s = elf_nextscn(e, NULL); s != NULL; s = elf_nextscn(e, s)) {
+ sh = gelf_getshdr(s, &sh_store);
+ if (sh == NULL) {
+ goto elffail;
+ }
+ if (sh->sh_type == SHT_SYMTAB) {
+ break;
+ }
+ }
+ if (s == NULL) {
+ errx(EXIT_FAILURE, "no symtab");
+ }
+ d = elf_getdata(s, NULL);
+ if (d == NULL) {
+ goto elffail;
+ }
+ assert(sh->sh_size == d->d_size);
+ size = sh->sh_size / sh->sh_entsize;
+ for (i = 1; i < size; i++) {
+ GElf_Sym st_store;
+ GElf_Sym *st;
+ struct sym *sym;
+
+ st = gelf_getsym(d, (int)i, &st_store);
+ if (st == NULL) {
+ goto elffail;
+ }
+ if (ELF_ST_TYPE(st->st_info) != STT_FUNC) {
+ continue;
+ }
+ sym = emalloc(sizeof(*sym));
+ sym->name = estrdup(elf_strptr(e, sh->sh_link, st->st_name));
+ sym->value = (uint64_t)st->st_value;
+ sym->size = st->st_size;
+ nsyms++;
+ syms = erealloc(syms, sizeof(*syms) * nsyms);
+ syms[nsyms - 1] = sym;
+ }
+ qsort(syms, nsyms, sizeof(*syms), compare_value);
+ return;
+elffail:
+ errx(EXIT_FAILURE, "libelf: %s", elf_errmsg(elf_errno()));
+}
+
+static const char *
+ksymlookup(uint64_t value, uint64_t *offset)
+{
+ size_t hi;
+ size_t lo;
+ size_t i;
+
+ /*
+ * try to find the smallest i for which syms[i]->value <= value.
+ * syms[] is ordered by syms[]->value in the descending order.
+ */
+
+ hi = nsyms - 1;
+ lo = 0;
+ while (lo < hi) {
+ const size_t mid = (lo + hi) / 2;
+ const struct sym *sym = syms[mid];
+
+ assert(syms[lo]->value >= sym->value);
+ assert(sym->value >= syms[hi]->value);
+ if (sym->value <= value) {
+ hi = mid;
+ continue;
+ }
+ lo = mid + 1;
+ }
+ assert(lo == nsyms - 1 || syms[lo]->value <= value);
+ assert(lo == 0 || syms[lo - 1]->value > value);
+ for (i = lo; i < nsyms; i++) {
+ const struct sym *sym = syms[i];
+
+ if (sym->value <= value &&
+ (sym->size == 0 || value - sym->value <= sym->size )) {
+ *offset = value - sym->value;
+ return sym->name;
+ }
+ if (sym->size != 0 && sym->value + sym->size < value) {
+ break;
+ }
+ }
+ return NULL;
+}
+
+static signed int
+addrtree_compare_key(void *ctx, const void *n1, const void *keyp)
+{
+ const struct addr *a1 = n1;
+ const struct addr *a2 = (const struct addr *)keyp;
+
+ if (a1->addr > a2->addr) {
+ return 1;
+ } else if (a1->addr < a2->addr) {
+ return -1;
+ }
+ if (a1->pid > a2->pid) {
+ return -1;
+ } else if (a1->pid < a2->pid) {
+ return 1;
+ }
+ if (a1->lwpid > a2->lwpid) {
+ return -1;
+ } else if (a1->lwpid < a2->lwpid) {
+ return 1;
+ }
+ if (a1->cpuid > a2->cpuid) {
+ return -1;
+ } else if (a1->cpuid < a2->cpuid) {
+ return 1;
+ }
+ if (a1->in_kernel > a2->in_kernel) {
+ return -1;
+ } else if (a1->in_kernel < a2->in_kernel) {
+ return 1;
+ }
+ return 0;
+}
+
+static signed int
+addrtree_compare_nodes(void *ctx, const void *n1, const void *n2)
+{
+ const struct addr *a2 = n2;
+
+ return addrtree_compare_key(ctx, n1, a2);
+}
+
+static const rb_tree_ops_t addrtree_ops = {
+ .rbto_compare_nodes = addrtree_compare_nodes,
+ .rbto_compare_key = addrtree_compare_key,
+};
+
+static int
+compare_nsamples(const void *p1, const void *p2)
+{
+ const struct addr *a1 = *(const struct addr * const *)p1;
+ const struct addr *a2 = *(const struct addr * const *)p2;
+
+ if (a1->nsamples > a2->nsamples) {
+ return -1;
+ } else if (a1->nsamples < a2->nsamples) {
+ return 1;
+ }
+ return 0;
+}
+
+void
+tprof_analyze(int argc, char **argv)
+{
+ struct addr *a;
+ struct addr **l;
+ struct addr **p;
+ size_t naddrs, i;
+ int ch;
+ bool distinguish_processes = true;
+ bool distinguish_cpus = true;
+ bool distinguish_lwps = true;
+ bool kernel_only = false;
+ extern char *optarg;
+ extern int optind;
+
+ while ((ch = getopt(argc, argv, "CkLPp:s")) != -1) {
+ uintmax_t val;
+ char *ep;
+
+ switch (ch) {
+ case 'C': /* don't distinguish cpus */
+ distinguish_cpus = false;
+ break;
+ case 'k': /* kernel only */
+ kernel_only = true;
+ break;
+ case 'L': /* don't distinguish lwps */
+ distinguish_lwps = false;
+ break;
+ case 'p': /* only for the process for the given pid */
+ errno = 0;
+ val = strtoumax(optarg, &ep, 10);
+ if (optarg[0] == 0 || *ep != 0 ||
+ val > INT32_MAX) {
+ errx(EXIT_FAILURE, "invalid p option");
+ }
+ target_pid = val;
+ filter_by_pid = true;
+ break;
+ case 'P': /* don't distinguish processes */
+ distinguish_processes = false;
+ break;
+ case 's': /* per symbol */
+ per_symbol = true;
+ break;
+ default:
+ exit(EXIT_FAILURE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ ksymload();
+ rb_tree_init(&addrtree, &addrtree_ops);
+
+ /*
+ * read and count samples.
+ */
+
+ naddrs = 0;
+ while (/*CONSTCOND*/true) {
+ struct addr *o;
+ tprof_sample_t sample;
+ size_t n = fread(&sample, sizeof(sample), 1, stdin);
+ bool in_kernel;
+
+ if (n == 0) {
+ if (feof(stdin)) {
+ break;
+ }
+ if (ferror(stdin)) {
+ err(EXIT_FAILURE, "fread");
+ }
+ }
+ if (filter_by_pid && (pid_t)sample.s_pid != target_pid) {
+ continue;
+ }
+ in_kernel = (sample.s_flags & TPROF_SAMPLE_INKERNEL) != 0;
+ if (kernel_only && !in_kernel) {
+ continue;
+ }
+ a = emalloc(sizeof(*a));
+ a->addr = (uint64_t)sample.s_pc;
+ if (distinguish_processes) {
+ a->pid = sample.s_pid;
+ } else {
+ a->pid = 0;
+ }
+ if (distinguish_lwps) {
+ a->lwpid = sample.s_lwpid;
+ } else {
+ a->lwpid = 0;
+ }
+ if (distinguish_cpus) {
+ a->cpuid = sample.s_cpuid;
+ } else {
+ a->cpuid = 0;
+ }
+ a->in_kernel = in_kernel;
+ if (per_symbol) {
+ const char *name;
+ uint64_t offset;
+
+ name = ksymlookup(a->addr, &offset);
+ if (name != NULL) {
+ a->addr -= offset;
+ }
+ }
+ a->nsamples = 1;
+ o = rb_tree_insert_node(&addrtree, a);
+ if (o != a) {
+ assert(a->addr == o->addr);
+ assert(a->pid == o->pid);
+ assert(a->lwpid == o->lwpid);
+ assert(a->cpuid == o->cpuid);
+ assert(a->in_kernel == o->in_kernel);
+ free(a);
+ o->nsamples++;
+ } else {
+ naddrs++;
+ }
+ }
+
+ /*
+ * sort samples by addresses.
+ */
+
+ l = emalloc(naddrs * sizeof(*l));
+ p = l;
+ RB_TREE_FOREACH(a, &addrtree) {
+ *p++ = a;
+ }
+ assert(l + naddrs == p);
+ qsort(l, naddrs, sizeof(*l), compare_nsamples);
+
+ /*
+ * print addresses and number of samples, preferably with
+ * resolved symbol names.
+ */
+
+ for (i = 0; i < naddrs; i++) {
+ const char *name;
+ char buf[100];
+ uint64_t offset;
+
+ a = l[i];
+ if (a->in_kernel) {
+ name = ksymlookup(a->addr, &offset);
+ } else {
+ name = NULL;
+ }
+ if (name == NULL) {
+ (void)snprintf(buf, sizeof(buf), "<%016" PRIx64 ">",
+ a->addr);
+ name = buf;
+ } else if (offset != 0) {
+ (void)snprintf(buf, sizeof(buf), "%s+0x%" PRIx64, name,
+ offset);
+ name = buf;
+ }
+ printf("%8u %6" PRIu32 " %4" PRIu32 " %2" PRIu32 " %u %016"
+ PRIx64 " %s\n",
+ a->nsamples, a->pid, a->lwpid, a->cpuid, a->in_kernel,
+ a->addr, name);
+ }
+}