1 /*-
2  * Copyright (c) 1992, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * %sccs.include.redist.c%
6  */
7 
8 #ifndef lint
9 static char copyright[] =
10 "@(#) Copyright (c) 1992, 1993\n\
11 	The Regents of the University of California.  All rights reserved.\n";
12 #endif /* not lint */
13 
14 #ifndef lint
15 static char sccsid[] = "@(#)cleanerd.c	8.5 (Berkeley) 06/10/95";
16 #endif /* not lint */
17 
18 #include <sys/param.h>
19 #include <sys/mount.h>
20 #include <sys/time.h>
21 
22 #include <ufs/ufs/dinode.h>
23 #include <ufs/lfs/lfs.h>
24 
25 #include <signal.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 
30 #include "clean.h"
31 char *special = "cleanerd";
32 int do_small = 0;
33 int do_mmap = 0;
34 int stat_report = 0;
35 struct cleaner_stats {
36 	double	util_tot;
37 	double	util_sos;
38 	int	blocks_read;
39 	int	blocks_written;
40 	int	segs_cleaned;
41 	int	segs_empty;
42 	int	segs_error;
43 } cleaner_stats;
44 
45 struct seglist {
46 	int sl_id;	/* segment number */
47 	int sl_cost; 	/* cleaning cost */
48 	char sl_bytes;	/* bytes in segment */
49 };
50 
51 struct tossstruct {
52 	struct lfs *lfs;
53 	int seg;
54 };
55 
56 #define	CLEAN_BYTES	0x1
57 
58 /* function prototypes for system calls; not sure where they should go */
59 int	 lfs_segwait __P((fsid_t *, struct timeval *));
60 int	 lfs_segclean __P((fsid_t *, u_long));
61 int	 lfs_bmapv __P((fsid_t *, BLOCK_INFO *, int));
62 int	 lfs_markv __P((fsid_t *, BLOCK_INFO *, int));
63 
64 /* function prototypes */
65 int	 bi_tossold __P((const void *, const void *, const void *));
66 int	 choose_segments __P((FS_INFO *, struct seglist *,
67 	     int (*)(FS_INFO *, SEGUSE *)));
68 void	 clean_fs __P((FS_INFO	*, int (*)(FS_INFO *, SEGUSE *), int, long));
69 int	 clean_loop __P((FS_INFO *, int, long));
70 int	 clean_segment __P((FS_INFO *, int));
71 int	 cost_benefit __P((FS_INFO *, SEGUSE *));
72 int	 cost_compare __P((const void *, const void *));
73 void	 sig_report __P((int));
74 
75 /*
76  * Cleaning Cost Functions:
77  *
78  * These return the cost of cleaning a segment.  The higher the cost value
79  * the better it is to clean the segment, so empty segments have the highest
80  * cost.  (It is probably better to think of this as a priority value
81  * instead).
82  *
83  * This is the cost-benefit policy simulated and described in Rosenblum's
84  * 1991 SOSP paper.
85  */
86 
87 int
cost_benefit(fsp,su)88 cost_benefit(fsp, su)
89 	FS_INFO *fsp;		/* file system information */
90 	SEGUSE *su;
91 {
92 	struct lfs *lfsp;
93 	struct timeval t;
94 	int age;
95 	int live;
96 
97 	gettimeofday(&t, NULL);
98 
99 	live = su->su_nbytes;
100 	age = t.tv_sec < su->su_lastmod ? 0 : t.tv_sec - su->su_lastmod;
101 
102 	lfsp = &fsp->fi_lfs;
103 	if (live == 0)
104 		return (t.tv_sec * lblkno(lfsp, seg_size(lfsp)));
105 	else {
106 		/*
107 		 * from lfsSegUsage.c (Mendel's code).
108 		 * priority calculation is done using INTEGER arithmetic.
109 		 * sizes are in BLOCKS (that is why we use lblkno below).
110 		 * age is in seconds.
111 		 *
112 		 * priority = ((seg_size - live) * age) / (seg_size + live)
113 		 */
114 #ifdef VERBOSE
115 		if (live < 0 || live > seg_size(lfsp)) {
116 			err(0, "Bad segusage count: %d", live);
117 			live = 0;
118 		}
119 #endif
120 		return (lblkno(lfsp, seg_size(lfsp) - live) * age)
121 			/ lblkno(lfsp, seg_size(lfsp) + live);
122 	}
123 }
124 
125 int
main(argc,argv)126 main(argc, argv)
127 	int argc;
128 	char *argv[];
129 {
130 	FS_INFO	*fsp;
131 	struct statfs *lstatfsp;	/* file system stats */
132 	struct timeval timeout;		/* sleep timeout */
133 	fsid_t fsid;
134 	long clean_opts;		/* cleaning options */
135 	int i, nodaemon, segs_per_clean;
136 	int opt, cmd_err;
137 	char *fs_name;			/* name of filesystem to clean */
138 	extern int optind;
139 
140 	cmd_err = nodaemon = 0;
141 	clean_opts = 0;
142 	segs_per_clean = 1;
143 	while ((opt = getopt(argc, argv, "bdmn:r:s")) != EOF) {
144 		switch (opt) {
145 			case 'b':	/*
146 					 * Use live bytes to determine
147 					 * how many segs to clean.
148 					 */
149 				clean_opts |= CLEAN_BYTES;
150 				break;
151 			case 'd':	/* Debug mode. */
152 				nodaemon = 1;
153 				break;
154 			case 'm':	/* Use mmap instead of read/write */
155 				do_mmap = 1;
156 				break;
157 			case 'n':	/* How many segs to clean at once */
158 				segs_per_clean = atoi(optarg);
159 				break;
160 			case 'r':	/* Report every stat_report segments */
161 				stat_report = atoi(optarg);
162 				break;
163 			case 's':	/* small writes */
164 				do_small = 1;
165 				break;
166 			default:
167 				++cmd_err;
168 		}
169 	}
170 	argc -= optind;
171 	argv += optind;
172 	if (cmd_err || (argc != 1))
173 		err(1, "usage: lfs_cleanerd [-smd] fs_name");
174 
175 	fs_name = argv[0];
176 
177 	signal(SIGINT, sig_report);
178 	signal(SIGUSR1, sig_report);
179 	signal(SIGUSR2, sig_report);
180 	if (fs_getmntinfo(&lstatfsp, fs_name, "lfs") == 0) {
181 		/* didn't find the filesystem */
182 		err(1, "lfs_cleanerd: filesystem %s isn't an LFS!", fs_name);
183 	}
184 
185 	if (!nodaemon)	/* should we become a daemon, chdir to / & close fd's */
186 		if (daemon(0, 0) == -1)
187 			err(1, "lfs_cleanerd: couldn't become a daemon!");
188 
189 	timeout.tv_sec = 5*60; /* five minutes */
190 	timeout.tv_usec = 0;
191 	fsid.val[0] = 0;
192 	fsid.val[1] = 0;
193 
194 	for (fsp = get_fs_info(lstatfsp, do_mmap); ;
195 	    reread_fs_info(fsp, do_mmap)) {
196 		/*
197 		 * clean the filesystem, and, if it needed cleaning
198 		 * (i.e. it returned nonzero) try it again
199 		 * to make sure that some nasty process hasn't just
200 		 * filled the disk system up.
201 		 */
202 		if (clean_loop(fsp, segs_per_clean, clean_opts))
203 			continue;
204 
205 #ifdef VERBOSE
206 		(void)printf("Cleaner going to sleep.\n");
207 #endif
208 		if (lfs_segwait(&fsid, &timeout) < 0)
209 			err(0, "lfs_segwait: returned error\n");
210 #ifdef VERBOSE
211 		(void)printf("Cleaner waking up.\n");
212 #endif
213 	}
214 }
215 
216 /* return the number of segments cleaned */
217 int
clean_loop(fsp,nsegs,options)218 clean_loop(fsp, nsegs, options)
219 	FS_INFO	*fsp;	/* file system information */
220 	int nsegs;
221 	long options;
222 {
223 	double loadavg[MAXLOADS];
224 	time_t	now;
225 	u_long max_free_segs;
226 	u_long db_per_seg;
227 
228         /*
229 	 * Compute the maximum possible number of free segments, given the
230 	 * number of free blocks.
231 	 */
232 	db_per_seg = fsbtodb(&fsp->fi_lfs, fsp->fi_lfs.lfs_ssize);
233 	max_free_segs = fsp->fi_lfs.lfs_bfree / db_per_seg;
234 
235 	/*
236 	 * We will clean if there are not enough free blocks or total clean
237 	 * space is less than BUSY_LIM % of possible clean space.
238 	 */
239 	now = time((time_t *)NULL);
240 #ifdef VERBOSE
241 	printf("db_er_seg = %d max_free_segs = %d, bfree = %d avail = %d ",
242 	    db_per_seg, max_free_segs, fsp->fi_lfs.lfs_bfree,
243 	    fsp->fi_lfs.lfs_avail);
244 	printf("clean = %d\n", fsp->fi_cip->clean);
245 #endif
246 	if ((fsp->fi_lfs.lfs_bfree - fsp->fi_lfs.lfs_avail > db_per_seg &&
247 	    fsp->fi_lfs.lfs_avail < db_per_seg) ||
248 	    (fsp->fi_cip->clean < max_free_segs &&
249 	    (fsp->fi_cip->clean <= MIN_SEGS(&fsp->fi_lfs) ||
250 	    fsp->fi_cip->clean < max_free_segs * BUSY_LIM))) {
251 		printf("Cleaner Running  at %s (%d of %d segments available)\n",
252 		    ctime(&now), fsp->fi_cip->clean, max_free_segs);
253 		clean_fs(fsp, cost_benefit, nsegs, options);
254 		return (1);
255 	} else {
256 	        /*
257 		 * We will also clean if the system is reasonably idle and
258 		 * the total clean space is less then IDLE_LIM % of possible
259 		 * clean space.
260 		 */
261 		if (getloadavg(loadavg, MAXLOADS) == -1) {
262 			perror("getloadavg: failed\n");
263 			return (-1);
264 		}
265 		if (loadavg[ONE_MIN] == 0.0 && loadavg[FIVE_MIN] &&
266 		    fsp->fi_cip->clean < max_free_segs * IDLE_LIM) {
267 		        clean_fs(fsp, cost_benefit, nsegs, options);
268 			printf("Cleaner Running  at %s (system idle)\n",
269 			    ctime(&now));
270 			return (1);
271 		}
272 	}
273 	printf("Cleaner Not Running at %s\n", ctime(&now));
274 	return (0);
275 }
276 
277 
278 void
clean_fs(fsp,cost_func,nsegs,options)279 clean_fs(fsp, cost_func, nsegs, options)
280 	FS_INFO	*fsp;	/* file system information */
281 	int (*cost_func) __P((FS_INFO *, SEGUSE *));
282 	int nsegs;
283 	long options;
284 {
285 	struct seglist *segs, *sp;
286 	int to_clean, cleaned_bytes;
287 	int i;
288 
289 	if ((segs =
290 	    malloc(fsp->fi_lfs.lfs_nseg * sizeof(struct seglist))) == NULL) {
291 		err(0, "malloc failed");
292 		return;
293 	}
294 	i = choose_segments(fsp, segs, cost_func);
295 #ifdef VERBOSE
296 	printf("clean_fs: found %d segments to clean in file system %s\n",
297 		i, fsp->fi_statfsp->f_mntonname);
298 	fflush(stdout);
299 #endif
300 	if (i) {
301 		/* Check which cleaning algorithm to use. */
302 		if (options & CLEAN_BYTES) {
303 			cleaned_bytes = 0;
304 			to_clean = nsegs <<
305 			    (fsp->fi_lfs.lfs_segshift + fsp->fi_lfs.lfs_bshift);
306 			for (sp = segs; i && cleaned_bytes < to_clean;
307 			    i--, ++sp) {
308 				if (clean_segment(fsp, sp->sl_id) < 0)
309 					perror("clean_segment failed");
310 				else if (lfs_segclean(&fsp->fi_statfsp->f_fsid,
311 				    sp->sl_id) < 0)
312 					perror("lfs_segclean failed");
313 				printf("Cleaned segment %d (%d bytes)\n",
314 				    sp->sl_id, sp->sl_bytes);
315 				cleaned_bytes += sp->sl_bytes;
316 			}
317 		} else
318 			for (i = MIN(i, nsegs), sp = segs; i-- ; ++sp) {
319 				if (clean_segment(fsp, sp->sl_id) < 0)
320 					perror("clean_segment failed");
321 				else if (lfs_segclean(&fsp->fi_statfsp->f_fsid,
322 				    sp->sl_id) < 0)
323 					perror("lfs_segclean failed");
324 				printf("Completed cleaning segment %d\n", sp->sl_id);
325 			}
326 	}
327 	free(segs);
328 }
329 
330 /*
331  * Segment with the highest priority get sorted to the beginning of the
332  * list.  This sort assumes that empty segments always have a higher
333  * cost/benefit than any utilized segment.
334  */
335 int
cost_compare(a,b)336 cost_compare(a, b)
337 	const void *a;
338 	const void *b;
339 {
340 	return (((struct seglist *)b)->sl_cost -
341 	    ((struct seglist *)a)->sl_cost);
342 }
343 
344 
345 /*
346  * Returns the number of segments to be cleaned with the elements of seglist
347  * filled in.
348  */
349 int
choose_segments(fsp,seglist,cost_func)350 choose_segments(fsp, seglist, cost_func)
351 	FS_INFO *fsp;
352 	struct seglist *seglist;
353 	int (*cost_func) __P((FS_INFO *, SEGUSE *));
354 {
355 	struct lfs *lfsp;
356 	struct seglist *sp;
357 	SEGUSE *sup;
358 	int i, nsegs;
359 
360 	lfsp = &fsp->fi_lfs;
361 
362 #ifdef VERBOSE
363 	(void)printf("Entering choose_segments\n");
364 #endif
365 	dump_super(lfsp);
366 	dump_cleaner_info(fsp->fi_cip);
367 
368 	for (sp = seglist, i = 0; i < lfsp->lfs_nseg; ++i) {
369 		sup = SEGUSE_ENTRY(lfsp, fsp->fi_segusep, i);
370 		 PRINT_SEGUSE(sup, i);
371 		if (!(sup->su_flags & SEGUSE_DIRTY) ||
372 		    sup->su_flags & SEGUSE_ACTIVE)
373 			continue;
374 #ifdef VERBOSE
375 		(void)printf("\tchoosing segment %d\n", i);
376 #endif
377 		sp->sl_cost = (*cost_func)(fsp, sup);
378 		sp->sl_id = i;
379 		sp->sl_bytes = sup->su_nbytes;
380 		++sp;
381 	}
382 	nsegs = sp - seglist;
383 	qsort(seglist, nsegs, sizeof(struct seglist), cost_compare);
384 #ifdef VERBOSE
385 	(void)printf("Returning %d segments\n", nsegs);
386 #endif
387 	return (nsegs);
388 }
389 
390 
391 int
clean_segment(fsp,id)392 clean_segment(fsp, id)
393 	FS_INFO *fsp;	/* file system information */
394 	int id;		/* segment number */
395 {
396 	BLOCK_INFO *block_array, *bp;
397 	SEGUSE *sp;
398 	struct lfs *lfsp;
399 	struct tossstruct t;
400 	caddr_t seg_buf;
401 	double util;
402 	int num_blocks, maxblocks, clean_blocks;
403 
404 	lfsp = &fsp->fi_lfs;
405 	sp = SEGUSE_ENTRY(lfsp, fsp->fi_segusep, id);
406 
407 #ifdef VERBOSE
408 	(void)printf("cleaning segment %d: contains %lu bytes\n", id,
409 	    sp->su_nbytes);
410 	fflush(stdout);
411 #endif
412 	/* XXX could add debugging to verify that segment is really empty */
413 	if (sp->su_nbytes == sp->su_nsums * LFS_SUMMARY_SIZE) {
414 		++cleaner_stats.segs_empty;
415 		return (0);
416 	}
417 
418 	/* map the segment into a buffer */
419 	if (mmap_segment(fsp, id, &seg_buf, do_mmap) < 0) {
420 		err(0, "mmap_segment failed");
421 		++cleaner_stats.segs_error;
422 		return (-1);
423 	}
424 	/* get a list of blocks that are contained by the segment */
425 	if (lfs_segmapv(fsp, id, seg_buf, &block_array, &num_blocks) < 0) {
426 		err(0, "clean_segment: lfs_segmapv failed");
427 		++cleaner_stats.segs_error;
428 		return (-1);
429 	}
430 	cleaner_stats.blocks_read += fsp->fi_lfs.lfs_ssize;
431 
432 #ifdef VERBOSE
433 	(void)printf("lfs_segmapv returned %d blocks\n", num_blocks);
434 	fflush(stdout);
435 #endif
436 
437 	/* get the current disk address of blocks contained by the segment */
438 	if (lfs_bmapv(&fsp->fi_statfsp->f_fsid, block_array, num_blocks) < 0) {
439 		perror("clean_segment: lfs_bmapv failed\n");
440 		++cleaner_stats.segs_error;
441 		return -1;
442 	}
443 
444 	/* Now toss any blocks not in the current segment */
445 	t.lfs = lfsp;
446 	t.seg = id;
447 	toss(block_array, &num_blocks, sizeof(BLOCK_INFO), bi_tossold, &t);
448 
449 	/* Check if last element should be tossed */
450 	if (num_blocks && bi_tossold(&t, block_array + num_blocks - 1, NULL))
451 		--num_blocks;
452 
453 #ifdef VERBOSE
454 	{
455 		BLOCK_INFO *_bip;
456 		u_long *lp;
457 		int i;
458 
459 		(void)printf("after bmapv still have %d blocks\n", num_blocks);
460 		fflush(stdout);
461 		if (num_blocks)
462 			printf("BLOCK INFOS\n");
463 		for (_bip = block_array, i=0; i < num_blocks; ++_bip, ++i) {
464 			PRINT_BINFO(_bip);
465 			lp = (u_long *)_bip->bi_bp;
466 		}
467 	}
468 
469 #endif
470 	++cleaner_stats.segs_cleaned;
471 	cleaner_stats.blocks_written += num_blocks;
472 	util = ((double)num_blocks / fsp->fi_lfs.lfs_ssize);
473 	cleaner_stats.util_tot += util;
474 	cleaner_stats.util_sos += util * util;
475 
476 	if (do_small)
477 		maxblocks = MAXPHYS / fsp->fi_lfs.lfs_bsize - 1;
478 	else
479 		maxblocks = num_blocks;
480 
481 	for (bp = block_array; num_blocks > 0; bp += clean_blocks) {
482 		clean_blocks = maxblocks < num_blocks ? maxblocks : num_blocks;
483 		if (lfs_markv(&fsp->fi_statfsp->f_fsid,
484 		    bp, clean_blocks) < 0) {
485 			err(0, "clean_segment: lfs_markv failed");
486 			++cleaner_stats.segs_error;
487 			return (-1);
488 		}
489 		num_blocks -= clean_blocks;
490 	}
491 
492 	free(block_array);
493 	munmap_segment(fsp, seg_buf, do_mmap);
494 	if (stat_report && cleaner_stats.segs_cleaned % stat_report == 0)
495 		sig_report(SIGUSR1);
496 	return (0);
497 }
498 
499 
500 int
bi_tossold(client,a,b)501 bi_tossold(client, a, b)
502 	const void *client;
503 	const void *a;
504 	const void *b;
505 {
506 	const struct tossstruct *t;
507 
508 	t = (struct tossstruct *)client;
509 
510 	return (((BLOCK_INFO *)a)->bi_daddr == LFS_UNUSED_DADDR ||
511 	    datosn(t->lfs, ((BLOCK_INFO *)a)->bi_daddr) != t->seg);
512 }
513 
514 void
sig_report(sig)515 sig_report(sig)
516 	int sig;
517 {
518 	double avg;
519 
520 	printf("lfs_cleanerd:\t%s%d\n\t\t%s%d\n\t\t%s%d\n\t\t%s%d\n\t\t%s%d\n",
521 		"blocks_read    ", cleaner_stats.blocks_read,
522 		"blocks_written ", cleaner_stats.blocks_written,
523 		"segs_cleaned   ", cleaner_stats.segs_cleaned,
524 		"segs_empty     ", cleaner_stats.segs_empty,
525 		"seg_error      ", cleaner_stats.segs_error);
526 	printf("\t\t%s%5.2f\n\t\t%s%5.2f\n",
527 		"util_tot       ", cleaner_stats.util_tot,
528 		"util_sos       ", cleaner_stats.util_sos);
529 	printf("\t\tavg util: %4.2f std dev: %9.6f\n",
530 		avg = cleaner_stats.util_tot / cleaner_stats.segs_cleaned,
531 		cleaner_stats.util_sos / cleaner_stats.segs_cleaned - avg * avg);
532 
533 
534 	if (sig == SIGUSR2) {
535 		cleaner_stats.blocks_read = 0;
536 		cleaner_stats.blocks_written = 0;
537 		cleaner_stats.segs_cleaned = 0;
538 		cleaner_stats.segs_empty = 0;
539 		cleaner_stats.segs_error = 0;
540 		cleaner_stats.util_tot = 0.0;
541 		cleaner_stats.util_sos = 0.0;
542 	}
543 	if (sig == SIGINT)
544 		exit(0);
545 }
546