2 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3 * Copyright (c) 1991-1998 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.76.2.15.2.13.2.32.2.20 2005/09/20 21:31:52 jrjackson Exp $
29 * backup schedule planner for the Amanda backup system.
42 #include "amfeatures.h"
43 #include "server_util.h"
46 #define MAX_LEVELS 3 /* max# of estimates per filesys */
48 #define RUNS_REDZONE 5 /* should be in conf file? */
50 #define PROMOTE_THRESHOLD 0.05 /* if <5% unbalanced, don't promote */
51 #define DEFAULT_DUMPRATE 30.0 /* K/s */
53 /* configuration file stuff */
59 int conf_runspercycle;
65 #define HOST_READY ((void *)0) /* must be 0 */
66 #define HOST_ACTIVE ((void *)1)
67 #define HOST_DONE ((void *)2)
69 #define DISK_READY 0 /* must be 0 */
71 #define DISK_PARTIALY_DONE 2
74 typedef struct est_s {
80 int degr_level; /* if dump_level == 0, what would be the inc level */
87 double fullrate, incrrate;
88 double fullcomp, incrcomp;
90 int level[MAX_LEVELS];
91 char *dumpdate[MAX_LEVELS];
92 long est_size[MAX_LEVELS];
95 #define est(dp) ((est_t *)(dp)->up)
97 /* pestq = partial estimate */
98 disklist_t startq, waitq, pestq, estq, failq, schedq;
100 double total_lev0, balanced_size, balance_threshold;
101 unsigned long tape_length, tape_mark;
102 int result_port, amanda_port;
104 static am_feature_t *our_features = NULL;
105 static char *our_feature_string = NULL;
113 long tt_blocksize_kb;
114 int runs_per_cycle = 0;
119 /* We keep a LIFO queue of before images for all modifications made
120 * to schedq in our attempt to make the schedule fit on the tape.
121 * Enough information is stored to reinstate a dump if it turns out
122 * that it shouldn't have been touched after all.
124 typedef struct bi_s {
127 int deleted; /* 0=modified, 1=deleted */
128 disk_t *dp; /* The disk that was changed */
129 int level; /* The original level */
130 long size; /* The original size */
131 char *errstr; /* A message describing why this disk is here */
134 typedef struct bilist_s {
138 bilist_t biq; /* The BI queue itself */
140 char *datestamp = NULL;
143 * ========================================================================
148 static void setup_estimate P((disk_t *dp));
149 static void get_estimates P((void));
150 static void analyze_estimate P((disk_t *dp));
151 static void handle_failed P((disk_t *dp));
152 static void delay_dumps P((void));
153 static int promote_highest_priority_incremental P((void));
154 static int promote_hills P((void));
155 static void output_scheduleline P((disk_t *dp));
165 unsigned long malloc_hist_1, malloc_size_1;
166 unsigned long malloc_hist_2, malloc_size_2;
172 times_t section_start;
176 setvbuf(stderr, (char *)NULL, _IOLBF, 0);
179 config_name = stralloc(argv[1]);
180 config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
182 char my_cwd[STR_SIZE];
184 if (getcwd(my_cwd, sizeof(my_cwd)) == NULL) {
185 error("cannot determine current working directory");
187 config_dir = stralloc2(my_cwd, "/");
188 if ((config_name = strrchr(my_cwd, '/')) != NULL) {
189 config_name = stralloc(config_name + 1);
195 set_pname("planner");
197 malloc_size_1 = malloc_inuse(&malloc_hist_1);
199 erroutput_type = (ERR_AMANDALOG|ERR_INTERACTIVE);
200 set_logerror(logerror);
202 section_start = curclock();
204 our_features = am_init_feature_set();
205 our_feature_string = am_feature_to_string(our_features);
207 fprintf(stderr, "%s: pid %ld executable %s version %s\n",
208 get_pname(), (long) getpid(), argv[0], version());
209 for(vp = version_info; *vp != NULL; vp++)
210 fprintf(stderr, "%s: %s", get_pname(), *vp);
213 * 1. Networking Setup
215 * Planner runs setuid to get a priviledged socket for BSD security.
216 * We get the socket right away as root, then setuid back to a normal
217 * user. If we are not using BSD security, planner is not installed
221 /* set up dgram port first thing */
225 if(dgram_bind(msg, &result_port) == -1) {
226 error("could not bind result datagram port: %s", strerror(errno));
230 /* set both real and effective uid's to real uid, likewise for gid */
236 * From this point on we are running under our real uid, so we don't
237 * have to worry about opening security holes below. Make sure we
241 if(getpwuid(getuid()) == NULL) {
242 error("can't get login name for my uid %ld", (long)getuid());
246 * 2. Read in Configuration Information
248 * All the Amanda configuration files are loaded before we begin.
251 fprintf(stderr,"READING CONF FILES...\n");
253 conffile = stralloc2(config_dir, CONFFILE_NAME);
254 if(read_conffile(conffile)) {
255 error("errors processing config file \"%s\"", conffile);
258 conf_diskfile = getconf_str(CNF_DISKFILE);
259 if (*conf_diskfile == '/') {
260 conf_diskfile = stralloc(conf_diskfile);
262 conf_diskfile = stralloc2(config_dir, conf_diskfile);
264 if((origqp = read_diskfile(conf_diskfile)) == NULL) {
265 error("could not load disklist \"%s\"", conf_diskfile);
267 match_disklist(origqp, argc-2, argv+2);
268 for(dp = origqp->head; dp != NULL; dp = dp->next) {
270 log_add(L_DISK, "%s %s", dp->host->hostname, dp->name);
272 amfree(conf_diskfile);
273 conf_tapelist = getconf_str(CNF_TAPELIST);
274 if (*conf_tapelist == '/') {
275 conf_tapelist = stralloc(conf_tapelist);
277 conf_tapelist = stralloc2(config_dir, conf_tapelist);
279 if(read_tapelist(conf_tapelist)) {
280 error("could not load tapelist \"%s\"", conf_tapelist);
282 amfree(conf_tapelist);
283 conf_infofile = getconf_str(CNF_INFOFILE);
284 if (*conf_infofile == '/') {
285 conf_infofile = stralloc(conf_infofile);
287 conf_infofile = stralloc2(config_dir, conf_infofile);
289 if(open_infofile(conf_infofile)) {
290 error("could not open info db \"%s\"", conf_infofile);
292 amfree(conf_infofile);
294 conf_tapetype = getconf_str(CNF_TAPETYPE);
295 conf_maxdumpsize = getconf_int(CNF_MAXDUMPSIZE);
296 conf_runtapes = getconf_int(CNF_RUNTAPES);
297 conf_dumpcycle = getconf_int(CNF_DUMPCYCLE);
298 conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
299 conf_tapecycle = getconf_int(CNF_TAPECYCLE);
300 conf_etimeout = getconf_int(CNF_ETIMEOUT);
301 conf_reserve = getconf_int(CNF_RESERVE);
302 conf_autoflush = getconf_int(CNF_AUTOFLUSH);
306 datestamp = construct_datestamp(NULL);
307 log_add(L_START, "date %s", datestamp);
309 /* some initializations */
311 if(conf_runspercycle == 0) {
312 runs_per_cycle = conf_dumpcycle;
313 } else if(conf_runspercycle == -1 ) {
314 runs_per_cycle = guess_runs_from_tapelist();
316 runs_per_cycle = conf_runspercycle;
318 if (runs_per_cycle <= 0) {
323 * do some basic sanity checking
325 if(conf_tapecycle <= runs_per_cycle) {
326 log_add(L_WARNING, "tapecycle (%d) <= runspercycle (%d)",
327 conf_tapecycle, runs_per_cycle);
330 tape = lookup_tapetype(conf_tapetype);
331 if(conf_maxdumpsize > 0) {
332 tape_length = conf_maxdumpsize;
335 tape_length = tape->length * conf_runtapes;
337 tape_mark = tape->filemark;
338 tt_blocksize_kb = tape->blocksize;
339 tt_blocksize = tt_blocksize_kb * 1024;
341 proto_init(msg->socket, today, 1000); /* XXX handles should eq nhosts */
344 kerberos_service_init();
347 fprintf(stderr, "%s: time %s: startup took %s secs\n",
349 walltime_str(curclock()),
350 walltime_str(timessub(curclock(), section_start)));
353 * 3. Send autoflush dumps left on the holding disks
355 * This should give us something to do while we generate the new
359 fprintf(stderr,"\nSENDING FLUSHES...\n");
364 holding_list = get_flush(NULL, NULL, 0, 0);
365 for(holding_file=holding_list->first; holding_file != NULL;
366 holding_file = holding_file->next) {
367 get_dumpfile(holding_file->name, &file);
369 log_add(L_DISK, "%s %s", file.name, file.disk);
371 "FLUSH %s %s %s %d %s\n",
378 "FLUSH %s %s %s %d %s\n",
385 free_sl(holding_list);
388 fprintf(stderr, "ENDFLUSH\n");
389 fprintf(stdout, "ENDFLUSH\n");
393 * 4. Calculate Preliminary Dump Levels
395 * Before we can get estimates from the remote slave hosts, we make a
396 * first attempt at guessing what dump levels we will be dumping at
397 * based on the curinfo database.
400 fprintf(stderr,"\nSETTING UP FOR ESTIMATES...\n");
401 section_start = curclock();
403 startq.head = startq.tail = NULL;
404 while(!empty(*origqp)) {
405 disk_t *dp = dequeue_disk(origqp);
411 fprintf(stderr, "%s: time %s: setting up estimates took %s secs\n",
413 walltime_str(curclock()),
414 walltime_str(timessub(curclock(), section_start)));
418 * 5. Get Dump Size Estimates from Remote Client Hosts
420 * Each host is queried (in parallel) for dump size information on all
421 * of its disks, and the results gathered as they come in.
424 /* go out and get the dump estimates */
426 fprintf(stderr,"\nGETTING ESTIMATES...\n");
427 section_start = curclock();
429 estq.head = estq.tail = NULL;
430 pestq.head = pestq.tail = NULL;
431 waitq.head = waitq.tail = NULL;
432 failq.head = failq.tail = NULL;
436 fprintf(stderr, "%s: time %s: getting estimates took %s secs\n",
438 walltime_str(curclock()),
439 walltime_str(timessub(curclock(), section_start)));
442 * At this point, all disks with estimates are in estq, and
443 * all the disks on hosts that didn't respond to our inquiry
447 dump_queue("FAILED", failq, 15, stderr);
448 dump_queue("DONE", estq, 15, stderr);
452 * 6. Analyze Dump Estimates
454 * Each disk's estimates are looked at to determine what level it
455 * should dump at, and to calculate the expected size and time taking
456 * historical dump rates and compression ratios into account. The
457 * total expected size is accumulated as well.
460 fprintf(stderr,"\nANALYZING ESTIMATES...\n");
461 section_start = curclock();
463 /* an empty tape still has a label and an endmark */
464 total_size = (tt_blocksize_kb + tape_mark) * 2;
468 schedq.head = schedq.tail = NULL;
469 while(!empty(estq)) analyze_estimate(dequeue_disk(&estq));
470 while(!empty(failq)) handle_failed(dequeue_disk(&failq));
473 * At this point, all the disks are on schedq sorted by priority.
474 * The total estimated size of the backups is in total_size.
480 fprintf(stderr, "INITIAL SCHEDULE (size %ld):\n", total_size);
481 for(dp = schedq.head; dp != NULL; dp = dp->next) {
482 fprintf(stderr, " %s %s pri %d lev %d size %ld\n",
483 dp->host->hostname, dp->name, est(dp)->dump_priority,
484 est(dp)->dump_level, est(dp)->dump_size);
490 * 7. Delay Dumps if Schedule Too Big
492 * If the generated schedule is too big to fit on the tape, we need to
493 * delay some full dumps to make room. Incrementals will be done
494 * instead (except for new or forced disks).
496 * In extreme cases, delaying all the full dumps is not even enough.
497 * If so, some low-priority incrementals will be skipped completely
498 * until the dumps fit on the tape.
502 "\nDELAYING DUMPS IF NEEDED, total_size %ld, tape length %lu mark %lu\n",
503 total_size, tape_length, tape_mark);
505 initial_size = total_size;
509 /* XXX - why bother checking this? */
510 if(empty(schedq) && total_size < initial_size)
511 error("cannot fit anything on tape, bailing out");
515 * 8. Promote Dumps if Schedule Too Small
517 * Amanda attempts to balance the full dumps over the length of the
518 * dump cycle. If this night's full dumps are too small relative to
519 * the other nights, promote some high-priority full dumps that will be
520 * due for the next run, to full dumps for tonight, taking care not to
521 * overflow the tape size.
523 * This doesn't work too well for small sites. For these we scan ahead
524 * looking for nights that have an excessive number of dumps and promote
527 * Amanda never delays full dumps just for the sake of balancing the
528 * schedule, so it can take a full cycle to balance the schedule after
533 "\nPROMOTING DUMPS IF NEEDED, total_lev0 %1.0f, balanced_size %1.0f...\n",
534 total_lev0, balanced_size);
536 balance_threshold = balanced_size * PROMOTE_THRESHOLD;
538 while((balanced_size - total_lev0) > balance_threshold && moved_one)
539 moved_one = promote_highest_priority_incremental();
541 moved_one = promote_hills();
543 fprintf(stderr, "%s: time %s: analysis took %s secs\n",
545 walltime_str(curclock()),
546 walltime_str(timessub(curclock(), section_start)));
552 * The schedule goes to stdout, presumably to driver. A copy is written
553 * on stderr for the debug file.
556 fprintf(stderr,"\nGENERATING SCHEDULE:\n--------\n");
558 while(!empty(schedq)) output_scheduleline(dequeue_disk(&schedq));
559 fprintf(stderr, "--------\n");
562 log_add(L_FINISH, "date %s time %s", datestamp, walltime_str(curclock()));
568 amfree(our_feature_string);
569 am_release_feature_set(our_features);
572 malloc_size_2 = malloc_inuse(&malloc_hist_2);
574 if(malloc_size_1 != malloc_size_2) {
575 malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
584 * ========================================================================
585 * SETUP FOR ESTIMATES
589 static int last_level P((info_t *info)); /* subroutines */
590 static long est_size P((disk_t *dp, int level));
591 static long est_tape_size P((disk_t *dp, int level));
592 static int next_level0 P((disk_t *dp, info_t *info));
593 static int runs_at P((info_t *info, int lev));
594 static long bump_thresh P((int level, long size_level_0, int bumppercent, int bumpsize, double bumpmult));
595 static int when_overwrite P((char *label));
597 static void askfor(ep, seq, lev, info)
598 est_t *ep; /* esimate data block */
599 int seq; /* sequence number of request */
600 int lev; /* dump level being requested */
601 info_t *info; /* info block for disk */
603 if(seq < 0 || seq >= MAX_LEVELS) {
604 error("error [planner askfor: seq out of range 0..%d: %d]",
607 if(lev < -1 || lev >= DUMP_LEVELS) {
608 error("error [planner askfor: lev out of range -1..%d: %d]",
614 ep->dumpdate[seq] = (char *)0;
615 ep->est_size[seq] = -2;
619 ep->level[seq] = lev;
621 ep->dumpdate[seq] = stralloc(get_dumpdate(info,lev));
622 malloc_mark(ep->dumpdate[seq]);
624 ep->est_size[seq] = -2;
637 assert(dp && dp->host);
638 fprintf(stderr, "%s: time %s: setting up estimates for %s:%s\n",
639 get_pname(), walltime_str(curclock()),
640 dp->host->hostname, dp->name);
642 /* get current information about disk */
644 if(get_info(dp->host->hostname, dp->name, &info)) {
645 /* no record for this disk, make a note of it */
646 log_add(L_INFO, "Adding new disk %s:%s.", dp->host->hostname, dp->name);
649 /* setup working data struct for disk */
651 ep = alloc(sizeof(est_t));
653 dp->up = (void *) ep;
654 ep->state = DISK_READY;
656 ep->dump_priority = dp->priority;
660 /* calculated fields */
662 if(info.command & FORCE_FULL) {
663 /* force a level 0, kind of like a new disk */
664 if(dp->strategy == DS_NOFULL) {
666 * XXX - Not sure what it means to force a no-full disk. The
667 * purpose of no-full is to just dump changes relative to a
668 * stable base, for example root partitions that vary only
669 * slightly from a site-wide prototype. Only the variations
672 * If we allow a level 0 onto the Amanda cycle, then we are
673 * hosed when that tape gets re-used next. Disallow this for
677 "Cannot force full dump of %s:%s with no-full option.",
678 dp->host->hostname, dp->name);
680 /* clear force command */
681 if(info.command & FORCE_FULL)
682 info.command ^= FORCE_FULL;
683 if(put_info(dp->host->hostname, dp->name, &info))
684 error("could not put info record for %s:%s: %s",
685 dp->host->hostname, dp->name, strerror(errno));
686 ep->last_level = last_level(&info);
687 ep->next_level0 = next_level0(dp, &info);
691 ep->next_level0 = -conf_dumpcycle;
692 log_add(L_INFO, "Forcing full dump of %s:%s as directed.",
693 dp->host->hostname, dp->name);
696 else if(dp->strategy == DS_NOFULL) {
697 /* force estimate of level 1 */
699 ep->next_level0 = next_level0(dp, &info);
702 ep->last_level = last_level(&info);
703 ep->next_level0 = next_level0(dp, &info);
706 /* adjust priority levels */
708 if(ep->next_level0 < 0) {
709 fprintf(stderr,"%s:%s overdue %d day%s for level 0\n",
710 dp->host->hostname, dp->name,
711 - ep->next_level0, ((- ep->next_level0) == 1) ? "" : "s");
712 ep->dump_priority -= ep->next_level0;
713 /* warn if dump will be overwritten */
714 if(ep->last_level > -1) {
715 int overwrite_runs = when_overwrite(info.inf[0].label);
716 if(overwrite_runs == 0) {
718 "Last full dump of %s:%s on tape %s overwritten on this run.",
719 dp->host->hostname, dp->name, info.inf[0].label);
721 else if(overwrite_runs < RUNS_REDZONE) {
723 "Last full dump of %s:%s on tape %s overwritten in %d run%s.",
724 dp->host->hostname, dp->name, info.inf[0].label,
725 overwrite_runs, overwrite_runs == 1? "" : "s");
729 else if(info.command & FORCE_FULL)
730 ep->dump_priority += 1;
731 /* else XXX bump up the priority of incrementals that failed last night */
733 /* handle external level 0 dumps */
735 if(dp->skip_full && dp->strategy != DS_NOINC) {
736 if(ep->next_level0 <= 0) {
737 /* update the date field */
738 info.inf[0].date = today;
739 if(info.command & FORCE_FULL)
740 info.command ^= FORCE_FULL;
741 ep->next_level0 += conf_dumpcycle;
743 if(put_info(dp->host->hostname, dp->name, &info))
744 error("could not put info record for %s:%s: %s",
745 dp->host->hostname, dp->name, strerror(errno));
746 log_add(L_INFO, "Skipping full dump of %s:%s today.",
747 dp->host->hostname, dp->name);
748 fprintf(stderr,"%s:%s lev 0 skipped due to skip-full flag\n",
749 dp->host->hostname, dp->name);
750 /* don't enqueue the disk */
751 askfor(ep, 0, -1, &info);
752 askfor(ep, 1, -1, &info);
753 askfor(ep, 2, -1, &info);
754 fprintf(stderr, "%s: SKIPPED %s %s 0 [skip-full]\n",
755 get_pname(), dp->host->hostname, dp->name);
756 log_add(L_SUCCESS, "%s %s %s 0 [skipped: skip-full]",
757 dp->host->hostname, dp->name, datestamp);
761 if(ep->last_level == -1) {
762 /* probably a new disk, but skip-full means no full! */
766 if(ep->next_level0 == 1) {
767 log_add(L_WARNING, "Skipping full dump of %s:%s tomorrow.",
768 dp->host->hostname, dp->name);
772 /* handle "skip-incr" type archives */
774 if(dp->skip_incr && ep->next_level0 > 0) {
775 fprintf(stderr,"%s:%s lev 1 skipped due to skip-incr flag\n",
776 dp->host->hostname, dp->name);
777 /* don't enqueue the disk */
778 askfor(ep, 0, -1, &info);
779 askfor(ep, 1, -1, &info);
780 askfor(ep, 2, -1, &info);
782 fprintf(stderr, "%s: SKIPPED %s %s 1 [skip-incr]\n",
783 get_pname(), dp->host->hostname, dp->name);
785 log_add(L_SUCCESS, "%s %s %s 1 [skipped: skip-incr]",
786 dp->host->hostname, dp->name, datestamp);
790 if( ep->last_level == -1 && ep->next_level0 > 0 &&
791 dp->strategy != DS_NOFULL && dp->strategy != DS_INCRONLY &&
792 conf_reserve == 100) {
794 "%s:%s mismatch: no tapelist record, but curinfo next_level0: %d.",
795 dp->host->hostname, dp->name, ep->next_level0);
799 if(ep->last_level == 0) ep->level_days = 0;
800 else ep->level_days = runs_at(&info, ep->last_level);
801 ep->last_lev0size = info.inf[0].csize;
803 ep->fullrate = perf_average(info.full.rate, 0.0);
804 ep->incrrate = perf_average(info.incr.rate, 0.0);
806 ep->fullcomp = perf_average(info.full.comp, dp->comprate[0]);
807 ep->incrcomp = perf_average(info.incr.comp, dp->comprate[1]);
809 /* determine which estimates to get */
813 if(dp->strategy == DS_NOINC ||
815 (!(info.command & FORCE_BUMP) ||
817 ep->last_level == -1))){
819 if(info.command & FORCE_BUMP && ep->last_level == -1) {
821 "Remove force-bump command of %s:%s because it's a new disk.",
822 dp->host->hostname, dp->name);
824 switch (dp->strategy) {
827 askfor(ep, i++, 0, &info);
830 "Ignoring skip_full for %s:%s because the strategy is NOINC.",
831 dp->host->hostname, dp->name);
833 if(info.command & FORCE_BUMP) {
835 "Ignoring FORCE_BUMP for %s:%s because the strategy is NOINC.",
836 dp->host->hostname, dp->name);
845 if (info.command & FORCE_FULL)
846 askfor(ep, i++, 0, &info);
851 if(!dp->skip_incr && !(dp->strategy == DS_NOINC)) {
852 if(ep->last_level == -1) { /* a new disk */
853 if(dp->strategy == DS_NOFULL || dp->strategy == DS_INCRONLY) {
854 askfor(ep, i++, 1, &info);
856 assert(!dp->skip_full); /* should be handled above */
858 } else { /* not new, pick normally */
861 curr_level = ep->last_level;
863 if(info.command & FORCE_NO_BUMP) {
864 if(curr_level > 0) { /* level 0 already asked for */
865 askfor(ep, i++, curr_level, &info);
867 log_add(L_INFO,"Preventing bump of %s:%s as directed.",
868 dp->host->hostname, dp->name);
870 else if((info.command & FORCE_BUMP)
871 && curr_level + 1 < DUMP_LEVELS) {
872 askfor(ep, i++, curr_level+1, &info);
873 log_add(L_INFO,"Bumping of %s:%s at level %d as directed.",
874 dp->host->hostname, dp->name, curr_level+1);
876 else if(curr_level == 0) {
877 askfor(ep, i++, 1, &info);
880 askfor(ep, i++, curr_level, &info);
882 * If last time we dumped less than the threshold, then this
883 * time we will too, OR the extra size will be charged to both
884 * cur_level and cur_level + 1, so we will never bump. Also,
885 * if we haven't been at this level 2 days, or the dump failed
886 * last night, we can't bump.
888 if((info.inf[curr_level].size == 0 || /* no data, try it anyway */
889 (((info.inf[curr_level].size > bump_thresh(curr_level, info.inf[0].size,dp->bumppercent, dp->bumpsize, dp->bumpmult)))
890 && ep->level_days >= dp->bumpdays))
891 && curr_level + 1 < DUMP_LEVELS) {
892 askfor(ep, i++, curr_level+1, &info);
898 while(i < MAX_LEVELS) /* mark end of estimates */
899 askfor(ep, i++, -1, &info);
903 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",
904 dp->host->hostname, dp->name, info.command,
905 dp->strategy == DS_NOFULL ? "no-full" :
906 dp->strategy == DS_INCRONLY ? "incr-only" :
907 dp->skip_full ? "skip-full" :
908 dp->skip_incr ? "skip-incr" : "none",
909 ep->last_level, ep->next_level0, ep->level_days,
910 ep->level[0], ep->est_size[0],
911 ep->level[1], ep->est_size[1],
912 ep->level[2], ep->est_size[2]);
914 assert(ep->level[0] != -1);
915 enqueue_disk(&startq, dp);
918 static int when_overwrite(label)
923 if((tp = lookup_tapelabel(label)) == NULL)
924 return 1; /* "shouldn't happen", but trigger warning message */
925 else if(!reusable_tape(tp))
927 else if(lookup_nb_tape() > conf_tapecycle)
928 return (lookup_nb_tape() - tp->position) / conf_runtapes;
930 return (conf_tapecycle - tp->position) / conf_runtapes;
933 /* Return the estimated size for a particular dump */
934 static long est_size(dp, level)
940 for(i = 0; i < MAX_LEVELS; i++) {
941 if(level == est(dp)->level[i])
942 return est(dp)->est_size[i];
947 /* Return the estimated on-tape size of a particular dump */
948 static long est_tape_size(dp, level)
955 size = est_size(dp, level);
957 if(size == -1) return size;
959 if(dp->compress == COMP_NONE)
962 if(level == 0) ratio = est(dp)->fullcomp;
963 else ratio = est(dp)->incrcomp;
966 * make sure over-inflated compression ratios don't throw off the
967 * estimates, this is mostly for when you have a small dump getting
968 * compressed which takes up alot more disk/tape space relatively due
969 * to the overhead of the compression. This is specifically for
970 * Digital Unix vdump. This patch is courtesy of Rudolf Gabler
971 * (RUG@USM.Uni-Muenchen.DE)
974 if(ratio > 1.1) ratio = 1.1;
979 * Ratio can be very small in some error situations, so make sure
980 * size goes back greater than zero. It may not be right, but
981 * indicates we did get an estimate.
991 /* what was the level of the last successful dump to tape? */
992 static int last_level(info)
995 int min_pos, min_level, i;
996 time_t lev0_date, last_date;
999 if(info->last_level != -1)
1000 return info->last_level;
1002 /* to keep compatibility with old infofile */
1003 min_pos = 1000000000;
1007 for(i = 0; i < 9; i++) {
1008 if(conf_reserve < 100) {
1009 if(i == 0) lev0_date = info->inf[0].date;
1010 else if(info->inf[i].date < lev0_date) continue;
1011 if(info->inf[i].date > last_date) {
1012 last_date = info->inf[i].date;
1017 if((tp = lookup_tapelabel(info->inf[i].label)) == NULL) continue;
1018 /* cull any entries from previous cycles */
1019 if(i == 0) lev0_date = info->inf[0].date;
1020 else if(info->inf[i].date < lev0_date) continue;
1022 if(tp->position < min_pos) {
1023 min_pos = tp->position;
1028 info->last_level = i;
1032 /* when is next level 0 due? 0 = today, 1 = tomorrow, etc*/
1034 next_level0(dp, info)
1038 if(dp->strategy == DS_NOFULL || dp->strategy == DS_INCRONLY)
1039 return 1; /* fake it */
1040 else if (dp->strategy == DS_NOINC)
1042 else if(info->inf[0].date < (time_t)0)
1043 return -days_diff(EPOCH, today); /* new disk */
1045 return dp->dumpcycle - days_diff(info->inf[0].date, today);
1048 /* how many runs at current level? */
1049 static int runs_at(info, lev)
1053 tape_t *cur_tape, *old_tape;
1056 last = last_level(info);
1057 if(lev != last) return 0;
1058 if(lev == 0) return 1;
1060 if(info->consecutive_runs != -1)
1061 return info->consecutive_runs;
1063 /* to keep compatibility with old infofile */
1064 cur_tape = lookup_tapelabel(info->inf[lev].label);
1065 old_tape = lookup_tapelabel(info->inf[lev-1].label);
1066 if(cur_tape == NULL || old_tape == NULL) return 0;
1068 nb_runs = (old_tape->position - cur_tape->position) / conf_runtapes;
1069 info->consecutive_runs = nb_runs;
1075 static long bump_thresh(level, size_level_0, bumppercent, bumpsize, bumpmult)
1084 if(bumppercent != 0 && size_level_0 > 1024) {
1085 bump = (size_level_0 * bumppercent)/100.0;
1090 while(--level) bump = bump * bumpmult;
1098 * ========================================================================
1099 * GET REMOTE DUMP SIZE ESTIMATES
1103 static void getsize P((am_host_t *hostp));
1104 static disk_t *lookup_hostdisk P((am_host_t *hp, char *str));
1105 static void handle_result P((proto_t *p, pkt_t *pkt));
1108 static void get_estimates P((void))
1112 struct servent *amandad;
1113 int something_started;
1115 if((amandad = getservbyname(AMANDA_SERVICE_NAME, "udp")) == NULL)
1116 amanda_port = AMANDA_SERVICE_DEFAULT;
1118 amanda_port = ntohs(amandad->s_port);
1120 #ifdef KRB4_SECURITY
1121 if((amandad = getservbyname(KAMANDA_SERVICE_NAME, "udp")) == NULL)
1122 kamanda_port = KAMANDA_SERVICE_DEFAULT;
1124 kamanda_port = ntohs(amandad->s_port);
1127 something_started = 1;
1128 while(something_started) {
1129 something_started = 0;
1130 for(dp = startq.head; dp != NULL; dp = dp->next) {
1132 if(hostp->up == HOST_READY) {
1133 something_started = 1;
1137 * dp is no longer on startq, so dp->next is not valid
1138 * and we have to start all over.
1146 while(!empty(waitq)) {
1147 disk_t *dp = dequeue_disk(&waitq);
1148 est(dp)->errstr = "hmm, disk was stranded on waitq";
1149 enqueue_disk(&failq, dp);
1152 while(!empty(pestq)) {
1153 disk_t *dp = dequeue_disk(&pestq);
1155 if(est(dp)->level[0] != -1 && est(dp)->est_size[0] < 0) {
1156 if(est(dp)->est_size[0] == -1) {
1158 "disk %s:%s, estimate of level %d failed: %lu.",
1159 dp->host->hostname, dp->name,
1160 est(dp)->level[0], est(dp)->est_size[0]);
1164 "disk %s:%s, estimate of level %d timed out: %lu.",
1165 dp->host->hostname, dp->name,
1166 est(dp)->level[0], est(dp)->est_size[0]);
1168 est(dp)->level[0] = -1;
1171 if(est(dp)->level[1] != -1 && est(dp)->est_size[1] < 0) {
1172 if(est(dp)->est_size[1] == -1) {
1174 "disk %s:%s, estimate of level %d failed: %lu.",
1175 dp->host->hostname, dp->name,
1176 est(dp)->level[1], est(dp)->est_size[1]);
1180 "disk %s:%s, estimate of level %d timed out: %lu.",
1181 dp->host->hostname, dp->name,
1182 est(dp)->level[1], est(dp)->est_size[1]);
1184 est(dp)->level[1] = -1;
1187 if(est(dp)->level[2] != -1 && est(dp)->est_size[2] < 0) {
1188 if(est(dp)->est_size[2] == -1) {
1190 "disk %s:%s, estimate of level %d failed: %lu.",
1191 dp->host->hostname, dp->name,
1192 est(dp)->level[2], est(dp)->est_size[2]);
1196 "disk %s:%s, estimate of level %d timed out: %lu.",
1197 dp->host->hostname, dp->name,
1198 est(dp)->level[2], est(dp)->est_size[2]);
1200 est(dp)->level[2] = -1;
1203 if((est(dp)->level[0] != -1 && est(dp)->est_size[0] > 0) ||
1204 (est(dp)->level[1] != -1 && est(dp)->est_size[1] > 0) ||
1205 (est(dp)->level[2] != -1 && est(dp)->est_size[2] > 0)) {
1206 enqueue_disk(&estq, dp);
1209 est(dp)->errstr = vstralloc("disk ", dp->name,
1210 ", all estimate timed out", NULL);
1211 enqueue_disk(&failq, dp);
1216 static void getsize(hostp)
1221 char *req = NULL, *errstr = NULL;
1222 int i, estimates, rc, timeout, disk_state, req_len;
1223 char number[NUM_STR_SIZE];
1226 assert(hostp->disks != NULL);
1228 if(hostp->up != HOST_READY) {
1233 * The first time through here we send a "noop" request. This will
1234 * return the feature list from the client if it supports that.
1235 * If it does not, handle_result() will set the feature list to an
1236 * empty structure. In either case, we do the disks on the second
1237 * (and subsequent) pass(es).
1240 if(hostp->features != NULL) { /* sendsize service */
1244 int has_features = am_has_feature(hostp->features,
1245 fe_req_options_features);
1246 int has_hostname = am_has_feature(hostp->features,
1247 fe_req_options_hostname);
1248 int has_maxdumps = am_has_feature(hostp->features,
1249 fe_req_options_maxdumps);
1251 ap_snprintf(number, sizeof(number), "%d", hostp->maxdumps);
1252 req = vstralloc("SERVICE ", "sendsize", "\n",
1254 has_features ? "features=" : "",
1255 has_features ? our_feature_string : "",
1256 has_features ? ";" : "",
1257 has_maxdumps ? "maxdumps=" : "",
1258 has_maxdumps ? number : "",
1259 has_maxdumps ? ";" : "",
1260 has_hostname ? "hostname=" : "",
1261 has_hostname ? hostp->hostname : "",
1262 has_hostname ? ";" : "",
1265 req_len = strlen(req);
1266 req_len += 128; /* room for SECURITY ... */
1268 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1272 if(dp->todo == 0) continue;
1274 if(est(dp)->state != DISK_READY) {
1278 est(dp)->got_estimate = 0;
1279 if(est(dp)->level[0] == -1) {
1280 est(dp)->state = DISK_DONE;
1281 continue; /* ignore this disk */
1284 if(dp->estimate == ES_CLIENT ||
1285 dp->estimate == ES_CALCSIZE) {
1288 for(i = 0; i < MAX_LEVELS; i++) {
1290 char *exclude1 = "";
1291 char *exclude2 = "";
1292 char *excludefree = NULL;
1293 char spindle[NUM_STR_SIZE];
1294 char level[NUM_STR_SIZE];
1295 int lev = est(dp)->level[i];
1297 if(lev == -1) break;
1299 ap_snprintf(level, sizeof(level), "%d", lev);
1300 ap_snprintf(spindle, sizeof(spindle), "%d", dp->spindle);
1301 if(am_has_feature(hostp->features,fe_sendsize_req_options)){
1302 exclude1 = " OPTIONS |";
1303 exclude2 = optionstr(dp, hostp->features, NULL);
1304 excludefree = exclude2;
1307 if(dp->exclude_file &&
1308 dp->exclude_file->nb_element == 1) {
1309 exclude1 = " exclude-file=";
1310 exclude2 = dp->exclude_file->first->name;
1312 else if(dp->exclude_list &&
1313 dp->exclude_list->nb_element == 1) {
1314 exclude1 = " exclude-list=";
1315 exclude2 = dp->exclude_list->first->name;
1318 if(dp->estimate == ES_CALCSIZE &&
1319 !am_has_feature(hostp->features, fe_calcsize_estimate)) {
1320 log_add(L_WARNING,"%s:%s does not support CALCSIZE for estimate, using CLIENT.\n",
1321 hostp->hostname, dp->name);
1322 dp->estimate = ES_CLIENT;
1324 if(dp->estimate == ES_CLIENT)
1327 calcsize = "CALCSIZE ";
1330 l = vstralloc(calcsize,
1335 est(dp)->dumpdate[i], " ", spindle,
1342 l = vstralloc(calcsize,
1346 est(dp)->dumpdate[i], " ", spindle,
1352 amfree(excludefree);
1358 * Allow 2X for err response.
1360 if(req_len + s_len > MAX_DGRAM / 2) {
1368 est(dp)->state = DISK_ACTIVE;
1369 remove_disk(&startq, dp);
1371 else if (dp->estimate == ES_SERVER) {
1375 get_info(dp->host->hostname, dp->name, &info);
1376 for(i = 0; i < MAX_LEVELS; i++) {
1378 int lev = est(dp)->level[i];
1380 if(lev == -1) break;
1381 if(lev == 0) { /* use latest level 0, should do extrapolation */
1385 for(j=NB_HISTORY-2;j>=0;j--) {
1386 if(info.history[j].level == 0) {
1387 if(info.history[j].size < 0) continue;
1388 est_size = info.history[j].size;
1393 est(dp)->est_size[i] = est_size;
1395 else if(info.inf[lev].size > 1000) { /* stats */
1396 est(dp)->est_size[i] = info.inf[lev].size;
1399 est(dp)->est_size[i] = 1000000;
1402 else if(lev == est(dp)->last_level) {
1403 /* means of all X day at the same level */
1406 long est_size_day[NB_DAY];
1407 int nb_est_day[NB_DAY];
1409 for(j=0;j<NB_DAY;j++) {
1414 for(j=NB_HISTORY-2;j>=0;j--) {
1415 if(info.history[j].level <= 0) continue;
1416 if(info.history[j].size < 0) continue;
1417 if(info.history[j].level == info.history[j+1].level) {
1418 if(nb_day <NB_DAY-1) nb_day++;
1419 est_size_day[nb_day] += info.history[j].size;
1420 nb_est_day[nb_day]++;
1426 nb_day = info.consecutive_runs + 1;
1427 if(nb_day > NB_DAY-1) nb_day = NB_DAY-1;
1429 while(nb_day > 0 && nb_est_day[nb_day] == 0) nb_day--;
1431 if(nb_est_day[nb_day] > 0) {
1432 est(dp)->est_size[i] =
1433 est_size_day[nb_day] / nb_est_day[nb_day];
1435 else if(info.inf[lev].size > 1000) { /* stats */
1436 est(dp)->est_size[i] = info.inf[lev].size;
1439 est(dp)->est_size[i] = 10000;
1442 else if(lev == est(dp)->last_level + 1) {
1443 /* means of all first day at a new level */
1447 for(j=NB_HISTORY-2;j>=0;j--) {
1448 if(info.history[j].level <= 0) continue;
1449 if(info.history[j].size < 0) continue;
1450 if(info.history[j].level == info.history[j+1].level + 1 ) {
1451 est_size += info.history[j].size;
1456 est(dp)->est_size[i] = est_size / nb_est;
1458 else if(info.inf[lev].size > 1000) { /* stats */
1459 est(dp)->est_size[i] = info.inf[lev].size;
1462 est(dp)->est_size[i] = 100000;
1466 fprintf(stderr,"%s time %s: got result for host %s disk %s:",
1467 get_pname(), walltime_str(curclock()),
1468 dp->host->hostname, dp->name);
1469 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1470 est(dp)->level[0], est(dp)->est_size[0],
1471 est(dp)->level[1], est(dp)->est_size[1],
1472 est(dp)->level[2], est(dp)->est_size[2]);
1473 est(dp)->state = DISK_DONE;
1474 remove_disk(&startq, dp);
1475 enqueue_disk(&estq, dp);
1478 if(estimates == 0) {
1480 hostp->up = HOST_DONE;
1484 if (conf_etimeout < 0) {
1485 timeout = - conf_etimeout;
1487 timeout = estimates * conf_etimeout;
1489 } else { /* noop service */
1490 req = vstralloc("SERVICE ", "noop", "\n",
1492 "features=", our_feature_string, ";",
1496 * We use ctimeout for the "noop" request because it should be
1497 * very fast and etimeout has other side effects.
1499 timeout = getconf_int(CNF_CTIMEOUT);
1502 #ifdef KRB4_SECURITY
1503 if(hostp->disks->auth == AUTH_KRB4)
1504 rc = make_krb_request(hostp->hostname, kamanda_port, req,
1505 hostp, timeout, handle_result);
1508 rc = make_request(hostp->hostname, amanda_port, req,
1509 hostp, timeout, handle_result);
1511 req = NULL; /* do not own this any more */
1514 errstr = vstralloc("could not resolve hostname \"",
1519 hostp->up = HOST_DONE;
1520 disk_state = DISK_DONE;
1525 hostp->up = HOST_ACTIVE;
1526 disk_state = DISK_ACTIVE;
1529 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1530 if(dp->todo && est(dp)->state == DISK_ACTIVE) {
1531 est(dp)->state = disk_state;
1532 est(dp)->errstr = errstr;
1534 enqueue_disk(destqp, dp);
1540 static disk_t *lookup_hostdisk(hp, str)
1546 for(dp = hp->disks; dp != NULL; dp = dp->hostnext)
1547 if(strcmp(str, dp->name) == 0) return dp;
1553 static void handle_result(p, pkt)
1561 char *msgdisk=NULL, *msgdisk_undo=NULL, msgdisk_undo_ch = '\0';
1562 char *errbuf = NULL;
1570 hostp = (am_host_t *) p->datap;
1571 hostp->up = HOST_READY;
1573 if(p->state == S_FAILED && pkt == NULL) {
1574 if(p->prevstate == S_REPWAIT) {
1575 errbuf = vstralloc("Estimate timeout from ", hostp->hostname,
1579 errbuf = vstralloc("Request to ", hostp->hostname, " timed out.",
1586 fprintf(stderr, "got %sresponse from %s:\n----\n%s----\n\n",
1587 (p->state == S_FAILED) ? "NAK " : "", hostp->hostname, pkt->body);
1590 #ifdef KRB4_SECURITY
1591 if(hostp->disks->auth == AUTH_KRB4 &&
1592 !check_mutual_authenticator(host2key(hostp->hostname), pkt, p)) {
1593 errbuf = vstralloc(hostp->hostname,
1594 "[mutual-authentication failed]",
1600 msgdisk_undo = NULL;
1606 if (s[-2] == '\n') {
1610 #define sc "OPTIONS "
1611 if(strncmp(line, sc, sizeof(sc)-1) == 0) {
1614 #define sc "features="
1615 t = strstr(line, sc);
1616 if(t != NULL && (isspace((int)t[-1]) || t[-1] == ';')) {
1619 am_release_feature_set(hostp->features);
1620 if((hostp->features = am_string_to_feature(t)) == NULL) {
1621 errbuf = vstralloc(hostp->hostname,
1622 ": bad features value: ",
1634 if(strncmp(line, sc, sizeof(sc)-1) == 0) {
1635 t = line + sizeof(sc)-1;
1640 skip_whitespace(t, tch);
1645 * If the "error" is that the "noop" service is unknown, it
1646 * just means the client is "old" (does not support the servie).
1647 * We can ignore this.
1649 if(hostp->features == NULL
1650 && p->state == S_FAILED
1651 && (strcmp(t - 1, "unknown service: noop") == 0
1652 || strcmp(t - 1, "noop: invalid service") == 0)) {
1655 errbuf = vstralloc(hostp->hostname,
1656 (p->state == S_FAILED) ? "NAK " : "",
1666 skip_non_whitespace(t, tch);
1667 msgdisk_undo = t - 1;
1668 msgdisk_undo_ch = *msgdisk_undo;
1669 *msgdisk_undo = '\0';
1671 skip_whitespace(t, tch);
1672 if (sscanf(t - 1, "%d SIZE %ld", &level, &size) != 2) {
1676 dp = lookup_hostdisk(hostp, msgdisk);
1678 *msgdisk_undo = msgdisk_undo_ch; /* for error message */
1679 msgdisk_undo = NULL;
1682 log_add(L_ERROR, "%s: invalid reply from sendsize: `%s'\n",
1683 hostp->hostname, line);
1685 for(i = 0; i < MAX_LEVELS; i++) {
1686 if(est(dp)->level[i] == level) {
1687 est(dp)->est_size[i] = size;
1691 if(i == MAX_LEVELS) {
1692 goto bad_msg; /* this est wasn't requested */
1694 est(dp)->got_estimate++;
1698 if(hostp->up == HOST_READY && hostp->features == NULL) {
1700 * The client does not support the features list, so give it an
1703 dbprintf(("%s: no feature set from host %s\n",
1704 debug_prefix_time(NULL), hostp->hostname));
1705 hostp->features = am_set_default_feature_set();
1708 /* XXX what about disks that only got some estimates... do we care? */
1709 /* XXX amanda 2.1 treated that case as a bad msg */
1711 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1712 if(dp->todo == 0) continue;
1713 if(est(dp)->state != DISK_ACTIVE &&
1714 est(dp)->state != DISK_PARTIALY_DONE) continue;
1716 if(est(dp)->state == DISK_ACTIVE) {
1717 remove_disk(&waitq, dp);
1719 else if(est(dp)->state == DISK_PARTIALY_DONE) {
1720 remove_disk(&pestq, dp);
1723 if(pkt->type == P_REP) {
1724 est(dp)->state = DISK_DONE;
1726 else if(pkt->type == P_PREP) {
1727 est(dp)->state = DISK_PARTIALY_DONE;
1730 if(est(dp)->level[0] == -1) continue; /* ignore this disk */
1733 if(pkt->type == P_PREP) {
1734 fprintf(stderr,"%s: time %s: got partial result for host %s disk %s:",
1735 get_pname(), walltime_str(curclock()),
1736 dp->host->hostname, dp->name);
1737 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1738 est(dp)->level[0], est(dp)->est_size[0],
1739 est(dp)->level[1], est(dp)->est_size[1],
1740 est(dp)->level[2], est(dp)->est_size[2]);
1741 enqueue_disk(&pestq, dp);
1743 else if(pkt->type == P_REP) {
1744 fprintf(stderr,"%s: time %s: got result for host %s disk %s:",
1745 get_pname(), walltime_str(curclock()),
1746 dp->host->hostname, dp->name);
1747 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1748 est(dp)->level[0], est(dp)->est_size[0],
1749 est(dp)->level[1], est(dp)->est_size[1],
1750 est(dp)->level[2], est(dp)->est_size[2]);
1751 if((est(dp)->level[0] != -1 && est(dp)->est_size[0] > 0) ||
1752 (est(dp)->level[1] != -1 && est(dp)->est_size[1] > 0) ||
1753 (est(dp)->level[2] != -1 && est(dp)->est_size[2] > 0)) {
1755 if(est(dp)->level[2] != -1 && est(dp)->est_size[2] < 0) {
1757 "disk %s:%s, estimate of level %d failed: %lu.",
1758 dp->host->hostname, dp->name,
1759 est(dp)->level[2], est(dp)->est_size[2]);
1760 est(dp)->level[2] = -1;
1762 if(est(dp)->level[1] != -1 && est(dp)->est_size[1] < 0) {
1764 "disk %s:%s, estimate of level %d failed: %lu.",
1765 dp->host->hostname, dp->name,
1766 est(dp)->level[1], est(dp)->est_size[1]);
1767 est(dp)->level[1] = -1;
1769 if(est(dp)->level[0] != -1 && est(dp)->est_size[0] < 0) {
1771 "disk %s:%s, estimate of level %d failed: %lu.",
1772 dp->host->hostname, dp->name,
1773 est(dp)->level[0], est(dp)->est_size[0]);
1774 est(dp)->level[0] = -1;
1776 enqueue_disk(&estq, dp);
1779 enqueue_disk(&failq, dp);
1780 if(est(dp)->got_estimate) {
1781 est(dp)->errstr = vstralloc("disk ", dp->name,
1782 ", all estimate failed", NULL);
1785 fprintf(stderr, "error result for host %s disk %s: missing estimate\n",
1786 dp->host->hostname, dp->name);
1787 est(dp)->errstr = vstralloc("missing result for ", dp->name,
1788 " in ", dp->host->hostname,
1801 *msgdisk_undo = msgdisk_undo_ch;
1802 msgdisk_undo = NULL;
1804 fprintf(stderr,"got a bad message, stopped at:\n");
1805 fprintf(stderr,"----\n%s\n----\n\n", line);
1806 errbuf = stralloc2("badly formatted response from ", hostp->hostname);
1807 /* fall through to ... */
1812 *msgdisk_undo = msgdisk_undo_ch;
1813 msgdisk_undo = NULL;
1816 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1817 if(est(dp)->state == DISK_ACTIVE) {
1818 est(dp)->state = DISK_DONE;
1819 remove_disk(&waitq, dp);
1820 enqueue_disk(&failq, dp);
1823 est(dp)->errstr = stralloc(errbuf);
1824 fprintf(stderr, "%s: time %s: error result for host %s disk %s: %s\n",
1825 get_pname(), walltime_str(curclock()),
1826 dp->host->hostname, dp->name, errbuf);
1831 * If there were no disks involved, make sure the error gets
1834 log_add(L_ERROR, "%s", errbuf);
1836 hostp->up = HOST_DONE;
1844 * ========================================================================
1849 static int schedule_order P((disk_t *a, disk_t *b)); /* subroutines */
1850 static int pick_inclevel P((disk_t *dp));
1852 static void analyze_estimate(dp)
1861 fprintf(stderr, "pondering %s:%s... ",
1862 dp->host->hostname, dp->name);
1863 fprintf(stderr, "next_level0 %d last_level %d ",
1864 ep->next_level0, ep->last_level);
1866 if(get_info(dp->host->hostname, dp->name, &info) == 0) {
1870 ep->degr_level = -1;
1873 if(ep->next_level0 <= 0
1874 || (have_info && ep->last_level == 0 && (info.command & FORCE_NO_BUMP))) {
1875 if(ep->next_level0 <= 0) {
1876 fprintf(stderr,"(due for level 0) ");
1879 ep->dump_size = est_tape_size(dp, 0);
1880 if(ep->dump_size <= 0) {
1882 "(no estimate for level 0, picking an incr level)\n");
1883 ep->dump_level = pick_inclevel(dp);
1884 ep->dump_size = est_tape_size(dp, ep->dump_level);
1886 if(ep->dump_size == -1) {
1887 ep->dump_level = ep->dump_level + 1;
1888 ep->dump_size = est_tape_size(dp, ep->dump_level);
1892 total_lev0 += (double) ep->dump_size;
1893 if(ep->last_level == -1 || dp->skip_incr) {
1894 fprintf(stderr,"(%s disk, can't switch to degraded mode)\n",
1895 dp->skip_incr? "skip-incr":"new");
1896 ep->degr_level = -1;
1900 /* fill in degraded mode info */
1901 fprintf(stderr,"(picking inclevel for degraded mode)");
1902 ep->degr_level = pick_inclevel(dp);
1903 ep->degr_size = est_tape_size(dp, ep->degr_level);
1904 if(ep->degr_size == -1) {
1905 ep->degr_level = ep->degr_level + 1;
1906 ep->degr_size = est_tape_size(dp, ep->degr_level);
1908 if(ep->degr_size == -1) {
1909 fprintf(stderr,"(no inc estimate)");
1910 ep->degr_level = -1;
1912 fprintf(stderr,"\n");
1917 fprintf(stderr,"(not due for a full dump, picking an incr level)\n");
1918 /* XXX - if this returns -1 may be we should force a total? */
1919 ep->dump_level = pick_inclevel(dp);
1920 ep->dump_size = est_tape_size(dp, ep->dump_level);
1922 if(ep->dump_size == -1) {
1923 ep->dump_level = ep->last_level;
1924 ep->dump_size = est_tape_size(dp, ep->dump_level);
1926 if(ep->dump_size == -1) {
1927 ep->dump_level = ep->last_level + 1;
1928 ep->dump_size = est_tape_size(dp, ep->dump_level);
1930 if(ep->dump_size == -1) {
1932 ep->dump_size = est_tape_size(dp, ep->dump_level);
1936 fprintf(stderr," curr level %d size %ld ", ep->dump_level, ep->dump_size);
1938 insert_disk(&schedq, dp, schedule_order);
1940 total_size += tt_blocksize_kb + ep->dump_size + tape_mark;
1942 /* update the balanced size */
1943 if(!(dp->skip_full || dp->strategy == DS_NOFULL ||
1944 dp->strategy == DS_INCRONLY)) {
1947 lev0size = est_tape_size(dp, 0);
1948 if(lev0size == -1) lev0size = ep->last_lev0size;
1950 balanced_size += lev0size / runs_per_cycle;
1953 fprintf(stderr,"total size %ld total_lev0 %1.0f balanced-lev0size %1.0f\n",
1954 total_size, total_lev0, balanced_size);
1957 static void handle_failed(dp)
1963 * From George Scott <George.Scott@cc.monash.edu.au>:
1965 * If a machine is down when the planner is run it guesses from historical
1966 * data what the size of tonights dump is likely to be and schedules a
1967 * dump anyway. The dumper then usually discovers that that machine is
1968 * still down and ends up with a half full tape. Unfortunately the
1969 * planner had to delay another dump because it thought that the tape was
1970 * full. The fix here is for the planner to ignore unavailable machines
1971 * rather than ignore the fact that they are unavailable.
1976 if(est(dp)->last_level != -1) {
1978 "Could not get estimate for %s:%s, using historical data.",
1979 dp->host->hostname, dp->name);
1980 analyze_estimate(dp);
1985 errstr = est(dp)->errstr? est(dp)->errstr : "hmm, no error indicator!";
1987 fprintf(stderr, "%s: FAILED %s %s %s 0 [%s]\n",
1988 get_pname(), dp->host->hostname, dp->name, datestamp, errstr);
1990 log_add(L_FAIL, "%s %s %s 0 [%s]", dp->host->hostname, dp->name, datestamp, errstr);
1992 /* XXX - memory leak with *dp */
1996 static int schedule_order(a, b)
1999 * insert-sort by decreasing priority, then
2000 * by decreasing size within priority levels.
2006 diff = est(b)->dump_priority - est(a)->dump_priority;
2007 if(diff != 0) return diff;
2009 ldiff = est(b)->dump_size - est(a)->dump_size;
2010 if(ldiff < 0) return -1; /* XXX - there has to be a better way to dothis */
2011 if(ldiff > 0) return 1;
2016 static int pick_inclevel(dp)
2019 int base_level, bump_level;
2020 long base_size, bump_size;
2023 base_level = est(dp)->last_level;
2025 /* if last night was level 0, do level 1 tonight, no ifs or buts */
2026 if(base_level == 0) {
2027 fprintf(stderr," picklev: last night 0, so tonight level 1\n");
2031 /* if no-full option set, always do level 1 */
2032 if(dp->strategy == DS_NOFULL) {
2033 fprintf(stderr," picklev: no-full set, so always level 1\n");
2037 base_size = est_size(dp, base_level);
2039 /* if we didn't get an estimate, we can't do an inc */
2040 if(base_size == -1) {
2041 base_size = est_size(dp, base_level+1);
2042 if(base_size > 0) /* FORCE_BUMP */
2043 return base_level+1;
2044 fprintf(stderr," picklev: no estimate for level %d, so no incs\n", base_level);
2048 thresh = bump_thresh(base_level, est_size(dp, 0), dp->bumppercent, dp->bumpsize, dp->bumpmult);
2051 " pick: size %ld level %d days %d (thresh %ldK, %d days)\n",
2052 base_size, base_level, est(dp)->level_days,
2053 thresh, dp->bumpdays);
2056 || est(dp)->level_days < dp->bumpdays
2057 || base_size <= thresh)
2060 bump_level = base_level + 1;
2061 bump_size = est_size(dp, bump_level);
2063 if(bump_size == -1) return base_level;
2065 fprintf(stderr, " pick: next size %ld... ", bump_size);
2067 if(base_size - bump_size < thresh) {
2068 fprintf(stderr, "not bumped\n");
2072 fprintf(stderr, "BUMPED\n");
2073 log_add(L_INFO, "Incremental of %s:%s bumped to level %d.",
2074 dp->host->hostname, dp->name, bump_level);
2083 ** ========================================================================
2086 ** We have two strategies here:
2090 ** If we are trying to fit too much on the tape something has to go. We
2091 ** try to delay totals until tomorrow by converting them into incrementals
2092 ** and, if that is not effective enough, dropping incrementals altogether.
2093 ** While we are searching for the guilty dump (the one that is really
2094 ** causing the schedule to be oversize) we have probably trampled on a lot of
2095 ** innocent dumps, so we maintain a "before image" list and use this to
2096 ** put back what we can.
2098 ** 2. Promote dumps.
2100 ** We try to keep the amount of tape used by total dumps the same each night.
2101 ** If there is some spare tape in this run we have a look to see if any of
2102 ** tonights incrementals could be promoted to totals and leave us with a
2103 ** more balanced cycle.
2106 static void delay_one_dump P((disk_t *dp, int delete, ...));
2108 static void delay_dumps P((void))
2109 /* delay any dumps that will not fit */
2111 disk_t *dp, *ndp, *preserve;
2113 long new_total; /* New total_size */
2114 char est_kb[20]; /* Text formatted dump size */
2115 int nb_forced_level_0;
2120 biq.head = biq.tail = NULL;
2123 ** 1. Delay dumps that are way oversize.
2125 ** Dumps larger that the size of the tapes we are using are just plain
2126 ** not going to fit no matter how many other dumps we drop. Delay
2127 ** oversize totals until tomorrow (by which time my owner will have
2128 ** resolved the problem!) and drop incrementals altogether. Naturally
2129 ** a large total might be delayed into a large incremental so these
2130 ** need to be checked for separately.
2133 for(dp = schedq.head; dp != NULL; dp = ndp) {
2134 ndp = dp->next; /* remove_disk zaps this */
2136 if (est(dp)->dump_size == -1 || est(dp)->dump_size <= tape->length) {
2140 /* Format dumpsize for messages */
2141 ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2143 if(est(dp)->dump_level == 0) {
2146 message = "but cannot incremental dump skip-incr disk";
2148 else if(est(dp)->last_level < 0) {
2150 message = "but cannot incremental dump new disk";
2152 else if(est(dp)->degr_level < 0) {
2154 message = "but no incremental estimate";
2156 else if (est(dp)->degr_size > tape->length) {
2158 message = "incremental dump also larger than tape";
2162 message = "full dump delayed";
2167 message = "skipping incremental";
2169 delay_one_dump(dp, delete, "dump larger than tape,", est_kb,
2174 ** 2. Delay total dumps.
2176 ** Delay total dumps until tomorrow (or the day after!). We start with
2177 ** the lowest priority (most dispensable) and work forwards. We take
2178 ** care not to delay *all* the dumps since this could lead to a stale
2179 ** mate [for any one disk there are only three ways tomorrows dump will
2180 ** be smaller than todays: 1. we do a level 0 today so tomorows dump
2181 ** will be a level 1; 2. the disk gets more data so that it is bumped
2182 ** tomorrow (this can be a slow process); and, 3. the disk looses some
2183 ** data (when does that ever happen?)].
2186 nb_forced_level_0 = 0;
2188 for(dp = schedq.head; dp != NULL && preserve == NULL; dp = dp->next)
2189 if(est(dp)->dump_level == 0)
2192 /* 2.a. Do not delay forced full */
2193 for(dp = schedq.tail;
2194 dp != NULL && total_size > tape_length;
2198 if(est(dp)->dump_level != 0) continue;
2200 get_info(dp->host->hostname, dp->name, &info);
2201 if(info.command & FORCE_FULL) {
2202 nb_forced_level_0 += 1;
2207 if(dp != preserve) {
2209 /* Format dumpsize for messages */
2210 ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2214 message = "but cannot incremental dump skip-incr disk";
2216 else if(est(dp)->last_level < 0) {
2218 message = "but cannot incremental dump new disk";
2220 else if(est(dp)->degr_level < 0) {
2222 message = "but no incremental estimate";
2226 message = "full dump delayed";
2228 delay_one_dump(dp, delete, "dumps too big,", est_kb,
2233 /* 2.b. Delay forced full if needed */
2234 if(nb_forced_level_0 > 0 && total_size > tape_length) {
2235 for(dp = schedq.tail;
2236 dp != NULL && total_size > tape_length;
2240 if(est(dp)->dump_level == 0 && dp != preserve) {
2242 /* Format dumpsize for messages */
2243 ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2247 message = "but cannot incremental dump skip-incr disk";
2249 else if(est(dp)->last_level < 0) {
2251 message = "but cannot incremental dump new disk";
2253 else if(est(dp)->degr_level < 0) {
2255 message = "but no incremental estimate";
2259 message = "full dump delayed";
2261 delay_one_dump(dp, delete, "dumps too big,", est_kb,
2268 ** 3. Delay incremental dumps.
2270 ** Delay incremental dumps until tomorrow. This is a last ditch attempt
2271 ** at making things fit. Again, we start with the lowest priority (most
2272 ** dispensable) and work forwards.
2275 for(dp = schedq.tail;
2276 dp != NULL && total_size > tape_length;
2280 if(est(dp)->dump_level != 0) {
2282 /* Format dumpsize for messages */
2283 ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2285 delay_one_dump(dp, 1,
2286 "dumps way too big,",
2288 "must skip incremental dumps",
2294 ** 4. Reinstate delayed dumps.
2296 ** We might not have needed to stomp on all of the dumps we have just
2297 ** delayed above. Try to reinstate them all starting with the last one
2298 ** and working forwards. It is unlikely that the last one will fit back
2299 ** in but why complicate the code?
2302 for(bi = biq.tail; bi != NULL; bi = nbi) {
2307 new_total = total_size + tt_blocksize_kb + bi->size + tape_mark;
2309 new_total = total_size - est(dp)->dump_size + bi->size;
2311 if(new_total <= tape_length && bi->size < tape->length) {
2313 total_size = new_total;
2315 if(bi->level == 0) {
2316 total_lev0 += (double) bi->size;
2318 insert_disk(&schedq, dp, schedule_order);
2321 est(dp)->dump_level = bi->level;
2322 est(dp)->dump_size = bi->size;
2326 if(bi->next == NULL)
2327 biq.tail = bi->prev;
2329 (bi->next)->prev = bi->prev;
2330 if(bi->prev == NULL)
2331 biq.head = bi->next;
2333 (bi->prev)->next = bi->next;
2340 ** 5. Output messages about what we have done.
2342 ** We can't output messages while we are delaying dumps because we might
2343 ** reinstate them later. We remember all the messages and output them
2347 for(bi = biq.head; bi != NULL; bi = nbi) {
2351 fprintf(stderr, "%s: FAILED %s\n", get_pname(), bi->errstr);
2352 log_add(L_FAIL, "%s", bi->errstr);
2356 fprintf(stderr, " delay: %s now at level %d\n",
2357 bi->errstr, est(dp)->dump_level);
2358 log_add(L_INFO, "%s", bi->errstr);
2361 /* Clean up - dont be too fancy! */
2366 fprintf(stderr, " delay: Total size now %ld.\n", total_size);
2373 * Remove a dump or modify it from full to incremental.
2374 * Keep track of it on the bi q in case we can add it back later.
2376 arglist_function1(static void delay_one_dump,
2382 char level_str[NUM_STR_SIZE];
2386 arglist_start(argp, delete);
2388 total_size -= tt_blocksize_kb + est(dp)->dump_size + tape_mark;
2389 if(est(dp)->dump_level == 0) {
2390 total_lev0 -= (double) est(dp)->dump_size;
2393 bi = alloc(sizeof(bi_t));
2395 bi->prev = biq.tail;
2396 if(biq.tail == NULL)
2399 biq.tail->next = bi;
2402 bi->deleted = delete;
2404 bi->level = est(dp)->dump_level;
2405 bi->size = est(dp)->dump_size;
2407 ap_snprintf(level_str, sizeof(level_str), "%d", est(dp)->dump_level);
2408 bi->errstr = vstralloc(dp->host->hostname,
2410 " ", datestamp ? datestamp : "?",
2414 while ((next = arglist_val(argp, char *)) != NULL) {
2415 bi->errstr = newvstralloc(bi->errstr, bi->errstr, sep, next, NULL);
2418 strappend(bi->errstr, "]");
2422 remove_disk(&schedq, dp);
2424 est(dp)->dump_level = est(dp)->degr_level;
2425 est(dp)->dump_size = est(dp)->degr_size;
2426 total_size += tt_blocksize_kb + est(dp)->dump_size + tape_mark;
2433 static int promote_highest_priority_incremental P((void))
2435 disk_t *dp, *dp1, *dp_promote;
2436 long new_size, new_total, new_lev0;
2438 int nb_today, nb_same_day, nb_today2;
2439 int nb_disk_today, nb_disk_same_day;
2442 * return 1 if did so; must update total_size correctly; must not
2443 * cause total_size to exceed tape_length
2447 for(dp = schedq.head; dp != NULL; dp = dp->next) {
2449 est(dp)->promote = -1000;
2451 if(est_size(dp,0) <= 0)
2454 if(est(dp)->next_level0 <= 0)
2457 if(est(dp)->next_level0 > dp->maxpromoteday)
2460 new_size = est_tape_size(dp, 0);
2461 new_total = total_size - est(dp)->dump_size + new_size;
2462 new_lev0 = total_lev0 + new_size;
2467 nb_disk_same_day = 0;
2468 for(dp1 = schedq.head; dp1 != NULL; dp1 = dp1->next) {
2469 if(est(dp1)->dump_level == 0)
2471 else if(est(dp1)->next_level0 == est(dp)->next_level0)
2473 if(strcmp(dp->host->hostname, dp1->host->hostname) == 0) {
2474 if(est(dp1)->dump_level == 0)
2476 else if(est(dp1)->next_level0 == est(dp)->next_level0)
2481 /* do not promote if overflow tape */
2482 if(new_total > tape_length) continue;
2484 /* do not promote if overflow balanced size and something today */
2485 /* promote if nothing today */
2486 if(new_lev0 > balanced_size+balance_threshold && nb_disk_today > 0)
2489 /* do not promote if only one disk due that day and nothing today */
2490 if(nb_disk_same_day == 1 && nb_disk_today == 0) continue;
2492 nb_today2 = nb_today*nb_today;
2493 if(nb_today == 0 && nb_same_day > 1) nb_same_day++;
2495 if(nb_same_day >= nb_today2) {
2496 est(dp)->promote = ((nb_same_day - nb_today2)*(nb_same_day - nb_today2)) +
2497 conf_dumpcycle - est(dp)->next_level0;
2500 est(dp)->promote = -nb_today2 +
2501 conf_dumpcycle - est(dp)->next_level0;
2504 if(!dp_promote || est(dp_promote)->promote < est(dp)->promote) {
2506 fprintf(stderr," try %s:%s %d %d %d = %d\n",
2507 dp->host->hostname, dp->name, nb_same_day, nb_today, est(dp)->next_level0, est(dp)->promote);
2510 fprintf(stderr,"no try %s:%s %d %d %d = %d\n",
2511 dp->host->hostname, dp->name, nb_same_day, nb_today, est(dp)->next_level0, est(dp)->promote);
2518 new_size = est_tape_size(dp, 0);
2519 new_total = total_size - est(dp)->dump_size + new_size;
2520 new_lev0 = total_lev0 + new_size;
2522 total_size = new_total;
2523 total_lev0 = new_lev0;
2524 check_days = est(dp)->next_level0;
2525 est(dp)->degr_level = est(dp)->dump_level;
2526 est(dp)->degr_size = est(dp)->dump_size;
2527 est(dp)->dump_level = 0;
2528 est(dp)->dump_size = new_size;
2529 est(dp)->next_level0 = 0;
2532 " promote: moving %s:%s up, total_lev0 %1.0f, total_size %ld\n",
2533 dp->host->hostname, dp->name,
2534 total_lev0, total_size);
2537 "Full dump of %s:%s promoted from %d day%s ahead.",
2538 dp->host->hostname, dp->name,
2539 check_days, (check_days == 1) ? "" : "s");
2546 static int promote_hills P((void))
2549 struct balance_stats {
2560 /* If we are already doing a level 0 don't bother */
2564 /* Do the guts of an "amadmin balance" */
2565 my_dumpcycle = conf_dumpcycle;
2566 if(my_dumpcycle > 10000) my_dumpcycle = 10000;
2568 sp = (struct balance_stats *)
2569 alloc(sizeof(struct balance_stats) * my_dumpcycle);
2571 for(days = 0; days < my_dumpcycle; days++)
2572 sp[days].disks = sp[days].size = 0;
2574 for(dp = schedq.head; dp != NULL; dp = dp->next) {
2575 days = est(dp)->next_level0; /* This is > 0 by definition */
2576 if(days<my_dumpcycle && !dp->skip_full && dp->strategy != DS_NOFULL &&
2577 dp->strategy != DS_INCRONLY) {
2579 sp[days].size += est(dp)->last_lev0size;
2583 /* Search for a suitable big hill and cut it down */
2585 /* Find the tallest hill */
2587 for(days = 0; days < my_dumpcycle; days++) {
2588 if(sp[days].disks > 1 && sp[days].size > hill_size) {
2589 hill_size = sp[days].size;
2594 if(hill_size <= 0) break; /* no suitable hills */
2596 /* Find all the dumps in that hill and try and remove one */
2597 for(dp = schedq.head; dp != NULL; dp = dp->next) {
2598 if(est(dp)->next_level0 != hill_days ||
2599 est(dp)->next_level0 > dp->maxpromoteday ||
2601 dp->strategy == DS_NOFULL ||
2602 dp->strategy == DS_INCRONLY)
2604 new_size = est_tape_size(dp, 0);
2605 new_total = total_size - est(dp)->dump_size + new_size;
2606 if(new_total > tape_length)
2608 /* We found a disk we can promote */
2609 total_size = new_total;
2610 total_lev0 += new_size;
2611 est(dp)->degr_level = est(dp)->dump_level;
2612 est(dp)->degr_size = est(dp)->dump_size;
2613 est(dp)->dump_level = 0;
2614 est(dp)->next_level0 = 0;
2615 est(dp)->dump_size = new_size;
2618 " promote: moving %s:%s up, total_lev0 %1.0f, total_size %ld\n",
2619 dp->host->hostname, dp->name,
2620 total_lev0, total_size);
2623 "Full dump of %s:%s specially promoted from %d day%s ahead.",
2624 dp->host->hostname, dp->name,
2625 hill_days, (hill_days == 1) ? "" : "s");
2630 /* All the disks in that hill were unsuitable. */
2631 sp[hill_days].disks = 0; /* Don't get tricked again */
2639 * ========================================================================
2642 * XXX - memory leak - we shouldn't just throw away *dp
2644 static void output_scheduleline(dp)
2648 long dump_time = 0, degr_time = 0;
2649 char *schedline = NULL, *degr_str = NULL;
2650 char dump_priority_str[NUM_STR_SIZE];
2651 char dump_level_str[NUM_STR_SIZE];
2652 char dump_size_str[NUM_STR_SIZE];
2653 char dump_time_str[NUM_STR_SIZE];
2654 char degr_level_str[NUM_STR_SIZE];
2655 char degr_size_str[NUM_STR_SIZE];
2656 char degr_time_str[NUM_STR_SIZE];
2657 char *dump_date, *degr_date;
2663 if(ep->dump_size == -1) {
2664 /* no estimate, fail the disk */
2666 "%s: FAILED %s %s %s %d [no estimate]\n",
2668 dp->host->hostname, dp->name, datestamp, ep->dump_level);
2669 log_add(L_FAIL, "%s %s %s %d [no estimate]",
2670 dp->host->hostname, dp->name, datestamp, ep->dump_level);
2674 dump_date = degr_date = (char *)0;
2675 for(i = 0; i < MAX_LEVELS; i++) {
2676 if(ep->dump_level == ep->level[i])
2677 dump_date = ep->dumpdate[i];
2678 if(ep->degr_level == ep->level[i])
2679 degr_date = ep->dumpdate[i];
2682 #define fix_rate(rate) (rate < 1.0 ? DEFAULT_DUMPRATE : rate)
2684 if(ep->dump_level == 0) {
2685 dump_time = ep->dump_size / fix_rate(ep->fullrate);
2687 if(ep->degr_size != -1) {
2688 degr_time = ep->degr_size / fix_rate(ep->incrrate);
2692 dump_time = ep->dump_size / fix_rate(ep->incrrate);
2695 if(ep->dump_level == 0 && ep->degr_size != -1) {
2696 ap_snprintf(degr_level_str, sizeof(degr_level_str),
2697 "%d", ep->degr_level);
2698 ap_snprintf(degr_size_str, sizeof(degr_size_str),
2699 "%ld", ep->degr_size);
2700 ap_snprintf(degr_time_str, sizeof(degr_time_str),
2702 degr_str = vstralloc(" ", degr_level_str,
2708 ap_snprintf(dump_priority_str, sizeof(dump_priority_str),
2709 "%d", ep->dump_priority);
2710 ap_snprintf(dump_level_str, sizeof(dump_level_str),
2711 "%d", ep->dump_level);
2712 ap_snprintf(dump_size_str, sizeof(dump_size_str),
2713 "%ld", ep->dump_size);
2714 ap_snprintf(dump_time_str, sizeof(dump_time_str),
2716 features = am_feature_to_string(dp->host->features);
2717 schedline = vstralloc("DUMP ",dp->host->hostname,
2721 " ", dump_priority_str,
2722 " ", dump_level_str,
2726 degr_str ? degr_str : "",
2729 fputs(schedline, stdout);
2730 fputs(schedline, stderr);