Subject: statistics gathering for tape drives
To: None <tech-kern@netbsd.org>
From: Brett Lymn <blymn@baesystems.com.au>
List: tech-kern
Date: 07/27/2005 22:07:22
Folks,

I noticed that our tape drives were not instrumented so there was no
way one could monitor the read/write/utiliation of a tape drive on a
NetBSD system.  I decided to fix this, now I can do:

[blymn@siren] iostat -x st0 5
device  read KB/t    r/s   time     MB/s write KB/t    w/s   time     MB/s
st0          0.00      9   0.48     0.27      10.21     53   0.48     0.53
st0          0.00      0   0.98     0.00      64.00     80   0.98     5.00
st0          0.00      0   0.98     0.00      64.00     79   0.98     4.94
st0          0.00      0   0.97     0.00      64.00     79   0.97     4.95
st0          0.00      0   0.98     0.00      64.00     79   0.98     4.94

... this is a DLT4000 attached to a Adaptec 29160, these figures are
when I am dd'ing 1Mb blocks of zeros to the tape device.  The
manufacturer rates the drive at 6Mb/s for compressed data so the above
numbers are not too shabby.  I have also fixed vmstat and systat to
show the tape statistics.

Below is the patch that modifies all the relevant files, there are
some new files you need to fetch if you want to play with this, these
files are on ftp.netbsd.org under /pub/NetBSD/misc/blymn/tape_stats/
in a file called extra_files.tar.gz.  Unpack the tar file in the root
of your NetBSD src directory, apply the patch from the same place.

Comments and feedback are welcome, if there are no major problems
noted with the code I shall commit it to the tree for all to enjoy.

...diff follows...

Index: sys/dev/scsipi/st.c
===================================================================
RCS file: /cvsroot/src/sys/dev/scsipi/st.c,v
retrieving revision 1.182
diff -u -r1.182 st.c
--- sys/dev/scsipi/st.c	16 Jul 2005 05:12:26 -0000	1.182
+++ sys/dev/scsipi/st.c	27 Jul 2005 12:26:48 -0000
@@ -76,6 +76,8 @@
 #include <sys/conf.h>
 #include <sys/kernel.h>
 #include <sys/vnode.h>
+#include <sys/tape.h>
+#include <sys/sysctl.h>
 
 #include <dev/scsipi/scsi_spc.h>
 #include <dev/scsipi/scsipi_all.h>
@@ -347,6 +349,14 @@
 	stdone
 };
 
+/*
+ * A global list of all tape drives attached to the system.  May grow or
+ * shrink over time.
+ */
+struct  tapelist_head tapelist = TAILQ_HEAD_INITIALIZER(tapelist);
+int     tape_count = 0;             /* number of drives in global tapelist */
+struct simplelock tapelist_slock = SIMPLELOCK_INITIALIZER;
+
 #if	defined(ST_ENABLE_EARLYWARN)
 #define	ST_INIT_FLAGS	ST_EARLYWARN
 #else
@@ -362,6 +372,7 @@
 {
 	struct scsipibus_attach_args *sa = aux;
 	struct scsipi_periph *periph = sa->sa_periph;
+	int s;
 
 	SC_DEBUG(periph, SCSIPI_DB2, ("stattach: "));
 
@@ -378,6 +389,28 @@
 
 	st->flags = ST_INIT_FLAGS;
 
+	  /* Allocate and initialise statistics */
+	st->stats = (struct tape *) malloc(sizeof(struct tape), M_DEVBUF,
+					   M_WAITOK);
+	st->stats->rxfer = st->stats->wxfer = st->stats->rbytes = 0;
+	st->stats->wbytes = st->stats->busy = 0;
+
+	/*
+	 * Set the attached timestamp.
+	 */
+	s = splclock();
+	st->stats->attachtime = mono_time;
+	splx(s);
+
+	  /* and clear the utilisation time */
+	timerclear(&st->stats->time);
+
+	  /* link the tape drive to the tapelist */
+	simple_lock(&tapelist_slock);
+	TAILQ_INSERT_TAIL(&tapelist, st->stats, link);
+	tape_count++;
+	simple_unlock(&tapelist_slock);
+	
 	/*
 	 * Set up the buf queue for this device
 	 */
@@ -410,6 +443,8 @@
 		    (st->flags & ST_READONLY) ? "protected" : "enabled");
 	}
 
+	st->stats->name = st->sc_dev.dv_xname;
+	
 #if NRND > 0
 	rnd_attach_source(&st->rnd_source, st->sc_dev.dv_xname,
 			  RND_TYPE_TAPE, 0);
@@ -465,6 +500,16 @@
 	vdevgone(bmaj, mn, mn+STNMINOR-1, VBLK);
 	vdevgone(cmaj, mn, mn+STNMINOR-1, VCHR);
 
+	if (tape_count == 0) {
+		printf("%s detach: tape_count already zero\n",
+		       st->sc_dev.dv_xname);
+	} else {
+		simple_lock(&tapelist_slock);
+		TAILQ_REMOVE(&tapelist, st->stats, link);
+		tape_count--;
+		simple_unlock(&tapelist_slock);
+		free(st->stats, M_DEVBUF);
+	}
 
 #if NRND > 0
 	/* Unhook the entropy source. */
@@ -1162,7 +1207,7 @@
 	struct buf *bp;
 	struct scsi_rw_tape cmd;
 	struct scsipi_xfer *xs;
