2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1999 University of Maryland at College Park
6 * Permission to use, copy, modify, distribute, and sell this software and its
7 * documentation for any purpose is hereby granted without fee, provided that
8 * the above copyright notice appear in all copies and that both that
9 * copyright notice and this permission notice appear in supporting
10 * documentation, and that the name of U.M. not be used in advertising or
11 * publicity pertaining to distribution of the software without specific,
12 * written prior permission. U.M. makes no representations about the
13 * suitability of this software for any purpose. It is provided "as is"
14 * without express or implied warranty.
16 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * Authors: the Amanda Development Team. Its members are listed in a
24 * file named AUTHORS, in the root directory of this distribution.
27 * $Id: planner.c,v 1.180.2.1 2006/04/24 11:16:43 martinea Exp $
29 * backup schedule planner for the Amanda backup system.
43 #include "amfeatures.h"
44 #include "server_util.h"
47 #define MAX_LEVELS 3 /* max# of estimates per filesys */
49 #define RUNS_REDZONE 5 /* should be in conf file? */
51 #define PROMOTE_THRESHOLD 0.05 /* if <5% unbalanced, don't promote */
52 #define DEFAULT_DUMPRATE 1024.0 /* K/s */
54 /* configuration file stuff */
57 am64_t conf_maxdumpsize;
60 int conf_runspercycle;
66 #define HOST_READY ((void *)0) /* must be 0 */
67 #define HOST_ACTIVE ((void *)1)
68 #define HOST_DONE ((void *)2)
70 #define DISK_READY 0 /* must be 0 */
72 #define DISK_PARTIALY_DONE 2
75 typedef struct est_s {
81 int degr_level; /* if dump_level == 0, what would be the inc level */
88 double fullrate, incrrate;
89 double fullcomp, incrcomp;
91 int level[MAX_LEVELS];
92 char *dumpdate[MAX_LEVELS];
93 long est_size[MAX_LEVELS];
96 #define est(dp) ((est_t *)(dp)->up)
98 /* pestq = partial estimate */
99 disklist_t startq, waitq, pestq, estq, failq, schedq;
101 double total_lev0, balanced_size, balance_threshold;
102 am64_t tape_length, tape_mark;
106 long tt_blocksize_kb;
107 int runs_per_cycle = 0;
109 char *datestamp = NULL;
111 static am_feature_t *our_features = NULL;
112 static char *our_feature_string = NULL;
114 /* We keep a LIFO queue of before images for all modifications made
115 * to schedq in our attempt to make the schedule fit on the tape.
116 * Enough information is stored to reinstate a dump if it turns out
117 * that it shouldn't have been touched after all.
119 typedef struct bi_s {
122 int deleted; /* 0=modified, 1=deleted */
123 disk_t *dp; /* The disk that was changed */
124 int level; /* The original level */
125 long size; /* The original size */
126 char *errstr; /* A message describing why this disk is here */
129 typedef struct bilist_s {
133 bilist_t biq; /* The BI queue itself */
136 * ========================================================================
141 static void setup_estimate P((disk_t *dp));
142 static void get_estimates P((void));
143 static void analyze_estimate P((disk_t *dp));
144 static void handle_failed P((disk_t *dp));
145 static void delay_dumps P((void));
146 static int promote_highest_priority_incremental P((void));
147 static int promote_hills P((void));
148 static void output_scheduleline P((disk_t *dp));
149 int main P((int, char **));
158 unsigned long malloc_hist_1, malloc_size_1;
159 unsigned long malloc_hist_2, malloc_size_2;
166 times_t section_start;
170 setvbuf(stderr, (char *)NULL, _IOLBF, 0);
173 config_name = stralloc(argv[1]);
174 config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
176 char my_cwd[STR_SIZE];
178 if (getcwd(my_cwd, sizeof(my_cwd)) == NULL) {
179 error("cannot determine current working directory");
181 config_dir = stralloc2(my_cwd, "/");
182 if ((config_name = strrchr(my_cwd, '/')) != NULL) {
183 config_name = stralloc(config_name + 1);
189 set_pname("planner");
191 /* Don't die when child closes pipe */
192 signal(SIGPIPE, SIG_IGN);
194 malloc_size_1 = malloc_inuse(&malloc_hist_1);
196 erroutput_type = (ERR_AMANDALOG|ERR_INTERACTIVE);
197 set_logerror(logerror);
199 section_start = curclock();
201 our_features = am_init_feature_set();
202 our_feature_string = am_feature_to_string(our_features);
204 fprintf(stderr, "%s: pid %ld executable %s version %s\n",
205 get_pname(), (long) getpid(), argv[0], version());
206 for (i = 0; version_info[i] != NULL; i++)
207 fprintf(stderr, "%s: %s", get_pname(), version_info[i]);
210 * 1. Networking Setup
212 * Planner runs setuid to get a priviledged socket for BSD security.
213 * We get the socket right away as root, then setuid back to a normal
214 * user. If we are not using BSD security, planner is not installed
221 uid_t ruid = getuid();
228 * From this point on we are running under our real uid, so we don't
229 * have to worry about opening security holes below. Make sure we
233 if(getpwuid(getuid()) == NULL)
234 error("can't get login name for my uid %ld", (long)getuid());
237 * 2. Read in Configuration Information
239 * All the Amanda configuration files are loaded before we begin.
242 fprintf(stderr,"READING CONF FILES...\n");
244 conffile = stralloc2(config_dir, CONFFILE_NAME);
245 if(read_conffile(conffile)) {
246 error("errors processing config file \"%s\"", conffile);
250 conf_diskfile = getconf_str(CNF_DISKFILE);
251 if (*conf_diskfile == '/') {
252 conf_diskfile = stralloc(conf_diskfile);
254 conf_diskfile = stralloc2(config_dir, conf_diskfile);
256 if (read_diskfile(conf_diskfile, &origq) < 0) {
257 error("could not load disklist \"%s\"", conf_diskfile);
259 match_disklist(&origq, argc-2, argv+2);
260 for(dp = origq.head; dp != NULL; dp = dp->next) {
262 log_add(L_DISK, "%s %s", dp->host->hostname, dp->name);
264 amfree(conf_diskfile);
266 conf_tapelist = getconf_str(CNF_TAPELIST);
267 if (*conf_tapelist == '/') {
268 conf_tapelist = stralloc(conf_tapelist);
270 conf_tapelist = stralloc2(config_dir, conf_tapelist);
272 if(read_tapelist(conf_tapelist)) {
273 error("could not load tapelist \"%s\"", conf_tapelist);
275 amfree(conf_tapelist);
277 conf_infofile = getconf_str(CNF_INFOFILE);
278 if (*conf_infofile == '/') {
279 conf_infofile = stralloc(conf_infofile);
281 conf_infofile = stralloc2(config_dir, conf_infofile);
283 if(open_infofile(conf_infofile)) {
284 error("could not open info db \"%s\"", conf_infofile);
286 amfree(conf_infofile);
288 conf_tapetype = getconf_str(CNF_TAPETYPE);
289 conf_maxdumpsize = getconf_am64(CNF_MAXDUMPSIZE);
290 conf_runtapes = getconf_int(CNF_RUNTAPES);
291 conf_dumpcycle = getconf_int(CNF_DUMPCYCLE);
292 conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
293 conf_tapecycle = getconf_int(CNF_TAPECYCLE);
294 conf_etimeout = getconf_int(CNF_ETIMEOUT);
295 conf_reserve = getconf_int(CNF_RESERVE);
296 conf_autoflush = getconf_int(CNF_AUTOFLUSH);
300 datestamp = construct_datestamp(NULL);
301 log_add(L_START, "date %s", datestamp);
303 /* some initializations */
305 if(conf_runspercycle == 0) {
306 runs_per_cycle = conf_dumpcycle;
307 } else if(conf_runspercycle == -1 ) {
308 runs_per_cycle = guess_runs_from_tapelist();
310 runs_per_cycle = conf_runspercycle;
312 if (runs_per_cycle <= 0) {
317 * do some basic sanity checking
319 if(conf_tapecycle <= runs_per_cycle) {
320 log_add(L_WARNING, "tapecycle (%d) <= runspercycle (%d)",
321 conf_tapecycle, runs_per_cycle);
324 tape = lookup_tapetype(conf_tapetype);
325 if(conf_maxdumpsize > 0) {
326 tape_length = conf_maxdumpsize;
329 tape_length = tape->length * conf_runtapes;
331 tape_mark = tape->filemark;
332 tt_blocksize_kb = tape->blocksize;
333 tt_blocksize = tt_blocksize_kb * 1024;
335 fprintf(stderr, "%s: time %s: startup took %s secs\n",
337 walltime_str(curclock()),
338 walltime_str(timessub(curclock(), section_start)));
341 * 3. Send autoflush dumps left on the holding disks
343 * This should give us something to do while we generate the new
347 fprintf(stderr,"\nSENDING FLUSHES...\n");
353 holding_list = get_flush(NULL, NULL, 0, 0);
354 for(holding_file=holding_list->first; holding_file != NULL;
355 holding_file = holding_file->next) {
356 get_dumpfile(holding_file->name, &file);
358 log_add(L_DISK, "%s %s", file.name, file.disk);
360 "FLUSH %s %s %s %d %s\n",
367 "FLUSH %s %s %s %d %s\n",
374 free_sl(holding_list);
377 fprintf(stderr, "ENDFLUSH\n");
378 fprintf(stdout, "ENDFLUSH\n");
382 * 4. Calculate Preliminary Dump Levels
384 * Before we can get estimates from the remote slave hosts, we make a
385 * first attempt at guessing what dump levels we will be dumping at
386 * based on the curinfo database.
389 fprintf(stderr,"\nSETTING UP FOR ESTIMATES...\n");
390 section_start = curclock();
392 startq.head = startq.tail = NULL;
393 while(!empty(origq)) {
394 disk_t *dp = dequeue_disk(&origq);
400 fprintf(stderr, "%s: time %s: setting up estimates took %s secs\n",
402 walltime_str(curclock()),
403 walltime_str(timessub(curclock(), section_start)));
407 * 5. Get Dump Size Estimates from Remote Client Hosts
409 * Each host is queried (in parallel) for dump size information on all
410 * of its disks, and the results gathered as they come in.
413 /* go out and get the dump estimates */
415 fprintf(stderr,"\nGETTING ESTIMATES...\n");
416 section_start = curclock();
418 estq.head = estq.tail = NULL;
419 pestq.head = pestq.tail = NULL;
420 waitq.head = waitq.tail = NULL;
421 failq.head = failq.tail = NULL;
425 fprintf(stderr, "%s: time %s: getting estimates took %s secs\n",
427 walltime_str(curclock()),
428 walltime_str(timessub(curclock(), section_start)));
431 * At this point, all disks with estimates are in estq, and
432 * all the disks on hosts that didn't respond to our inquiry
436 dump_queue("FAILED", failq, 15, stderr);
437 dump_queue("DONE", estq, 15, stderr);
441 * 6. Analyze Dump Estimates
443 * Each disk's estimates are looked at to determine what level it
444 * should dump at, and to calculate the expected size and time taking
445 * historical dump rates and compression ratios into account. The
446 * total expected size is accumulated as well.
449 fprintf(stderr,"\nANALYZING ESTIMATES...\n");
450 section_start = curclock();
452 /* an empty tape still has a label and an endmark */
453 total_size = (tt_blocksize_kb + tape_mark) * 2;
457 schedq.head = schedq.tail = NULL;
458 while(!empty(estq)) analyze_estimate(dequeue_disk(&estq));
459 while(!empty(failq)) handle_failed(dequeue_disk(&failq));
462 * At this point, all the disks are on schedq sorted by priority.
463 * The total estimated size of the backups is in total_size.
469 fprintf(stderr, "INITIAL SCHEDULE (size " AM64_FMT "):\n", total_size);
470 for(dp = schedq.head; dp != NULL; dp = dp->next) {
471 fprintf(stderr, " %s %s pri %d lev %d size %ld\n",
472 dp->host->hostname, dp->name, est(dp)->dump_priority,
473 est(dp)->dump_level, est(dp)->dump_size);
479 * 7. Delay Dumps if Schedule Too Big
481 * If the generated schedule is too big to fit on the tape, we need to
482 * delay some full dumps to make room. Incrementals will be done
483 * instead (except for new or forced disks).
485 * In extreme cases, delaying all the full dumps is not even enough.
486 * If so, some low-priority incrementals will be skipped completely
487 * until the dumps fit on the tape.
491 "\nDELAYING DUMPS IF NEEDED, total_size " AM64_FMT ", tape length " AM64_FMT " mark " AM64_FMT "\n",
492 total_size, tape_length, tape_mark);
494 initial_size = total_size;
498 /* XXX - why bother checking this? */
499 if(empty(schedq) && total_size < initial_size)
500 error("cannot fit anything on tape, bailing out");
504 * 8. Promote Dumps if Schedule Too Small
506 * Amanda attempts to balance the full dumps over the length of the
507 * dump cycle. If this night's full dumps are too small relative to
508 * the other nights, promote some high-priority full dumps that will be
509 * due for the next run, to full dumps for tonight, taking care not to
510 * overflow the tape size.
512 * This doesn't work too well for small sites. For these we scan ahead
513 * looking for nights that have an excessive number of dumps and promote
516 * Amanda never delays full dumps just for the sake of balancing the
517 * schedule, so it can take a full cycle to balance the schedule after
522 "\nPROMOTING DUMPS IF NEEDED, total_lev0 %1.0f, balanced_size %1.0f...\n",
523 total_lev0, balanced_size);
525 balance_threshold = balanced_size * PROMOTE_THRESHOLD;
527 while((balanced_size - total_lev0) > balance_threshold && moved_one)
528 moved_one = promote_highest_priority_incremental();
530 moved_one = promote_hills();
532 fprintf(stderr, "%s: time %s: analysis took %s secs\n",
534 walltime_str(curclock()),
535 walltime_str(timessub(curclock(), section_start)));
541 * The schedule goes to stdout, presumably to driver. A copy is written
542 * on stderr for the debug file.
545 fprintf(stderr,"\nGENERATING SCHEDULE:\n--------\n");
547 while(!empty(schedq)) output_scheduleline(dequeue_disk(&schedq));
548 fprintf(stderr, "--------\n");
551 log_add(L_FINISH, "date %s time %s", datestamp, walltime_str(curclock()));
556 amfree(our_feature_string);
557 am_release_feature_set(our_features);
560 malloc_size_2 = malloc_inuse(&malloc_hist_2);
562 if(malloc_size_1 != malloc_size_2) {
563 malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
572 * ========================================================================
573 * SETUP FOR ESTIMATES
577 static void askfor P((est_t *, int, int, info_t *));
578 static int last_level P((info_t *info)); /* subroutines */
579 static long est_size P((disk_t *dp, int level));
580 static long est_tape_size P((disk_t *dp, int level));
581 static int next_level0 P((disk_t *dp, info_t *info));
582 static int runs_at P((info_t *info, int lev));
583 static long bump_thresh P((int level, long size_level_0, int bumppercent, int bumpsize, double bumpmult));
584 static int when_overwrite P((char *label));
586 static void askfor(ep, seq, lev, info)
587 est_t *ep; /* esimate data block */
588 int seq; /* sequence number of request */
589 int lev; /* dump level being requested */
590 info_t *info; /* info block for disk */
592 if(seq < 0 || seq >= MAX_LEVELS) {
593 error("error [planner askfor: seq out of range 0..%d: %d]",
596 if(lev < -1 || lev >= DUMP_LEVELS) {
597 error("error [planner askfor: lev out of range -1..%d: %d]",
603 ep->dumpdate[seq] = (char *)0;
604 ep->est_size[seq] = -2;
608 ep->level[seq] = lev;
610 ep->dumpdate[seq] = stralloc(get_dumpdate(info,lev));
611 malloc_mark(ep->dumpdate[seq]);
613 ep->est_size[seq] = -2;
626 assert(dp && dp->host);
627 fprintf(stderr, "%s: time %s: setting up estimates for %s:%s\n",
628 get_pname(), walltime_str(curclock()),
629 dp->host->hostname, dp->name);
631 /* get current information about disk */
633 if(get_info(dp->host->hostname, dp->name, &info)) {
634 /* no record for this disk, make a note of it */
635 log_add(L_INFO, "Adding new disk %s:%s.", dp->host->hostname, dp->name);
638 /* setup working data struct for disk */
640 ep = alloc(sizeof(est_t));
642 dp->up = (void *) ep;
643 ep->state = DISK_READY;
645 ep->dump_priority = dp->priority;
649 /* calculated fields */
651 if (ISSET(info.command, FORCE_FULL)) {
652 /* force a level 0, kind of like a new disk */
653 if(dp->strategy == DS_NOFULL) {
655 * XXX - Not sure what it means to force a no-full disk. The
656 * purpose of no-full is to just dump changes relative to a
657 * stable base, for example root partitions that vary only
658 * slightly from a site-wide prototype. Only the variations
661 * If we allow a level 0 onto the Amanda cycle, then we are
662 * hosed when that tape gets re-used next. Disallow this for
666 "Cannot force full dump of %s:%s with no-full option.",
667 dp->host->hostname, dp->name);
669 /* clear force command */
670 CLR(info.command, FORCE_FULL);
671 if(put_info(dp->host->hostname, dp->name, &info))
672 error("could not put info record for %s:%s: %s",
673 dp->host->hostname, dp->name, strerror(errno));
674 ep->last_level = last_level(&info);
675 ep->next_level0 = next_level0(dp, &info);
679 ep->next_level0 = -conf_dumpcycle;
680 log_add(L_INFO, "Forcing full dump of %s:%s as directed.",
681 dp->host->hostname, dp->name);
684 else if(dp->strategy == DS_NOFULL) {
685 /* force estimate of level 1 */
687 ep->next_level0 = next_level0(dp, &info);
690 ep->last_level = last_level(&info);
691 ep->next_level0 = next_level0(dp, &info);
694 /* adjust priority levels */
696 if(ep->next_level0 < 0) {
697 fprintf(stderr,"%s:%s overdue %d day%s for level 0\n",
698 dp->host->hostname, dp->name,
699 - ep->next_level0, ((- ep->next_level0) == 1) ? "" : "s");
700 ep->dump_priority -= ep->next_level0;
701 /* warn if dump will be overwritten */
702 if(ep->last_level > -1) {
703 int overwrite_runs = when_overwrite(info.inf[0].label);
704 if(overwrite_runs == 0) {
706 "Last full dump of %s:%s on tape %s overwritten on this run.",
707 dp->host->hostname, dp->name, info.inf[0].label);
709 else if(overwrite_runs < RUNS_REDZONE) {
711 "Last full dump of %s:%s on tape %s overwritten in %d run%s.",
712 dp->host->hostname, dp->name, info.inf[0].label,
713 overwrite_runs, overwrite_runs == 1? "" : "s");
717 else if (ISSET(info.command, FORCE_FULL))
718 ep->dump_priority += 1;
719 /* else XXX bump up the priority of incrementals that failed last night */
721 /* handle external level 0 dumps */
723 if(dp->skip_full && dp->strategy != DS_NOINC) {
724 if(ep->next_level0 <= 0) {
725 /* update the date field */
726 info.inf[0].date = today;
727 CLR(info.command, FORCE_FULL);
728 ep->next_level0 += conf_dumpcycle;
730 if(put_info(dp->host->hostname, dp->name, &info))
731 error("could not put info record for %s:%s: %s",
732 dp->host->hostname, dp->name, strerror(errno));
733 log_add(L_INFO, "Skipping full dump of %s:%s today.",
734 dp->host->hostname, dp->name);
735 fprintf(stderr,"%s:%s lev 0 skipped due to skip-full flag\n",
736 dp->host->hostname, dp->name);
737 /* don't enqueue the disk */
738 askfor(ep, 0, -1, &info);
739 askfor(ep, 1, -1, &info);
740 askfor(ep, 2, -1, &info);
741 fprintf(stderr, "%s: SKIPPED %s %s 0 [skip-full]\n",
742 get_pname(), dp->host->hostname, dp->name);
743 log_add(L_SUCCESS, "%s %s %s 0 [skipped: skip-full]",
744 dp->host->hostname, dp->name, datestamp);
748 if(ep->last_level == -1) {
749 /* probably a new disk, but skip-full means no full! */
753 if(ep->next_level0 == 1) {
754 log_add(L_WARNING, "Skipping full dump of %s:%s tomorrow.",
755 dp->host->hostname, dp->name);
759 if(dp->strategy == DS_INCRONLY && ep->last_level == -1 && !ISSET(info.command, FORCE_FULL)) {
760 /* don't enqueue the disk */
761 askfor(ep, 0, -1, &info);
762 askfor(ep, 1, -1, &info);
763 askfor(ep, 2, -1, &info);
764 log_add(L_FAIL, "%s %s 19000101 1 [Skipping incronly because no full dump were done]",
765 dp->host->hostname, dp->name);
766 fprintf(stderr,"%s:%s lev 1 skipped due to strategy incronly and no full dump were done\n",
767 dp->host->hostname, dp->name);
771 /* handle "skip-incr" type archives */
773 if(dp->skip_incr && ep->next_level0 > 0) {
774 fprintf(stderr,"%s:%s lev 1 skipped due to skip-incr flag\n",
775 dp->host->hostname, dp->name);
776 /* don't enqueue the disk */
777 askfor(ep, 0, -1, &info);
778 askfor(ep, 1, -1, &info);
779 askfor(ep, 2, -1, &info);
781 fprintf(stderr, "%s: SKIPPED %s %s 1 [skip-incr]\n",
782 get_pname(), dp->host->hostname, dp->name);
784 log_add(L_SUCCESS, "%s %s %s 1 [skipped: skip-incr]",
785 dp->host->hostname, dp->name, datestamp);
789 if( ep->last_level == -1 && ep->next_level0 > 0 &&
790 dp->strategy != DS_NOFULL && dp->strategy != DS_INCRONLY &&
791 conf_reserve == 100) {
793 "%s:%s mismatch: no tapelist record, but curinfo next_level0: %d.",
794 dp->host->hostname, dp->name, ep->next_level0);
798 if(ep->last_level == 0) ep->level_days = 0;
799 else ep->level_days = runs_at(&info, ep->last_level);
800 ep->last_lev0size = info.inf[0].csize;
802 ep->fullrate = perf_average(info.full.rate, 0.0);
803 ep->incrrate = perf_average(info.incr.rate, 0.0);
805 ep->fullcomp = perf_average(info.full.comp, dp->comprate[0]);
806 ep->incrcomp = perf_average(info.incr.comp, dp->comprate[1]);
808 /* determine which estimates to get */
812 if (dp->strategy == DS_NOINC ||
814 (!ISSET(info.command, FORCE_BUMP) ||
816 ep->last_level == -1))) {
817 if(info.command & FORCE_BUMP && ep->last_level == -1) {
819 "Remove force-bump command of %s:%s because it's a new disk.",
820 dp->host->hostname, dp->name);
822 switch (dp->strategy) {
825 askfor(ep, i++, 0, &info);
828 "Ignoring skip_full for %s:%s because the strategy is NOINC.",
829 dp->host->hostname, dp->name);
831 if(info.command & FORCE_BUMP) {
833 "Ignoring FORCE_BUMP for %s:%s because the strategy is NOINC.",
834 dp->host->hostname, dp->name);
843 if (ISSET(info.command, FORCE_FULL))
844 askfor(ep, i++, 0, &info);
849 if(!dp->skip_incr && !(dp->strategy == DS_NOINC)) {
850 if(ep->last_level == -1) { /* a new disk */
851 if(dp->strategy == DS_NOFULL || dp->strategy == DS_INCRONLY) {
852 askfor(ep, i++, 1, &info);
854 assert(!dp->skip_full); /* should be handled above */
856 } else { /* not new, pick normally */
859 curr_level = ep->last_level;
861 if (ISSET(info.command, FORCE_NO_BUMP)) {
862 if(curr_level > 0) { /* level 0 already asked for */
863 askfor(ep, i++, curr_level, &info);
865 log_add(L_INFO,"Preventing bump of %s:%s as directed.",
866 dp->host->hostname, dp->name);
867 } else if (ISSET(info.command, FORCE_BUMP)
868 && curr_level + 1 < DUMP_LEVELS) {
869 askfor(ep, i++, curr_level+1, &info);
870 log_add(L_INFO,"Bumping of %s:%s at level %d as directed.",
871 dp->host->hostname, dp->name, curr_level+1);
872 } else if (curr_level == 0) {
873 askfor(ep, i++, 1, &info);
875 askfor(ep, i++, curr_level, &info);
877 * If last time we dumped less than the threshold, then this
878 * time we will too, OR the extra size will be charged to both
879 * cur_level and cur_level + 1, so we will never bump. Also,
880 * if we haven't been at this level 2 days, or the dump failed
881 * last night, we can't bump.
883 if((info.inf[curr_level].size == 0 || /* no data, try it anyway */
884 (((info.inf[curr_level].size > bump_thresh(curr_level, info.inf[0].size,dp->bumppercent, dp->bumpsize, dp->bumpmult)))
885 && ep->level_days >= dp->bumpdays))
886 && curr_level + 1 < DUMP_LEVELS) {
887 askfor(ep, i++, curr_level+1, &info);
893 while(i < MAX_LEVELS) /* mark end of estimates */
894 askfor(ep, i++, -1, &info);
898 fprintf(stderr, "setup_estimate: %s:%s: command %d, options: %s last_level %d next_level0 %d level_days %d getting estimates %d (%ld) %d (%ld) %d (%ld)\n",
899 dp->host->hostname, dp->name, info.command,
900 dp->strategy == DS_NOFULL ? "no-full" :
901 dp->strategy == DS_INCRONLY ? "incr-only" :
902 dp->skip_full ? "skip-full" :
903 dp->skip_incr ? "skip-incr" : "none",
904 ep->last_level, ep->next_level0, ep->level_days,
905 ep->level[0], ep->est_size[0],
906 ep->level[1], ep->est_size[1],
907 ep->level[2], ep->est_size[2]);
909 assert(ep->level[0] != -1);
910 enqueue_disk(&startq, dp);
913 static int when_overwrite(label)
919 runtapes = conf_runtapes;
920 if(runtapes == 0) runtapes = 1;
922 if((tp = lookup_tapelabel(label)) == NULL)
923 return 1; /* "shouldn't happen", but trigger warning message */
924 else if(!reusable_tape(tp))
926 else if(lookup_nb_tape() > conf_tapecycle)
927 return (lookup_nb_tape() - tp->position) / runtapes;
929 return (conf_tapecycle - tp->position) / runtapes;
932 /* Return the estimated size for a particular dump */
933 static long est_size(dp, level)
939 for(i = 0; i < MAX_LEVELS; i++) {
940 if(level == est(dp)->level[i])
941 return est(dp)->est_size[i];
946 /* Return the estimated on-tape size of a particular dump */
947 static long est_tape_size(dp, level)
954 size = est_size(dp, level);
956 if(size == -1) return size;
958 if(dp->compress == COMP_NONE)
961 if(level == 0) ratio = est(dp)->fullcomp;
962 else ratio = est(dp)->incrcomp;
965 * make sure over-inflated compression ratios don't throw off the
966 * estimates, this is mostly for when you have a small dump getting
967 * compressed which takes up alot more disk/tape space relatively due
968 * to the overhead of the compression. This is specifically for
969 * Digital Unix vdump. This patch is courtesy of Rudolf Gabler
970 * (RUG@USM.Uni-Muenchen.DE)
973 if(ratio > 1.1) ratio = 1.1;
978 * Ratio can be very small in some error situations, so make sure
979 * size goes back greater than zero. It may not be right, but
980 * indicates we did get an estimate.
990 /* what was the level of the last successful dump to tape? */
991 static int last_level(info)
994 int min_pos, min_level, i;
995 time_t lev0_date, last_date;
998 if(info->last_level != -1)
999 return info->last_level;
1001 /* to keep compatibility with old infofile */
1002 min_pos = 1000000000;
1006 for(i = 0; i < 9; i++) {
1007 if(conf_reserve < 100) {
1008 if(i == 0) lev0_date = info->inf[0].date;
1009 else if(info->inf[i].date < lev0_date) continue;
1010 if(info->inf[i].date > last_date) {
1011 last_date = info->inf[i].date;
1016 if((tp = lookup_tapelabel(info->inf[i].label)) == NULL) continue;
1017 /* cull any entries from previous cycles */
1018 if(i == 0) lev0_date = info->inf[0].date;
1019 else if(info->inf[i].date < lev0_date) continue;
1021 if(tp->position < min_pos) {
1022 min_pos = tp->position;
1027 info->last_level = i;
1031 /* when is next level 0 due? 0 = today, 1 = tomorrow, etc*/
1033 next_level0(dp, info)
1037 if(dp->strategy == DS_NOFULL || dp->strategy == DS_INCRONLY)
1038 return 1; /* fake it */
1039 else if (dp->strategy == DS_NOINC)
1041 else if(info->inf[0].date < (time_t)0)
1042 return -days_diff(EPOCH, today); /* new disk */
1044 return dp->dumpcycle - days_diff(info->inf[0].date, today);
1047 /* how many runs at current level? */
1048 static int runs_at(info, lev)
1052 tape_t *cur_tape, *old_tape;
1055 last = last_level(info);
1056 if(lev != last) return 0;
1057 if(lev == 0) return 1;
1059 if(info->consecutive_runs != -1)
1060 return info->consecutive_runs;
1062 /* to keep compatibility with old infofile */
1063 cur_tape = lookup_tapelabel(info->inf[lev].label);
1064 old_tape = lookup_tapelabel(info->inf[lev-1].label);
1065 if(cur_tape == NULL || old_tape == NULL) return 0;
1067 if(conf_runtapes == 0)
1068 nb_runs = (old_tape->position - cur_tape->position) / 1;
1070 nb_runs = (old_tape->position - cur_tape->position) / conf_runtapes;
1071 info->consecutive_runs = nb_runs;
1077 static long bump_thresh(level, size_level_0, bumppercent, bumpsize, bumpmult)
1086 if(bumppercent != 0 && size_level_0 > 1024) {
1087 bump = (size_level_0 * bumppercent)/100.0;
1092 while(--level) bump = bump * bumpmult;
1100 * ========================================================================
1101 * GET REMOTE DUMP SIZE ESTIMATES
1105 static void getsize P((am_host_t *hostp));
1106 static disk_t *lookup_hostdisk P((am_host_t *hp, char *str));
1107 static void handle_result P((void *datap, pkt_t *pkt, security_handle_t *sech));
1110 static void get_estimates P((void))
1114 int something_started;
1116 something_started = 1;
1117 while(something_started) {
1118 something_started = 0;
1119 for(dp = startq.head; dp != NULL; dp = dp->next) {
1121 if(hostp->up == HOST_READY) {
1122 something_started = 1;
1126 * dp is no longer on startq, so dp->next is not valid
1127 * and we have to start all over.
1135 while(!empty(waitq)) {
1136 disk_t *dp = dequeue_disk(&waitq);
1137 est(dp)->errstr = "hmm, disk was stranded on waitq";
1138 enqueue_disk(&failq, dp);
1141 while(!empty(pestq)) {
1142 disk_t *dp = dequeue_disk(&pestq);
1144 if(est(dp)->level[0] != -1 && est(dp)->est_size[0] < 0) {
1145 if(est(dp)->est_size[0] == -1) {
1147 "disk %s:%s, estimate of level %d failed.",
1148 dp->host->hostname, dp->name,
1153 "disk %s:%s, estimate of level %d timed out.",
1154 dp->host->hostname, dp->name,
1157 est(dp)->level[0] = -1;
1160 if(est(dp)->level[1] != -1 && est(dp)->est_size[1] < 0) {
1161 if(est(dp)->est_size[1] == -1) {
1163 "disk %s:%s, estimate of level %d failed.",
1164 dp->host->hostname, dp->name,
1169 "disk %s:%s, estimate of level %d timed out.",
1170 dp->host->hostname, dp->name,
1173 est(dp)->level[1] = -1;
1176 if(est(dp)->level[2] != -1 && est(dp)->est_size[2] < 0) {
1177 if(est(dp)->est_size[2] == -1) {
1179 "disk %s:%s, estimate of level %d failed.",
1180 dp->host->hostname, dp->name,
1185 "disk %s:%s, estimate of level %d timed out.",
1186 dp->host->hostname, dp->name,
1189 est(dp)->level[2] = -1;
1192 if((est(dp)->level[0] != -1 && est(dp)->est_size[0] > 0) ||
1193 (est(dp)->level[1] != -1 && est(dp)->est_size[1] > 0) ||
1194 (est(dp)->level[2] != -1 && est(dp)->est_size[2] > 0)) {
1195 enqueue_disk(&estq, dp);
1198 est(dp)->errstr = vstralloc("disk ", dp->name,
1199 ", all estimate timed out", NULL);
1200 enqueue_disk(&failq, dp);
1205 static void getsize(hostp)
1208 char number[NUM_STR_SIZE], *req;
1210 int i, estimates, timeout, req_len;
1211 const security_driver_t *secdrv;
1215 assert(hostp->disks != NULL);
1217 if(hostp->up != HOST_READY) {
1222 * The first time through here we send a "noop" request. This will
1223 * return the feature list from the client if it supports that.
1224 * If it does not, handle_result() will set the feature list to an
1225 * empty structure. In either case, we do the disks on the second
1226 * (and subsequent) pass(es).
1228 if(hostp->features != NULL) { /* sendsize service */
1232 int has_features = am_has_feature(hostp->features,
1233 fe_req_options_features);
1234 int has_hostname = am_has_feature(hostp->features,
1235 fe_req_options_hostname);
1236 int has_maxdumps = am_has_feature(hostp->features,
1237 fe_req_options_maxdumps);
1239 snprintf(number, sizeof(number), "%d", hostp->maxdumps);
1240 req = vstralloc("SERVICE ", "sendsize", "\n",
1242 has_features ? "features=" : "",
1243 has_features ? our_feature_string : "",
1244 has_features ? ";" : "",
1245 has_maxdumps ? "maxdumps=" : "",
1246 has_maxdumps ? number : "",
1247 has_maxdumps ? ";" : "",
1248 has_hostname ? "hostname=" : "",
1249 has_hostname ? hostp->hostname : "",
1250 has_hostname ? ";" : "",
1253 req_len = strlen(req);
1254 req_len += 128; /* room for SECURITY ... */
1256 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1260 if(dp->todo == 0) continue;
1262 if(est(dp)->state != DISK_READY) continue;
1264 est(dp)->got_estimate = 0;
1265 if(est(dp)->level[0] == -1) {
1266 est(dp)->state = DISK_DONE;
1270 if(dp->estimate == ES_CLIENT ||
1271 dp->estimate == ES_CALCSIZE) {
1274 for(i = 0; i < MAX_LEVELS; i++) {
1276 char *exclude1 = "";
1277 char *exclude2 = "";
1278 char *excludefree = NULL;
1279 char spindle[NUM_STR_SIZE];
1280 char level[NUM_STR_SIZE];
1281 int lev = est(dp)->level[i];
1283 if(lev == -1) break;
1285 snprintf(level, sizeof(level), "%d", lev);
1286 snprintf(spindle, sizeof(spindle), "%d", dp->spindle);
1287 if(am_has_feature(hostp->features,fe_sendsize_req_options)){
1288 exclude1 = " OPTIONS |";
1289 exclude2 = optionstr(dp, hostp->features, NULL);
1290 excludefree = exclude2;
1293 if(dp->exclude_file &&
1294 dp->exclude_file->nb_element == 1) {
1295 exclude1 = " exclude-file=";
1296 exclude2 = dp->exclude_file->first->name;
1298 else if(dp->exclude_list &&
1299 dp->exclude_list->nb_element == 1) {
1300 exclude1 = " exclude-list=";
1301 exclude2 = dp->exclude_list->first->name;
1305 if(dp->estimate == ES_CALCSIZE &&
1306 !am_has_feature(hostp->features, fe_calcsize_estimate)) {
1307 log_add(L_WARNING,"%s:%s does not support CALCSIZE for estimate, using CLIENT.\n",
1308 hostp->hostname, dp->name);
1309 dp->estimate = ES_CLIENT;
1311 if(dp->estimate == ES_CLIENT)
1314 calcsize = "CALCSIZE ";
1316 if(strncmp(dp->program,"DUMP",4) == 0 ||
1317 strncmp(dp->program,"GNUTAR",6) == 0) {
1322 l = vstralloc(calcsize,
1326 " ", dp->device ? dp->device : "",
1328 " ", est(dp)->dumpdate[i],
1330 " ", exclude1, exclude2,
1336 amfree(excludefree);
1339 * Allow 2X for err response.
1341 if(req_len + s_len > MAX_PACKET / 2) {
1351 est(dp)->state = DISK_ACTIVE;
1352 remove_disk(&startq, dp);
1354 else if (dp->estimate == ES_SERVER) {
1357 get_info(dp->host->hostname, dp->name, &info);
1358 for(i = 0; i < MAX_LEVELS; i++) {
1360 int lev = est(dp)->level[i];
1362 if(lev == -1) break;
1363 if(lev == 0) { /* use latest level 0, should do extrapolation */
1367 for(j=NB_HISTORY-2;j>=0;j--) {
1368 if(info.history[j].level == 0) {
1369 if(info.history[j].size < 0) continue;
1370 est_size = info.history[j].size;
1375 est(dp)->est_size[i] = est_size;
1377 else if(info.inf[lev].size > 1000) { /* stats */
1378 est(dp)->est_size[i] = info.inf[lev].size;
1381 est(dp)->est_size[i] = 1000000;
1384 else if(lev == est(dp)->last_level) {
1385 /* means of all X day at the same level */
1388 long est_size_day[NB_DAY];
1389 int nb_est_day[NB_DAY];
1390 for(j=0;j<NB_DAY;j++) {
1395 for(j=NB_HISTORY-2;j>=0;j--) {
1396 if(info.history[j].level <= 0) continue;
1397 if(info.history[j].size < 0) continue;
1398 if(info.history[j].level==info.history[j+1].level) {
1399 if(nb_day <NB_DAY-1) nb_day++;
1400 est_size_day[nb_day] += info.history[j].size;
1401 nb_est_day[nb_day]++;
1407 nb_day = info.consecutive_runs + 1;
1408 if(nb_day > NB_DAY-1) nb_day = NB_DAY-1;
1410 while(nb_day > 0 && nb_est_day[nb_day] == 0) nb_day--;
1412 if(nb_est_day[nb_day] > 0) {
1413 est(dp)->est_size[i] =
1414 est_size_day[nb_day] / nb_est_day[nb_day];
1416 else if(info.inf[lev].size > 1000) { /* stats */
1417 est(dp)->est_size[i] = info.inf[lev].size;
1420 est(dp)->est_size[i] = 10000;
1423 else if(lev == est(dp)->last_level + 1) {
1424 /* means of all first day at a new level */
1428 for(j=NB_HISTORY-2;j>=0;j--) {
1429 if(info.history[j].level <= 0) continue;
1430 if(info.history[j].size < 0) continue;
1431 if(info.history[j].level == info.history[j+1].level + 1 ) {
1432 est_size += info.history[j].size;
1437 est(dp)->est_size[i] = est_size / nb_est;
1439 else if(info.inf[lev].size > 1000) { /* stats */
1440 est(dp)->est_size[i] = info.inf[lev].size;
1443 est(dp)->est_size[i] = 100000;
1447 fprintf(stderr,"%s time %s: got result for host %s disk %s:",
1448 get_pname(), walltime_str(curclock()),
1449 dp->host->hostname, dp->name);
1450 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1451 est(dp)->level[0], est(dp)->est_size[0],
1452 est(dp)->level[1], est(dp)->est_size[1],
1453 est(dp)->level[2], est(dp)->est_size[2]);
1454 est(dp)->state = DISK_DONE;
1455 remove_disk(&startq, dp);
1456 enqueue_disk(&estq, dp);
1460 if(estimates == 0) {
1462 hostp->up = HOST_DONE;
1466 if (conf_etimeout < 0) {
1467 timeout = - conf_etimeout;
1469 timeout = estimates * conf_etimeout;
1471 } else { /* noop service */
1472 req = vstralloc("SERVICE ", "noop", "\n",
1474 "features=", our_feature_string, ";",
1478 * We use ctimeout for the "noop" request because it should be
1479 * very fast and etimeout has other side effects.
1481 timeout = getconf_int(CNF_CTIMEOUT);
1484 secdrv = security_getdriver(hostp->disks->security_driver);
1485 if (secdrv == NULL) {
1486 error("could not find security driver '%s' for host '%s'",
1487 hostp->disks->security_driver, hostp->hostname);
1489 hostp->up = HOST_ACTIVE;
1491 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1495 if(est(dp)->state == DISK_ACTIVE) {
1496 est(dp)->errstr = NULL;
1497 enqueue_disk(&waitq, dp);
1501 protocol_sendreq(hostp->hostname, secdrv, generic_get_security_conf,
1502 req, timeout, handle_result, hostp);
1506 static disk_t *lookup_hostdisk(hp, str)
1512 for(dp = hp->disks; dp != NULL; dp = dp->hostnext)
1513 if(strcmp(str, dp->name) == 0) return dp;
1519 static void handle_result(datap, pkt, sech)
1522 security_handle_t *sech;
1528 char *msgdisk=NULL, *msgdisk_undo=NULL, msgdisk_undo_ch = '\0';
1529 char *remoterr, *errbuf = NULL;
1537 hostp = (am_host_t *)datap;
1538 hostp->up = HOST_READY;
1541 errbuf = vstralloc("Request to ", hostp->hostname, " failed: ",
1542 security_geterror(sech), NULL);
1545 if (pkt->type == P_NAK) {
1547 if(strncmp(pkt->body, sc, sizeof(sc)-1) == 0) {
1548 s = pkt->body + sizeof(sc)-1;
1552 goto NAK_parse_failed;
1554 skip_whitespace(s, ch);
1555 if(ch == '\0') goto NAK_parse_failed;
1557 if((s = strchr(remoterr, '\n')) != NULL) {
1558 if(s == remoterr) goto NAK_parse_failed;
1561 if (strcmp(remoterr, "unknown service: noop") != 0
1562 && strcmp(remoterr, "noop: invalid service") != 0) {
1563 errbuf = vstralloc(hostp->hostname, " NAK: ", remoterr, NULL);
1569 msgdisk_undo = NULL;
1575 if (s[-2] == '\n') {
1579 #define sc "OPTIONS "
1580 if(strncmp(line, sc, sizeof(sc)-1) == 0) {
1583 #define sc "features="
1584 t = strstr(line, sc);
1585 if(t != NULL && (isspace((int)t[-1]) || t[-1] == ';')) {
1588 am_release_feature_set(hostp->features);
1589 if((hostp->features = am_string_to_feature(t)) == NULL) {
1590 errbuf = vstralloc(hostp->hostname,
1591 ": bad features value: ",
1603 if(strncmp(line, sc, sizeof(sc)-1) == 0) {
1604 t = line + sizeof(sc)-1;
1609 skip_whitespace(t, tch);
1614 * If the "error" is that the "noop" service is unknown, it
1615 * just means the client is "old" (does not support the servie).
1616 * We can ignore this.
1618 if(hostp->features == NULL
1619 && pkt->type == P_NAK
1620 && (strcmp(t - 1, "unknown service: noop") == 0
1621 || strcmp(t - 1, "noop: invalid service") == 0)) {
1624 errbuf = vstralloc(hostp->hostname,
1625 (pkt->type == P_NAK) ? "NAK " : "",
1635 skip_non_whitespace(t, tch);
1636 msgdisk_undo = t - 1;
1637 msgdisk_undo_ch = *msgdisk_undo;
1638 *msgdisk_undo = '\0';
1640 skip_whitespace(t, tch);
1641 if (sscanf(t - 1, "%d SIZE %ld", &level, &size) != 2) {
1645 dp = lookup_hostdisk(hostp, msgdisk);
1647 *msgdisk_undo = msgdisk_undo_ch; /* for error message */
1648 msgdisk_undo = NULL;
1651 log_add(L_ERROR, "%s: invalid reply from sendsize: `%s'\n",
1652 hostp->hostname, line);
1654 for(i = 0; i < MAX_LEVELS; i++) {
1655 if(est(dp)->level[i] == level) {
1656 est(dp)->est_size[i] = size;
1660 if(i == MAX_LEVELS) {
1661 goto bad_msg; /* this est wasn't requested */
1663 est(dp)->got_estimate++;
1667 if(hostp->up == HOST_READY && hostp->features == NULL) {
1669 * The client does not support the features list, so give it an
1672 dbprintf(("%s: no feature set from host %s\n",
1673 debug_prefix_time(NULL), hostp->hostname));
1674 hostp->features = am_set_default_feature_set();
1678 /* XXX what about disks that only got some estimates... do we care? */
1679 /* XXX amanda 2.1 treated that case as a bad msg */
1681 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1682 if(dp->todo == 0) continue;
1683 if(est(dp)->state != DISK_ACTIVE &&
1684 est(dp)->state != DISK_PARTIALY_DONE) continue;
1686 if(est(dp)->state == DISK_ACTIVE) {
1687 remove_disk(&waitq, dp);
1689 else if(est(dp)->state == DISK_PARTIALY_DONE) {
1690 remove_disk(&pestq, dp);
1693 if(pkt->type == P_REP) {
1694 est(dp)->state = DISK_DONE;
1696 else if(pkt->type == P_PREP) {
1697 est(dp)->state = DISK_PARTIALY_DONE;
1700 if(est(dp)->level[0] == -1) continue; /* ignore this disk */
1703 if(pkt->type == P_PREP) {
1704 fprintf(stderr,"%s: time %s: got partial result for host %s disk %s:",
1705 get_pname(), walltime_str(curclock()),
1706 dp->host->hostname, dp->name);
1707 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1708 est(dp)->level[0], est(dp)->est_size[0],
1709 est(dp)->level[1], est(dp)->est_size[1],
1710 est(dp)->level[2], est(dp)->est_size[2]);
1711 enqueue_disk(&pestq, dp);
1713 else if(pkt->type == P_REP) {
1714 fprintf(stderr,"%s: time %s: got result for host %s disk %s:",
1715 get_pname(), walltime_str(curclock()),
1716 dp->host->hostname, dp->name);
1717 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1718 est(dp)->level[0], est(dp)->est_size[0],
1719 est(dp)->level[1], est(dp)->est_size[1],
1720 est(dp)->level[2], est(dp)->est_size[2]);
1721 if((est(dp)->level[0] != -1 && est(dp)->est_size[0] > 0) ||
1722 (est(dp)->level[1] != -1 && est(dp)->est_size[1] > 0) ||
1723 (est(dp)->level[2] != -1 && est(dp)->est_size[2] > 0)) {
1725 if(est(dp)->level[2] != -1 && est(dp)->est_size[2] < 0) {
1727 "disk %s:%s, estimate of level %d failed.",
1728 dp->host->hostname, dp->name,
1730 est(dp)->level[2] = -1;
1732 if(est(dp)->level[1] != -1 && est(dp)->est_size[1] < 0) {
1734 "disk %s:%s, estimate of level %d failed.",
1735 dp->host->hostname, dp->name,
1737 est(dp)->level[1] = -1;
1739 if(est(dp)->level[0] != -1 && est(dp)->est_size[0] < 0) {
1741 "disk %s:%s, estimate of level %d failed.",
1742 dp->host->hostname, dp->name,
1744 est(dp)->level[0] = -1;
1746 enqueue_disk(&estq, dp);
1749 enqueue_disk(&failq, dp);
1750 if(est(dp)->got_estimate) {
1751 est(dp)->errstr = vstralloc("disk ", dp->name,
1752 ", all estimate failed", NULL);
1755 fprintf(stderr, "error result for host %s disk %s: missing estimate\n",
1756 dp->host->hostname, dp->name);
1757 est(dp)->errstr = vstralloc("missing result for ", dp->name,
1758 " in ", dp->host->hostname,
1770 /* msgdisk_undo is always NULL */
1771 /* if(msgdisk_undo) { */
1772 /* *msgdisk_undo = msgdisk_undo_ch; */
1773 /* msgdisk_undo = NULL; */
1775 errbuf = stralloc2(hostp->hostname, " NAK: [NAK parse failed]");
1776 fprintf(stderr, "got strange nak from %s:\n----\n%s----\n\n",
1777 hostp->hostname, pkt->body);
1783 *msgdisk_undo = msgdisk_undo_ch;
1784 msgdisk_undo = NULL;
1786 fprintf(stderr,"got a bad message, stopped at:\n");
1787 fprintf(stderr,"----\n%s----\n\n", line);
1788 errbuf = stralloc2("badly formatted response from ", hostp->hostname);
1789 /* fall through to ... */
1794 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1795 if(est(dp)->state != DISK_ACTIVE) continue;
1796 est(dp)->state = DISK_DONE;
1797 if(est(dp)->state == DISK_ACTIVE) {
1798 est(dp)->state = DISK_DONE;
1799 remove_disk(&waitq, dp);
1800 enqueue_disk(&failq, dp);
1803 est(dp)->errstr = stralloc(errbuf);
1804 fprintf(stderr, "error result for host %s disk %s: %s\n",
1805 dp->host->hostname, dp->name, errbuf);
1810 * If there were no disks involved, make sure the error gets
1813 log_add(L_ERROR, "%s", errbuf);
1815 hostp->up = HOST_DONE;
1823 * ========================================================================
1828 static int schedule_order P((disk_t *a, disk_t *b)); /* subroutines */
1829 static int pick_inclevel P((disk_t *dp));
1831 static void analyze_estimate(dp)
1840 fprintf(stderr, "pondering %s:%s... ",
1841 dp->host->hostname, dp->name);
1842 fprintf(stderr, "next_level0 %d last_level %d ",
1843 ep->next_level0, ep->last_level);
1845 if(get_info(dp->host->hostname, dp->name, &info) == 0) {
1849 ep->degr_level = -1;
1852 if(ep->next_level0 <= 0
1853 || (have_info && ep->last_level == 0 && (info.command & FORCE_NO_BUMP))) {
1854 if(ep->next_level0 <= 0) {
1855 fprintf(stderr,"(due for level 0) ");
1858 ep->dump_size = est_tape_size(dp, 0);
1859 if(ep->dump_size <= 0) {
1861 "(no estimate for level 0, picking an incr level)\n");
1862 ep->dump_level = pick_inclevel(dp);
1863 ep->dump_size = est_tape_size(dp, ep->dump_level);
1865 if(ep->dump_size == -1) {
1866 ep->dump_level = ep->dump_level + 1;
1867 ep->dump_size = est_tape_size(dp, ep->dump_level);
1871 total_lev0 += (double) ep->dump_size;
1872 if(ep->last_level == -1 || dp->skip_incr) {
1873 fprintf(stderr,"(%s disk, can't switch to degraded mode)\n",
1874 dp->skip_incr? "skip-incr":"new");
1875 ep->degr_level = -1;
1879 /* fill in degraded mode info */
1880 fprintf(stderr,"(picking inclevel for degraded mode)");
1881 ep->degr_level = pick_inclevel(dp);
1882 ep->degr_size = est_tape_size(dp, ep->degr_level);
1883 if(ep->degr_size == -1) {
1884 ep->degr_level = ep->degr_level + 1;
1885 ep->degr_size = est_tape_size(dp, ep->degr_level);
1887 if(ep->degr_size == -1) {
1888 fprintf(stderr,"(no inc estimate)");
1889 ep->degr_level = -1;
1891 fprintf(stderr,"\n");
1896 fprintf(stderr,"(not due for a full dump, picking an incr level)\n");
1897 /* XXX - if this returns -1 may be we should force a total? */
1898 ep->dump_level = pick_inclevel(dp);
1899 ep->dump_size = est_tape_size(dp, ep->dump_level);
1901 if(ep->dump_size == -1) {
1902 ep->dump_level = ep->last_level;
1903 ep->dump_size = est_tape_size(dp, ep->dump_level);
1905 if(ep->dump_size == -1) {
1906 ep->dump_level = ep->last_level + 1;
1907 ep->dump_size = est_tape_size(dp, ep->dump_level);
1909 if(ep->dump_size == -1) {
1911 ep->dump_size = est_tape_size(dp, ep->dump_level);
1915 fprintf(stderr," curr level %d size %ld ", ep->dump_level, ep->dump_size);
1917 insert_disk(&schedq, dp, schedule_order);
1919 total_size += tt_blocksize_kb + ep->dump_size + tape_mark;
1921 /* update the balanced size */
1922 if(!(dp->skip_full || dp->strategy == DS_NOFULL ||
1923 dp->strategy == DS_INCRONLY)) {
1926 lev0size = est_tape_size(dp, 0);
1927 if(lev0size == -1) lev0size = ep->last_lev0size;
1929 balanced_size += lev0size / runs_per_cycle;
1932 fprintf(stderr,"total size " AM64_FMT " total_lev0 %1.0f balanced-lev0size %1.0f\n",
1933 total_size, total_lev0, balanced_size);
1936 static void handle_failed(dp)
1942 * From George Scott <George.Scott@cc.monash.edu.au>:
1944 * If a machine is down when the planner is run it guesses from historical
1945 * data what the size of tonights dump is likely to be and schedules a
1946 * dump anyway. The dumper then usually discovers that that machine is
1947 * still down and ends up with a half full tape. Unfortunately the
1948 * planner had to delay another dump because it thought that the tape was
1949 * full. The fix here is for the planner to ignore unavailable machines
1950 * rather than ignore the fact that they are unavailable.
1955 if(est(dp)->last_level != -1) {
1957 "Could not get estimate for %s:%s, using historical data.",
1958 dp->host->hostname, dp->name);
1959 analyze_estimate(dp);
1964 errstr = est(dp)->errstr? est(dp)->errstr : "hmm, no error indicator!";
1966 fprintf(stderr, "%s: FAILED %s %s %s 0 [%s]\n",
1967 get_pname(), dp->host->hostname, dp->name, datestamp, errstr);
1969 log_add(L_FAIL, "%s %s %s 0 [%s]", dp->host->hostname, dp->name,
1972 /* XXX - memory leak with *dp */
1976 static int schedule_order(a, b)
1979 * insert-sort by decreasing priority, then
1980 * by decreasing size within priority levels.
1986 diff = est(b)->dump_priority - est(a)->dump_priority;
1987 if(diff != 0) return diff;
1989 ldiff = est(b)->dump_size - est(a)->dump_size;
1990 if(ldiff < 0) return -1; /* XXX - there has to be a better way to dothis */
1991 if(ldiff > 0) return 1;
1996 static int pick_inclevel(dp)
1999 int base_level, bump_level;
2000 long base_size, bump_size;
2003 base_level = est(dp)->last_level;
2005 /* if last night was level 0, do level 1 tonight, no ifs or buts */
2006 if(base_level == 0) {
2007 fprintf(stderr," picklev: last night 0, so tonight level 1\n");
2011 /* if no-full option set, always do level 1 */
2012 if(dp->strategy == DS_NOFULL) {
2013 fprintf(stderr," picklev: no-full set, so always level 1\n");
2017 base_size = est_size(dp, base_level);
2019 /* if we didn't get an estimate, we can't do an inc */
2020 if(base_size == -1) {
2021 base_size = est_size(dp, base_level+1);
2022 if(base_size > 0) /* FORCE_BUMP */
2023 return base_level+1;
2024 fprintf(stderr," picklev: no estimate for level %d, so no incs\n", base_level);
2028 thresh = bump_thresh(base_level, est_size(dp, 0), dp->bumppercent, dp->bumpsize, dp->bumpmult);
2031 " pick: size %ld level %d days %d (thresh %ldK, %d days)\n",
2032 base_size, base_level, est(dp)->level_days,
2033 thresh, dp->bumpdays);
2036 || est(dp)->level_days < dp->bumpdays
2037 || base_size <= thresh)
2040 bump_level = base_level + 1;
2041 bump_size = est_size(dp, bump_level);
2043 if(bump_size == -1) return base_level;
2045 fprintf(stderr, " pick: next size %ld... ", bump_size);
2047 if(base_size - bump_size < thresh) {
2048 fprintf(stderr, "not bumped\n");
2052 fprintf(stderr, "BUMPED\n");
2053 log_add(L_INFO, "Incremental of %s:%s bumped to level %d.",
2054 dp->host->hostname, dp->name, bump_level);
2063 ** ========================================================================
2066 ** We have two strategies here:
2070 ** If we are trying to fit too much on the tape something has to go. We
2071 ** try to delay totals until tomorrow by converting them into incrementals
2072 ** and, if that is not effective enough, dropping incrementals altogether.
2073 ** While we are searching for the guilty dump (the one that is really
2074 ** causing the schedule to be oversize) we have probably trampled on a lot of
2075 ** innocent dumps, so we maintain a "before image" list and use this to
2076 ** put back what we can.
2078 ** 2. Promote dumps.
2080 ** We try to keep the amount of tape used by total dumps the same each night.
2081 ** If there is some spare tape in this run we have a look to see if any of
2082 ** tonights incrementals could be promoted to totals and leave us with a
2083 ** more balanced cycle.
2086 static void delay_one_dump P((disk_t *dp, int delete, ...));
2088 static void delay_dumps P((void))
2089 /* delay any dumps that will not fit */
2091 disk_t *dp, *ndp, *preserve;
2093 am64_t new_total; /* New total_size */
2094 char est_kb[20]; /* Text formatted dump size */
2095 int nb_forced_level_0;
2100 biq.head = biq.tail = NULL;
2103 ** 1. Delay dumps that are way oversize.
2105 ** Dumps larger that the size of the tapes we are using are just plain
2106 ** not going to fit no matter how many other dumps we drop. Delay
2107 ** oversize totals until tomorrow (by which time my owner will have
2108 ** resolved the problem!) and drop incrementals altogether. Naturally
2109 ** a large total might be delayed into a large incremental so these
2110 ** need to be checked for separately.
2113 for(dp = schedq.head; dp != NULL; dp = ndp) {
2114 int avail_tapes = 1;
2115 if (dp->tape_splitsize > 0)
2116 avail_tapes = conf_runtapes;
2118 ndp = dp->next; /* remove_disk zaps this */
2120 if (est(dp)->dump_size == -1 ||
2121 est(dp)->dump_size <= tape->length * avail_tapes) {
2125 /* Format dumpsize for messages */
2126 snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2128 if(est(dp)->dump_level == 0) {
2131 message = "but cannot incremental dump skip-incr disk";
2133 else if(est(dp)->last_level < 0) {
2135 message = "but cannot incremental dump new disk";
2137 else if(est(dp)->degr_level < 0) {
2139 message = "but no incremental estimate";
2141 else if (est(dp)->degr_size > tape->length) {
2143 message = "incremental dump also larger than tape";
2147 message = "full dump delayed";
2152 message = "skipping incremental";
2154 delay_one_dump(dp, delete, "dump larger than available tape space,", est_kb,
2159 ** 2. Delay total dumps.
2161 ** Delay total dumps until tomorrow (or the day after!). We start with
2162 ** the lowest priority (most dispensable) and work forwards. We take
2163 ** care not to delay *all* the dumps since this could lead to a stale
2164 ** mate [for any one disk there are only three ways tomorrows dump will
2165 ** be smaller than todays: 1. we do a level 0 today so tomorows dump
2166 ** will be a level 1; 2. the disk gets more data so that it is bumped
2167 ** tomorrow (this can be a slow process); and, 3. the disk looses some
2168 ** data (when does that ever happen?)].
2171 nb_forced_level_0 = 0;
2173 for(dp = schedq.head; dp != NULL && preserve == NULL; dp = dp->next)
2174 if(est(dp)->dump_level == 0)
2177 /* 2.a. Do not delay forced full */
2178 for(dp = schedq.tail;
2179 dp != NULL && total_size > tape_length;
2183 if(est(dp)->dump_level != 0) continue;
2185 get_info(dp->host->hostname, dp->name, &info);
2186 if(info.command & FORCE_FULL) {
2187 nb_forced_level_0 += 1;
2192 if(dp != preserve) {
2194 /* Format dumpsize for messages */
2195 snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2199 message = "but cannot incremental dump skip-incr disk";
2201 else if(est(dp)->last_level < 0) {
2203 message = "but cannot incremental dump new disk";
2205 else if(est(dp)->degr_level < 0) {
2207 message = "but no incremental estimate";
2211 message = "full dump delayed";
2213 delay_one_dump(dp, delete, "dumps too big,", est_kb,
2218 /* 2.b. Delay forced full if needed */
2219 if(nb_forced_level_0 > 0 && total_size > tape_length) {
2220 for(dp = schedq.tail;
2221 dp != NULL && total_size > tape_length;
2225 if(est(dp)->dump_level == 0 && dp != preserve) {
2227 /* Format dumpsize for messages */
2228 snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2232 message = "but cannot incremental dump skip-incr disk";
2234 else if(est(dp)->last_level < 0) {
2236 message = "but cannot incremental dump new disk";
2238 else if(est(dp)->degr_level < 0) {
2240 message = "but no incremental estimate";
2244 message = "full dump delayed";
2246 delay_one_dump(dp, delete, "dumps too big,", est_kb,
2253 ** 3. Delay incremental dumps.
2255 ** Delay incremental dumps until tomorrow. This is a last ditch attempt
2256 ** at making things fit. Again, we start with the lowest priority (most
2257 ** dispensable) and work forwards.
2260 for(dp = schedq.tail;
2261 dp != NULL && total_size > tape_length;
2265 if(est(dp)->dump_level != 0) {
2267 /* Format dumpsize for messages */
2268 snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2270 delay_one_dump(dp, 1,
2271 "dumps way too big,",
2273 "must skip incremental dumps",
2279 ** 4. Reinstate delayed dumps.
2281 ** We might not have needed to stomp on all of the dumps we have just
2282 ** delayed above. Try to reinstate them all starting with the last one
2283 ** and working forwards. It is unlikely that the last one will fit back
2284 ** in but why complicate the code?
2287 for(bi = biq.tail; bi != NULL; bi = nbi) {
2288 int avail_tapes = 1;
2291 if(dp->tape_splitsize > 0) avail_tapes = conf_runtapes;
2294 new_total = total_size + tt_blocksize_kb + bi->size + tape_mark;
2296 new_total = total_size - est(dp)->dump_size + bi->size;
2298 if(new_total <= tape_length && bi->size < tape->length * avail_tapes) {
2300 total_size = new_total;
2302 if(bi->level == 0) {
2303 total_lev0 += (double) bi->size;
2305 insert_disk(&schedq, dp, schedule_order);
2308 est(dp)->dump_level = bi->level;
2309 est(dp)->dump_size = bi->size;
2313 if(bi->next == NULL)
2314 biq.tail = bi->prev;
2316 (bi->next)->prev = bi->prev;
2317 if(bi->prev == NULL)
2318 biq.head = bi->next;
2320 (bi->prev)->next = bi->next;
2327 ** 5. Output messages about what we have done.
2329 ** We can't output messages while we are delaying dumps because we might
2330 ** reinstate them later. We remember all the messages and output them
2334 for(bi = biq.head; bi != NULL; bi = nbi) {
2338 fprintf(stderr, "%s: FAILED %s\n", get_pname(), bi->errstr);
2339 log_add(L_FAIL, "%s", bi->errstr);
2343 fprintf(stderr, " delay: %s now at level %d\n",
2344 bi->errstr, est(dp)->dump_level);
2345 log_add(L_INFO, "%s", bi->errstr);
2348 /* Clean up - dont be too fancy! */
2353 fprintf(stderr, " delay: Total size now " AM64_FMT ".\n", total_size);
2360 * Remove a dump or modify it from full to incremental.
2361 * Keep track of it on the bi q in case we can add it back later.
2363 arglist_function1(static void delay_one_dump,
2369 char level_str[NUM_STR_SIZE];
2373 arglist_start(argp, delete);
2375 total_size -= tt_blocksize_kb + est(dp)->dump_size + tape_mark;
2376 if(est(dp)->dump_level == 0) {
2377 total_lev0 -= (double) est(dp)->dump_size;
2380 bi = alloc(sizeof(bi_t));
2382 bi->prev = biq.tail;
2383 if(biq.tail == NULL)
2386 biq.tail->next = bi;
2389 bi->deleted = delete;
2391 bi->level = est(dp)->dump_level;
2392 bi->size = est(dp)->dump_size;
2394 snprintf(level_str, sizeof(level_str), "%d", est(dp)->dump_level);
2395 bi->errstr = vstralloc(dp->host->hostname,
2397 " ", datestamp ? datestamp : "?",
2401 while ((next = arglist_val(argp, char *)) != NULL) {
2402 bi->errstr = newvstralloc(bi->errstr, bi->errstr, sep, next, NULL);
2405 strappend(bi->errstr, "]");
2409 remove_disk(&schedq, dp);
2411 est(dp)->dump_level = est(dp)->degr_level;
2412 est(dp)->dump_size = est(dp)->degr_size;
2413 total_size += tt_blocksize_kb + est(dp)->dump_size + tape_mark;
2420 static int promote_highest_priority_incremental P((void))
2422 disk_t *dp, *dp1, *dp_promote;
2423 long new_size, new_total, new_lev0;
2425 int nb_today, nb_same_day, nb_today2;
2426 int nb_disk_today, nb_disk_same_day;
2429 * return 1 if did so; must update total_size correctly; must not
2430 * cause total_size to exceed tape_length
2434 for(dp = schedq.head; dp != NULL; dp = dp->next) {
2436 est(dp)->promote = -1000;
2438 if(est_size(dp,0) <= 0)
2441 if(est(dp)->next_level0 <= 0)
2444 if(est(dp)->next_level0 > dp->maxpromoteday)
2447 new_size = est_tape_size(dp, 0);
2448 new_total = total_size - est(dp)->dump_size + new_size;
2449 new_lev0 = total_lev0 + new_size;
2454 nb_disk_same_day = 0;
2455 for(dp1 = schedq.head; dp1 != NULL; dp1 = dp1->next) {
2456 if(est(dp1)->dump_level == 0)
2458 else if(est(dp1)->next_level0 == est(dp)->next_level0)
2460 if(strcmp(dp->host->hostname, dp1->host->hostname) == 0) {
2461 if(est(dp1)->dump_level == 0)
2463 else if(est(dp1)->next_level0 == est(dp)->next_level0)
2468 /* do not promote if overflow tape */
2469 if(new_total > tape_length) continue;
2471 /* do not promote if overflow balanced size and something today */
2472 /* promote if nothing today */
2473 if(new_lev0 > balanced_size+balance_threshold && nb_disk_today > 0)
2476 /* do not promote if only one disk due that day and nothing today */
2477 if(nb_disk_same_day == 1 && nb_disk_today == 0) continue;
2479 nb_today2 = nb_today*nb_today;
2480 if(nb_today == 0 && nb_same_day > 1) nb_same_day++;
2482 if(nb_same_day >= nb_today2) {
2483 est(dp)->promote = ((nb_same_day - nb_today2)*(nb_same_day - nb_today2)) +
2484 conf_dumpcycle - est(dp)->next_level0;
2487 est(dp)->promote = -nb_today2 +
2488 conf_dumpcycle - est(dp)->next_level0;
2491 if(!dp_promote || est(dp_promote)->promote < est(dp)->promote) {
2493 fprintf(stderr," try %s:%s %d %d %d = %d\n",
2494 dp->host->hostname, dp->name, nb_same_day, nb_today, est(dp)->next_level0, est(dp)->promote);
2497 fprintf(stderr,"no try %s:%s %d %d %d = %d\n",
2498 dp->host->hostname, dp->name, nb_same_day, nb_today, est(dp)->next_level0, est(dp)->promote);
2505 new_size = est_tape_size(dp, 0);
2506 new_total = total_size - est(dp)->dump_size + new_size;
2507 new_lev0 = total_lev0 + new_size;
2509 total_size = new_total;
2510 total_lev0 = new_lev0;
2511 check_days = est(dp)->next_level0;
2512 est(dp)->degr_level = est(dp)->dump_level;
2513 est(dp)->degr_size = est(dp)->dump_size;
2514 est(dp)->dump_level = 0;
2515 est(dp)->dump_size = new_size;
2516 est(dp)->next_level0 = 0;
2519 " promote: moving %s:%s up, total_lev0 %1.0f, total_size " AM64_FMT "\n",
2520 dp->host->hostname, dp->name,
2521 total_lev0, total_size);
2524 "Full dump of %s:%s promoted from %d day%s ahead.",
2525 dp->host->hostname, dp->name,
2526 check_days, (check_days == 1) ? "" : "s");
2533 static int promote_hills P((void))
2536 struct balance_stats {
2547 /* If we are already doing a level 0 don't bother */
2551 /* Do the guts of an "amadmin balance" */
2552 my_dumpcycle = conf_dumpcycle;
2553 if(my_dumpcycle > 10000) my_dumpcycle = 10000;
2555 sp = (struct balance_stats *)
2556 alloc(sizeof(struct balance_stats) * my_dumpcycle);
2558 for(days = 0; days < my_dumpcycle; days++)
2559 sp[days].disks = sp[days].size = 0;
2561 for(dp = schedq.head; dp != NULL; dp = dp->next) {
2562 days = est(dp)->next_level0; /* This is > 0 by definition */
2563 if(days<my_dumpcycle && !dp->skip_full && dp->strategy != DS_NOFULL &&
2564 dp->strategy != DS_INCRONLY) {
2566 sp[days].size += est(dp)->last_lev0size;
2570 /* Search for a suitable big hill and cut it down */
2572 /* Find the tallest hill */
2574 for(days = 0; days < my_dumpcycle; days++) {
2575 if(sp[days].disks > 1 && sp[days].size > hill_size) {
2576 hill_size = sp[days].size;
2581 if(hill_size <= 0) break; /* no suitable hills */
2583 /* Find all the dumps in that hill and try and remove one */
2584 for(dp = schedq.head; dp != NULL; dp = dp->next) {
2585 if(est(dp)->next_level0 != hill_days ||
2586 est(dp)->next_level0 > dp->maxpromoteday ||
2588 dp->strategy == DS_NOFULL ||
2589 dp->strategy == DS_INCRONLY)
2591 new_size = est_tape_size(dp, 0);
2592 new_total = total_size - est(dp)->dump_size + new_size;
2593 if(new_total > tape_length)
2595 /* We found a disk we can promote */
2596 total_size = new_total;
2597 total_lev0 += new_size;
2598 est(dp)->degr_level = est(dp)->dump_level;
2599 est(dp)->degr_size = est(dp)->dump_size;
2600 est(dp)->dump_level = 0;
2601 est(dp)->next_level0 = 0;
2602 est(dp)->dump_size = new_size;
2605 " promote: moving %s:%s up, total_lev0 %1.0f, total_size " AM64_FMT "\n",
2606 dp->host->hostname, dp->name,
2607 total_lev0, total_size);
2610 "Full dump of %s:%s specially promoted from %d day%s ahead.",
2611 dp->host->hostname, dp->name,
2612 hill_days, (hill_days == 1) ? "" : "s");
2617 /* All the disks in that hill were unsuitable. */
2618 sp[hill_days].disks = 0; /* Don't get tricked again */
2626 * ========================================================================
2629 * XXX - memory leak - we shouldn't just throw away *dp
2631 static void output_scheduleline(dp)
2635 long dump_time = 0, degr_time = 0;
2636 char *schedline = NULL, *degr_str = NULL;
2637 char dump_priority_str[NUM_STR_SIZE];
2638 char dump_level_str[NUM_STR_SIZE];
2639 char dump_size_str[NUM_STR_SIZE];
2640 char dump_time_str[NUM_STR_SIZE];
2641 char degr_level_str[NUM_STR_SIZE];
2642 char degr_size_str[NUM_STR_SIZE];
2643 char degr_time_str[NUM_STR_SIZE];
2644 char *dump_date, *degr_date;
2650 if(ep->dump_size == -1) {
2651 /* no estimate, fail the disk */
2653 "%s: FAILED %s %s %s %d [no estimate]\n",
2655 dp->host->hostname, dp->name, datestamp, ep->dump_level);
2656 log_add(L_FAIL, "%s %s %s %d [no estimate]",
2657 dp->host->hostname, dp->name, datestamp, ep->dump_level);
2661 dump_date = degr_date = (char *)0;
2662 for(i = 0; i < MAX_LEVELS; i++) {
2663 if(ep->dump_level == ep->level[i])
2664 dump_date = ep->dumpdate[i];
2665 if(ep->degr_level == ep->level[i])
2666 degr_date = ep->dumpdate[i];
2669 #define fix_rate(rate) (rate < 1.0 ? DEFAULT_DUMPRATE : rate)
2671 if(ep->dump_level == 0) {
2672 dump_time = ep->dump_size / fix_rate(ep->fullrate);
2674 if(ep->degr_size != -1) {
2675 degr_time = ep->degr_size / fix_rate(ep->incrrate);
2679 dump_time = ep->dump_size / fix_rate(ep->incrrate);
2682 if(ep->dump_level == 0 && ep->degr_size != -1) {
2683 snprintf(degr_level_str, sizeof(degr_level_str),
2684 "%d", ep->degr_level);
2685 snprintf(degr_size_str, sizeof(degr_size_str),
2686 "%ld", ep->degr_size);
2687 snprintf(degr_time_str, sizeof(degr_time_str),
2689 degr_str = vstralloc(" ", degr_level_str,
2695 snprintf(dump_priority_str, sizeof(dump_priority_str),
2696 "%d", ep->dump_priority);
2697 snprintf(dump_level_str, sizeof(dump_level_str),
2698 "%d", ep->dump_level);
2699 snprintf(dump_size_str, sizeof(dump_size_str),
2700 "%ld", ep->dump_size);
2701 snprintf(dump_time_str, sizeof(dump_time_str),
2703 features = am_feature_to_string(dp->host->features);
2704 schedline = vstralloc("DUMP ",dp->host->hostname,
2708 " ", dump_priority_str,
2709 " ", dump_level_str,
2713 degr_str ? degr_str : "",
2716 fputs(schedline, stdout);
2717 fputs(schedline, stderr);