Add the lockstat command: displays a summary of kernel locking events

recorded over the lifetime of a called program.
This commit is contained in:
ad 2006-09-07 00:50:45 +00:00
parent cd9bde5343
commit 297f461929
9 changed files with 1223 additions and 4 deletions

View File

@ -1,4 +1,4 @@
# $NetBSD: mi,v 1.647 2006/08/26 19:31:53 matt Exp $
# $NetBSD: mi,v 1.648 2006/09/07 00:50:45 ad Exp $
. base-sys-root
./altroot base-sys-root
./bin base-sys-root
@ -1080,6 +1080,7 @@
./usr/sbin/linkfarm base-pkgutil-bin
./usr/sbin/lmcconfig base-netutil-bin
./usr/sbin/lmtp base-obsolete obsolete
./usr/sbin/lockstat base-sysutil-bin
./usr/sbin/lpc base-lpr-bin
./usr/sbin/lpd base-lpr-bin
./usr/sbin/lptest base-lpr-bin

View File

@ -1,4 +1,4 @@
# $NetBSD: mi,v 1.926 2006/09/02 23:39:56 wiz Exp $
# $NetBSD: mi,v 1.927 2006/09/07 00:50:45 ad Exp $
./etc/mtree/set.man man-sys-root
./usr/share/info/am-utils.info man-amd-info info
./usr/share/info/as.info man-computil-info bfd,info
@ -1972,6 +1972,7 @@
./usr/share/man/cat8/local.0 man-postfix-catman postfix,.cat
./usr/share/man/cat8/locate.updatedb.0 man-sysutil-catman .cat
./usr/share/man/cat8/lockd.0 man-nfsserver-catman .cat
./usr/share/man/cat8/lockstat.0 man-sysutil-catman .cat
./usr/share/man/cat8/lpc.0 man-lpr-catman .cat
./usr/share/man/cat8/lpd.0 man-lpr-catman .cat
./usr/share/man/cat8/luna68k/MAKEDEV.0 man-obsolete obsolete
@ -4341,6 +4342,7 @@
./usr/share/man/man8/local.8 man-postfix-man postfix,.man
./usr/share/man/man8/locate.updatedb.8 man-sysutil-man .man
./usr/share/man/man8/lockd.8 man-nfsserver-man .man
./usr/share/man/man8/lockstat.8 man-sysutil-man .man
./usr/share/man/man8/lpc.8 man-lpr-man .man
./usr/share/man/man8/lpd.8 man-lpr-man .man
./usr/share/man/man8/luna68k/MAKEDEV.8 man-obsolete obsolete

View File

@ -1,4 +1,4 @@
# $NetBSD: Makefile,v 1.218 2006/08/26 17:26:00 matt Exp $
# $NetBSD: Makefile,v 1.219 2006/09/07 00:50:45 ad Exp $
# from: @(#)Makefile 5.20 (Berkeley) 6/12/93
.include <bsd.own.mk>
@ -10,7 +10,7 @@ SUBDIR= ac accton altq amd apm apmd arp bad144 bind bootp \
envstat eshconfig etcupdate extattrctl fssconfig fwctl gpioctl \
grfconfig grfinfo gspa hilinfo ifwatchd inetd installboot \
iopctl iostat ipwctl irdaattach isdn iteconfig iwictl\
kgmon lastlogin link lmcconfig lpr mailwrapper makefs \
kgmon lastlogin link lmcconfig lockstat lpr mailwrapper makefs \
map-mbone mdconfig memswitch mlxctl mopd mountd moused \
mrinfo mrouted mscdlabel mtrace \
mtree ndbootd ndiscvt netgroup_mkdb nfsd ntp pcictl pkg_install pppd \

View File

@ -0,0 +1,14 @@
# $NetBSD: Makefile,v 1.1 2006/09/07 00:50:45 ad Exp $
.include <bsd.own.mk>
SRCS= elf32.c elf64.c main.c
PROG= lockstat
MAN= lockstat.8
DPADD= ${LIBUTIL}
LDADD= -lutil
elf64.o: elf32.c
.include <bsd.prog.mk>

241
usr.sbin/lockstat/elf32.c Normal file
View File