-	int flags, error;
+	int flags, error, s;
 
 	SC_DEBUG(periph, SCSIPI_DB2, ("ststart "));
 	/*
@@ -1199,6 +1244,12 @@
 		if ((bp = BUFQ_PEEK(&st->buf_queue)) == NULL)
 			return;
 
+		if (st->stats->busy++ == 0) {
+			s = splclock();
+			st->stats->timestamp = mono_time;
+			splx(s);
+		}
+		
 		/*
 		 * only FIXEDBLOCK devices have pending I/O or space operations.
 		 */
@@ -1325,6 +1376,8 @@
 {
 	struct st_softc *st = (void *)xs->xs_periph->periph_dev;
 	struct buf *bp = xs->bp;
+	int s;
+	struct timeval st_time, diff_time;
 
 	if (bp) {
 		bp->b_error = error;
@@ -1336,6 +1389,33 @@
 			st->flags |= ST_WRITTEN;
 		else
 			st->flags &= ~ST_WRITTEN;
+
+		if (st->stats->busy-- == 0) {
+			  /* this is not really fatal so we don't panic */
+			printf("%s: busy < 0, Oops.\n", st->stats->name);
+			st->stats->busy = 0;
+		} else {
+			s = splclock();
+			st_time = mono_time;
+			splx(s);
+
+			timersub(&st_time, &st->stats->timestamp, &diff_time);
+			timeradd(&st->stats->time, &diff_time,
+				 &st->stats->time);
+
+			st->stats->timestamp = st_time;
+			if (bp->b_bcount > 0) {
+				if ((bp->b_flags & B_READ) == B_WRITE) {
+					st->stats->wbytes += bp->b_bcount;
+					st->stats->wxfer++;
+				} else {
+					st->stats->rbytes += bp->b_bcount;
+					st->stats->rxfer++;
+				}
+			}
+		}
+		
+			
 #if NRND > 0
 		rnd_add_uint32(&st->rnd_source, bp->b_blkno);
 #endif
@@ -2393,3 +2473,108 @@
 	/* Not implemented. */
 	return (ENXIO);
 }
+
+int
+sysctl_hw_tapenames(SYSCTLFN_ARGS)
+{
+	char bf[TAPENAMELEN + 1];
+	char *where = oldp;
+	struct tape *tapep;
+	size_t needed, left, slen;
+	int error, first;
+
+	if (newp != NULL)
+		return (EPERM);
+	if (namelen != 0)
+		return (EINVAL);
+
+	first = 1;
+	error = 0;
+	needed = 0;
+	left = *oldlenp;
+
+	simple_lock(&tapelist_slock);
+	for (tapep = TAILQ_FIRST(&tapelist); tapep != NULL;
+	    tapep = TAILQ_NEXT(tapep, link)) {
+		if (where == NULL)
+			needed += strlen(tapep->name) + 1;
+		else {
+			memset(bf, 0, sizeof(bf));
+			if (first) {
+				strncpy(bf, tapep->name, sizeof(bf));
+				first = 0;
+			} else {
+				bf[0] = ' ';
+				strncpy(bf + 1, tapep->name, sizeof(bf) - 1);
+			}
+			bf[TAPENAMELEN] = '\0';
+			slen = strlen(bf);
+			if (left < slen + 1)
+				break;
+			/* +1 to copy out the trailing NUL byte */
+			error = copyout(bf, where, slen + 1);
+			if (error)
+				break;
+			where += slen;
+			needed += slen;
+			left -= slen;
+		}
+	}
+	simple_unlock(&tapelist_slock);
+	*oldlenp = needed;
+	return (error);
+}
+
+int
+sysctl_hw_tapestats(SYSCTLFN_ARGS)
+{
+	struct tape_sysctl stape;
+	struct tape *tapep;
+	char *where = oldp;
+	size_t tocopy, left;
+	int error;
+
+	if (newp != NULL)
+		return (EPERM);
+
+	tocopy = name[0];
+
+	if (where == NULL) {
+		*oldlenp = tape_count * tocopy;
+		return (0);
+	}
+
+	error = 0;
+	left = *oldlenp;
+	memset(&stape, 0, sizeof(stape));
+	*oldlenp = 0;
+
+	simple_lock(&tapelist_slock);
+	TAILQ_FOREACH(tapep, &tapelist, link) {
+		if (left < tocopy)
+			break;
+		strncpy(stape.name, tapep->name, sizeof(stape.name));
+		stape.xfer = tapep->rxfer + tapep->wxfer;
+		stape.rxfer = tapep->rxfer;
+		stape.wxfer = tapep->wxfer;
+		stape.bytes = tapep->rbytes + tapep->wbytes;
+		stape.rbytes = tapep->rbytes;
+		stape.wbytes = tapep->wbytes;
+		stape.attachtime_sec = tapep->attachtime.tv_sec;
+		stape.attachtime_usec = tapep->attachtime.tv_usec;
+		stape.timestamp_sec = tapep->timestamp.tv_sec;
+		stape.timestamp_usec = tapep->timestamp.tv_usec;
+		stape.time_sec = tapep->time.tv_sec;
+		stape.time_usec = tapep->time.tv_usec;
+		stape.busy = tapep->busy;
+
+		error = copyout(&stape, where, min(tocopy, sizeof(stape)));
+		if (error)
+			break;
+		where += tocopy;
+		*oldlenp += tocopy;
+		left -= tocopy;
+	}
+	simple_unlock(&tapelist_slock);
+	return (error);
+}
Index: sys/dev/scsipi/stvar.h
===================================================================
RCS file: /cvsroot/src/sys/dev/scsipi/stvar.h,v
retrieving revision 1.13
diff -u -r1.13 stvar.h
--- sys/dev/scsipi/stvar.h	29 May 2005 22:00:50 -0000	1.13
+++ sys/dev/scsipi/stvar.h	27 Jul 2005 12:26:49 -0000
@@ -150,6 +150,9 @@
 					/* operations */
 	struct callout sc_callout;	/* restarting the queue after */
 					/* transient error */
+	
+	struct tape *stats;		/* statistics for the drive */
+	
 #if NRND > 0
 	rndsource_element_t	rnd_source;
 #endif
Index: sys/kern/init_sysctl.c
===================================================================
RCS file: /cvsroot/src/sys/kern/init_sysctl.c,v
retrieving revision 1.47
diff -u -r1.47 init_sysctl.c
--- sys/kern/init_sysctl.c	16 Jul 2005 22:47:18 -0000	1.47
+++ sys/kern/init_sysctl.c	27 Jul 2005 12:26:49 -0000
@@ -891,6 +891,18 @@
 		       CTL_HW, HW_DISKSTATS, CTL_EOL);
 	sysctl_createv(clog, 0, NULL, NULL,
 		       CTLFLAG_PERMANENT,
+		       CTLTYPE_STRING, "tapenames",
+		       SYSCTL_DESCR("List of tape devices present"),
+		       sysctl_hw_tapenames, 0, NULL, 0,
+		       CTL_HW, HW_TAPENAMES, CTL_EOL);
+	sysctl_createv(clog, 0, NULL, NULL,
+		       CTLFLAG_PERMANENT,
+		       CTLTYPE_STRUCT, "tapestats",
+		       SYSCTL_DESCR("Statistics on tape drive operation"),
+		       sysctl_hw_tapestats, 0, NULL, 0,
+		       CTL_HW, HW_TAPESTATS, CTL_EOL);
+	sysctl_createv(clog, 0, NULL, NULL,
+		       CTLFLAG_PERMANENT,
 		       CTLTYPE_STRING, "machine_arch",
 		       SYSCTL_DESCR("Machine CPU class"),
 		       NULL, 0, machine_arch, 0,
Index: sys/sys/Makefile
===================================================================
RCS file: /cvsroot/src/sys/sys/Makefile,v
retrieving revision 1.73
diff -u -r1.73 Makefile
--- sys/sys/Makefile	22 May 2005 12:44:24 -0000	1.73
+++ sys/sys/Makefile	27 Jul 2005 12:26:50 -0000
@@ -26,7 +26,7 @@
 	siginfo.h signal.h signalvar.h sigtypes.h socket.h \
 	socketvar.h sockio.h stat.h statvfs.h syscall.h syscallargs.h \
 	sysctl.h stdint.h swap.h syslimits.h syslog.h systm.h \
-	tablet.h termios.h time.h timeb.h timepps.h times.h \
+	tablet.h tape.h termios.h time.h timeb.h timepps.h times.h \
 	timex.h tprintf.h trace.h tree.h tty.h ttychars.h ttycom.h \
 	ttydefaults.h ttydev.h types.h \
 	ucontext.h ucred.h uio.h un.h unistd.h unpcb.h user.h utsname.h uuid.h \
Index: sys/sys/sysctl.h
===================================================================
RCS file: /cvsroot/src/sys/sys/sysctl.h,v
retrieving revision 1.138
diff -u -r1.138 sysctl.h
--- sys/sys/sysctl.h	20 Jun 2005 02:49:19 -0000	1.138
+++ sys/sys/sysctl.h	27 Jul 2005 12:26:50 -0000
@@ -705,7 +705,9 @@
 #define	HW_CNMAGIC	12		/* string: console magic sequence(s) */
 #define	HW_PHYSMEM64	13		/* quad: total memory (bytes) */
 #define	HW_USERMEM64	14		/* quad: non-kernel memory (bytes) */
-#define	HW_MAXID	15		/* number of valid hw ids */
+#define	HW_TAPENAMES	15		/* string: tape drive names */
+#define	HW_TAPESTATS	16		/* struct: tapestats[] */
+#define	HW_MAXID	16		/* number of valid hw ids */
 
 #define	CTL_HW_NAMES { \
 	{ 0, 0 }, \
@@ -1065,6 +1067,8 @@
  */
 int	sysctl_hw_disknames(SYSCTLFN_PROTO);
 int	sysctl_hw_diskstats(SYSCTLFN_PROTO);
+int	sysctl_hw_tapenames(SYSCTLFN_PROTO);
+int	sysctl_hw_tapestats(SYSCTLFN_PROTO);
 int	sysctl_kern_vnode(SYSCTLFN_PROTO);
 int	sysctl_net_inet_ip_ports(SYSCTLFN_PROTO);
 int	sysctl_consdev(SYSCTLFN_PROTO);
Index: usr.bin/systat/Makefile
===================================================================
RCS file: /cvsroot/src/usr.bin/systat/Makefile,v
retrieving revision 1.30
diff -u -r1.30 Makefile
--- usr.bin/systat/Makefile	26 Feb 2005 22:12:33 -0000	1.30
+++ usr.bin/systat/Makefile	27 Jul 2005 12:26:51 -0000
@@ -11,7 +11,7 @@
 CPPFLAGS+=-I${NETBSDSRCDIR}/usr.bin/vmstat -DSUPPORT_UTMP -DSUPPORT_UTMPX \
 	-I${NETBSDSRCDIR}/usr.bin/who
 CWARNFLAGS+=    -Wno-format-y2k
-SRCS=	bufcache.c cmds.c cmdtab.c disks.c df.c dkstats.c fetch.c \
+SRCS=	bufcache.c cmds.c cmdtab.c disks.c df.c dkstats.c tpstats.c fetch.c \
 	globalcmds.c icmp.c iostat.c ip.c keyboard.c main.c mbufs.c \
 	netcmds.c netstat.c pigs.c ps.c swap.c tcp.c vmstat.c utmpentry.c
 DPADD=	${LIBCURSES} ${LIBM} ${LIBKVM}
Index: usr.bin/systat/iostat.c
===================================================================
RCS file: /cvsroot/src/usr.bin/systat/iostat.c,v
retrieving revision 1.32
diff -u -r1.32 iostat.c
--- usr.bin/systat/iostat.c	26 Feb 2005 22:28:23 -0000	1.32
+++ usr.bin/systat/iostat.c	27 Jul 2005 12:26:51 -0000
@@ -44,6 +44,7 @@
 #include "systat.h"
 #include "extern.h"
 #include "dkstats.h"
+#include "tpstats.h"
 
 static  int linesperregion;
 static  double etime;
@@ -55,6 +56,7 @@
 static void histogram(double, int, double);
 static int numlabels(int);
 static int stats(int, int, int);
+static int tpstats(int, int, int);
 static void stat1(int, int);
 
 
@@ -81,7 +83,9 @@
 {
 
 	dkinit(1);
+	tpinit(1);
 	dkreadstats();
+	tpreadstats();
 	return(1);
 }
 
@@ -91,7 +95,13 @@
 
 	if (dk_ndrive == 0)
 		return;
-	dkreadstats();
+	else
+		dkreadstats();
+
+	if (tp_ndrive == 0)
+		return;
+	else
+		tpreadstats();
 }
 
 #define	INSET	14
