Subject: increasing dump's speed
To: None <tech-userlevel@netbsd.org>
From: Manuel Bouyer <bouyer@antioche.lip6.fr>
List: tech-userlevel
Date: 03/15/1999 21:06:56
--IS0zKkzwUGydFO0o
Content-Type: text/plain; charset=us-ascii

Hi,
Martin J. Laubach <mjl@emsi.priv.at> and myself have spend some time on how
to increase dump's performances. (Martin gets credit for the code, I only
provided some feedback and ran some tests). After some tests we found that
adding some read-ahead caching would improve performances.
The patch below remplaces the bread() routine with a cahing one.
The buffer cache uses a shared memory buffer, used by all 3 slave processes.
Cache is LRU. A flock on the disk device is used to serialise access.
When a cache miss occurs, datas are pulled in in the cache with a read
of CACHEBLOCKS disks sectors. Tests has shown that 32k reads is what
gives best performances in average (64k gives better results on some fil
systems, and awfull performances on others, with nearly 100% overhead). Also,
it has been shown that aligning reads on CACHEBLOCKS, instead using the
disk block number for which the miss occured, greatly increase performances.
This is because of the parallelism of the 3 readers, reads are not necesserely
sequencials.
The cache size is limited to 15% of the user memory, or 512 buffers.
Here are the speeds ncreases I've seen on a few machines I have here.

k6 2/350, adaptec 2940, IBM Ultra-wide disk (can do 6MB/s throuh the file
system), a 28MB partition full at 65%
DUMP: Average transfer rate: 1862 KB/s without cache
DUMP: Average transfer rate: 2831 KB/s with cache

P166, 64Mb RAM, DMA mode 2 IDE disk ( ~10MB/s throuh the file system), a 4GB
partition (whole disk) full at 83%:
DUMP: Average transfer rate: 3239 KB/s without cache
DUMP: Average transfer rate: 3952 KB/s with cache

Ppro 200, 64Mb RAM, SCSI disk (can do 9Mb throuh the filesystem), a 35MB
partition full at 60%:
DUMP: Average transfer rate: 2731 KB/s without cache
DUMP: Average transfer rate: 4261 KB/s with cache

Same machine and disk, a 3Gb partition full at 85%:
DUMP: Average transfer rate: 1935 KB/s without cache
DUMP: Average transfer rate: 2973 KB/s with cache

Sun 3/60, 8Mb RAM, old SCSI disk (720Kb/s throuh the FS, I'm not sure if the
bottleneck is the disk or CPU), 1Gb partition (whole disk) full at 27%:
DUMP: Average transfer rate: 208 KB/s without cache
DUMP: Average transfer rate: 355 KB/s with cache

These changes shows a significant improvement in all cases we have tested.
So, unless someone complains, I'll commit this Friday.

--
Manuel Bouyer, LIP6, Universite Paris VI.           Manuel.Bouyer@lip6.fr
--

--IS0zKkzwUGydFO0o
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename=diff

Index: Makefile
===================================================================
RCS file: /cvsroot/src/sbin/dump/Makefile,v
retrieving revision 1.21
diff -u -r1.21 Makefile
--- Makefile	1999/03/09 17:25:52	1.21
+++ Makefile	1999/03/15 19:21:56
@@ -16,8 +16,8 @@
 PROG=	dump
 LINKS=	${BINDIR}/dump ${BINDIR}/rdump
 CPPFLAGS+=-DRDUMP
-# CPPFLAGS+= -DDEBUG -DTDEBUG -DFDEBUG -DWRITEDEBUG
-SRCS=	itime.c main.c optr.c dumprmt.c tape.c traverse.c unctime.c \
+# CPPFLAGS+= -DDEBUG -DTDEBUG -DFDEBUG -DWRITEDEBUG -DSTATS -DDIAGNOSTICS
+SRCS=	itime.c main.c optr.c dumprmt.c rcache.c tape.c traverse.c unctime.c \
 	ffs_bswap.c
 BINGRP=	tty
 BINMODE=2555
Index: dump.8
===================================================================
RCS file: /cvsroot/src/sbin/dump/dump.8,v
retrieving revision 1.31
diff -u -r1.31 dump.8
--- dump.8	1999/03/09 17:25:52	1.31
+++ dump.8	1999/03/15 19:21:57
@@ -50,6 +50,7 @@
 .Op Fl f Ar file
 .Op Fl h Ar level
 .Op Fl L Ar label
