Ejemplo n.º 1
0
void
diskbase_debug(dbref player)
{
    double ph, pm;

    ph = propcache_hits;
    pm = propcache_misses;

    notify(player, "Cache info:");
    notifyf(player, "Propcache hit ratio: %.3f%% (%ld hits / %ld fetches)",
            (100.0 * ph / (ph + pm)), propcache_hits, propcache_misses);
    report_fetchstats(player);

    notifyf(player, "PropLoaded count: %d", proploaded_Q.count);
    notifyf(player, "PropPriority count: %d", proppri_Q.count);
    notifyf(player, "PropChanged count: %d", propchanged_Q.count);
    report_cachestats(player);
    notify(player, "Done.");
}
Ejemplo n.º 2
0
void
do_restrict(dbref player, const char *arg)
{
	if (!strcmp(arg, "on")) {
		wizonly_mode = 1;
		notify(player, "Login access is now restricted to wizards only.");
	} else if (!strcmp(arg, "off")) {
		wizonly_mode = 0;
		notify(player, "Login access is now unrestricted.");
	} else {
		notifyf(player, "Restricted connection mode is currently %s.",
			wizonly_mode ? "on" : "off"
		);
	}
}
Ejemplo n.º 3
0
void
report_cachestats(dbref player)
{
    dbref obj;
    int count, total, checked, gap, ipct;
    time_t when, now;
    double pct;

    notify(player, "LRU proploaded cache time distribution graph.");

    total = proploaded_Q.count;
    checked = 0;
    gap = 0;
    when = now = time(NULL);
    notify(player, "Mins  Objs (%of db) Graph of #objs vs. age.");
    for (; checked < total; when -= 60) {
        count = 0;
        obj = first_ringqueue_obj(&proploaded_Q);
        while (obj != NOTHING) {
            if (DBFETCH(obj)->propstime > (when - 60) && DBFETCH(obj)->propstime <= when)
                count++;
            obj = next_ringqueue_obj(&proploaded_Q, obj);
        }
        checked += count;
        pct = count * 100.0 / total;
        ipct = count * 100 / total;
        if (ipct > 50)
            ipct = 50;
        if (count) {
            if (gap)
                notify(player, "[gap]");
            notifyf(player, "%3ld:%6d (%5.2f%%) %*s", ((now - when) / 60), count, pct, ipct, "*");
            gap = 0;
        } else {
            gap = 1;
        }
    }
}
Ejemplo n.º 4
0
static void
index_file(dbref player, const char *onwhat, const char *file)
{
    FILE *f;
    char buf[BUFFER_LEN];
    char topic[BUFFER_LEN];
    char *p;
    size_t arglen;
    int found;

    *topic = '\0';
    strcpyn(topic, sizeof(topic), onwhat);
    if (*onwhat) {
	strcatn(topic, sizeof(topic), "|");
    }

    if ((f = fopen(file, "rb")) == NULL) {
	notifyf(player, "Sorry, %s is missing.  Management has been notified.", file);
	fprintf(stderr, "help: No file %s!\n", file);
    } else {
	if (*topic) {
	    arglen = strlen(topic);
	    do {
		do {
		    if (!(fgets(buf, sizeof buf, f))) {
			notifyf(player, "Sorry, no help available on topic \"%s\"", onwhat);
			fclose(f);
			return;
		    }
		} while (*buf != '~');
		do {
		    if (!(fgets(buf, sizeof buf, f))) {
			notifyf(player, "Sorry, no help available on topic \"%s\"", onwhat);
			fclose(f);
			return;
		    }
		} while (*buf == '~');
		p = buf;
		found = 0;
		buf[strlen(buf) - 1] = '|';
		while (*p && !found) {
		    if (strncasecmp(p, topic, arglen)) {
			while (*p && (*p != '|'))
			    p++;
			if (*p)
			    p++;
		    } else {
			found = 1;
		    }
		}
	    } while (!found);
	}
	while (fgets(buf, sizeof buf, f)) {
	    if (*buf == '~')
		break;
	    for (p = buf; *p; p++) {
		if (*p == '\n' || *p == '\r') {
		    *p = '\0';
		    break;
		}
	    }
	    if (*buf) {
		notify(player, buf);
	    } else {
		notify(player, "  ");
	    }
	}
	fclose(f);
    }
}
Ejemplo n.º 5
0
/**
 * Implementation of the MUF debugger
 *
 * This implements the command parsing for the MUF debugger.  It also clears
 * temporary bookmarks if this was triggered from a temporary one.
 *
 * This relies on some static globals, so it is not threadsafe.  If the
 * 'prim' debugger command is ever used to trigger the MUF debugger somehow
 * in a recursive fashion, and then you call 'prim' again, it will probably
 * cause havock.
 *
 * @param descr the descriptor of the debugging player
 * @param player the debugging player
 * @param program the program we are debugging
 * @param text the input text from the user
 * @param fr the current frame pointer
 * @return boolean true if the program should exit, false if not
 */