@@ -101,7 +111,7 @@
 {
 	int row;
 
-	if (dk_ndrive == 0) {
+	if ((dk_ndrive == 0) && (tp_ndrive == 0)) {
 		error("No drives defined.");
 		return;
 	}
@@ -130,6 +140,10 @@
 	for (ndrives = 0, i = 0; i < dk_ndrive; i++)
 		if (cur.dk_select[i])
 			ndrives++;
+	for (i = 0; i < tp_ndrive; i++)
+		if (cur_tape.select[i])
+			ndrives++;
+	
 	regions = howmany(ndrives, DRIVESPERLINE);
 	/*
 	 * Deduct -regions for blank line after each scrolling region.
@@ -142,14 +156,22 @@
 	if (linesperregion < 3)
 		linesperregion = 3;
 	col = 0;
-	for (i = 0; i < dk_ndrive; i++)
-		if (cur.dk_select[i]) {
+	for (i = 0; i < (dk_ndrive + tp_ndrive); i++)
+		if (((i < dk_ndrive) && (cur.dk_select[i])) ||
+		    ((i >= dk_ndrive) && (cur_tape.select[i - dk_ndrive]))) {
 			if (col + COLWIDTH - 1 > getmaxx(wnd)) {
 				col = 0, row += linesperregion + 1;
 				if (row > getmaxy(wnd) - (linesperregion))
 					break;
 			}
-			mvwprintw(wnd, row, col + 5, "%s", cur.dk_name[i]);
+
+			if (i < dk_ndrive)
+				mvwprintw(wnd, row, col + 5, "%s",
+					  cur.dk_name[i]);
+			else
+				mvwprintw(wnd, row, col + 5, "%s",
+					  cur_tape.name[i - dk_ndrive]);
+			
 			if (read_write)
 				mvwprintw(wnd, row, col + 11 + secs * 5,
 				    "(write)");
@@ -188,6 +210,20 @@
 			if (secs)
 				mvwaddstr(wnd, row++, 0, "         msec|");
 		}
+	for (i = 0; i < tp_ndrive; i++)
+		if (cur_tape.select[i]) {
+			if (row > getmaxy(wnd) - linesperregion)
+				break;
+			mvwprintw(wnd, row++, 0, "%7.7s  kBps|",
+			    cur_tape.name[i]);
+			mvwaddstr(wnd, row++, 0, "          tps|");
+			if (read_write) {
+				mvwprintw(wnd, row++, 0, " (write) kBps|");
+				mvwaddstr(wnd, row++, 0, "          tps|");
+			}
+			if (secs)
+				mvwaddstr(wnd, row++, 0, "         msec|");
+		}
 	return (row);
 }
 
@@ -199,6 +235,7 @@
 	if (dk_ndrive == 0)
 		return;
 	dkswap();
+	tpswap();
 
 	etime = cur.cp_etime;
 	row = 1;
@@ -216,6 +253,12 @@
 					break;
 				row = stats(row, INSET, i);
 			}
+		for (i = 0; i < tp_ndrive; i++)
+			if (cur_tape.select[i]) {
+				if (row > getmaxy(wnd) - linesperregion)
+					break;
+				row = tpstats(row, INSET, i);
+			}
 		return;
 	}
 	col = 0;
@@ -237,6 +280,20 @@
 			(void) stats(row + 3, col, i);
 			col += COLWIDTH;
 		}
+	for (i = 0; i < tp_ndrive; i++)
+		if (cur_tape.select[i]) {
+			if (col + COLWIDTH - 1 > getmaxx(wnd)) {
+				col = 0, row += linesperregion + 1;
+				if (row > getmaxy(wnd) - (linesperregion + 1))
+					break;
+				wmove(wnd, row + linesperregion, 0);
+				wdeleteln(wnd);
+				wmove(wnd, row + 3, 0);
+				winsertln(wnd);
+			}
+			(void) stats(row + 3, col, i);
+			col += COLWIDTH;
+		}
 }
 
 static int
@@ -287,6 +344,54 @@
 	return (row);
 }
 
+static int
+tpstats(int row, int col, int dn)
+{
+	double atime, rwords, wwords;
+	uint64_t rxfer;
+
+	/* time busy in disk activity */
+	atime = (double)cur_tape.time[dn].tv_sec +
+		((double)cur_tape.time[dn].tv_usec / (double)1000000);
+
+	/* # of k transferred */
+	rwords = cur_tape.rbytes[dn] / 1024.0;
+	wwords = cur_tape.wbytes[dn] / 1024.0;
+	rxfer = cur_tape.rxfer[dn];
+	if (!read_write) {
+		rwords = wwords;
+		rxfer += cur_tape.wxfer[dn];
+	}
+	if (numbers) {
+		mvwprintw(wnd, row, col, "%5.0f%4.0f",
+		    rwords / etime, rxfer / etime);
+		if (secs)
+			wprintw(wnd, "%5.1f", atime / etime);
+		if (read_write)
+			wprintw(wnd, " %5.0f%4.0f",
+			    wwords / etime, cur_tape.wxfer[dn] / etime);
+		return (row);
+	}
+
+	wmove(wnd, row++, col);
+	histogram(rwords / etime, 50, 0.5);
+	wmove(wnd, row++, col);
+	histogram(rxfer / etime, 50, 0.5);
+	if (read_write) {
+		wmove(wnd, row++, col);
+		histogram(wwords / etime, 50, 0.5);
+		wmove(wnd, row++, col);
+		histogram(cur_tape.wxfer[dn] / etime, 50, 0.5);
+	}
+
+	if (secs) {
+		wmove(wnd, row++, col);
+		atime *= 1000;	/* In milliseconds */
+		histogram(atime / etime, 50, 0.5);
+	}
+	return (row);
+}
+
 static void
 stat1(int row, int o)
 {
Index: usr.bin/systat/vmstat.c
===================================================================
RCS file: /cvsroot/src/usr.bin/systat/vmstat.c,v
retrieving revision 1.60
diff -u -r1.60 vmstat.c
--- usr.bin/systat/vmstat.c	22 May 2005 14:00:59 -0000	1.60
+++ usr.bin/systat/vmstat.c	27 Jul 2005 12:26:51 -0000
@@ -56,6 +56,7 @@
 #include "systat.h"
 #include "extern.h"
 #include "dkstats.h"
+#include "tpstats.h"
 #include "utmpentry.h"
 
 static struct Info {
@@ -79,6 +80,7 @@
 static void copyinfo(struct Info *, struct Info *);
 static float cputime(int);
 static void dinfo(int, int, int);
+static void tinfo(int, int, int);
 static void getinfo(struct Info *, enum state);
 static void putint(int, int, int, int);
 static void putfloat(double, int, int, int, int, int);
@@ -221,6 +223,8 @@
 	hertz = stathz ? stathz : hz;
 	if (!dkinit(1))
 		return(0);
+	if (!tpinit(1))
+		return(0);
 
 	/* Old style interrupt counts - deprecated */
 	nintr = (namelist[X_EINTRCNT].n_value -
@@ -423,6 +427,7 @@
 
 	if (state == TIME) {
 		dkswap();
+		tpswap();
 		etime = cur.cp_etime;
 		/* < 5 ticks - ignore this trash */
 		if ((etime * hertz) < 1.0) {
@@ -551,16 +556,23 @@
 	PUTRATE(uvmexp.intrs, GENSTATROW + 1, GENSTATCOL + 17, 5);
 	PUTRATE(uvmexp.softs, GENSTATROW + 1, GENSTATCOL + 22, 5);
 	PUTRATE(uvmexp.faults, GENSTATROW + 1, GENSTATCOL + 27, 6);
-	for (l = 0, i = 0, r = DISKROW, c = DISKCOL; i < dk_ndrive; i++) {
-		if (!dk_select[i])
-			continue;
+	for (l = 0, i = 0, r = DISKROW, c = DISKCOL;
+	     i < (dk_ndrive + tp_ndrive); i++) {
+		if (i < dk_ndrive) {
+			if (!dk_select[i])
+				continue;
+		} else {
+			if (!tp_select[i - dk_ndrive])
+				continue;
+		}
+
 		if (disk_horiz)
 			c += DISKCOLWIDTH;
 		else
 			r++;
 		if (c + DISKCOLWIDTH > DISKCOLEND) {
 			if (disk_horiz && LINES - 1 - DISKROW >
-					(DISKCOLEND - DISKCOL) / DISKCOLWIDTH) {
+			    (DISKCOLEND - DISKCOL) / DISKCOLWIDTH) {
 				disk_horiz = 0;
 				relabel = 1;
 			}
@@ -568,7 +580,7 @@
 		}
 		if (r >= LINES - 1) {
 			if (!disk_horiz && LINES - 1 - DISKROW <
-					(DISKCOLEND - DISKCOL) / DISKCOLWIDTH) {
+			    (DISKCOLEND - DISKCOL) / DISKCOLWIDTH) {
 				disk_horiz = 1;
 				relabel = 1;
 			}
@@ -576,7 +588,10 @@
 		}
 		l++;
 
-		dinfo(i, r, c);
+		if (i < dk_ndrive)
+			dinfo(i, r, c);
+		else
+			tinfo(i - dk_ndrive, r, c);
 	}
 	/* blank out if we lost any disks */
 	for (i = l; i < last_disks; i++) {
@@ -735,6 +750,7 @@
 	int i;
 
 	dkreadstats();
+	tpreadstats();
 	NREAD(X_NCHSTATS, &stats->nchstats, sizeof stats->nchstats);
 	if (nintr)
 		NREAD(X_INTRCNT, stats->intrcnt, nintr * LONG);
@@ -810,5 +826,32 @@
 		putint(100, r, c, DISKCOLWIDTH);
 	else
 		putfloat(atime, r, c, DISKCOLWIDTH, 1, 1);
+}
+
+static void
+tinfo(int dn, int r, int c)
+{
+	double atime;
+
+	mvprintw(r, c, "%*.*s", DISKCOLWIDTH, DISKCOLWIDTH, tp_name[dn]);
+	ADV;
+	ADV; /* skip over the seeks column - not relevant for tape drives */
+
+	putint((int)((cur_tape.rxfer[dn]+cur_tape.wxfer[dn])/etime+0.5),
+	    r, c, DISKCOLWIDTH);
+	ADV;
+	puthumanint((cur_tape.rbytes[dn] + cur_tape.wbytes[dn]) / etime + 0.5,
+		    r, c, DISKCOLWIDTH);
+	ADV;
+
+	/* time busy in disk activity */
+	atime = cur_tape.time[dn].tv_sec + cur_tape.time[dn].tv_usec / 1000000.0;
+	atime = atime * 100.0 / etime;
+	if (atime >= 100)
+		putint(100, r, c, DISKCOLWIDTH);
+	else
+		putfloat(atime, r, c, DISKCOLWIDTH, 1, 1);
 #undef ADV
 }
+
+
Index: usr.bin/vmstat/Makefile
===================================================================
RCS file: /cvsroot/src/usr.bin/vmstat/Makefile,v
retrieving revision 1.21
diff -u -r1.21 Makefile
--- usr.bin/vmstat/Makefile	26 Feb 2005 21:19:18 -0000	1.21
+++ usr.bin/vmstat/Makefile	27 Jul 2005 12:26:51 -0000
@@ -4,7 +4,7 @@
 PROG=	vmstat
 WARNS=3
 
-SRCS=	dkstats.c vmstat.c
+SRCS=	dkstats.c tpstats.c vmstat.c
 MAN=	vmstat.1
 DPADD=	${LIBKVM}
 LDADD=	-lkvm
Index: usr.bin/vmstat/vmstat.c
===================================================================
RCS file: /cvsroot/src/usr.bin/vmstat/vmstat.c,v
retrieving revision 1.135
diff -u -r1.135 vmstat.c
--- usr.bin/vmstat/vmstat.c	2 Jun 2005 04:34:57 -0000	1.135
+++ usr.bin/vmstat/vmstat.c	27 Jul 2005 12:26:52 -0000
@@ -133,6 +133,7 @@
 #include <util.h>
 
 #include "dkstats.h"
+#include "tpstats.h"
 
 /*
  * General namelist
@@ -252,6 +253,7 @@
 void	cpustats(void);
 void	deref_kptr(const void *, void *, size_t, const char *);
 void	dkstats(void);
+void	tpstats(void);
 void	doevcnt(int verbose);
 void	dohashstat(int, int, const char *);
 void	dointr(int verbose);
@@ -404,6 +406,7 @@
 		struct winsize winsize;
 
 		dkinit(0);	/* Initialize disk stats, no disks selected. */
+		tpinit(0);
 
 		(void)setgid(getgid()); /* don't need privs anymore */
 
@@ -515,6 +518,13 @@
 			++ndrives;
 			break;
 		}
+		for (i = 0; i < tp_ndrive; i++) {
+			if (strcmp(tp_name[i], *argv))
+				continue;
+			tp_select[i] = 1;
+			++ndrives;
+			break;
+		}
 	}
 	for (i = 0; i < dk_ndrive && ndrives < 3; i++) {
 		if (dk_select[i])
@@ -522,6 +532,14 @@
 		dk_select[i] = 1;
 		++ndrives;
 	}
+	
+	for (i = 0; i < tp_ndrive && ndrives < 3; i++) {
+		if (tp_select[i])
+			continue;
+		tp_select[i] = 1;
+		++ndrives;
+	}
+	
 	return (argv);
 }
 
@@ -660,6 +678,7 @@
 		(void)printf("%4lu ", rate(uvmexp.pdfreed - ouvmexp.pdfreed));
 		(void)printf("%4lu ", rate(uvmexp.pdscans - ouvmexp.pdscans));
 		dkstats();
+		tpstats();
 		(void)printf("%4lu %4lu %3lu ",
 		    rate(uvmexp.intrs - ouvmexp.intrs),
 		    rate(uvmexp.syscalls - ouvmexp.syscalls),
@@ -881,6 +900,24 @@
 }
 
 void
+tpstats(void)
+{
+	int dn;
+	double etime;
+
+	/* Calculate tape stat deltas. */
+	tpswap();
+	etime = cur.cp_etime;
+
+	for (dn = 0; dn < tp_ndrive; ++dn) {
+		if (!tp_select[dn])
+			continue;
+		(void)printf("%2.0f ",
+		    (cur_tape.rxfer[dn] + cur_tape.wxfer[dn]) / etime);
+	}
+}
+
+void
 cpustats(void)
 {
 	int state;
Index: usr.sbin/iostat/Makefile
===================================================================
RCS file: /cvsroot/src/usr.sbin/iostat/Makefile,v
retrieving revision 1.20
diff -u -r1.20 Makefile
--- usr.sbin/iostat/Makefile	18 Sep 2002 03:54:30 -0000	1.20
+++ usr.sbin/iostat/Makefile	27 Jul 2005 12:26:52 -0000
@@ -11,7 +11,7 @@
 CPPFLAGS+=-I${NETBSDSRCDIR}/usr.bin/vmstat
 
 # dkstats.c pulled in from ../../usr.bin/vmstat
-SRCS=	dkstats.c iostat.c
+SRCS=	dkstats.c tpstats.c iostat.c
 
 DPADD=	${LIBKVM}
 LDADD=	-lkvm
Index: usr.sbin/iostat/iostat.c
===================================================================
RCS file: /cvsroot/src/usr.sbin/iostat/iostat.c,v
retrieving revision 1.44
diff -u -r1.44 iostat.c
--- usr.sbin/iostat/iostat.c	7 Jul 2005 22:31:45 -0000	1.44
+++ usr.sbin/iostat/iostat.c	27 Jul 2005 12:26:52 -0000
@@ -89,6 +89,7 @@
 #include <unistd.h>
 
 #include "dkstats.h"
+#include "tpstats.h"
 
 /* Namelist and memory files. */
 char	*nlistf, *memf;
@@ -194,7 +195,9 @@
 	defdrives /= 18;		/* XXX magic number */
 
 	dkinit(0);
+	tpinit(0);
 	dkreadstats();
+	tpreadstats();
 	ndrives = selectdrives(argc, argv);
 	if (ndrives == 0) {
 		/* No drives are selected.  No need to show disk stats. */
@@ -223,14 +226,18 @@
 				hdrcnt = winlines - 4;
 		}
 
-		if (!ISSET(todo, SHOW_TOTALS))
+		if (!ISSET(todo, SHOW_TOTALS)) {
 			dkswap();
+			tpswap();
+		}
+		
 		display();
 
 		if (reps >= 0 && --reps <= 0)
 			break;
 		nanosleep(&tv, NULL);
 		dkreadstats();
+		tpreadstats();
 	}
 	exit(0);
 }
@@ -263,16 +270,26 @@
 	if (ISSET(todo, SHOW_TTY))
 		(void)printf("      tty");
 
-	if (ISSET(todo, SHOW_STATS_1))
+	if (ISSET(todo, SHOW_STATS_1)) {
 		for (i = 0; i < dk_ndrive; i++)
 			if (cur.dk_select[i])
 				(void)printf("        %9.9s ", cur.dk_name[i]);
+		for (i = 0; i < tp_ndrive; i++)
+			if (cur_tape.select[i])
+				(void)printf("        %9.9s ",
+					     cur_tape.name[i]);
+	}
 
-	if (ISSET(todo, SHOW_STATS_2))
+	if (ISSET(todo, SHOW_STATS_2)) {
 		for (i = 0; i < dk_ndrive; i++)
 			if (cur.dk_select[i])
 				(void)printf("        %9.9s ", cur.dk_name[i]);
-
+		for (i = 0; i < tp_ndrive; i++)
+			if (cur_tape.select[i])
+				(void)printf("        %9.9s ",
+					     cur_tape.name[i]);
+	}
+	
 	if (ISSET(todo, SHOW_CPU))
 		(void)printf("            CPU");
 
@@ -290,12 +307,23 @@
 				else
 					(void)printf("  KB/t  t/s  MB/s ");
 			}
+		for (i = 0; i < tp_ndrive; i++)
+			if (cur_tape.select[i]) {
+				if (ISSET(todo, SHOW_TOTALS))
+					(void)printf("  KB/t  xfr  MB   ");
+				else
+					(void)printf("  KB/t  t/s  MB/s ");
+			}
 	}
 
-	if (ISSET(todo, SHOW_STATS_2))
+	if (ISSET(todo, SHOW_STATS_2)) {
 		for (i = 0; i < dk_ndrive; i++)
 			if (cur.dk_select[i])
 				(void)printf("    KB   xfr time ");
+		for (i = 0; i < tp_ndrive; i++)
+			if (cur_tape.select[i])
+				(void)printf("    KB   xfr time ");
+	}
 
 	if (ISSET(todo, SHOW_CPU))
 		(void)printf(" us ni sy in id");
@@ -420,6 +448,123 @@
 }
 
 static void