+.Op Fl r Ar cachesize
 .Op Fl s Ar feet
 .Op Fl T Ar date
 .Ar files-to-dump
@@ -190,6 +191,11 @@
 .Qq operator
 by means similar to a
 .Xr wall 1 .
+.It Fl r Ar cachesize
+Use that many 32K buffers for read cache operations. 
+A value of zero disables the read cache altogether, higher values
+improve read performance by reading larger data blocks from the
+disk and maintaining them in an LRU cache.
 .It Fl s Ar feet
 Attempt to calculate the amount of tape needed
 at a particular density.
Index: dump.h
===================================================================
RCS file: /cvsroot/src/sbin/dump/dump.h,v
retrieving revision 1.15
diff -u -r1.15 dump.h
--- dump.h	1999/01/15 13:32:06	1.15
+++ dump.h	1999/03/15 19:21:57
@@ -146,10 +146,15 @@
 
 /* file dumping routines */
 void	blksout __P((daddr_t *blkp, int frags, ino_t ino));
-void	bread __P((daddr_t blkno, char *buf, int size));	
 void	dumpino __P((struct dinode *dp, ino_t ino));
 void	dumpmap __P((char *map, int type, ino_t ino));
 void	writeheader __P((ino_t ino));
+
+/* data block caching */
+void	bread __P((daddr_t blkno, char *buf, int size));	
+void	RawReadBlocks __P((daddr_t, char *, int));
+void	initCache __P((int));
+void	printcachestats __P((void));
 
 /* tape writing routines */
 int	alloctape __P((void));
Index: main.c
===================================================================
RCS file: /cvsroot/src/sbin/dump/main.c,v
retrieving revision 1.21
diff -u -r1.21 main.c
--- main.c	1999/01/03 02:17:46	1.21
+++ main.c	1999/03/15 19:21:57
@@ -93,6 +93,7 @@
 long	dev_bsize = 1;		/* recalculated below */
 long	blocksperfile = 0;	/* output blocks per file */
 char	*host = NULL;		/* remote host (if any) */
+int	readcache = -1;
 
 int	main __P((int, char *[]));
 static long numarg __P((char *, long, long));
@@ -136,7 +137,7 @@
 
 	obsolete(&argc, &argv);
 	while ((ch = getopt(argc, argv,
-	    "0123456789B:b:cd:f:h:L:ns:ST:uWw")) != -1)
+	    "0123456789B:b:cd:f:h:L:nr:s:ST:uWw")) != -1)
 		switch (ch) {
 		/* dump level */
 		case '0': case '1': case '2': case '3': case '4':
@@ -191,6 +192,10 @@
 			notify = 1;
 			break;
 
+		case 'r':		/* read cache size */
+			readcache = numarg("read cache size", 0, 512);
+			break;
+		
 		case 's':		/* tape size, feet */
 			tsize = numarg("tape size", 1L, 0L) * 12 * 10;
 			break;
@@ -379,7 +384,7 @@
 	}
 	sync();
 	sblock = (struct fs *)sblock_buf;
