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.16 2005/03/16 18:09:50 martinea 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;
173 times_t section_start;
175 for(fd = 3; fd < FD_SETSIZE; fd++) {
177 * Make sure nobody spoofs us with a lot of extra open files
178 * that would cause an open we do to get a very high file
179 * descriptor, which in turn might be used as an index into
180 * an array (e.g. an fd_set).
185 setvbuf(stderr, (char *)NULL, _IOLBF, 0);
188 config_name = stralloc(argv[1]);
189 config_dir = vstralloc(CONFIG_DIR, "/", config_name, "/", NULL);
191 char my_cwd[STR_SIZE];
193 if (getcwd(my_cwd, sizeof(my_cwd)) == NULL) {
194 error("cannot determine current working directory");
196 config_dir = stralloc2(my_cwd, "/");
197 if ((config_name = strrchr(my_cwd, '/')) != NULL) {
198 config_name = stralloc(config_name + 1);
204 set_pname("planner");
206 malloc_size_1 = malloc_inuse(&malloc_hist_1);
208 erroutput_type = (ERR_AMANDALOG|ERR_INTERACTIVE);
209 set_logerror(logerror);
211 section_start = curclock();
213 our_features = am_init_feature_set();
214 our_feature_string = am_feature_to_string(our_features);
216 fprintf(stderr, "%s: pid %ld executable %s version %s\n",
217 get_pname(), (long) getpid(), argv[0], version());
218 for(vp = version_info; *vp != NULL; vp++)
219 fprintf(stderr, "%s: %s", get_pname(), *vp);
222 * 1. Networking Setup
224 * Planner runs setuid to get a priviledged socket for BSD security.
225 * We get the socket right away as root, then setuid back to a normal
226 * user. If we are not using BSD security, planner is not installed
230 /* set up dgram port first thing */
234 if(dgram_bind(msg, &result_port) == -1) {
235 error("could not bind result datagram port: %s", strerror(errno));
239 /* set both real and effective uid's to real uid, likewise for gid */
245 * From this point on we are running under our real uid, so we don't
246 * have to worry about opening security holes below. Make sure we
250 if(getpwuid(getuid()) == NULL) {
251 error("can't get login name for my uid %ld", (long)getuid());
255 * 2. Read in Configuration Information
257 * All the Amanda configuration files are loaded before we begin.
260 fprintf(stderr,"READING CONF FILES...\n");
262 conffile = stralloc2(config_dir, CONFFILE_NAME);
263 if(read_conffile(conffile)) {
264 error("errors processing config file \"%s\"", conffile);
267 conf_diskfile = getconf_str(CNF_DISKFILE);
268 if (*conf_diskfile == '/') {
269 conf_diskfile = stralloc(conf_diskfile);
271 conf_diskfile = stralloc2(config_dir, conf_diskfile);
273 if((origqp = read_diskfile(conf_diskfile)) == NULL) {
274 error("could not load disklist \"%s\"", conf_diskfile);
276 match_disklist(origqp, argc-2, argv+2);
277 for(dp = origqp->head; dp != NULL; dp = dp->next) {
279 log_add(L_DISK, "%s %s", dp->host->hostname, dp->name);
281 amfree(conf_diskfile);
282 conf_tapelist = getconf_str(CNF_TAPELIST);
283 if (*conf_tapelist == '/') {
284 conf_tapelist = stralloc(conf_tapelist);
286 conf_tapelist = stralloc2(config_dir, conf_tapelist);
288 if(read_tapelist(conf_tapelist)) {
289 error("could not load tapelist \"%s\"", conf_tapelist);
291 amfree(conf_tapelist);
292 conf_infofile = getconf_str(CNF_INFOFILE);
293 if (*conf_infofile == '/') {
294 conf_infofile = stralloc(conf_infofile);
296 conf_infofile = stralloc2(config_dir, conf_infofile);
298 if(open_infofile(conf_infofile)) {
299 error("could not open info db \"%s\"", conf_infofile);
301 amfree(conf_infofile);
303 conf_tapetype = getconf_str(CNF_TAPETYPE);
304 conf_maxdumpsize = getconf_int(CNF_MAXDUMPSIZE);
305 conf_runtapes = getconf_int(CNF_RUNTAPES);
306 conf_dumpcycle = getconf_int(CNF_DUMPCYCLE);
307 conf_runspercycle = getconf_int(CNF_RUNSPERCYCLE);
308 conf_tapecycle = getconf_int(CNF_TAPECYCLE);
309 conf_etimeout = getconf_int(CNF_ETIMEOUT);
310 conf_reserve = getconf_int(CNF_RESERVE);
311 conf_autoflush = getconf_int(CNF_AUTOFLUSH);
315 datestamp = construct_datestamp(NULL);
316 log_add(L_START, "date %s", datestamp);
318 /* some initializations */
320 if(conf_runspercycle == 0) {
321 runs_per_cycle = conf_dumpcycle;
322 } else if(conf_runspercycle == -1 ) {
323 runs_per_cycle = guess_runs_from_tapelist();
325 runs_per_cycle = conf_runspercycle;
327 if (runs_per_cycle <= 0) {
332 * do some basic sanity checking
334 if(conf_tapecycle <= runs_per_cycle) {
335 log_add(L_WARNING, "tapecycle (%d) <= runspercycle (%d)",
336 conf_tapecycle, runs_per_cycle);
339 tape = lookup_tapetype(conf_tapetype);
340 if(conf_maxdumpsize > 0) {
341 tape_length = conf_maxdumpsize;
344 tape_length = tape->length * conf_runtapes;
346 tape_mark = tape->filemark;
347 tt_blocksize_kb = tape->blocksize;
348 tt_blocksize = tt_blocksize_kb * 1024;
350 proto_init(msg->socket, today, 1000); /* XXX handles should eq nhosts */
353 kerberos_service_init();
356 fprintf(stderr, "%s: time %s: startup took %s secs\n",
358 walltime_str(curclock()),
359 walltime_str(timessub(curclock(), section_start)));
362 * 3. Send autoflush dumps left on the holding disks
364 * This should give us something to do while we generate the new
368 fprintf(stderr,"\nSENDING FLUSHES...\n");
373 holding_list = get_flush(NULL, NULL, 0, 0);
374 for(holding_file=holding_list->first; holding_file != NULL;
375 holding_file = holding_file->next) {
376 get_dumpfile(holding_file->name, &file);
378 log_add(L_DISK, "%s %s", file.name, file.disk);
380 "FLUSH %s %s %s %d %s\n",
387 "FLUSH %s %s %s %d %s\n",
394 free_sl(holding_list);
397 fprintf(stderr, "ENDFLUSH\n");
398 fprintf(stdout, "ENDFLUSH\n");
402 * 4. Calculate Preliminary Dump Levels
404 * Before we can get estimates from the remote slave hosts, we make a
405 * first attempt at guessing what dump levels we will be dumping at
406 * based on the curinfo database.
409 fprintf(stderr,"\nSETTING UP FOR ESTIMATES...\n");
410 section_start = curclock();
412 startq.head = startq.tail = NULL;
413 while(!empty(*origqp)) {
414 disk_t *dp = dequeue_disk(origqp);
420 fprintf(stderr, "%s: time %s: setting up estimates took %s secs\n",
422 walltime_str(curclock()),
423 walltime_str(timessub(curclock(), section_start)));
427 * 5. Get Dump Size Estimates from Remote Client Hosts
429 * Each host is queried (in parallel) for dump size information on all
430 * of its disks, and the results gathered as they come in.
433 /* go out and get the dump estimates */
435 fprintf(stderr,"\nGETTING ESTIMATES...\n");
436 section_start = curclock();
438 estq.head = estq.tail = NULL;
439 pestq.head = pestq.tail = NULL;
440 waitq.head = waitq.tail = NULL;
441 failq.head = failq.tail = NULL;
445 fprintf(stderr, "%s: time %s: getting estimates took %s secs\n",
447 walltime_str(curclock()),
448 walltime_str(timessub(curclock(), section_start)));
451 * At this point, all disks with estimates are in estq, and
452 * all the disks on hosts that didn't respond to our inquiry
456 dump_queue("FAILED", failq, 15, stderr);
457 dump_queue("DONE", estq, 15, stderr);
461 * 6. Analyze Dump Estimates
463 * Each disk's estimates are looked at to determine what level it
464 * should dump at, and to calculate the expected size and time taking
465 * historical dump rates and compression ratios into account. The
466 * total expected size is accumulated as well.
469 fprintf(stderr,"\nANALYZING ESTIMATES...\n");
470 section_start = curclock();
472 /* an empty tape still has a label and an endmark */
473 total_size = (tt_blocksize_kb + tape_mark) * 2;
477 schedq.head = schedq.tail = NULL;
478 while(!empty(estq)) analyze_estimate(dequeue_disk(&estq));
479 while(!empty(failq)) handle_failed(dequeue_disk(&failq));
482 * At this point, all the disks are on schedq sorted by priority.
483 * The total estimated size of the backups is in total_size.
489 fprintf(stderr, "INITIAL SCHEDULE (size %ld):\n", total_size);
490 for(dp = schedq.head; dp != NULL; dp = dp->next) {
491 fprintf(stderr, " %s %s pri %d lev %d size %ld\n",
492 dp->host->hostname, dp->name, est(dp)->dump_priority,
493 est(dp)->dump_level, est(dp)->dump_size);
499 * 7. Delay Dumps if Schedule Too Big
501 * If the generated schedule is too big to fit on the tape, we need to
502 * delay some full dumps to make room. Incrementals will be done
503 * instead (except for new or forced disks).
505 * In extreme cases, delaying all the full dumps is not even enough.
506 * If so, some low-priority incrementals will be skipped completely
507 * until the dumps fit on the tape.
511 "\nDELAYING DUMPS IF NEEDED, total_size %ld, tape length %lu mark %lu\n",
512 total_size, tape_length, tape_mark);
514 initial_size = total_size;
518 /* XXX - why bother checking this? */
519 if(empty(schedq) && total_size < initial_size)
520 error("cannot fit anything on tape, bailing out");
524 * 8. Promote Dumps if Schedule Too Small
526 * Amanda attempts to balance the full dumps over the length of the
527 * dump cycle. If this night's full dumps are too small relative to
528 * the other nights, promote some high-priority full dumps that will be
529 * due for the next run, to full dumps for tonight, taking care not to
530 * overflow the tape size.
532 * This doesn't work too well for small sites. For these we scan ahead
533 * looking for nights that have an excessive number of dumps and promote
536 * Amanda never delays full dumps just for the sake of balancing the
537 * schedule, so it can take a full cycle to balance the schedule after
542 "\nPROMOTING DUMPS IF NEEDED, total_lev0 %1.0f, balanced_size %1.0f...\n",
543 total_lev0, balanced_size);
545 balance_threshold = balanced_size * PROMOTE_THRESHOLD;
547 while((balanced_size - total_lev0) > balance_threshold && moved_one)
548 moved_one = promote_highest_priority_incremental();
550 moved_one = promote_hills();
552 fprintf(stderr, "%s: time %s: analysis took %s secs\n",
554 walltime_str(curclock()),
555 walltime_str(timessub(curclock(), section_start)));
561 * The schedule goes to stdout, presumably to driver. A copy is written
562 * on stderr for the debug file.
565 fprintf(stderr,"\nGENERATING SCHEDULE:\n--------\n");
567 while(!empty(schedq)) output_scheduleline(dequeue_disk(&schedq));
568 fprintf(stderr, "--------\n");
571 log_add(L_FINISH, "date %s time %s", datestamp, walltime_str(curclock()));
577 amfree(our_feature_string);
578 am_release_feature_set(our_features);
581 malloc_size_2 = malloc_inuse(&malloc_hist_2);
583 if(malloc_size_1 != malloc_size_2) {
584 malloc_list(fileno(stderr), malloc_hist_1, malloc_hist_2);
593 * ========================================================================
594 * SETUP FOR ESTIMATES
598 static int last_level P((info_t *info)); /* subroutines */
599 static long est_size P((disk_t *dp, int level));
600 static long est_tape_size P((disk_t *dp, int level));
601 static int next_level0 P((disk_t *dp, info_t *info));
602 static int runs_at P((info_t *info, int lev));
603 static long bump_thresh P((int level, long size_level_0, int bumppercent, int bumpsize, double bumpmult));
604 static int when_overwrite P((char *label));
606 static void askfor(ep, seq, lev, info)
607 est_t *ep; /* esimate data block */
608 int seq; /* sequence number of request */
609 int lev; /* dump level being requested */
610 info_t *info; /* info block for disk */
612 if(seq < 0 || seq >= MAX_LEVELS) {
613 error("error [planner askfor: seq out of range 0..%d: %d]",
616 if(lev < -1 || lev >= DUMP_LEVELS) {
617 error("error [planner askfor: lev out of range -1..%d: %d]",
623 ep->dumpdate[seq] = (char *)0;
624 ep->est_size[seq] = -2;
628 ep->level[seq] = lev;
630 ep->dumpdate[seq] = stralloc(get_dumpdate(info,lev));
631 malloc_mark(ep->dumpdate[seq]);
633 ep->est_size[seq] = -2;
646 assert(dp && dp->host);
647 fprintf(stderr, "%s: time %s: setting up estimates for %s:%s\n",
648 get_pname(), walltime_str(curclock()),
649 dp->host->hostname, dp->name);
651 /* get current information about disk */
653 if(get_info(dp->host->hostname, dp->name, &info)) {
654 /* no record for this disk, make a note of it */
655 log_add(L_INFO, "Adding new disk %s:%s.", dp->host->hostname, dp->name);
658 /* setup working data struct for disk */
660 ep = alloc(sizeof(est_t));
662 dp->up = (void *) ep;
663 ep->state = DISK_READY;
665 ep->dump_priority = dp->priority;
669 /* calculated fields */
671 if(info.command & FORCE_FULL) {
672 /* force a level 0, kind of like a new disk */
673 if(dp->strategy == DS_NOFULL) {
675 * XXX - Not sure what it means to force a no-full disk. The
676 * purpose of no-full is to just dump changes relative to a
677 * stable base, for example root partitions that vary only
678 * slightly from a site-wide prototype. Only the variations
681 * If we allow a level 0 onto the Amanda cycle, then we are
682 * hosed when that tape gets re-used next. Disallow this for
686 "Cannot force full dump of %s:%s with no-full option.",
687 dp->host->hostname, dp->name);
689 /* clear force command */
690 if(info.command & FORCE_FULL)
691 info.command ^= FORCE_FULL;
692 if(put_info(dp->host->hostname, dp->name, &info))
693 error("could not put info record for %s:%s: %s",
694 dp->host->hostname, dp->name, strerror(errno));
695 ep->last_level = last_level(&info);
696 ep->next_level0 = next_level0(dp, &info);
700 ep->next_level0 = -conf_dumpcycle;
701 log_add(L_INFO, "Forcing full dump of %s:%s as directed.",
702 dp->host->hostname, dp->name);
705 else if(dp->strategy == DS_NOFULL) {
706 /* force estimate of level 1 */
708 ep->next_level0 = next_level0(dp, &info);
711 ep->last_level = last_level(&info);
712 ep->next_level0 = next_level0(dp, &info);
715 /* adjust priority levels */
717 if(ep->next_level0 < 0) {
718 fprintf(stderr,"%s:%s overdue %d day%s for level 0\n",
719 dp->host->hostname, dp->name,
720 - ep->next_level0, ((- ep->next_level0) == 1) ? "" : "s");
721 ep->dump_priority -= ep->next_level0;
722 /* warn if dump will be overwritten */
723 if(ep->last_level > -1) {
724 int overwrite_runs = when_overwrite(info.inf[0].label);
725 if(overwrite_runs == 0) {
727 "Last full dump of %s:%s on tape %s overwritten on this run.",
728 dp->host->hostname, dp->name, info.inf[0].label);
730 else if(overwrite_runs < RUNS_REDZONE) {
732 "Last full dump of %s:%s on tape %s overwritten in %d run%s.",
733 dp->host->hostname, dp->name, info.inf[0].label,
734 overwrite_runs, overwrite_runs == 1? "" : "s");
738 else if(info.command & FORCE_FULL)
739 ep->dump_priority += 1;
740 /* else XXX bump up the priority of incrementals that failed last night */
742 /* handle external level 0 dumps */
744 if(dp->skip_full && dp->strategy != DS_NOINC) {
745 if(ep->next_level0 <= 0) {
746 /* update the date field */
747 info.inf[0].date = today;
748 if(info.command & FORCE_FULL)
749 info.command ^= FORCE_FULL;
750 ep->next_level0 += conf_dumpcycle;
752 if(put_info(dp->host->hostname, dp->name, &info))
753 error("could not put info record for %s:%s: %s",
754 dp->host->hostname, dp->name, strerror(errno));
755 log_add(L_INFO, "Skipping full dump of %s:%s today.",
756 dp->host->hostname, dp->name);
757 fprintf(stderr,"%s:%s lev 0 skipped due to skip-full flag\n",
758 dp->host->hostname, dp->name);
759 /* don't enqueue the disk */
760 askfor(ep, 0, -1, &info);
761 askfor(ep, 1, -1, &info);
762 askfor(ep, 2, -1, &info);
763 fprintf(stderr, "%s: SKIPPED %s %s 0 [skip-full]\n",
764 get_pname(), dp->host->hostname, dp->name);
765 log_add(L_SUCCESS, "%s %s %s 0 [skipped: skip-full]",
766 dp->host->hostname, dp->name, datestamp);
770 if(ep->last_level == -1) {
771 /* probably a new disk, but skip-full means no full! */
775 if(ep->next_level0 == 1) {
776 log_add(L_WARNING, "Skipping full dump of %s:%s tomorrow.",
777 dp->host->hostname, dp->name);
781 /* handle "skip-incr" type archives */
783 if(dp->skip_incr && ep->next_level0 > 0) {
784 fprintf(stderr,"%s:%s lev 1 skipped due to skip-incr flag\n",
785 dp->host->hostname, dp->name);
786 /* don't enqueue the disk */
787 askfor(ep, 0, -1, &info);
788 askfor(ep, 1, -1, &info);
789 askfor(ep, 2, -1, &info);
791 fprintf(stderr, "%s: SKIPPED %s %s 1 [skip-incr]\n",
792 get_pname(), dp->host->hostname, dp->name);
794 log_add(L_SUCCESS, "%s %s %s 1 [skipped: skip-incr]",
795 dp->host->hostname, dp->name, datestamp);
799 if( ep->last_level == -1 && ep->next_level0 > 0 &&
800 dp->strategy != DS_NOFULL && dp->strategy != DS_INCRONLY &&
801 conf_reserve == 100) {
803 "%s:%s mismatch: no tapelist record, but curinfo next_level0: %d.",
804 dp->host->hostname, dp->name, ep->next_level0);
808 if(ep->last_level == 0) ep->level_days = 0;
809 else ep->level_days = runs_at(&info, ep->last_level);
810 ep->last_lev0size = info.inf[0].csize;
812 ep->fullrate = perf_average(info.full.rate, 0.0);
813 ep->incrrate = perf_average(info.incr.rate, 0.0);
815 ep->fullcomp = perf_average(info.full.comp, dp->comprate[0]);
816 ep->incrcomp = perf_average(info.incr.comp, dp->comprate[1]);
818 /* determine which estimates to get */
822 if(dp->strategy == DS_NOINC ||
824 (!(info.command & FORCE_BUMP) ||
826 ep->last_level == -1))){
828 if(info.command & FORCE_BUMP && ep->last_level == -1) {
830 "Remove force-bump command of %s:%s because it's a new disk.",
831 dp->host->hostname, dp->name);
833 switch (dp->strategy) {
836 askfor(ep, i++, 0, &info);
839 "Ignoring skip_full for %s:%s because the strategy is NOINC.",
840 dp->host->hostname, dp->name);
842 if(info.command & FORCE_BUMP) {
844 "Ignoring FORCE_BUMP for %s:%s because the strategy is NOINC.",
845 dp->host->hostname, dp->name);
854 if (info.command & FORCE_FULL)
855 askfor(ep, i++, 0, &info);
860 if(!dp->skip_incr && !(dp->strategy == DS_NOINC)) {
861 if(ep->last_level == -1) { /* a new disk */
862 if(dp->strategy == DS_NOFULL || dp->strategy == DS_INCRONLY) {
863 askfor(ep, i++, 1, &info);
865 assert(!dp->skip_full); /* should be handled above */
867 } else { /* not new, pick normally */
870 curr_level = ep->last_level;
872 if(info.command & FORCE_NO_BUMP) {
873 if(curr_level > 0) { /* level 0 already asked for */
874 askfor(ep, i++, curr_level, &info);
876 log_add(L_INFO,"Preventing bump of %s:%s as directed.",
877 dp->host->hostname, dp->name);
879 else if((info.command & FORCE_BUMP)
880 && curr_level + 1 < DUMP_LEVELS) {
881 askfor(ep, i++, curr_level+1, &info);
882 log_add(L_INFO,"Bumping of %s:%s at level %d as directed.",
883 dp->host->hostname, dp->name, curr_level+1);
885 else if(curr_level == 0) {
886 askfor(ep, i++, 1, &info);
889 askfor(ep, i++, curr_level, &info);
891 * If last time we dumped less than the threshold, then this
892 * time we will too, OR the extra size will be charged to both
893 * cur_level and cur_level + 1, so we will never bump. Also,
894 * if we haven't been at this level 2 days, or the dump failed
895 * last night, we can't bump.
897 if((info.inf[curr_level].size == 0 || /* no data, try it anyway */
898 (((info.inf[curr_level].size > bump_thresh(curr_level, info.inf[0].size,dp->bumppercent, dp->bumpsize, dp->bumpmult)))
899 && ep->level_days >= dp->bumpdays))
900 && curr_level + 1 < DUMP_LEVELS) {
901 askfor(ep, i++, curr_level+1, &info);
907 while(i < MAX_LEVELS) /* mark end of estimates */
908 askfor(ep, i++, -1, &info);
912 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",
913 dp->host->hostname, dp->name, info.command,
914 dp->strategy == DS_NOFULL ? "no-full" :
915 dp->strategy == DS_INCRONLY ? "incr-only" :
916 dp->skip_full ? "skip-full" :
917 dp->skip_incr ? "skip-incr" : "none",
918 ep->last_level, ep->next_level0, ep->level_days,
919 ep->level[0], ep->est_size[0],
920 ep->level[1], ep->est_size[1],
921 ep->level[2], ep->est_size[2]);
923 assert(ep->level[0] != -1);
924 enqueue_disk(&startq, dp);
927 static int when_overwrite(label)
932 if((tp = lookup_tapelabel(label)) == NULL)
933 return 1; /* "shouldn't happen", but trigger warning message */
934 else if(!reusable_tape(tp))
936 else if(lookup_nb_tape() > conf_tapecycle)
937 return (lookup_nb_tape() - tp->position) / conf_runtapes;
939 return (conf_tapecycle - tp->position) / conf_runtapes;
942 /* Return the estimated size for a particular dump */
943 static long est_size(dp, level)
949 for(i = 0; i < MAX_LEVELS; i++) {
950 if(level == est(dp)->level[i])
951 return est(dp)->est_size[i];
956 /* Return the estimated on-tape size of a particular dump */
957 static long est_tape_size(dp, level)
964 size = est_size(dp, level);
966 if(size == -1) return size;
968 if(dp->compress == COMP_NONE)
971 if(level == 0) ratio = est(dp)->fullcomp;
972 else ratio = est(dp)->incrcomp;
975 * make sure over-inflated compression ratios don't throw off the
976 * estimates, this is mostly for when you have a small dump getting
977 * compressed which takes up alot more disk/tape space relatively due
978 * to the overhead of the compression. This is specifically for
979 * Digital Unix vdump. This patch is courtesy of Rudolf Gabler
980 * (RUG@USM.Uni-Muenchen.DE)
983 if(ratio > 1.1) ratio = 1.1;
988 * Ratio can be very small in some error situations, so make sure
989 * size goes back greater than zero. It may not be right, but
990 * indicates we did get an estimate.
1000 /* what was the level of the last successful dump to tape? */
1001 static int last_level(info)
1004 int min_pos, min_level, i;
1005 time_t lev0_date, last_date;
1008 if(info->last_level != -1)
1009 return info->last_level;
1011 /* to keep compatibility with old infofile */
1012 min_pos = 1000000000;
1016 for(i = 0; i < 9; i++) {
1017 if(conf_reserve < 100) {
1018 if(i == 0) lev0_date = info->inf[0].date;
1019 else if(info->inf[i].date < lev0_date) continue;
1020 if(info->inf[i].date > last_date) {
1021 last_date = info->inf[i].date;
1026 if((tp = lookup_tapelabel(info->inf[i].label)) == NULL) continue;
1027 /* cull any entries from previous cycles */
1028 if(i == 0) lev0_date = info->inf[0].date;
1029 else if(info->inf[i].date < lev0_date) continue;
1031 if(tp->position < min_pos) {
1032 min_pos = tp->position;
1037 info->last_level = i;
1041 /* when is next level 0 due? 0 = today, 1 = tomorrow, etc*/
1043 next_level0(dp, info)
1047 if(dp->strategy == DS_NOFULL || dp->strategy == DS_INCRONLY)
1048 return 1; /* fake it */
1049 else if (dp->strategy == DS_NOINC)
1051 else if(info->inf[0].date < (time_t)0)
1052 return -days_diff(EPOCH, today); /* new disk */
1054 return dp->dumpcycle - days_diff(info->inf[0].date, today);
1057 /* how many runs at current level? */
1058 static int runs_at(info, lev)
1062 tape_t *cur_tape, *old_tape;
1065 last = last_level(info);
1066 if(lev != last) return 0;
1067 if(lev == 0) return 1;
1069 if(info->consecutive_runs != -1)
1070 return info->consecutive_runs;
1072 /* to keep compatibility with old infofile */
1073 cur_tape = lookup_tapelabel(info->inf[lev].label);
1074 old_tape = lookup_tapelabel(info->inf[lev-1].label);
1075 if(cur_tape == NULL || old_tape == NULL) return 0;
1077 nb_runs = (old_tape->position - cur_tape->position) / conf_runtapes;
1078 info->consecutive_runs = nb_runs;
1084 static long bump_thresh(level, size_level_0, bumppercent, bumpsize, bumpmult)
1093 if(bumppercent != 0 && size_level_0 > 1024) {
1094 bump = (size_level_0 * bumppercent)/100.0;
1099 while(--level) bump = bump * bumpmult;
1107 * ========================================================================
1108 * GET REMOTE DUMP SIZE ESTIMATES
1112 static void getsize P((am_host_t *hostp));
1113 static disk_t *lookup_hostdisk P((am_host_t *hp, char *str));
1114 static void handle_result P((proto_t *p, pkt_t *pkt));
1117 static void get_estimates P((void))
1121 struct servent *amandad;
1122 int something_started;
1124 if((amandad = getservbyname(AMANDA_SERVICE_NAME, "udp")) == NULL)
1125 amanda_port = AMANDA_SERVICE_DEFAULT;
1127 amanda_port = ntohs(amandad->s_port);
1129 #ifdef KRB4_SECURITY
1130 if((amandad = getservbyname(KAMANDA_SERVICE_NAME, "udp")) == NULL)
1131 kamanda_port = KAMANDA_SERVICE_DEFAULT;
1133 kamanda_port = ntohs(amandad->s_port);
1136 something_started = 1;
1137 while(something_started) {
1138 something_started = 0;
1139 for(dp = startq.head; dp != NULL; dp = dp->next) {
1141 if(hostp->up == HOST_READY) {
1142 something_started = 1;
1146 * dp is no longer on startq, so dp->next is not valid
1147 * and we have to start all over.
1155 while(!empty(waitq)) {
1156 disk_t *dp = dequeue_disk(&waitq);
1157 est(dp)->errstr = "hmm, disk was stranded on waitq";
1158 enqueue_disk(&failq, dp);
1161 while(!empty(pestq)) {
1162 disk_t *dp = dequeue_disk(&pestq);
1164 if(est(dp)->level[0] != -1 && est(dp)->est_size[0] < 0) {
1165 if(est(dp)->est_size[0] == -1) {
1167 "disk %s:%s, estimate of level %d failed: %d.",
1168 dp->host->hostname, dp->name,
1169 est(dp)->level[0], est(dp)->est_size[0]);
1173 "disk %s:%s, estimate of level %d timed out: %d.",
1174 dp->host->hostname, dp->name,
1175 est(dp)->level[0], est(dp)->est_size[0]);
1177 est(dp)->level[0] = -1;
1180 if(est(dp)->level[1] != -1 && est(dp)->est_size[1] < 0) {
1181 if(est(dp)->est_size[1] == -1) {
1183 "disk %s:%s, estimate of level %d failed: %d.",
1184 dp->host->hostname, dp->name,
1185 est(dp)->level[1], est(dp)->est_size[1]);
1189 "disk %s:%s, estimate of level %d timed out: %d.",
1190 dp->host->hostname, dp->name,
1191 est(dp)->level[1], est(dp)->est_size[1]);
1193 est(dp)->level[1] = -1;
1196 if(est(dp)->level[2] != -1 && est(dp)->est_size[2] < 0) {
1197 if(est(dp)->est_size[2] == -1) {
1199 "disk %s:%s, estimate of level %d failed: %d.",
1200 dp->host->hostname, dp->name,
1201 est(dp)->level[2], est(dp)->est_size[2]);
1205 "disk %s:%s, estimate of level %d timed out: %d.",
1206 dp->host->hostname, dp->name,
1207 est(dp)->level[2], est(dp)->est_size[2]);
1209 est(dp)->level[2] = -1;
1212 if((est(dp)->level[0] != -1 && est(dp)->est_size[0] > 0) ||
1213 (est(dp)->level[1] != -1 && est(dp)->est_size[1] > 0) ||
1214 (est(dp)->level[2] != -1 && est(dp)->est_size[2] > 0)) {
1215 enqueue_disk(&estq, dp);
1218 est(dp)->errstr = vstralloc("disk ", dp->name,
1219 ", all estimate timed out", NULL);
1220 enqueue_disk(&failq, dp);
1225 static void getsize(hostp)
1230 char *req = NULL, *errstr = NULL;
1231 int i, estimates, rc, timeout, disk_state, req_len;
1232 char number[NUM_STR_SIZE];
1235 assert(hostp->disks != NULL);
1237 if(hostp->up != HOST_READY) {
1242 * The first time through here we send a "noop" request. This will
1243 * return the feature list from the client if it supports that.
1244 * If it does not, handle_result() will set the feature list to an
1245 * empty structure. In either case, we do the disks on the second
1246 * (and subsequent) pass(es).
1249 if(hostp->features != NULL) { /* sendsize service */
1253 int has_features = am_has_feature(hostp->features,
1254 fe_req_options_features);
1255 int has_hostname = am_has_feature(hostp->features,
1256 fe_req_options_hostname);
1257 int has_maxdumps = am_has_feature(hostp->features,
1258 fe_req_options_maxdumps);
1260 ap_snprintf(number, sizeof(number), "%d", hostp->maxdumps);
1261 req = vstralloc("SERVICE ", "sendsize", "\n",
1263 has_features ? "features=" : "",
1264 has_features ? our_feature_string : "",
1265 has_features ? ";" : "",
1266 has_maxdumps ? "maxdumps=" : "",
1267 has_maxdumps ? number : "",
1268 has_maxdumps ? ";" : "",
1269 has_hostname ? "hostname=" : "",
1270 has_hostname ? hostp->hostname : "",
1271 has_hostname ? ";" : "",
1274 req_len = strlen(req);
1275 req_len += 128; /* room for SECURITY ... */
1277 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1281 if(dp->todo == 0) continue;
1283 if(est(dp)->state != DISK_READY) {
1287 est(dp)->got_estimate = 0;
1288 if(est(dp)->level[0] == -1) {
1289 est(dp)->state = DISK_DONE;
1290 continue; /* ignore this disk */
1293 if(dp->estimate == ES_CLIENT ||
1294 dp->estimate == ES_CALCSIZE) {
1297 for(i = 0; i < MAX_LEVELS; i++) {
1299 char *exclude1 = "";
1300 char *exclude2 = "";
1301 char *excludefree = NULL;
1302 char spindle[NUM_STR_SIZE];
1303 char level[NUM_STR_SIZE];
1304 int lev = est(dp)->level[i];
1306 if(lev == -1) break;
1308 ap_snprintf(level, sizeof(level), "%d", lev);
1309 ap_snprintf(spindle, sizeof(spindle), "%d", dp->spindle);
1310 if(am_has_feature(hostp->features,fe_sendsize_req_options)){
1311 exclude1 = " OPTIONS |";
1312 exclude2 = optionstr(dp, hostp->features, NULL);
1313 excludefree = exclude2;
1316 if(dp->exclude_file &&
1317 dp->exclude_file->nb_element == 1) {
1318 exclude1 = " exclude-file=";
1319 exclude2 = dp->exclude_file->first->name;
1321 else if(dp->exclude_list &&
1322 dp->exclude_list->nb_element == 1) {
1323 exclude1 = " exclude-list=";
1324 exclude2 = dp->exclude_list->first->name;
1327 if(dp->estimate == ES_CALCSIZE &&
1328 !am_has_feature(hostp->features, fe_calcsize_estimate)) {
1329 log_add(L_WARNING,"%s:%s does not support CALCSIZE for estimate, using CLIENT.\n",
1330 hostp->hostname, dp->name);
1331 dp->estimate = ES_CLIENT;
1333 if(dp->estimate == ES_CLIENT)
1336 calcsize = "CALCSIZE ";
1339 l = vstralloc(calcsize,
1344 est(dp)->dumpdate[i], " ", spindle,
1351 l = vstralloc(calcsize,
1355 est(dp)->dumpdate[i], " ", spindle,
1361 amfree(excludefree);
1367 * Allow 2X for err response.
1369 if(req_len + s_len > MAX_DGRAM / 2) {
1377 est(dp)->state = DISK_ACTIVE;
1378 remove_disk(&startq, dp);
1380 else if (dp->estimate == ES_SERVER) {
1384 get_info(dp->host->hostname, dp->name, &info);
1385 for(i = 0; i < MAX_LEVELS; i++) {
1387 int lev = est(dp)->level[i];
1389 if(lev == -1) break;
1390 if(lev == 0) { /* use latest level 0, should do extrapolation */
1394 for(j=NB_HISTORY-2;j>=0;j--) {
1395 if(info.history[j].level == 0) {
1396 est_size = info.history[j].size;
1401 est(dp)->est_size[i] = est_size;
1403 else if(info.inf[lev].size > 1000) { /* stats */
1404 est(dp)->est_size[i] = info.inf[lev].size;
1407 est(dp)->est_size[i] = 1000000;
1410 else if(lev == est(dp)->last_level) {
1411 /* means of all X day at the same level */
1414 long est_size_day[NB_DAY];
1415 int nb_est_day[NB_DAY];
1417 for(j=0;j<NB_DAY;j++) {
1422 for(j=NB_HISTORY-2;j>=0;j--) {
1423 if(info.history[j].level <= 0) continue;
1424 if(info.history[j].level == info.history[j+1].level) {
1425 if(nb_day <NB_DAY-1) nb_day++;
1426 est_size_day[nb_day] += info.history[j].size;
1427 nb_est_day[nb_day]++;
1433 nb_day = info.consecutive_runs + 1;
1434 if(nb_day > NB_DAY-1) nb_day = NB_DAY-1;
1436 while(nb_day > 0 && nb_est_day[nb_day] == 0) nb_day--;
1438 if(nb_est_day[nb_day] > 0) {
1439 est(dp)->est_size[i] =
1440 est_size_day[nb_day] / nb_est_day[nb_day];
1442 else if(info.inf[lev].size > 1000) { /* stats */
1443 est(dp)->est_size[i] = info.inf[lev].size;
1446 est(dp)->est_size[i] = 10000;
1449 else if(lev == est(dp)->last_level + 1) {
1450 /* means of all first day at a new level */
1454 for(j=NB_HISTORY-2;j>=0;j--) {
1455 if(info.history[j].level <= 0) continue;
1456 if(info.history[j].level == info.history[j+1].level + 1 ) {
1457 est_size += info.history[j].size;
1462 est(dp)->est_size[i] = est_size / nb_est;
1464 else if(info.inf[lev].size > 1000) { /* stats */
1465 est(dp)->est_size[i] = info.inf[lev].size;
1468 est(dp)->est_size[i] = 100000;
1472 fprintf(stderr,"%s time %s: got result for host %s disk %s:",
1473 get_pname(), walltime_str(curclock()),
1474 dp->host->hostname, dp->name);
1475 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1476 est(dp)->level[0], est(dp)->est_size[0],
1477 est(dp)->level[1], est(dp)->est_size[1],
1478 est(dp)->level[2], est(dp)->est_size[2]);
1479 est(dp)->state = DISK_DONE;
1480 remove_disk(&startq, dp);
1481 enqueue_disk(&estq, dp);
1484 if(estimates == 0) {
1486 hostp->up = HOST_DONE;
1490 if (conf_etimeout < 0) {
1491 timeout = - conf_etimeout;
1493 timeout = estimates * conf_etimeout;
1495 } else { /* noop service */
1496 req = vstralloc("SERVICE ", "noop", "\n",
1498 "features=", our_feature_string, ";",
1502 * We use ctimeout for the "noop" request because it should be
1503 * very fast and etimeout has other side effects.
1505 timeout = getconf_int(CNF_CTIMEOUT);
1508 #ifdef KRB4_SECURITY
1509 if(hostp->disks->auth == AUTH_KRB4)
1510 rc = make_krb_request(hostp->hostname, kamanda_port, req,
1511 hostp, timeout, handle_result);
1514 rc = make_request(hostp->hostname, amanda_port, req,
1515 hostp, timeout, handle_result);
1517 req = NULL; /* do not own this any more */
1520 errstr = vstralloc("could not resolve hostname \"",
1525 hostp->up = HOST_DONE;
1526 disk_state = DISK_DONE;
1531 hostp->up = HOST_ACTIVE;
1532 disk_state = DISK_ACTIVE;
1535 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1536 if(dp->todo && est(dp)->state == DISK_ACTIVE) {
1537 est(dp)->state = disk_state;
1538 est(dp)->errstr = errstr;
1540 enqueue_disk(destqp, dp);
1546 static disk_t *lookup_hostdisk(hp, str)
1552 for(dp = hp->disks; dp != NULL; dp = dp->hostnext)
1553 if(strcmp(str, dp->name) == 0) return dp;
1559 static void handle_result(p, pkt)
1567 char *msgdisk=NULL, *msgdisk_undo=NULL, msgdisk_undo_ch = '\0';
1568 char *errbuf = NULL;
1576 hostp = (am_host_t *) p->datap;
1577 hostp->up = HOST_READY;
1579 if(p->state == S_FAILED && pkt == NULL) {
1580 if(p->prevstate == S_REPWAIT) {
1581 errbuf = vstralloc("Estimate timeout from ", hostp->hostname,
1585 errbuf = vstralloc("Request to ", hostp->hostname, " timed out.",
1592 fprintf(stderr, "got %sresponse from %s:\n----\n%s----\n\n",
1593 (p->state == S_FAILED) ? "NAK " : "", hostp->hostname, pkt->body);
1596 #ifdef KRB4_SECURITY
1597 if(hostp->disks->auth == AUTH_KRB4 &&
1598 !check_mutual_authenticator(host2key(hostp->hostname), pkt, p)) {
1599 errbuf = vstralloc(hostp->hostname,
1600 "[mutual-authentication failed]",
1606 msgdisk_undo = NULL;
1612 if (s[-2] == '\n') {
1616 #define sc "OPTIONS "
1617 if(strncmp(line, sc, sizeof(sc)-1) == 0) {
1620 #define sc "features="
1621 t = strstr(line, sc);
1622 if(t != NULL && (isspace((int)t[-1]) || t[-1] == ';')) {
1625 am_release_feature_set(hostp->features);
1626 if((hostp->features = am_string_to_feature(t)) == NULL) {
1627 errbuf = vstralloc(hostp->hostname,
1628 ": bad features value: ",
1640 if(strncmp(line, sc, sizeof(sc)-1) == 0) {
1641 t = line + sizeof(sc)-1;
1646 skip_whitespace(t, tch);
1651 * If the "error" is that the "noop" service is unknown, it
1652 * just means the client is "old" (does not support the servie).
1653 * We can ignore this.
1655 if(hostp->features == NULL
1656 && p->state == S_FAILED
1657 && (strcmp(t - 1, "unknown service: noop") == 0
1658 || strcmp(t - 1, "noop: invalid service") == 0)) {
1661 errbuf = vstralloc(hostp->hostname,
1662 (p->state == S_FAILED) ? "NAK " : "",
1672 skip_non_whitespace(t, tch);
1673 msgdisk_undo = t - 1;
1674 msgdisk_undo_ch = *msgdisk_undo;
1675 *msgdisk_undo = '\0';
1677 skip_whitespace(t, tch);
1678 if (sscanf(t - 1, "%d SIZE %ld", &level, &size) != 2) {
1682 dp = lookup_hostdisk(hostp, msgdisk);
1684 *msgdisk_undo = msgdisk_undo_ch; /* for error message */
1685 msgdisk_undo = NULL;
1688 log_add(L_ERROR, "%s: invalid reply from sendsize: `%s'\n",
1689 hostp->hostname, line);
1691 for(i = 0; i < MAX_LEVELS; i++) {
1692 if(est(dp)->level[i] == level) {
1693 est(dp)->est_size[i] = size;
1697 if(i == MAX_LEVELS) {
1698 goto bad_msg; /* this est wasn't requested */
1700 est(dp)->got_estimate++;
1704 if(hostp->up == HOST_READY && hostp->features == NULL) {
1706 * The client does not support the features list, so give it an
1709 dbprintf(("%s: no feature set from host %s\n",
1710 debug_prefix_time(NULL), hostp->hostname));
1711 hostp->features = am_set_default_feature_set();
1714 /* XXX what about disks that only got some estimates... do we care? */
1715 /* XXX amanda 2.1 treated that case as a bad msg */
1717 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1718 if(dp->todo == 0) continue;
1719 if(est(dp)->state != DISK_ACTIVE &&
1720 est(dp)->state != DISK_PARTIALY_DONE) continue;
1722 if(est(dp)->state == DISK_ACTIVE) {
1723 remove_disk(&waitq, dp);
1725 else if(est(dp)->state == DISK_PARTIALY_DONE) {
1726 remove_disk(&pestq, dp);
1729 if(pkt->type == P_REP) {
1730 est(dp)->state = DISK_DONE;
1732 else if(pkt->type == P_PREP) {
1733 est(dp)->state = DISK_PARTIALY_DONE;
1736 if(est(dp)->level[0] == -1) continue; /* ignore this disk */
1739 if(pkt->type == P_PREP) {
1740 fprintf(stderr,"%s: time %s: got partial result for host %s disk %s:",
1741 get_pname(), walltime_str(curclock()),
1742 dp->host->hostname, dp->name);
1743 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1744 est(dp)->level[0], est(dp)->est_size[0],
1745 est(dp)->level[1], est(dp)->est_size[1],
1746 est(dp)->level[2], est(dp)->est_size[2]);
1747 enqueue_disk(&pestq, dp);
1749 else if(pkt->type == P_REP) {
1750 fprintf(stderr,"%s: time %s: got result for host %s disk %s:",
1751 get_pname(), walltime_str(curclock()),
1752 dp->host->hostname, dp->name);
1753 fprintf(stderr," %d -> %ldK, %d -> %ldK, %d -> %ldK\n",
1754 est(dp)->level[0], est(dp)->est_size[0],
1755 est(dp)->level[1], est(dp)->est_size[1],
1756 est(dp)->level[2], est(dp)->est_size[2]);
1757 if((est(dp)->level[0] != -1 && est(dp)->est_size[0] > 0) ||
1758 (est(dp)->level[1] != -1 && est(dp)->est_size[1] > 0) ||
1759 (est(dp)->level[2] != -1 && est(dp)->est_size[2] > 0)) {
1761 if(est(dp)->level[2] != -1 && est(dp)->est_size[2] < 0) {
1763 "disk %s:%s, estimate of level %d failed: %d.",
1764 dp->host->hostname, dp->name,
1765 est(dp)->level[2], est(dp)->est_size[2]);
1766 est(dp)->level[2] = -1;
1768 if(est(dp)->level[1] != -1 && est(dp)->est_size[1] < 0) {
1770 "disk %s:%s, estimate of level %d failed: %d.",
1771 dp->host->hostname, dp->name,
1772 est(dp)->level[1], est(dp)->est_size[1]);
1773 est(dp)->level[1] = -1;
1775 if(est(dp)->level[0] != -1 && est(dp)->est_size[0] < 0) {
1777 "disk %s:%s, estimate of level %d failed: %d.",
1778 dp->host->hostname, dp->name,
1779 est(dp)->level[0], est(dp)->est_size[0]);
1780 est(dp)->level[0] = -1;
1782 enqueue_disk(&estq, dp);
1785 enqueue_disk(&failq, dp);
1786 if(est(dp)->got_estimate) {
1787 est(dp)->errstr = vstralloc("disk ", dp->name,
1788 ", all estimate failed", NULL);
1791 fprintf(stderr, "error result for host %s disk %s: missing estimate\n",
1792 dp->host->hostname, dp->name);
1793 est(dp)->errstr = vstralloc("missing result for ", dp->name,
1794 " in ", dp->host->hostname,
1807 *msgdisk_undo = msgdisk_undo_ch;
1808 msgdisk_undo = NULL;
1810 fprintf(stderr,"got a bad message, stopped at:\n");
1811 fprintf(stderr,"----\n%s\n----\n\n", line);
1812 errbuf = stralloc2("badly formatted response from ", hostp->hostname);
1813 /* fall through to ... */
1818 *msgdisk_undo = msgdisk_undo_ch;
1819 msgdisk_undo = NULL;
1822 for(dp = hostp->disks; dp != NULL; dp = dp->hostnext) {
1823 if(est(dp)->state == DISK_ACTIVE) {
1824 est(dp)->state = DISK_DONE;
1825 remove_disk(&waitq, dp);
1826 enqueue_disk(&failq, dp);
1829 est(dp)->errstr = stralloc(errbuf);
1830 fprintf(stderr, "%s: time %s: error result for host %s disk %s: %s\n",
1831 get_pname(), walltime_str(curclock()),
1832 dp->host->hostname, dp->name, errbuf);
1837 * If there were no disks involved, make sure the error gets
1840 log_add(L_ERROR, "%s", errbuf);
1842 hostp->up = HOST_DONE;
1850 * ========================================================================
1855 static int schedule_order P((disk_t *a, disk_t *b)); /* subroutines */
1856 static int pick_inclevel P((disk_t *dp));
1858 static void analyze_estimate(dp)
1867 fprintf(stderr, "pondering %s:%s... ",
1868 dp->host->hostname, dp->name);
1869 fprintf(stderr, "next_level0 %d last_level %d ",
1870 ep->next_level0, ep->last_level);
1872 if(get_info(dp->host->hostname, dp->name, &info) == 0) {
1876 ep->degr_level = -1;
1879 if(ep->next_level0 <= 0
1880 || (have_info && ep->last_level == 0 && (info.command & FORCE_NO_BUMP))) {
1881 if(ep->next_level0 <= 0) {
1882 fprintf(stderr,"(due for level 0) ");
1885 ep->dump_size = est_tape_size(dp, 0);
1886 if(ep->dump_size <= 0) {
1888 "(no estimate for level 0, picking an incr level)\n");
1889 ep->dump_level = pick_inclevel(dp);
1890 ep->dump_size = est_tape_size(dp, ep->dump_level);
1892 if(ep->dump_size == -1) {
1893 ep->dump_level = ep->dump_level + 1;
1894 ep->dump_size = est_tape_size(dp, ep->dump_level);
1898 total_lev0 += (double) ep->dump_size;
1899 if(ep->last_level == -1 || dp->skip_incr) {
1900 fprintf(stderr,"(%s disk, can't switch to degraded mode)\n",
1901 dp->skip_incr? "skip-incr":"new");
1902 ep->degr_level = -1;
1906 /* fill in degraded mode info */
1907 fprintf(stderr,"(picking inclevel for degraded mode)");
1908 ep->degr_level = pick_inclevel(dp);
1909 ep->degr_size = est_tape_size(dp, ep->degr_level);
1910 if(ep->degr_size == -1) {
1911 ep->degr_level = ep->degr_level + 1;
1912 ep->degr_size = est_tape_size(dp, ep->degr_level);
1914 if(ep->degr_size == -1) {
1915 fprintf(stderr,"(no inc estimate)");
1916 ep->degr_level = -1;
1918 fprintf(stderr,"\n");
1923 fprintf(stderr,"(not due for a full dump, picking an incr level)\n");
1924 /* XXX - if this returns -1 may be we should force a total? */
1925 ep->dump_level = pick_inclevel(dp);
1926 ep->dump_size = est_tape_size(dp, ep->dump_level);
1928 if(ep->dump_size == -1) {
1929 ep->dump_level = ep->last_level;
1930 ep->dump_size = est_tape_size(dp, ep->dump_level);
1932 if(ep->dump_size == -1) {
1933 ep->dump_level = ep->last_level + 1;
1934 ep->dump_size = est_tape_size(dp, ep->dump_level);
1936 if(ep->dump_size == -1) {
1938 ep->dump_size = est_tape_size(dp, ep->dump_level);
1942 fprintf(stderr," curr level %d size %ld ", ep->dump_level, ep->dump_size);
1944 insert_disk(&schedq, dp, schedule_order);
1946 total_size += tt_blocksize_kb + ep->dump_size + tape_mark;
1948 /* update the balanced size */
1949 if(!(dp->skip_full || dp->strategy == DS_NOFULL ||
1950 dp->strategy == DS_INCRONLY)) {
1953 lev0size = est_tape_size(dp, 0);
1954 if(lev0size == -1) lev0size = ep->last_lev0size;
1956 balanced_size += lev0size / runs_per_cycle;
1959 fprintf(stderr,"total size %ld total_lev0 %1.0f balanced-lev0size %1.0f\n",
1960 total_size, total_lev0, balanced_size);
1963 static void handle_failed(dp)
1969 * From George Scott <George.Scott@cc.monash.edu.au>:
1971 * If a machine is down when the planner is run it guesses from historical
1972 * data what the size of tonights dump is likely to be and schedules a
1973 * dump anyway. The dumper then usually discovers that that machine is
1974 * still down and ends up with a half full tape. Unfortunately the
1975 * planner had to delay another dump because it thought that the tape was
1976 * full. The fix here is for the planner to ignore unavailable machines
1977 * rather than ignore the fact that they are unavailable.
1982 if(est(dp)->last_level != -1) {
1984 "Could not get estimate for %s:%s, using historical data.",
1985 dp->host->hostname, dp->name);
1986 analyze_estimate(dp);
1991 errstr = est(dp)->errstr? est(dp)->errstr : "hmm, no error indicator!";
1993 fprintf(stderr, "%s: FAILED %s %s %s 0 [%s]\n",
1994 get_pname(), dp->host->hostname, dp->name, datestamp, errstr);
1996 log_add(L_FAIL, "%s %s %s 0 [%s]", dp->host->hostname, dp->name, datestamp, errstr);
1998 /* XXX - memory leak with *dp */
2002 static int schedule_order(a, b)
2005 * insert-sort by decreasing priority, then
2006 * by decreasing size within priority levels.
2012 diff = est(b)->dump_priority - est(a)->dump_priority;
2013 if(diff != 0) return diff;
2015 ldiff = est(b)->dump_size - est(a)->dump_size;
2016 if(ldiff < 0) return -1; /* XXX - there has to be a better way to dothis */
2017 if(ldiff > 0) return 1;
2022 static int pick_inclevel(dp)
2025 int base_level, bump_level;
2026 long base_size, bump_size;
2029 base_level = est(dp)->last_level;
2031 /* if last night was level 0, do level 1 tonight, no ifs or buts */
2032 if(base_level == 0) {
2033 fprintf(stderr," picklev: last night 0, so tonight level 1\n");
2037 /* if no-full option set, always do level 1 */
2038 if(dp->strategy == DS_NOFULL) {
2039 fprintf(stderr," picklev: no-full set, so always level 1\n");
2043 base_size = est_size(dp, base_level);
2045 /* if we didn't get an estimate, we can't do an inc */
2046 if(base_size == -1) {
2047 base_size = est_size(dp, base_level+1);
2048 if(base_size > 0) /* FORCE_BUMP */
2049 return base_level+1;
2050 fprintf(stderr," picklev: no estimate for level %d, so no incs\n", base_level);
2054 thresh = bump_thresh(base_level, est_size(dp, 0), dp->bumppercent, dp->bumpsize, dp->bumpmult);
2057 " pick: size %ld level %d days %d (thresh %ldK, %d days)\n",
2058 base_size, base_level, est(dp)->level_days,
2059 thresh, dp->bumpdays);
2062 || est(dp)->level_days < dp->bumpdays
2063 || base_size <= thresh)
2066 bump_level = base_level + 1;
2067 bump_size = est_size(dp, bump_level);
2069 if(bump_size == -1) return base_level;
2071 fprintf(stderr, " pick: next size %ld... ", bump_size);
2073 if(base_size - bump_size < thresh) {
2074 fprintf(stderr, "not bumped\n");
2078 fprintf(stderr, "BUMPED\n");
2079 log_add(L_INFO, "Incremental of %s:%s bumped to level %d.",
2080 dp->host->hostname, dp->name, bump_level);
2089 ** ========================================================================
2092 ** We have two strategies here:
2096 ** If we are trying to fit too much on the tape something has to go. We
2097 ** try to delay totals until tomorrow by converting them into incrementals
2098 ** and, if that is not effective enough, dropping incrementals altogether.
2099 ** While we are searching for the guilty dump (the one that is really
2100 ** causing the schedule to be oversize) we have probably trampled on a lot of
2101 ** innocent dumps, so we maintain a "before image" list and use this to
2102 ** put back what we can.
2104 ** 2. Promote dumps.
2106 ** We try to keep the amount of tape used by total dumps the same each night.
2107 ** If there is some spare tape in this run we have a look to see if any of
2108 ** tonights incrementals could be promoted to totals and leave us with a
2109 ** more balanced cycle.
2112 static void delay_one_dump P((disk_t *dp, int delete, ...));
2114 static void delay_dumps P((void))
2115 /* delay any dumps that will not fit */
2117 disk_t *dp, *ndp, *preserve;
2119 long new_total; /* New total_size */
2120 char est_kb[20]; /* Text formatted dump size */
2121 int nb_forced_level_0;
2126 biq.head = biq.tail = NULL;
2129 ** 1. Delay dumps that are way oversize.
2131 ** Dumps larger that the size of the tapes we are using are just plain
2132 ** not going to fit no matter how many other dumps we drop. Delay
2133 ** oversize totals until tomorrow (by which time my owner will have
2134 ** resolved the problem!) and drop incrementals altogether. Naturally
2135 ** a large total might be delayed into a large incremental so these
2136 ** need to be checked for separately.
2139 for(dp = schedq.head; dp != NULL; dp = ndp) {
2140 ndp = dp->next; /* remove_disk zaps this */
2142 if (est(dp)->dump_size <= tape->length) {
2146 /* Format dumpsize for messages */
2147 ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2149 if(est(dp)->dump_level == 0) {
2152 message = "but cannot incremental dump skip-incr disk";
2154 else if(est(dp)->last_level < 0) {
2156 message = "but cannot incremental dump new disk";
2158 else if(est(dp)->degr_level < 0) {
2160 message = "but no incremental estimate";
2164 message = "full dump delayed";
2169 message = "skipping incremental";
2171 delay_one_dump(dp, delete, "dump larger than tape,", est_kb,
2176 ** 2. Delay total dumps.
2178 ** Delay total dumps until tomorrow (or the day after!). We start with
2179 ** the lowest priority (most dispensable) and work forwards. We take
2180 ** care not to delay *all* the dumps since this could lead to a stale
2181 ** mate [for any one disk there are only three ways tomorrows dump will
2182 ** be smaller than todays: 1. we do a level 0 today so tomorows dump
2183 ** will be a level 1; 2. the disk gets more data so that it is bumped
2184 ** tomorrow (this can be a slow process); and, 3. the disk looses some
2185 ** data (when does that ever happen?)].
2188 nb_forced_level_0 = 0;
2190 for(dp = schedq.head; dp != NULL && preserve == NULL; dp = dp->next)
2191 if(est(dp)->dump_level == 0)
2194 /* 2.a. Do not delay forced full */
2195 for(dp = schedq.tail;
2196 dp != NULL && total_size > tape_length;
2200 if(est(dp)->dump_level != 0) continue;
2202 get_info(dp->host->hostname, dp->name, &info);
2203 if(info.command & FORCE_FULL) {
2204 nb_forced_level_0 += 1;
2209 if(dp != preserve) {
2211 /* Format dumpsize for messages */
2212 ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2216 message = "but cannot incremental dump skip-incr disk";
2218 else if(est(dp)->last_level < 0) {
2220 message = "but cannot incremental dump new disk";
2222 else if(est(dp)->degr_level < 0) {
2224 message = "but no incremental estimate";
2228 message = "full dump delayed";
2230 delay_one_dump(dp, delete, "dumps too big,", est_kb,
2235 /* 2.b. Delay forced full if needed */
2236 if(nb_forced_level_0 > 0 && total_size > tape_length) {
2237 for(dp = schedq.tail;
2238 dp != NULL && total_size > tape_length;
2242 if(est(dp)->dump_level == 0 && dp != preserve) {
2244 /* Format dumpsize for messages */
2245 ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2249 message = "but cannot incremental dump skip-incr disk";
2251 else if(est(dp)->last_level < 0) {
2253 message = "but cannot incremental dump new disk";
2255 else if(est(dp)->degr_level < 0) {
2257 message = "but no incremental estimate";
2261 message = "full dump delayed";
2263 delay_one_dump(dp, delete, "dumps too big,", est_kb,
2270 ** 3. Delay incremental dumps.
2272 ** Delay incremental dumps until tomorrow. This is a last ditch attempt
2273 ** at making things fit. Again, we start with the lowest priority (most
2274 ** dispensable) and work forwards.
2277 for(dp = schedq.tail;
2278 dp != NULL && total_size > tape_length;
2282 if(est(dp)->dump_level != 0) {
2284 /* Format dumpsize for messages */
2285 ap_snprintf(est_kb, 20, "%ld KB,", est(dp)->dump_size);
2287 delay_one_dump(dp, 1,
2288 "dumps way too big,",
2290 "must skip incremental dumps",
2296 ** 4. Reinstate delayed dumps.
2298 ** We might not have needed to stomp on all of the dumps we have just
2299 ** delayed above. Try to reinstate them all starting with the last one
2300 ** and working forwards. It is unlikely that the last one will fit back
2301 ** in but why complicate the code?
2304 for(bi = biq.tail; bi != NULL; bi = nbi) {
2309 new_total = total_size + tt_blocksize_kb + bi->size + tape_mark;
2311 new_total = total_size - est(dp)->dump_size + bi->size;
2313 if(new_total <= tape_length && bi->size < tape->length) {
2315 total_size = new_total;
2317 if(bi->level == 0) {
2318 total_lev0 += (double) bi->size;
2320 insert_disk(&schedq, dp, schedule_order);
2323 est(dp)->dump_level = bi->level;
2324 est(dp)->dump_size = bi->size;
2328 if(bi->next == NULL)
2329 biq.tail = bi->prev;
2331 (bi->next)->prev = bi->prev;
2332 if(bi->prev == NULL)
2333 biq.head = bi->next;
2335 (bi->prev)->next = bi->next;
2342 ** 5. Output messages about what we have done.
2344 ** We can't output messages while we are delaying dumps because we might
2345 ** reinstate them later. We remember all the messages and output them
2349 for(bi = biq.head; bi != NULL; bi = nbi) {
2353 fprintf(stderr, "%s: FAILED %s\n", get_pname(), bi->errstr);
2354 log_add(L_FAIL, "%s", bi->errstr);
2358 fprintf(stderr, " delay: %s now at level %d\n",
2359 bi->errstr, est(dp)->dump_level);
2360 log_add(L_INFO, "%s", bi->errstr);
2363 /* Clean up - dont be too fancy! */
2368 fprintf(stderr, " delay: Total size now %ld.\n", total_size);
2375 * Remove a dump or modify it from full to incremental.
2376 * Keep track of it on the bi q in case we can add it back later.
2378 arglist_function1(static void delay_one_dump,
2384 char level_str[NUM_STR_SIZE];
2388 arglist_start(argp, delete);
2390 total_size -= tt_blocksize_kb + est(dp)->dump_size + tape_mark;
2391 if(est(dp)->dump_level == 0) {
2392 total_lev0 -= (double) est(dp)->dump_size;
2395 bi = alloc(sizeof(bi_t));
2397 bi->prev = biq.tail;
2398 if(biq.tail == NULL)
2401 biq.tail->next = bi;
2404 bi->deleted = delete;
2406 bi->level = est(dp)->dump_level;
2407 bi->size = est(dp)->dump_size;
2409 ap_snprintf(level_str, sizeof(level_str), "%d", est(dp)->dump_level);
2410 bi->errstr = vstralloc(dp->host->hostname,
2412 " ", datestamp ? datestamp : "?",
2416 while ((next = arglist_val(argp, char *)) != NULL) {
2417 bi->errstr = newvstralloc(bi->errstr, bi->errstr, sep, next, NULL);
2420 strappend(bi->errstr, "]");
2424 remove_disk(&schedq, dp);
2426 est(dp)->dump_level = est(dp)->degr_level;
2427 est(dp)->dump_size = est(dp)->degr_size;
2428 total_size += tt_blocksize_kb + est(dp)->dump_size + tape_mark;
2435 static int promote_highest_priority_incremental P((void))
2437 disk_t *dp, *dp1, *dp_promote;
2438 long new_size, new_total, new_lev0;
2440 int nb_today, nb_same_day, nb_today2;
2441 int nb_disk_today, nb_disk_same_day;
2444 * return 1 if did so; must update total_size correctly; must not
2445 * cause total_size to exceed tape_length
2449 for(dp = schedq.head; dp != NULL; dp = dp->next) {
2451 est(dp)->promote = -1000;
2453 if(est_size(dp,0) <= 0)
2456 if(est(dp)->next_level0 <= 0)
2459 if(est(dp)->next_level0 > dp->maxpromoteday)
2462 new_size = est_tape_size(dp, 0);
2463 new_total = total_size - est(dp)->dump_size + new_size;
2464 new_lev0 = total_lev0 + new_size;
2469 nb_disk_same_day = 0;
2470 for(dp1 = schedq.head; dp1 != NULL; dp1 = dp1->next) {
2471 if(est(dp1)->dump_level == 0)
2473 else if(est(dp1)->next_level0 == est(dp)->next_level0)
2475 if(strcmp(dp->host->hostname, dp1->host->hostname) == 0) {
2476 if(est(dp1)->dump_level == 0)
2478 else if(est(dp1)->next_level0 == est(dp)->next_level0)
2483 /* do not promote if overflow tape */
2484 if(new_total > tape_length) continue;
2486 /* do not promote if overflow balanced size and something today */
2487 /* promote if nothing today */
2488 if(new_lev0 > balanced_size+balance_threshold && nb_disk_today > 0)
2491 /* do not promote if only one disk due that day and nothing today */
2492 if(nb_disk_same_day == 1 && nb_disk_today == 0) continue;
2494 nb_today2 = nb_today*nb_today;
2495 if(nb_today == 0 && nb_same_day > 1) nb_same_day++;
2497 if(nb_same_day >= nb_today2) {
2498 est(dp)->promote = ((nb_same_day - nb_today2)*(nb_same_day - nb_today2)) +
2499 conf_dumpcycle - est(dp)->next_level0;
2502 est(dp)->promote = -nb_today2 +
2503 conf_dumpcycle - est(dp)->next_level0;
2506 if(!dp_promote || est(dp_promote)->promote < est(dp)->promote) {
2508 fprintf(stderr," try %s:%s %d %d %d = %d\n",
2509 dp->host->hostname, dp->name, nb_same_day, nb_today, est(dp)->next_level0, est(dp)->promote);
2512 fprintf(stderr,"no try %s:%s %d %d %d = %d\n",
2513 dp->host->hostname, dp->name, nb_same_day, nb_today, est(dp)->next_level0, est(dp)->promote);
2520 new_size = est_tape_size(dp, 0);
2521 new_total = total_size - est(dp)->dump_size + new_size;
2522 new_lev0 = total_lev0 + new_size;
2524 total_size = new_total;
2525 total_lev0 = new_lev0;
2526 check_days = est(dp)->next_level0;
2527 est(dp)->degr_level = est(dp)->dump_level;
2528 est(dp)->degr_size = est(dp)->dump_size;
2529 est(dp)->dump_level = 0;
2530 est(dp)->dump_size = new_size;
2531 est(dp)->next_level0 = 0;
2534 " promote: moving %s:%s up, total_lev0 %1.0f, total_size %ld\n",
2535 dp->host->hostname, dp->name,
2536 total_lev0, total_size);
2539 "Full dump of %s:%s promoted from %d day%s ahead.",
2540 dp->host->hostname, dp->name,
2541 check_days, (check_days == 1) ? "" : "s");
2548 static int promote_hills P((void))
2551 struct balance_stats {
2562 /* If we are already doing a level 0 don't bother */
2566 /* Do the guts of an "amadmin balance" */
2567 my_dumpcycle = conf_dumpcycle;
2568 if(my_dumpcycle > 10000) my_dumpcycle = 10000;
2570 sp = (struct balance_stats *)
2571 alloc(sizeof(struct balance_stats) * my_dumpcycle);
2573 for(days = 0; days < my_dumpcycle; days++)
2574 sp[days].disks = sp[days].size = 0;
2576 for(dp = schedq.head; dp != NULL; dp = dp->next) {
2577 days = est(dp)->next_level0; /* This is > 0 by definition */
2578 if(days<my_dumpcycle && !dp->skip_full && dp->strategy != DS_NOFULL &&
2579 dp->strategy != DS_INCRONLY) {
2581 sp[days].size += est(dp)->last_lev0size;
2585 /* Search for a suitable big hill and cut it down */
2587 /* Find the tallest hill */
2589 for(days = 0; days < my_dumpcycle; days++) {
2590 if(sp[days].disks > 1 && sp[days].size > hill_size) {
2591 hill_size = sp[days].size;
2596 if(hill_size <= 0) break; /* no suitable hills */
2598 /* Find all the dumps in that hill and try and remove one */
2599 for(dp = schedq.head; dp != NULL; dp = dp->next) {
2600 if(est(dp)->next_level0 != hill_days ||
2601 est(dp)->next_level0 > dp->maxpromoteday ||
2603 dp->strategy == DS_NOFULL ||
2604 dp->strategy == DS_INCRONLY)
2606 new_size = est_tape_size(dp, 0);
2607 new_total = total_size - est(dp)->dump_size + new_size;
2608 if(new_total > tape_length)
2610 /* We found a disk we can promote */
2611 total_size = new_total;
2612 total_lev0 += new_size;
2613 est(dp)->degr_level = est(dp)->dump_level;
2614 est(dp)->degr_size = est(dp)->dump_size;
2615 est(dp)->dump_level = 0;
2616 est(dp)->next_level0 = 0;
2617 est(dp)->dump_size = new_size;
2620 " promote: moving %s:%s up, total_lev0 %1.0f, total_size %ld\n",
2621 dp->host->hostname, dp->name,
2622 total_lev0, total_size);
2625 "Full dump of %s:%s specially promoted from %d day%s ahead.",
2626 dp->host->hostname, dp->name,
2627 hill_days, (hill_days == 1) ? "" : "s");
2632 /* All the disks in that hill were unsuitable. */
2633 sp[hill_days].disks = 0; /* Don't get tricked again */
2641 * ========================================================================
2644 * XXX - memory leak - we shouldn't just throw away *dp
2646 static void output_scheduleline(dp)
2650 long dump_time = 0, degr_time = 0;
2651 char *schedline = NULL, *degr_str = NULL;
2652 char dump_priority_str[NUM_STR_SIZE];
2653 char dump_level_str[NUM_STR_SIZE];
2654 char dump_size_str[NUM_STR_SIZE];
2655 char dump_time_str[NUM_STR_SIZE];
2656 char degr_level_str[NUM_STR_SIZE];
2657 char degr_size_str[NUM_STR_SIZE];
2658 char degr_time_str[NUM_STR_SIZE];
2659 char *dump_date, *degr_date;
2665 if(ep->dump_size == -1) {
2666 /* no estimate, fail the disk */
2668 "%s: FAILED %s %s %s %d [no estimate]\n",
2670 dp->host->hostname, dp->name, datestamp, ep->dump_level);
2671 log_add(L_FAIL, "%s %s %s %d [no estimate]",
2672 dp->host->hostname, dp->name, datestamp, ep->dump_level);
2676 dump_date = degr_date = (char *)0;
2677 for(i = 0; i < MAX_LEVELS; i++) {
2678 if(ep->dump_level == ep->level[i])
2679 dump_date = ep->dumpdate[i];
2680 if(ep->degr_level == ep->level[i])
2681 degr_date = ep->dumpdate[i];
2684 #define fix_rate(rate) (rate < 1.0 ? DEFAULT_DUMPRATE : rate)
2686 if(ep->dump_level == 0) {
2687 dump_time = ep->dump_size / fix_rate(ep->fullrate);
2689 if(ep->degr_size != -1) {
2690 degr_time = ep->degr_size / fix_rate(ep->incrrate);
2694 dump_time = ep->dump_size / fix_rate(ep->incrrate);
2697 if(ep->dump_level == 0 && ep->degr_size != -1) {
2698 ap_snprintf(degr_level_str, sizeof(degr_level_str),
2699 "%d", ep->degr_level);
2700 ap_snprintf(degr_size_str, sizeof(degr_size_str),
2701 "%ld", ep->degr_size);
2702 ap_snprintf(degr_time_str, sizeof(degr_time_str),
2704 degr_str = vstralloc(" ", degr_level_str,
2710 ap_snprintf(dump_priority_str, sizeof(dump_priority_str),
2711 "%d", ep->dump_priority);
2712 ap_snprintf(dump_level_str, sizeof(dump_level_str),
2713 "%d", ep->dump_level);
2714 ap_snprintf(dump_size_str, sizeof(dump_size_str),
2715 "%ld", ep->dump_size);
2716 ap_snprintf(dump_time_str, sizeof(dump_time_str),
2718 features = am_feature_to_string(dp->host->features);
2719 schedline = vstralloc("DUMP ",dp->host->hostname,
2723 " ", dump_priority_str,
2724 " ", dump_level_str,
2728 degr_str ? degr_str : "",
2731 fputs(schedline, stdout);
2732 fputs(schedline, stderr);