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