-	bread(SBOFF, (char *) sblock, SBSIZE);
+	RawReadBlocks(SBOFF, (char *) sblock, SBSIZE);
 	if (sblock->fs_magic != FS_MAGIC) {
 		if (sblock->fs_magic == bswap32(FS_MAGIC)) {
 			ffs_sb_swap(sblock, sblock, 0);
@@ -442,6 +447,8 @@
 
 	nonodump = iswap32(spcl.c_level) < honorlevel;
 
+	initCache(readcache);
+	
 	(void)signal(SIGINFO, statussig);
 
 	msg("mapping (Pass I) [regular files]\n");
Index: rcache.c
===================================================================
RCS file: rcache.c
diff -N rcache.c
--- /dev/null	Mon Mar 15 06:42:01 1999
+++ rcache.c	Mon Mar 15 11:21:58 1999
@@ -0,0 +1,443 @@
+/*      $NetBSD$       */
+
+/*-
+ * Copyright (c) 1999 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Martin J. Laubach <mjl@emsi.priv.at> and 
+ *    Manuel Bouyer <Manuel.Bouyer@lip6.fr>.
+ *
+ * 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.
+ */
+/*-----------------------------------------------------------------------*/
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <ufs/ufs/dinode.h>
+#include <ufs/ffs/fs.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+#include "dump.h"
+
+/*-----------------------------------------------------------------------*/
+#define CACHEBLOCKS	64	/* 32k blocks */
+#define MAXCACHEBUFS	512	/* max 512 buffers */
+#define MAXMEMPART	6	/* max 15% of the user mem */
+
+/*-----------------------------------------------------------------------*/
+struct cheader {
+	volatile size_t count;
+};
+
+struct cdesc {
+	volatile daddr_t blkstart;
+	volatile daddr_t blkend;/* start + CACHEBLOCKS */
+	volatile daddr_t blocksRead;
+	volatile size_t time;
+#ifdef DIAGNOSTICS
+	volatile pid_t owner;
+#endif
+};
+
+static int findLRU __P((void));
+
+static void *shareBuffer = NULL;
+static struct cheader *cheader;
+static struct cdesc *cdesc;
+static char *cdata;
+static int cachebufs;
+
+#ifdef STATS
+static int nreads;
+static int nphysread;
+static int64_t readsize;
+static int64_t physreadsize;
+#endif
+
+#define CDATA(i)	(cdata + ((i) * CACHEBLOCKS * dev_bsize))
+
+/*-----------------------------------------------------------------------*/
+void 
+initCache(cachesize)
+	int cachesize;
+{
+	size_t len;
+	size_t  sharedSize;
+
+	if(cachesize == -1) {	/* Compute from memory available */
+		int usermem;
+		int mib[2] = { CTL_HW, HW_USERMEM };
+		
+		len = sizeof(usermem);
+		if (sysctl(mib, 2, &usermem, &len, NULL, 0) < 0) {
+			msg("sysctl(hw.usermem) failed: %s\n", strerror(errno));
+			return;
+		}
+		cachebufs = (usermem / MAXMEMPART) / (CACHEBLOCKS * dev_bsize);
+	} else {		/* User specified */
+		cachebufs = cachesize;
+	}
+	
+	if(cachebufs) {	/* Don't allocate if zero --> no caching */
+		if (cachebufs > MAXCACHEBUFS)
+			cachebufs = MAXCACHEBUFS;
+
+		sharedSize = sizeof(struct cheader) +
+	   	    sizeof(struct cdesc) * cachebufs +
+	   	    CACHEBLOCKS * cachebufs * dev_bsize;
+#ifdef STATS	
+		fprintf(stderr, "Using %d buffers (%d bytes)\n", cachebufs,
+	   	    sharedSize);
+#endif
+		shareBuffer = mmap(NULL, sharedSize, PROT_READ | PROT_WRITE,
+	   	    MAP_ANON | MAP_SHARED, -1, 0);
+		if (shareBuffer == (void *)-1) {
+			msg("can't mmap shared memory for buffer: %s\n",
+			    strerror(errno));
+			return;
+		}
+		cheader = shareBuffer;
+		cdesc = (struct cdesc *) (((char *) shareBuffer) +
+		    sizeof(struct cheader));
+		cdata = ((char *) shareBuffer) + sizeof(struct cheader) +
+	   	    sizeof(struct cdesc) * cachebufs;
+
+		memset(shareBuffer, '\0', sharedSize);
+	}
+}
+/*-----------------------------------------------------------------------*/
+/* Find the cache buffer descriptor that shows the minimal access time */
+
+static int 
+findLRU()
+{
+	int     i;
+	int     minTime = cdesc[0].time;
+	int     minIdx = 0;
+
+	for (i = 0; i < cachebufs; i++) {
+		if (cdesc[i].time < minTime) {
+			minIdx = i;
+			minTime = cdesc[i].time;
+		}
+	}
+
+	return minIdx;
+}
+/*-----------------------------------------------------------------------*/
+/*
+ * Read data directly from disk, with smart error handling.
+ * Try to recover from hard errors by reading in sector sized pieces.
+ * Error recovery is attempted at most BREADEMAX times before seeking
+ * consent from the operator to continue.
+ */
+
+
+static int breaderrors = 0;
+#define BREADEMAX 32
+
+void 
+RawReadBlocks(blkno, buf, size)
+	daddr_t blkno;
+	char *buf;
+	int size;
+{
+	int cnt, i;
+#ifdef STATS
+	nphysread++;
+	physreadsize += size;
+#endif
+
+	if (lseek(diskfd, ((off_t) blkno << dev_bshift), 0) < 0) {
+		msg("RawReadBlocks: lseek fails\n");
+		goto err;
+	}
+	if ((cnt =  read(diskfd, buf, size)) == size)
+		return;
+	if (cnt == -1)
+		msg("read error from %s: %s: [block %d]: count=%d\n",
+			disk, strerror(errno), blkno, size);
+	else
+		msg("short read error from %s: [block %d]: count=%d, got=%d\n",
+			disk, blkno, size, cnt);
+err:
+	if (++breaderrors > BREADEMAX) {
+		msg("More than %d block read errors from %d\n",
+			BREADEMAX, disk);
+		broadcast("DUMP IS AILING!\n");
+		msg("This is an unrecoverable error.\n");
+		if (!query("Do you want to attempt to continue?")){
+			dumpabort(0);
+			/*NOTREACHED*/
+		} else
+			breaderrors = 0;
+	}
+	/*
+	 * Zero buffer, then try to read each sector of buffer separately.
+	 */
+	memset(buf, 0, size);
+	for (i = 0; i < size; i += dev_bsize, buf += dev_bsize, blkno++) {
+		if (lseek(diskfd, ((off_t)blkno << dev_bshift), 0) < 0) {
+			msg("RawReadBlocks: lseek2 fails: %s!\n",
+			    strerror(errno));
+			continue;
+		}
+		if ((cnt = read(diskfd, buf, (int)dev_bsize)) == dev_bsize)
+			continue;
+		if (cnt == -1) {
+			msg("read error from %s: %s: [sector %d]: count=%d: "
+			    "%s\n", disk, strerror(errno), blkno, dev_bsize,
+			    strerror(errno));
+			continue;
+		}
+		msg("short read error from %s: [sector %d]: count=%d, got=%d\n",
+		    disk, blkno, dev_bsize, cnt);
+	}
+}
+
+/*-----------------------------------------------------------------------*/
+#define min(a,b)	(((a) < (b)) ? (a) : (b))
+
+void 
+bread(blkno, buf, size)
+	daddr_t blkno;
+	char *buf;
+	int size;
+{
+	int     osize = size;
+	daddr_t oblkno = blkno;
+	char   *obuf = buf;
+	daddr_t numBlocks = (size + dev_bsize -1) / dev_bsize;
+
+#ifdef STATS
+	nreads++;
+	readsize += size;
+#endif
+
+	if (!shareBuffer) {
+		RawReadBlocks(blkno, buf, size);
+		return;
+	}
+
+	if (flock(diskfd, LOCK_EX)) {
+		msg("flock(LOCK_EX) failed: %s\n",
+		    strerror(errno));
+		RawReadBlocks(blkno, buf, size);
+		return;
+	}
+
+
+retry:
+	while(size > 0) {
+		int     i;
+		
+		for (i = 0; i < cachebufs; i++) {
+			struct cdesc *curr = &cdesc[i];
+
+#ifdef DIAGNOSTICS
+			if (curr->owner) {
+				fprintf(stderr, "Owner is set (%d, me=%d), can"
+				    "not happen.\n", curr->owner, getpid());
+			}
+#endif
+
+			if (curr->blkend == 0)
+				continue;
+			/*
+			 * If we find a bit of the read in the buffers,
+			 * now compute how many blocks we can copy,
+			 * copy them out, adjust blkno, buf and size,
+			 * and restart
+			 */
+			if (curr->blkstart <= blkno &&
+			    blkno < curr->blkend) {
+				/* Number of data blocks to be copied */
+				int toCopy = min(size,
+				    (curr->blkend - blkno) * dev_bsize);
+#ifdef DIAGNOSTICS
+				if (toCopy <= 0 ||
+				    toCopy > CACHEBLOCKS * dev_bsize) {
+					fprintf(stderr, "toCopy %d !\n",
+					    toCopy);
+					dumpabort(0);
+				}
+				if (CDATA(i) + (blkno - curr->blkstart) *
+			   	    dev_bsize < CDATA(i) ||
+			   	    CDATA(i) + (blkno - curr->blkstart) *
+			   	    dev_bsize >
+				    CDATA(i) + CACHEBLOCKS * dev_bsize) {
+					fprintf(stderr, "%p < %p !!!\n",
+				   	   CDATA(i) + (blkno -
+						curr->blkstart) * dev_bsize,
+					   CDATA(i));
+					fprintf(stderr, "cdesc[i].blkstart %d "
+					    "blkno %d dev_bsize %ld\n", 
+				   	    curr->blkstart, blkno, dev_bsize);
+					dumpabort(0);
+				}
+#endif
+				memcpy(buf, CDATA(i) +
+				    (blkno - curr->blkstart) * dev_bsize,
+			   	    toCopy);
+
+				buf 	+= toCopy;
+				size 	-= toCopy;
+				blkno 	+= (toCopy + dev_bsize - 1) / dev_bsize;
+				numBlocks -=
+				    (toCopy  + dev_bsize - 1) / dev_bsize;
+
+				curr->time = cheader->count++;
+
+				/*
+				 * If all data of a cache block have been
+				 * read, chances are good no more reads
+				 * will occur, so expire the cache immediately
+				 */
+
+				curr->blocksRead +=
+				    (toCopy + dev_bsize -1) / dev_bsize;
+				if (curr->blocksRead >= CACHEBLOCKS)
+					curr->time = 0;
+
+				goto retry;
+			}
+		}
+
+		/* No more to do? */
+		if (size == 0)
+			break;
+			
+		/*
+		 * This does actually not happen if fs blocks are not greater
+		 * than CACHEBLOCKS.
+		 */
+		if (numBlocks > CACHEBLOCKS) {
+			RawReadBlocks(oblkno, obuf, osize);
+			break;
+		} else {
+			int     idx;
+			ssize_t rsize;
+			daddr_t blockBlkNo;
+
+			blockBlkNo = (blkno / CACHEBLOCKS) * CACHEBLOCKS;
+			idx = findLRU();
+			rsize = min(CACHEBLOCKS,
+			    fsbtodb(sblock, sblock->fs_size) - blockBlkNo) *
+			    dev_bsize;
+
+#ifdef DIAGNOSTICS
+			if (cdesc[idx].owner)
+				fprintf(stderr, "Owner is set (%d, me=%d), can"
+				    "not happen(2).\n", cdesc[idx].owner,
+				    getpid());
+			cdesc[idx].owner = getpid();
+#endif
+			cdesc[idx].time = cheader->count++;
+			cdesc[idx].blkstart = blockBlkNo;
+			cdesc[idx].blocksRead = 0;
+
+			if (lseek(diskfd,
+			    ((off_t) (blockBlkNo) << dev_bshift), 0) < 0) {
+				msg("readBlocks: lseek fails: %s\n",
+				    strerror(errno));
+				rsize = -1;
+			} else {
+				rsize = read(diskfd, CDATA(idx), rsize);
+				if (rsize < 0) {
+					msg("readBlocks: read fails: %s\n",
+					    strerror(errno));
+				}
+			}
+
+			/* On errors, panic, punt, try to read without
+			 * cache and let raw read routine do the rest.
+			 */
+
+			if (rsize <= 0) {
+				RawReadBlocks(oblkno, obuf, osize);
+#ifdef DIAGNOSTICS
+				if (cdesc[idx].owner != getpid())
+					fprintf(stderr, "Owner changed from "
+					    "%d to %d, can't happen\n",
+					    getpid(), cdesc[idx].owner);
+				cdesc[idx].owner = 0;
+#endif
+				break;
+			}
+
+			/* On short read, just note the fact and go on */
+			cdesc[idx].blkend = blockBlkNo + rsize / dev_bsize;
+
+#ifdef STATS
+			nphysread++;
+			physreadsize += rsize;
+#endif
+#ifdef DIAGNOSTICS
+			if (cdesc[idx].owner != getpid())
+				fprintf(stderr, "Owner changed from "
+				    "%d to %d, can't happen\n",
+				    getpid(), cdesc[idx].owner);
+			cdesc[idx].owner = 0;
+#endif
+			/*
+			 * We swapped some of data in, let the loop fetch
+			 * them from cache
+			 */
+		}
+	}
+	
+	if (flock(diskfd, LOCK_UN))
+		msg("flock(LOCK_UN) failed: %s\n",
+		    strerror(errno));
+	return;
+}
+
+/*-----------------------------------------------------------------------*/
+void
+printcachestats()
+{
+#ifdef STATS
+	fprintf(stderr, "Pid %d: %d reads (%u bytes) "
+	    "%d physical reads (%u bytes) %d%% hits, %d%% overhead\n",
+	    getpid(), nreads, (u_int) readsize, nphysread,
+	    (u_int) physreadsize, (nreads - nphysread) * 100 / nreads,
+	    (int) (((physreadsize - readsize) * 100) / readsize));
+#endif
+}
+
+/*-----------------------------------------------------------------------*/
Index: tape.c
===================================================================
RCS file: /cvsroot/src/sbin/dump/tape.c,v
retrieving revision 1.17
diff -u -r1.17 tape.c
--- tape.c	1998/07/18 05:04:36	1.17
+++ tape.c	1999/03/15 19:21:58
@@ -866,7 +866,7 @@
 				wrote = write(tapefd, slp->tblock[0]+size,
 				    writesize-size);
 #ifdef WRITEDEBUG
-			printf("slave %d wrote %d\n", slave_number, wrote);
+			fprintf(stderr, "slave %d wrote %d\n", slave_number, wrote);
 #endif
 			if (wrote < 0) 
 				break;
@@ -877,7 +877,7 @@
 
 #ifdef WRITEDEBUG
 		if (size != writesize) 
-		 printf("slave %d only wrote %d out of %d bytes and gave up.\n",
+		 fprintf(stderr, "slave %d only wrote %d out of %d bytes and gave up.\n",
 		     slave_number, size, writesize);
 #endif
 
@@ -907,6 +907,7 @@
 		 */
 		(void) kill(nextslave, SIGUSR2);
 	}
+	printcachestats();
 	if (nread != 0)
 		quit("error reading command pipe: %s\n", strerror(errno));
 }
Index: traverse.c
===================================================================
RCS file: /cvsroot/src/sbin/dump/traverse.c,v
retrieving revision 1.23
diff -u -r1.23 traverse.c
--- traverse.c	1999/03/09 17:25:52	1.23
+++ traverse.c	1999/03/15 19:21:58
@@ -696,76 +696,3 @@
 	maxino = minino + INOPB(sblock);
 	return (&inoblock[inum - minino]);
 }