+tape_stats(double etime)
+{
+	int dn;
+	double atime, mbps;
+
+	for (dn = 0; dn < tp_ndrive; ++dn) {
+		if (!cur_tape.select[dn])
+			continue;
+					/* average Kbytes per transfer. */
+		if (cur_tape.rxfer[dn] + cur_tape.wxfer[dn])
+			mbps = ((cur_tape.rbytes[dn] + cur_tape.wbytes[dn]) /
+			    1024.0) / (cur_tape.rxfer[dn] + cur_tape.wxfer[dn]);
+		else
+			mbps = 0.0;
+		(void)printf(" %5.2f", mbps);
+
+					/* average transfers per second. */
+		(void)printf(" %4.0f",
+		    (cur_tape.rxfer[dn] + cur_tape.wxfer[dn]) / etime);
+
+					/* time busy in disk activity */
+		atime = (double)cur_tape.time[dn].tv_sec +
+		    ((double)cur_tape.time[dn].tv_usec / (double)1000000);
+
+					/* Megabytes per second. */
+		if (atime != 0.0)
+			mbps = (cur_tape.rbytes[dn] + cur_tape.wbytes[dn]) /
+			    (double)(1024 * 1024);
+		else
+			mbps = 0;
+		(void)printf(" %5.2f ", mbps / etime);
+	}
+}
+
+static void
+tape_stats2(double etime)
+{
+	int dn;
+	double atime;
+
+	for (dn = 0; dn < tp_ndrive; ++dn) {
+		if (!cur_tape.select[dn])
+			continue;
+
+					/* average kbytes per second. */
+		(void)printf(" %5.0f",
+		    (cur_tape.rbytes[dn] + cur_tape.wbytes[dn]) / 1024.0 / etime);
+
+					/* average transfers per second. */
+		(void)printf(" %5.0f",
+		    (cur_tape.rxfer[dn] + cur_tape.wxfer[dn]) / etime);
+
+					/* average time busy in disk activity */
+		atime = (double)cur_tape.time[dn].tv_sec +
+		    ((double)cur_tape.time[dn].tv_usec / (double)1000000);
+		(void)printf(" %4.2f ", atime / etime);
+	}
+}
+
+static void
+tape_statsx(double etime)
+{
+	int dn;
+	double atime, kbps;
+
+	for (dn = 0; dn < tp_ndrive; ++dn) {
+		if (!cur_tape.select[dn])
+			continue;
+
+		(void)printf("%-8.8s", cur_tape.name[dn]);
+
+					/* average read Kbytes per transfer */
+		if (cur.dk_rxfer[dn])
+			kbps = (cur_tape.rbytes[dn] / 1024.0) / cur_tape.rxfer[dn];
+		else
+			kbps = 0.0;
+		(void)printf(" %8.2f", kbps);
+
+					/* average read transfers
+					   (per second) */
+		(void)printf(" %6.0f", cur_tape.rxfer[dn] / etime);
+
+					/* time read busy in disk activity */
+		atime = (double)cur_tape.time[dn].tv_sec +
+		    ((double)cur_tape.time[dn].tv_usec / (double)1000000);
+		(void)printf(" %6.2f", atime / etime);
+
+					/* average read megabytes
+					   (per second) */
+		(void)printf(" %8.2f",
+		    cur_tape.rbytes[dn] / (1024.0 * 1024) / etime);
+
+
+					/* average write Kbytes per transfer */
+		if (cur_tape.wxfer[dn])
+			kbps = (cur_tape.wbytes[dn] / 1024.0) / cur_tape.wxfer[dn];
+		else
+			kbps = 0.0;
+		(void)printf("   %8.2f", kbps);
+
+					/* average write transfers
+					   (per second) */
+		(void)printf(" %6.0f", cur_tape.wxfer[dn] / etime);
+
+					/* time write busy in disk activity */
+		atime = (double)cur_tape.time[dn].tv_sec +
+		    ((double)cur_tape.time[dn].tv_usec / (double)1000000);
+		(void)printf(" %6.2f", atime / etime);
+
+					/* average write megabytes
+					   (per second) */
+		(void)printf(" %8.2f\n",
+		    cur_tape.wbytes[dn] / (1024.0 * 1024) / etime);
+	}
+}
+
+static void
 cpustats(void)
 {
 	int state;
@@ -461,17 +606,24 @@
 
 	if (ISSET(todo, SHOW_STATS_X)) {
 		disk_statsx(etime);
+		tape_statsx(etime);
 		goto out;
 	}
 
 	if (ISSET(todo, SHOW_TTY))
 		printf("%4.0f %4.0f", cur.tk_nin / etime, cur.tk_nout / etime);
 
-	if (ISSET(todo, SHOW_STATS_1))
+	if (ISSET(todo, SHOW_STATS_1)) {
 		disk_stats(etime);
+		tape_stats(etime);
+	}
+	
 
-	if (ISSET(todo, SHOW_STATS_2))
+	if (ISSET(todo, SHOW_STATS_2)) {
 		disk_stats2(etime);
+		tape_stats2(etime);
+	}
+	
 
 	if (ISSET(todo, SHOW_CPU))
 		cpustats();
@@ -510,6 +662,13 @@
 			cur.dk_select[i] = 1;
 			++ndrives;
 		}
+		
+		for (i = 0; i < tp_ndrive; i++) {
+			if (strcmp(cur_tape.name[i], *argv))
+				continue;
+			cur_tape.select[i] = 1;
+			++ndrives;
+		}
 	}
 
 	if (ndrives == 0 && tried == 0) {
@@ -518,9 +677,15 @@
 		 * if none specified.
 		 */
 		maxdrives = (ISSET(todo, SHOW_STATS_X) ||
-			     dk_ndrive < defdrives) ? dk_ndrive : defdrives;
+			     (dk_ndrive + tp_ndrive) < defdrives)
+			? (dk_ndrive + tp_ndrive) : defdrives;
 		for (i = 0; i < maxdrives; i++) {
-			cur.dk_select[i] = 1;
+			if (i >= dk_ndrive) {
+				cur_tape.select[i - dk_ndrive] = 1;
+			} else {
+				cur.dk_select[i] = 1;
+			}
+			
 			++ndrives;
 			if (!ISSET(todo, SHOW_STATS_X) && ndrives == defdrives)
 				break;

-- 
Brett Lymn