@ -0,0 +1,241 @@
/* $NetBSD: elf32.c,v 1.1 2006/09/07 00:50:45 ad Exp $ */
/*-
* Copyright (c) 2006 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Andrew Doran.
*
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/
/*
* Copyright (c) 1996 Christopher G. Demetriou
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed for the
* NetBSD Project. See http://www.NetBSD.org/ for
* information about NetBSD.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
*
* <<Id: LICENSE,v 1.2 2000/06/14 15:57:33 cgd Exp>>
*/
#include <sys/cdefs.h>
#if !defined(lint)
__RCSID("$NetBSD: elf32.c,v 1.1 2006/09/07 00:50:45 ad Exp $");
#endif
#ifndef ELFSIZE
#define ELFSIZE 32
#endif
#include <sys/param.h>
#include <sys/exec_elf.h>
#include <sys/queue.h>
#include <sys/lockstat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include "extern.h"
#if (ELFSIZE == 32)
#define NAME(x) x##32
#elif (ELFSIZE == 64)
#define NAME(x) x##64
#endif
static int nsyms;
static Elf_Sym *symp;
static char *strp;
int
NAME(loadsym)(int fd)
{
Elf_Shdr symhdr, strhdr;
Elf_Ehdr ehdr;
size_t sz;
off_t off;
int i;
/*
* Read the ELF header and make sure it's OK.
*/
if (pread(fd, &ehdr, sizeof(ehdr), 0) != sizeof(ehdr))
return -1;
if (memcmp(ehdr.e_ident, ELFMAG, SELFMAG) != 0 ||
ehdr.e_ident[EI_CLASS] != ELFCLASS)
return -1;
switch (ehdr.e_machine) {
ELFDEFNNAME(MACHDEP_ID_CASES)
default:
return -1;
}
/*
* Find the symbol table header, and make sure the binary isn't
* stripped.
*/
off = ehdr.e_shoff;
for (i = 0; i < ehdr.e_shnum; i++, off += sizeof(symhdr)) {
sz = pread(fd, &symhdr, sizeof(symhdr), off);
if (sz != sizeof(symhdr))
err(EXIT_FAILURE, "pread (section headers)");
if (symhdr.sh_type == SHT_SYMTAB)
break;
}
if (i == ehdr.e_shnum || symhdr.sh_offset == 0)
err(EXIT_FAILURE, "namelist is stripped");
/*
* Pull in the string table header, and then read in both the symbol
* table and string table proper.
*
* XXX We can't use mmap(), as /dev/ksyms doesn't support mmap yet.
*/
off = ehdr.e_shoff + symhdr.sh_link * sizeof(symhdr);
if (pread(fd, &strhdr, sizeof(strhdr), off) != sizeof(strhdr))
err(EXIT_FAILURE, "pread");
if ((symp = malloc(symhdr.sh_size)) == NULL)
err(EXIT_FAILURE, "malloc (symbol table)");
sz = pread(fd, symp, symhdr.sh_size, symhdr.sh_offset );
if (sz != symhdr.sh_size)
err(EXIT_FAILURE, "pread (symbol table)");
if ((strp = malloc(strhdr.sh_size)) == NULL)
err(EXIT_FAILURE, "malloc (string table)");
sz = pread(fd, strp, strhdr.sh_size, strhdr.sh_offset);
if (sz != strhdr.sh_size)
err(EXIT_FAILURE, "pread (string table)");
nsyms = (int)(symhdr.sh_size / sizeof(Elf_Sym));
return 0;
}
int
NAME(findsym)(findsym_t find, char *name, uintptr_t *start, uintptr_t *end)
{
static int lastptr[FIND_MAX];
uintptr_t sa, ea;
int i, rv;
rv = -1;
#ifdef dump_core
for (i = lastptr[find];;) {
#else
for (i = 0; i < nsyms; i++) {
#endif
switch (find) {
case LOCK_BYNAME:
if (ELF_ST_TYPE(symp[i].st_info) != STT_OBJECT)
break;
if (strcmp(&strp[symp[i].st_name], name) != 0)
break;
*start = (uintptr_t)symp[i].st_value;
*end = *start + (uintptr_t)symp[i].st_size;
goto found;
case LOCK_BYADDR:
if (ELF_ST_TYPE(symp[i].st_info) != STT_OBJECT)
break;
if (*start != (uintptr_t)symp[i].st_value)
break;
strcpy(name, &strp[symp[i].st_name]);
goto found;
case FUNC_BYNAME:
if (ELF_ST_TYPE(symp[i].st_info) != STT_FUNC)
break;
if (strcmp(&strp[symp[i].st_name], name) != 0)
break;
*start = (uintptr_t)symp[i].st_value;
*end = *start + (uintptr_t)symp[i].st_size;
goto found;
case FUNC_BYADDR:
if (ELF_ST_TYPE(symp[i].st_info) != STT_FUNC)
break;
sa = (uintptr_t)symp[i].st_value;
ea = sa + (uintptr_t)symp[i].st_size - 1;
if (*start < sa || *start > ea)
break;
sprintf(name, "%s+0x%x",
&strp[symp[i].st_name], (int)(*start - sa));
goto found;
default:
break;
}
#ifdef dump_core
if (++i >= nsyms)
i = 0;
if (i == lastptr[find])
return -1;
#endif
}
return -1;
found:
lastptr[find] = i;
return 0;
}

44
usr.sbin/lockstat/elf64.c Normal file
View File

@ -0,0 +1,44 @@
/* $NetBSD: elf64.c,v 1.1 2006/09/07 00:50:45 ad Exp $ */
/*
* Copyright (c) 1996 Christopher G. Demetriou
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed for the
* NetBSD Project. See http://www.NetBSD.org/ for
* information about NetBSD.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
*
* <<Id: LICENSE,v 1.2 2000/06/14 15:57:33 cgd Exp>>
*/
#include <sys/cdefs.h>
#if !defined(lint)
__RCSID("$NetBSD: elf64.c,v 1.1 2006/09/07 00:50:45 ad Exp $");
#endif /* not lint */
#define ELFSIZE 64
#include "elf32.c"

View File

@ -0,0 +1,54 @@
/* $NetBSD: extern.h,v 1.1 2006/09/07 00:50:45 ad Exp $ */
/*-
* Copyright (c) 2006 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Andrew Doran.
*
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/
/*
* elf32.c, elf64.c
*/
typedef enum findsym_enum {
LOCK_BYNAME,
FUNC_BYNAME,
LOCK_BYADDR,
FUNC_BYADDR,
FIND_MAX
} findsym_t;
int loadsym32(int);
int loadsym64(int);
int findsym32(findsym_t, char *, uintptr_t *, uintptr_t *);
int findsym64(findsym_t, char *, uintptr_t *, uintptr_t *);

View File

@ -0,0 +1,166 @@
.\" $NetBSD: lockstat.8,v 1.1 2006/09/07 00:50:45 ad Exp $
.\"
.\" Copyright (c) 2006 The NetBSD Foundation, Inc.
.\" All rights reserved.
.\"
.\" This code is derived from software contributed to The NetBSD Foundation
.\" by Andrew Doran.
.\"
.\" 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.
.\" 3. All advertising materials mentioning features or use of this software
.\" must display the following acknowledgement:
.\" This product includes software developed by the NetBSD
.\" Foundation, Inc. and its contributors.
.\" 4. Neither the name of The NetBSD Foundation nor the names of its
.\" contributors may be used to endorse or promote products derived
.\" from this software without specific prior written permission.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
.\"
.Dd September 1, 2006
.Dt LOCKSTAT 8
.Os
.Sh NAME
.Nm lockstat
.Nd display kernel locking statistics
.Sh SYNOPSIS
.Nm
.Op Fl celpst
.Op Fl E Ar event
.Op Fl F Ar func
.Op Fl L Ar lock
.Op Fl N Ar nlist
.Op Fl T Ar type
.Op Fl b Ar nbuf
.Op Fl o Ar file
.Ar command ...
.Sh DESCRIPTION
The
.Nm
command enables system wide tracing of kernel lock events, executes
the specified command, and when finished reports statistics to the user.
.Pp
Tracing may be ended early by sending
.Dv SIGINT
(Ctrl-C) to the process being executed by lockstat.
.Pp
The
.Nm lockstat
pseudo-device
driver must be present in the kernel, and the
.Nm
command may only be used by the root user.
.Pp
The options are as follows:
.Bl -tag -width wellhello
.It Fl E Ar event
Limit tracing to one type of event.
Use the
.Fl e
option to list valid events.
.It Fl F Ar func
Limit tracing to locking operations performed within the specified function.
.Ar func
must be the name of a valid function in the kernel.
.It Fl L Ar lock
Limit tracing to one lock.
.Ar lock
may either be the name of a lock object in the kernel, or a kernel virtual
address.
.It Fl N Ar nlist
Extract symbol information from the
.Ar nlist
file.
.It Fl T Ar type
Limit tracing to one type of lock.
Use the
.Fl t
option to list valid lock types.
.It Fl b Ar nbuf
Adjust the number of trace buffers allocated by the kernel to
.Ar nbuf .
.It Fl c
Report percentage of total events by count, and sort the output by number
of events.
The default is to key on event timings.
.It Fl e
List valid event types for the
.Fl E
option and exit.
.It Fl l
Trace only by lock; do not report on calling functions.
.It Fl o Ar file
Send output to the file named by
.Ar file ,
instead of the standard output (the default).
.It Fl p
Show the average number of events and time spent per CPU.
The default is to show the total values.
May be used in conjunction with the
.Fl s
option.
.It Fl s
Show the average number of events per second, and the average time spent per
second.
The accuracy will naturally increase with longer run times.
The default is to show the total values.
.It Fl t
List valid lock types for the
.Fl T
option and exit.
.El
.Sh DIAGNOSTICS
.Bl -diag
.It "lockstat: incompatible lockstat interface version"
.Pp
The kernel device driver does not match the version of the
.Nm
command.
.It "lockstat: overflowed available kernel trace buffers"
.Pp
Increase the number of buffers using the
.Fl b
option.
.It "lockstat: ioctl: Invalid argument"
.Pp
The number of trace buffers is outside the minimum and maximum
bounds set by the kernel.
.El
.Sh FILES
.Bl -tag -width /dev/lockstat -compact
.It Pa /dev/lockstat
.Nm lockstat
control device
.It Pa /dev/ksyms
default namelist
.It Pa /netbsd
namelist
.El
.Sh SEE ALSO
.Xr ps 1 ,
.Xr pstat 1 ,
.Xr systat 1 ,
.Xr vmstat 1 ,
.Xr iostat 8
.Sh HISTORY
The
.Nm
command appeared in
.Nx 5.0 .

697
usr.sbin/lockstat/main.c Normal file
View File

@ -0,0 +1,697 @@
/* $NetBSD: main.c,v 1.1 2006/09/07 00:50:45 ad Exp $ */
/*-
* Copyright (c) 2006 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Andrew Doran.
*
* 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.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
*/
/*
* TODO:
*
* - Need better analysis and tracking of events.
* - Should be binary format agnostic, but given that we're likely to be using
* ELF for quite a while that's not a big problem.
* - Shouldn't have to parse the namelist here. We should use something like
* FreeBSD's libelf.
* - The way the namelist is searched sucks, is it worth doing something
* better?
*/
#include <sys/cdefs.h>
#ifndef lint
__RCSID("$NetBSD: main.c,v 1.1 2006/09/07 00:50:45 ad Exp $");
#endif /* not lint */
#include <sys/types.h>
#include <sys/param.h>
#include <sys/time.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/lockstat.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <unistd.h>
#include <err.h>
#include <paths.h>
#include <util.h>
#include <ctype.h>
#include <errno.h>
#include "extern.h"
#define _PATH_DEV_LOCKSTAT "/dev/lockstat"
#define MILLI 1000.0
#define MICRO 1000000.0
#define NANO 1000000000.0
#define PICO 1000000000000.0
TAILQ_HEAD(lock_head, lockstruct);
typedef struct lock_head locklist_t;
TAILQ_HEAD(buf_head, lsbuf);
typedef struct buf_head buflist_t;
typedef struct lockstruct {
TAILQ_ENTRY(lockstruct) chain;
buflist_t bufs;
uintptr_t lock;
double times[LB_NEVENT];
uint32_t counts[LB_NEVENT];
u_int flags;
} lock_t;
typedef struct name {
const char *name;
int mask;
} name_t;
const name_t locknames[] = {
{ "adaptive_mutex", LB_ADAPTIVE_MUTEX },
{ "adaptive_rwlock", LB_ADAPTIVE_RWLOCK },
{ "spin_mutex", LB_SPIN_MUTEX },
{ "spin_rwlock", LB_SPIN_RWLOCK },
{ "lockmgr", LB_LOCKMGR },
{ NULL, 0 }
};
const name_t eventnames[] = {
{ "spin", LB_SPIN },
{ "sleep", LB_SLEEP },
{ NULL, 0 },
};
const name_t alltypes[] = {
{ "Adaptive mutex spin", LB_ADAPTIVE_MUTEX | LB_SPIN },
{ "Adaptive mutex sleep", LB_ADAPTIVE_MUTEX | LB_SLEEP },
{ "Adaptive RW lock spin", LB_ADAPTIVE_RWLOCK | LB_SPIN },
{ "Adaptive RW lock sleep", LB_ADAPTIVE_RWLOCK | LB_SPIN },
{ "Spin mutex spin", LB_SPIN_MUTEX | LB_SPIN },
{ "Spin RW lock spin", LB_SPIN_RWLOCK | LB_SPIN },
{ "lockmgr sleep", LB_LOCKMGR | LB_SLEEP },
{ NULL, 0 }
};
locklist_t locklist[LB_NLOCK];
lsbuf_t *bufs;
lsdisable_t ld;
int lflag;
int nbufs;
int cflag;
int lsfd;
int displayed;
int bin64;
double tscale;
double cscale;
double cpuscale[sizeof(ld.ld_freq) / sizeof(ld.ld_freq[0])];
FILE *outfp;
void findsym(findsym_t, char *, uintptr_t *, uintptr_t *);
void spawn(int, char **);
void display(int, const char *name);
void listnames(const name_t *);
int matchname(const name_t *, const char *);
void makelists(void);
void nullsig(int);
void usage(void);
void resort(int, int);
int ncpu(void);
int
main(int argc, char **argv)
{
int eventtype, locktype, ch, nlfd, sflag, fd, i, pflag;
const char *nlistf, *outf;
char *lockname, *funcname;
const name_t *name;
lsenable_t le;
double ms;
char *p;
nlistf = NULL;
outf = NULL;
lockname = NULL;
funcname = NULL;
eventtype = -1;
locktype = -1;
nbufs = 0;
sflag = 0;
pflag = 0;
while ((ch = getopt(argc, argv, "E:F:L:M:N:T:b:ceflo:pst")) != -1)
switch (ch) {
case 'E':
eventtype = matchname(eventnames, optarg);
break;
case 'F':
funcname = optarg;
break;
case 'L':
lockname = optarg;
break;
case 'N':
nlistf = optarg;
break;
case 'T':
locktype = matchname(locknames, optarg);
break;
case 'b':
nbufs = (int)strtol(optarg, &p, 0);
if (!isdigit((u_int)*optarg) || *p != '\0')
usage();
break;
case 'c':
cflag = 1;
break;
case 'e':
listnames(eventnames);
break;
case 'l':
lflag = 1;
break;
case 'o':
outf = optarg;
break;
case 'p':
pflag = 1;
break;
case 's':
sflag = 1;
break;
case 't':
listnames(locknames);
break;
default:
usage();
}
argc -= optind;
argv += optind;
if (*argv == NULL)
usage();
if (outf) {
if ((fd = open(outf, O_WRONLY | O_CREAT, 0600)) == -1)
err(EXIT_FAILURE, "opening %s", outf);
outfp = fdopen(fd, "w");
} else
outfp = stdout;
/*
* Find the name list for resolving symbol names, and load it into
* memory.
*/
if (nlistf == NULL) {
nlfd = open(_PATH_KSYMS, O_RDONLY);
nlistf = getbootfile();
} else
nlfd = -1;
if (nlfd == -1) {
if ((nlfd = open(nlistf, O_RDONLY)) < 0)
err(EXIT_FAILURE, "cannot open " _PATH_KSYMS " or %s",
nlistf);
}
if (loadsym32(nlfd) != 0) {
if (loadsym64(nlfd) != 0)
errx(EXIT_FAILURE, "unable to load symbol table");
bin64 = 1;
}
close(nlfd);
memset(&le, 0, sizeof(le));
le.le_nbufs = nbufs;
/*
* Set up initial filtering.
*/
if (lockname != NULL) {
findsym(LOCK_BYNAME, lockname, &le.le_lock, NULL);
le.le_flags |= LE_ONE_LOCK;
}
if (!lflag)
le.le_flags |= LE_CALLSITE;
if (funcname != NULL) {
if (lflag)
usage();
findsym(FUNC_BYNAME, funcname, &le.le_csstart, &le.le_csend);
le.le_flags |= LE_ONE_CALLSITE;
}
le.le_mask = (eventtype & LB_EVENT_MASK) | (locktype & LB_LOCK_MASK);
/*
* Start tracing.
*/
if ((lsfd = open(_PATH_DEV_LOCKSTAT, O_RDONLY)) < 0)
err(EXIT_FAILURE, "cannot open " _PATH_DEV_LOCKSTAT);
if (ioctl(lsfd, IOC_LOCKSTAT_GVERSION, &ch) < 0)
err(EXIT_FAILURE, "ioctl");
if (ch != LS_VERSION)
errx(EXIT_FAILURE, "incompatible lockstat interface version");
if (ioctl(lsfd, IOC_LOCKSTAT_ENABLE, &le))
err(EXIT_FAILURE, "cannot enable tracing");
/*
* Execute the traced program.
*/
spawn(argc, argv);
/*
* Stop tracing, and read the trace buffers from the kernel.
*/
if (ioctl(lsfd, IOC_LOCKSTAT_DISABLE, &ld) == -1) {
if (errno == EOVERFLOW) {
warnx("overflowed available kernel trace buffers");
exit(EXIT_FAILURE);
}
err(EXIT_FAILURE, "cannot disable tracing");
}
if ((bufs = malloc(ld.ld_size)) == NULL)
err(EXIT_FAILURE, "cannot allocate memory for user buffers");
if (read(lsfd, bufs, ld.ld_size) != ld.ld_size)
err(EXIT_FAILURE, "reading from " _PATH_DEV_LOCKSTAT);
if (close(lsfd))
err(EXIT_FAILURE, "close(" _PATH_DEV_LOCKSTAT ")");
/*
* Figure out how to scale the results, and build the lists. For
* internal use we convert all times from CPU frequency based to
* picoseconds, and values are eventually displayed in ms.
*/
for (i = 0; i < sizeof(ld.ld_freq) / sizeof(ld.ld_freq[0]); i++)
if (ld.ld_freq[i] != 0)
cpuscale[i] = PICO / ld.ld_freq[i];
ms = ld.ld_time.tv_sec * MILLI + ld.ld_time.tv_nsec / MICRO;
if (pflag)
cscale = 1.0 / ncpu();
else
cscale = 1.0;
cscale *= (sflag ? MILLI / ms : 1.0);
tscale = cscale / NANO;
nbufs = (int)(ld.ld_size / sizeof(lsbuf_t));
makelists();
/*
* Display the results.
*/
fprintf(outfp, "Elapsed time: %.2f seconds.", ms / MILLI);
if (sflag || pflag) {
fprintf(outfp, " Displaying ");
if (pflag)
fprintf(outfp, "per-CPU ");
if (sflag)
fprintf(outfp, "per-second ");
fprintf(outfp, "averages.");
}
putc('\n', outfp);
for (name = alltypes; name->name != NULL; name++) {
if (eventtype != -1 &&
(name->mask & LB_EVENT_MASK) != eventtype)
continue;
if (locktype != -1 &&
(name->mask & LB_LOCK_MASK) != locktype)
continue;
display(name->mask, name->name);
}
if (displayed == 0)
fprintf(outfp, "None of the selected events were recorded.\n");
exit(EXIT_SUCCESS);
}
void
usage(void)
{
fprintf(stderr,
"%s: usage:\n"
"%s [options] <command>\n\n"
"-F func\t\tlimit trace to one function\n"
"-E evt\t\tdisplay only one type of event\n"
"-L lock\t\tlimit trace to one lock (name, or address)\n"
"-N nlist\tspecify name list file\n"
"-T type\t\tdisplay only one type of lock\n"
"-b nbuf\t\tset number of event buffers to allocate\n"
"-c\t\treport percentage of total events by count, not time\n"
"-e\t\tlist event types\n"
"-l\t\ttrace only by lock\n"
"-o file\t\tsend output to named file, not stdout\n"
"-p\t\tshow average count/time per CPU, not total\n"
"-s\t\tshow average count/time per second, not total\n"
"-t\t\tlist lock types\n",
getprogname(), getprogname());
exit(EXIT_FAILURE);
}
void
nullsig(int junk)
{
(void)junk;
}
void
listnames(const name_t *name)
{
for (; name->name != NULL; name++)
printf("%s\n", name->name);
exit(EXIT_SUCCESS);
}
int
matchname(const name_t *name, const char *string)
{
for (; name->name != NULL; name++)
if (strcasecmp(name->name, string) == 0)
return name->mask;
warnx("unknown type `%s'", string);
usage();
return 0;
}
/*
* Return the number of CPUs in the running system.
*/
int
ncpu(void)
{
int rv, mib[2];
size_t varlen;
mib[0] = CTL_HW;
mib[1] = HW_NCPU;
varlen = sizeof(rv);
if (sysctl(mib, 2, &rv, &varlen, NULL, (size_t)0) < 0)
rv = 1;
return (rv);
}
/*
* Call into the ELF parser and look up a symbol by name or by address.
*/
void
findsym(findsym_t find, char *name, uintptr_t *start, uintptr_t *end)
{
uintptr_t tend;
char *p;
int rv;
if (end == NULL)
end = &tend;
if (find == LOCK_BYNAME) {
if (isdigit((u_int)name[0])) {
*start = (uintptr_t)strtoul(name, &p, 0);
if (*p == '\0')
return;
}
}
if (bin64)
rv = findsym64(find, name, start, end);
else
rv = findsym32(find, name, start, end);
if (find == FUNC_BYNAME || find == LOCK_BYNAME) {
if (rv == -1)
errx(EXIT_FAILURE, "unable to find symbol `%s'", name);
return;
}
if (rv == -1)
sprintf(name, "0x%016lx", (long)*start);
}
/*
* Fork off the child process and wait for it to complete. We trap SIGINT
* so that the caller can use Ctrl-C to stop tracing early and still get
* useful results.
*/
void
spawn(int argc, char **argv)
{
pid_t pid;
switch (pid = fork()) {
case 0:
close(lsfd);
if (execvp(argv[0], argv) == -1)
err(EXIT_FAILURE, "cannot exec");
break;
case -1:
err(EXIT_FAILURE, "cannot fork to exec");
break;
default:
signal(SIGINT, nullsig);
wait(NULL);
signal(SIGINT, SIG_DFL);
break;
}
}
/*
* From the kernel supplied data, construct two dimensional lists of locks
* and event buffers, indexed by lock type.
*/
void
makelists(void)
{
lsbuf_t *lb, *lb2, *max;
int i, type;
lock_t *l;
for (i = 0; i < LB_NLOCK; i++)
TAILQ_INIT(&locklist[i]);
for (lb = bufs, max = bufs + nbufs; lb < max; lb++) {
if (lb->lb_flags == 0)
continue;
/*
* Look for a record descibing this lock, and allocate a
* new one if needed.
*/
type = ((lb->lb_flags & LB_LOCK_MASK) >> LB_LOCK_SHIFT) - 1;
TAILQ_FOREACH(l, &locklist[type], chain) {
if (l->lock == lb->lb_lock)
break;
}
if (l == NULL) {
l = (lock_t *)malloc(sizeof(*l));
l->flags = lb->lb_flags;
l->lock = lb->lb_lock;
memset(&l->counts, 0, sizeof(l->counts));
memset(&l->times, 0, sizeof(l->times));
TAILQ_INIT(&l->bufs);
TAILQ_INSERT_TAIL(&locklist[type], l, chain);
}
/*
* Scale the time values per buffer and summarise
* times+counts per lock.
*/
for (i = 0; i < LB_NEVENT; i++) {
lb->lb_times[i] *= cpuscale[lb->lb_cpu];
l->counts[i] += lb->lb_counts[i];
l->times[i] += lb->lb_times[i];
}
/*
* Merge same lock+callsite pairs from multiple CPUs
* together.
*/
TAILQ_FOREACH(lb2, &l->bufs, lb_chain.tailq) {
if (lb->lb_callsite == lb2->lb_callsite)
break;
}
if (lb2 != NULL) {
for (i = 0; i < LB_NEVENT; i++) {
lb2->lb_counts[i] += lb->lb_counts[i];
lb2->lb_times[i] += lb->lb_times[i];
}
} else
TAILQ_INSERT_HEAD(&l->bufs, lb, lb_chain.tailq);
}
}
/*
* Re-sort one list of locks / lock buffers by event type.
*/
void
resort(int type, int event)
{
lsbuf_t *lb, *lb2;
locklist_t llist;
buflist_t blist;
lock_t *l, *l2;
TAILQ_INIT(&llist);
while ((l = TAILQ_FIRST(&locklist[type])) != NULL) {
TAILQ_REMOVE(&locklist[type], l, chain);
/*
* Sort the buffers into the per-lock list.
*/
TAILQ_INIT(&blist);
while ((lb = TAILQ_FIRST(&l->bufs)) != NULL) {
TAILQ_REMOVE(&l->bufs, lb, lb_chain.tailq);
lb2 = TAILQ_FIRST(&blist);
while (lb2 != NULL) {
if (cflag) {
if (lb->lb_counts[event] >
lb2->lb_counts[event])
break;
} else if (lb->lb_times[event] >
lb2->lb_times[event])
break;
lb2 = TAILQ_NEXT(lb2, lb_chain.tailq);
}
if (lb2 == NULL)
TAILQ_INSERT_TAIL(&blist, lb, lb_chain.tailq);
else
TAILQ_INSERT_BEFORE(lb2, lb, lb_chain.tailq);
}
l->bufs = blist;
/*
* Sort this lock into the per-type list, based on the
* totals per lock.
*/
l2 = TAILQ_FIRST(&llist);
while (l2 != NULL) {
if (cflag) {
if (l->counts[event] > l2->counts[event])
break;
} else if (l->times[event] > l2->times[event])
break;
l2 = TAILQ_NEXT(l2, chain);
}
if (l2 == NULL)
TAILQ_INSERT_TAIL(&llist, l, chain);
else
TAILQ_INSERT_BEFORE(l2, l, chain);
}
locklist[type] = llist;
}
/*
* Display a summary table for one lock type / event type pair.
*/
void
display(int mask, const char *name)
{
lock_t *l;
lsbuf_t *lb;
int event, type;
double pcscale, metric;
char lname[256], fname[256];
type = ((mask & LB_LOCK_MASK) >> LB_LOCK_SHIFT) - 1;
if (TAILQ_FIRST(&locklist[type]) == NULL)
return;
event = (mask & LB_EVENT_MASK) - 1;
resort(type, event);
fprintf(outfp, "\n-- %s\n\n"
"Total%% Count Time/ms Lock Caller\n"
"------ ------- --------- ------------------ ----------------------------------\n",
name);
/*
* Sum up all events for this type of lock + event.
*/
pcscale = 0;
TAILQ_FOREACH(l, &locklist[type], chain) {
if (cflag)
pcscale += l->counts[event];
else
pcscale += l->times[event];
displayed++;
}
if (pcscale == 0)
pcscale = 100;
else
pcscale = (100.0 / pcscale);
/*
* For each lock, print a summary total, followed by a breakdown by
* caller.
*/
TAILQ_FOREACH(l, &locklist[type], chain) {
if (cflag)
metric = l->counts[event];
else
metric = l->times[event];
metric *= pcscale;
findsym(LOCK_BYADDR, lname, &l->lock, NULL);
fprintf(outfp, "%6.2f %7d %9.2f %-18s <all>\n", metric,
(int)(l->counts[event] * cscale),
l->times[event] * tscale, lname);
if (lflag)
continue;
TAILQ_FOREACH(lb, &l->bufs, lb_chain.tailq) {
if (cflag)
metric = lb->lb_counts[event];
else
metric = lb->lb_times[event];
metric *= pcscale;
findsym(LOCK_BYADDR, lname, &lb->lb_lock, NULL);
findsym(FUNC_BYADDR, fname, &lb->lb_callsite, NULL);
fprintf(outfp, "%6.2f %7d %9.2f %-18s %s\n", metric,
(int)(lb->lb_counts[event] * cscale),
lb->lb_times[event] * tscale,
lname, fname);
}
}
}