-
-/*
- * Read a chunk of data from the disk.
- * Try to recover from hard errors by reading in sector sized pieces.
- * Error recovery is attempted at most BREADEMAX times before seeking
- * consent from the operator to continue.
- */
-int	breaderrors = 0;		
-#define	BREADEMAX 32
-
-void
-bread(blkno, buf, size)
-	daddr_t blkno;
-	char *buf;
-	int size;	
-{
-	int cnt, i;
-	extern int errno;
-
-loop:
-	if (lseek(diskfd, ((off_t)blkno << dev_bshift), 0) < 0)
-		msg("bread: lseek fails\n");
-	if ((cnt = read(diskfd, buf, size)) == size)
-		return;
-	if (blkno + (size / dev_bsize) > fsbtodb(sblock, sblock->fs_size)) {
-		/*
-		 * Trying to read the final fragment.
-		 *
-		 * NB - dump only works in TP_BSIZE blocks, hence
-		 * rounds `dev_bsize' fragments up to TP_BSIZE pieces.
-		 * It should be smarter about not actually trying to
-		 * read more than it can get, but for the time being
-		 * we punt and scale back the read only when it gets
-		 * us into trouble. (mkm 9/25/83)
-		 */
-		size -= dev_bsize;
-		goto loop;
-	}
-	if (cnt == -1)
-		msg("read error from %s: %s: [block %d]: count=%d\n",
-			disk, strerror(errno), blkno, size);
-	else
-		msg("short read error from %s: [block %d]: count=%d, got=%d\n",
-			disk, blkno, size, cnt);
-	if (++breaderrors > BREADEMAX) {
-		msg("More than %d block read errors from %d\n",
-			BREADEMAX, disk);
-		broadcast("DUMP IS AILING!\n");
-		msg("This is an unrecoverable error.\n");
-		if (!query("Do you want to attempt to continue?")){
-			dumpabort(0);
-			/*NOTREACHED*/
-		} else
-			breaderrors = 0;
-	}
-	/*
-	 * Zero buffer, then try to read each sector of buffer separately.
-	 */
-	memset(buf, 0, size);
-	for (i = 0; i < size; i += dev_bsize, buf += dev_bsize, blkno++) {
-		if (lseek(diskfd, ((off_t)blkno << dev_bshift), 0) < 0)
-			msg("bread: lseek2 fails!\n");
-		if ((cnt = read(diskfd, buf, (int)dev_bsize)) == dev_bsize)
-			continue;
-		if (cnt == -1) {
-			msg("read error from %s: %s: [sector %d]: count=%d\n",
-				disk, strerror(errno), blkno, dev_bsize);
-			continue;
-		}
-		msg("short read error from %s: [sector %d]: count=%d, got=%d\n",
-			disk, blkno, dev_bsize, cnt);
-	}
-}

--IS0zKkzwUGydFO0o--