int
muf_debugger(int descr, dbref player, dbref program, const char *text, struct frame *fr)
{
    char cmd[BUFFER_LEN];
    char buf[BUFFER_LEN];
    char buf2[BUFFER_LEN];
    char *ptr, *ptr2, *arg;
    struct inst *pinst;
    int i, j, cnt;
    static struct inst primset[5];
    static struct muf_proc_data temp_muf_proc_data = {
        "__Temp_Debugger_Proc",
        0,
        0,
        NULL
    };

    /*
     * Basic massaging of the input - clearing spaces, finding the
     * argument.
     */
    skip_whitespace(&text);
    strcpyn(cmd, sizeof(cmd), text);

    ptr = cmd;
    remove_ending_whitespace(&ptr);

    for (arg = cmd; *arg && !isspace(*arg); arg++) ;

    if (*arg)
        *arg++ = '\0';

    /* Empty command means repeat last command, if available */
    if (!*cmd && fr->brkpt.lastcmd) {
        strcpyn(cmd, sizeof(cmd), fr->brkpt.lastcmd);
    } else {
        free(fr->brkpt.lastcmd);

        if (*cmd)
            fr->brkpt.lastcmd = strdup(cmd);
    }

    /* delete triggering breakpoint, if it's only temp. */
    j = fr->brkpt.breaknum;

    if (j >= 0 && fr->brkpt.temp[j]) {
        for (j++; j < fr->brkpt.count; j++) {
            fr->brkpt.temp[j - 1] = fr->brkpt.temp[j];
            fr->brkpt.level[j - 1] = fr->brkpt.level[j];
            fr->brkpt.line[j - 1] = fr->brkpt.line[j];
            fr->brkpt.linecount[j - 1] = fr->brkpt.linecount[j];
            fr->brkpt.pc[j - 1] = fr->brkpt.pc[j];
            fr->brkpt.pccount[j - 1] = fr->brkpt.pccount[j];
            fr->brkpt.prog[j - 1] = fr->brkpt.prog[j];
        }

        fr->brkpt.count--;
    }

    fr->brkpt.breaknum = -1;

    /**
     * @TODO This giant if statement is pretty gnarly; we'd be better
     *       of looping over an array of callbacks to break this up
     *       nicer and make it more extensible.
     */

    if (!strcasecmp(cmd, "cont")) {
        /* Nothing to do -- this will continue to next breakpoint */
    } else if (!strcasecmp(cmd, "finish")) {
        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player,
                            "Cannot finish because there are too many breakpoints set.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = fr->system.top - 1;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = program;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "stepi")) {
        i = atoi(arg);

        if (!i)
            i = 1;

        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player, "Cannot stepi because there are too many breakpoints set.",
                            1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = -1;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = i;
        fr->brkpt.prog[j] = NOTHING;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "step")) {
        i = atoi(arg);

        if (!i)
            i = 1;

        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player, "Cannot step because there are too many breakpoints set.",
                            1);

            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = -1;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = i;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = NOTHING;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "nexti")) {
        i = atoi(arg);

        if (!i)
            i = 1;

        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player, "Cannot nexti because there are too many breakpoints set.",
                            1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = fr->system.top;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = i;
        fr->brkpt.prog[j] = program;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "next")) {
        i = atoi(arg);

        if (!i)
            i = 1;

        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player, "Cannot next because there are too many breakpoints set.",
                            1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = fr->system.top;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = i;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = program;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "exec")) {
        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player,
                            "Cannot finish because there are too many breakpoints set.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        if (!(pinst = funcname_to_pc(program, arg))) {
            notify_nolisten(player, "I don't know a function by that name.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        if (fr->system.top >= STACK_SIZE) {
            notify_nolisten(player,
                            "That would exceed the system stack size for this program.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        fr->system.st[fr->system.top].progref = program;
        fr->system.st[fr->system.top++].offset = fr->pc;
        fr->pc = pinst;
        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = fr->system.top - 1;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = program;
        fr->brkpt.bypass = 1;

        return 0;
    } else if (!strcasecmp(cmd, "prim")) {
        /*
         * @TODO The way this works is a little funky.  It looks like
         *       it would be possible to cause some weird havoc if we
         *       manage to run a primitive that in turn triggers muf_debugger
         *
         *       I am uncertain if this is possible; looks like the only
         *       way it could happen is by typing 'prim debugger_break'
         *       but I don't know much about about how muf_debugger is
         *       triggered.  Some digging should be done to make this
         *       safe.
         *
         *       Even better would be to not use statics for this somehow
         *       without introducing a memory leak. (tanabi)
         */
        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player,
                            "Cannot finish because there are too many breakpoints set.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        if (!primitive(arg)) {
            notify_nolisten(player, "I don't recognize that primitive.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        if (fr->system.top >= STACK_SIZE) {
            notify_nolisten(player,
                            "That would exceed the system stack size for this program.", 1);
            add_muf_read_event(descr, player, program, fr);
            return 0;
        }

        primset[0].type = PROG_FUNCTION;
        primset[0].line = 0;
        primset[0].data.mufproc = &temp_muf_proc_data;
        primset[0].data.mufproc->vars = 0;
        primset[0].data.mufproc->args = 0;
        primset[0].data.mufproc->varnames = NULL;

        primset[1].type = PROG_PRIMITIVE;
        primset[1].line = 0;
        primset[1].data.number = get_primitive(arg);

        primset[2].type = PROG_PRIMITIVE;
        primset[2].line = 0;
        primset[2].data.number = IN_RET;

        /* primset[3].data.number = primitive("EXIT"); */

        fr->system.st[fr->system.top].progref = program;
        fr->system.st[fr->system.top++].offset = fr->pc;
        fr->pc = &primset[1];
        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 1;
        fr->brkpt.level[j] = -1;
        fr->brkpt.line[j] = -1;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = &primset[2];
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = program;
        fr->brkpt.bypass = 1;
        fr->brkpt.dosyspop = 1;

        return 0;
    } else if (!strcasecmp(cmd, "break")) {
        add_muf_read_event(descr, player, program, fr);

        if (fr->brkpt.count >= MAX_BREAKS) {
            notify_nolisten(player, "Too many breakpoints set.", 1);
            return 0;
        }

        if (number(arg)) {
            i = atoi(arg);
        } else {
            if (!(pinst = funcname_to_pc(program, arg))) {
                notify_nolisten(player, "I don't know a function by that name.", 1);
                return 0;
            } else {
                i = pinst->line;
            }
        }

        if (!i)
            i = fr->pc->line;

        j = fr->brkpt.count++;
        fr->brkpt.temp[j] = 0;
        fr->brkpt.level[j] = -1;
        fr->brkpt.line[j] = i;
        fr->brkpt.linecount[j] = -2;
        fr->brkpt.pc[j] = NULL;
        fr->brkpt.pccount[j] = -2;
        fr->brkpt.prog[j] = program;
        notify_nolisten(player, "Breakpoint set.", 1);

        return 0;
    } else if (!strcasecmp(cmd, "delete")) {
        add_muf_read_event(descr, player, program, fr);
        i = atoi(arg);

        if (!i) {
            notify_nolisten(player, "Which breakpoint did you want to delete?", 1);
            return 0;
        }

        if (i < 1 || i > fr->brkpt.count) {
            notify_nolisten(player, "No such breakpoint.", 1);
            return 0;
        }

        j = i - 1;

        for (j++; j < fr->brkpt.count; j++) {
            fr->brkpt.temp[j - 1] = fr->brkpt.temp[j];
            fr->brkpt.level[j - 1] = fr->brkpt.level[j];
            fr->brkpt.line[j - 1] = fr->brkpt.line[j];
            fr->brkpt.linecount[j - 1] = fr->brkpt.linecount[j];
            fr->brkpt.pc[j - 1] = fr->brkpt.pc[j];
            fr->brkpt.pccount[j - 1] = fr->brkpt.pccount[j];
            fr->brkpt.prog[j - 1] = fr->brkpt.prog[j];
        }

        fr->brkpt.count--;
        notify_nolisten(player, "Breakpoint deleted.", 1);

        return 0;
    } else if (!strcasecmp(cmd, "breaks")) {
        notify_nolisten(player, "Breakpoints:", 1);

        for (i = 0; i < fr->brkpt.count; i++) {
            ptr = unparse_breakpoint(fr, i);
            notify_nolisten(player, ptr, 1);
        }

        notify_nolisten(player, "*done*", 1);
        add_muf_read_event(descr, player, program, fr);

        return 0;
    } else if (!strcasecmp(cmd, "where")) {
        i = atoi(arg);
        muf_backtrace(player, program, i, fr);
        add_muf_read_event(descr, player, program, fr);
        return 0;
    } else if (!strcasecmp(cmd, "stack")) {
        notify_nolisten(player, "*Argument stack top*", 1);
        i = atoi(arg);

        if (!i)
            i = STACK_SIZE;

        ptr = "";

        for (j = fr->argument.top; j > 0 && i-- > 0;) {
            cnt = 0;

            do {
                strcpyn(buf, sizeof(buf), ptr);
                ptr = insttotext(NULL, 0, &fr->argument.st[--j], buf2, sizeof(buf2), 4000,
                                 program, 1);
                cnt++;
            } while (!strcasecmp(ptr, buf) && j > 0);

            if (cnt > 1)
                notifyf(player, "     [repeats %d times]", cnt);

            if (strcasecmp(ptr, buf))
                notifyf(player, "%3d) %s", j + 1, ptr);
        }

        notify_nolisten(player, "*done*", 1);
        add_muf_read_event(descr, player, program, fr);

        return 0;
    } else if (!strcasecmp(cmd, "list") || !strcasecmp(cmd, "listi")) {
        int startline, endline;

        add_muf_read_event(descr, player, program, fr);

        if ((ptr2 = (char *) strchr(arg, ','))) {
            *ptr2++ = '\0';
        } else {
            ptr2 = "";
        }

        if (!*arg) {
            if (fr->brkpt.lastlisted) {
                startline = fr->brkpt.lastlisted + 1;
            } else {
                startline = fr->pc->line;
            }

            endline = startline + 15;
        } else {
            if (!number(arg)) {
                if (!(pinst = funcname_to_pc(program, arg))) {
                    notify_nolisten(player,
                                    "I don't know a function by that name. (starting arg, 1)",
                                    1);
                    return 0;
                } else {
                    startline = pinst->line;
                    endline = startline + 15;
                }
            } else {
                if (*ptr2) {
                    endline = startline = atoi(arg);
                } else {
                    startline = atoi(arg) - 7;
                    endline = startline + 15;
                }
            }
        }

        if (*ptr2) {
            if (!number(ptr2)) {
                if (!(pinst = funcname_to_pc(program, ptr2))) {
                    notify_nolisten(player,
                                    "I don't know a function by that name. (ending arg, 1)",
                                    1);
                    return 0;
                } else {
                    endline = pinst->line;
                }
            } else {
                endline = atoi(ptr2);
            }
        }

        i = (PROGRAM_CODE(program) + PROGRAM_SIZ(program) - 1)->line;

        if (startline > i) {
            notify_nolisten(player, "Starting line is beyond end of program.", 1);
            return 0;
        }

        if (startline < 1)
            startline = 1;

        if (endline > i)
            endline = i;

        if (endline < startline)
            endline = startline;

        notify_nolisten(player, "Listing:", 1);

        if (!strcasecmp(cmd, "listi")) {
            for (i = startline; i <= endline; i++) {
                pinst = linenum_to_pc(program, i);

                if (pinst) {
                    notifyf_nolisten(player, "line %d: %s", i, (i == fr->pc->line) ?
                                     show_line_prims(program, fr->pc, STACK_SIZE, 1) :
                                     show_line_prims(program, pinst, STACK_SIZE, 0));
                }
            }
        } else {
            list_proglines(player, program, fr, startline, endline);
        }

        fr->brkpt.lastlisted = endline;
        notify_nolisten(player, "*done*", 1);

        return 0;
    } else if (!strcasecmp(cmd, "quit")) {
        notify_nolisten(player, "Halting execution.", 1);
        return 1;
    } else if (!strcasecmp(cmd, "trace")) {
        add_muf_read_event(descr, player, program, fr);

        if (!strcasecmp(arg, "on")) {
            fr->brkpt.showstack = 1;
            notify_nolisten(player, "Trace turned on.", 1);
        } else if (!strcasecmp(arg, "off")) {
            fr->brkpt.showstack = 0;
            notify_nolisten(player, "Trace turned off.", 1);
        } else {
            notifyf_nolisten(player, "Trace is currently %s.",
                             fr->brkpt.showstack ? "on" : "off");
        }

        return 0;
    } else if (!strcasecmp(cmd, "words")) {
        list_program_functions(player, program, arg);
        add_muf_read_event(descr, player, program, fr);
        return 0;
    } else if (!strcasecmp(cmd, "print")) {
        debug_printvar(player, program, fr, arg);
        add_muf_read_event(descr, player, program, fr);
        return 0;
    } else if (!strcasecmp(cmd, "push")) {
        push_arg(player, fr, arg);
        add_muf_read_event(descr, player, program, fr);
        return 0;
    } else if (!strcasecmp(cmd, "pop")) {
        add_muf_read_event(descr, player, program, fr);

        if (fr->argument.top < 1) {
            notify_nolisten(player, "Nothing to pop.", 1);
            return 0;
        }

        fr->argument.top--;
        CLEAR(fr->argument.st + fr->argument.top);
        notify_nolisten(player, "Stack item popped.", 1);

        return 0;
    } else if (!strcasecmp(cmd, "help")) {
        do_helpfile(player, tp_file_man_dir, tp_file_man, "debugger_commands", "");

        return 0;
    } else {
        notify_nolisten(player,
                        "I don't understand that debugger command. Type 'help' for help.", 1);
        add_muf_read_event(descr, player, program, fr);
        return 0;
    }

    return 0;
}
Ejemplo n.º 6
0
void
report_fetchstats(dbref player)
{
    int i, count, slot;
    double sum, minv, maxv;
    time_t now;

    now = time(NULL);
    i = slot = ((now / FETCHSTATS_SLOT_TIME) % FETCHSTATS_SLOTS);

    while (lastfetchslot != slot) {
        lastfetchslot = ((lastfetchslot + 1) % FETCHSTATS_SLOTS);
        fetchstats[lastfetchslot] = 0;
    }

    sum = maxv = 0.0;
    minv = fetchstats[i];
    count = 0;

    for (; count < (FETCHSTATS_INTERVAL1 / FETCHSTATS_SLOT_TIME); count++) {
        if (fetchstats[i] == -1)
            break;
        sum += fetchstats[i];
        if (fetchstats[i] < minv)
            minv = fetchstats[i];
        if (fetchstats[i] > maxv)
            maxv = fetchstats[i];
        i = ((i + FETCHSTATS_SLOTS - 1) % FETCHSTATS_SLOTS);
    }
    notifyf(player, "Disk Fetches %2g minute min/avg/max: %.2f/%.2f/%.2f",
            (FETCHSTATS_INTERVAL1 / 60.0),
            (minv * 60.0 / FETCHSTATS_SLOT_TIME),
            (sum * 60.0 / (FETCHSTATS_SLOT_TIME * count)),
            (maxv * 60.0 / FETCHSTATS_SLOT_TIME));

    for (; count < (FETCHSTATS_INTERVAL2 / FETCHSTATS_SLOT_TIME); count++) {
        if (fetchstats[i] == -1)
            break;
        sum += fetchstats[i];
        if (fetchstats[i] < minv)
            minv = fetchstats[i];
        if (fetchstats[i] > maxv)
            maxv = fetchstats[i];
        i = ((i + FETCHSTATS_SLOTS - 1) % FETCHSTATS_SLOTS);
    }
    notifyf(player, "Disk Fetches %2g minute min/avg/max: %.2f/%.2f/%.2f",
            (FETCHSTATS_INTERVAL2 / 60.0),
            (minv * 60.0 / FETCHSTATS_SLOT_TIME),
            (sum * 60.0 / (FETCHSTATS_SLOT_TIME * count)),
            (maxv * 60.0 / FETCHSTATS_SLOT_TIME));

    for (; count < (FETCHSTATS_INTERVAL3 / FETCHSTATS_SLOT_TIME); count++) {
        if (fetchstats[i] == -1)
            break;
        sum += fetchstats[i];
        if (fetchstats[i] < minv)
            minv = fetchstats[i];
        if (fetchstats[i] > maxv)
            maxv = fetchstats[i];
        i = ((i + FETCHSTATS_SLOTS - 1) % FETCHSTATS_SLOTS);
    }
    notifyf(player, "Disk Fetches %2g minute min/avg/max: %.2f/%.2f/%.2f",
            (FETCHSTATS_INTERVAL3 / 60.0),
            (minv * 60.0 / FETCHSTATS_SLOT_TIME),
            (sum * 60.0 / (FETCHSTATS_SLOT_TIME * count)),
            (maxv * 60.0 / FETCHSTATS_SLOT_TIME));
}