#include <stdio.h> #include <ctype.h> #include <sys/types.h> #include <sys/time.h> #ifdef AMIGA #include "hosts/amiga/nsignal.h" #else #include <signal.h> #include <sys/times.h> #endif #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <time.h> #ifdef MSDOS #define CLOCKS_PER_SEC 1000 #endif #ifndef CLOCKS_PER_SEC #define CLOCKS_PER_SEC CLK_TCK #ifndef CLK_TCK #define CLK_TCK 60 #endif #endif #include <math.h> #include "lint.h" #include "config.h" #include "interpret.h" #include "object.h" #include "wiz_list.h" #include "exec.h" #include "comm.h" struct error_recovery_info toplevel_error_recovery_info = { (struct error_recovery_info*)0, ERROR_RECOVERY_NONE }; struct error_recovery_info *error_recovery_pointer = &toplevel_error_recovery_info; /* * The 'current_time' is updated at every heart beat. */ int current_time; static void cycle_hb_list PROT((void)); extern struct object *command_giver, *current_interactive, *obj_list_destruct; extern int num_player, d_flag; extern struct object *previous_ob, *master_ob; extern int trace_level; extern int tracedepth; struct object *current_heart_beat; void call_heart_beat(), catch_alarm(); void load_first_objects(), prepare_ipc(), shutdowngame(), ed_cmd PROT((char *)), print_prompt(), call_out(), destruct2 PROT((struct object *)); extern int get_message PROT((char *, int)), player_parser PROT((char *)), call_function_interactive PROT((struct interactive *, char *)), resort_free_list(), swap PROT((struct object *)); extern void flush_all_player_mess(); extern int t_flag; int time_to_call_heart_beat; int comm_time_to_call_heart_beat = 0; /* this is set by interrupt, */ /* comm sets time_to_call_heart_beat sometime after */ /* * There are global variables that must be zeroed before any execution. * In case of errors, there will be a longjmp(), and the variables will * have to be cleared explicitely. They are normally maintained by the * code that use them. * * This routine must only be called from top level, not from inside * stack machine execution (as stack will be cleared). */ void clear_state() { extern struct object *previous_ob; extern char *current_file; current_file = 0; current_object = 0; command_giver = 0; current_interactive = 0; previous_ob = 0; current_prog = 0; reset_machine(0); /* Pop down the stack. */ } extern int check_state(); void logon(ob) struct object *ob; { struct svalue *ret; struct object *save = current_object; /* * current_object must be set here, so that the static "logon" in * player.c can be called. */ current_object = ob; ret = apply("logon", ob, 0); if (ret == 0) { add_message("prog %s:\n", ob->name); fatal("Could not find logon on the player %s\n", ob->name); } current_object = save; } /* * Take a player command and parse it. * The command can also come from a NPC. * Beware that 'str' can be modified and extended ! */ int parse_command(str, ob) char *str; struct object *ob; { struct object *save = command_giver; int res; command_giver = ob; res = player_parser(str); command_giver = check_object(save); return res; } #ifdef AMIGA /* Clean up the alarm timer, this is set as atexit() function */ void exit_alarm_timer() { alarm(0); } #endif /* * This is the backend. We will stay here for ever (almost). */ int32 initial_eval_cost = -MAX_COST; int32 eval_cost, assigned_eval_cost; int extra_jobs_to_do = 0; int garbage_collect_to_do = 0; void backend() { char buff[2000]; extern int game_is_being_shut_down; extern int slow_shut_down_to_do; extern int master_will_be_updated; extern mp_int num_dirty_mappings; extern int malloc_privilege; (void)printf("Setting up ipc.\n"); fflush(stdout); prepare_ipc(); (void)signal(SIGHUP, (void(*)())startshutdowngame); (void)signal(SIGUSR1, (void(*)())startmasterupdate); if (!t_flag) { catch_alarm(); call_heart_beat(); } #ifdef AMIGA atexit(exit_alarm_timer); #endif toplevel_error_recovery_info.type = ERROR_RECOVERY_BACKEND; setjmp(toplevel_error_recovery_info.context); /* * We come here after errors, and have to clear some global variables. */ clear_state(); flush_all_player_mess(); while(1) { /* * The call of clear_state() should not really have to be done * once every loop. However, there seem to be holes where the * state is not consistent. If these holes are removed, * then the call of clear_state() can be moved to just before the * while() - statment. *sigh* /Lars */ /* amylaar: I think inconsistencys should be found, rather than * the effects patched */ #ifdef DEBUF if ( check_state() ) { debug_message("Inconsistency in main loop\n"); dump_trace(1); shout_string("Gamedriver shouts: I feel inconsistent!\n"); clear_state(); } #endif CLEAR_EVAL_COST; #ifndef __GNUC__ alloca(0); /* free alloca'd values from deeper levels of nesting */ #endif remove_destructed_objects(); /* marion - before ref checks! */ #ifdef DEBUG if (d_flag > 1) check_a_lot_ref_counts(0); #endif if (extra_jobs_to_do) { current_interactive = 0; if (game_is_being_shut_down) { command_giver = 0; /* This statement was removed from the end * of the main loop. We have to compensate * for this. */ shutdowngame(); } if (master_will_be_updated) { extern struct object dummy_current_object_for_loads; emergency_destruct(master_ob); master_will_be_updated = 0; /* maybe you'll want the new master to reload the player files * as well :-) */ current_object = &dummy_current_object_for_loads; apply_master_ob("external_master_reload", 0); current_object = 0; } if (garbage_collect_to_do) { extern void garbage_collection(); command_giver = 0; garbage_collection(); garbage_collect_to_do = 0; if (slow_shut_down_to_do) { int tmp = slow_shut_down_to_do; slow_shut_down_to_do = 0; malloc_privilege = MALLOC_MASTER; slow_shut_down(tmp); } malloc_privilege = MALLOC_USER; } extra_jobs_to_do = 0; if (num_dirty_mappings) { extern void compact_mappings PROT((mp_int)); compact_mappings(num_dirty_mappings+80 >> 5); malloc_privilege = MALLOC_USER; } } if (get_message(buff, sizeof buff)) { void update_load_av PROT((void)); update_load_av(); /* * Now we have a string from the player. This string can go to * one of several places. If it is prepended with a '!', then * it is an escape from the 'ed' editor, so we send it * as a command to the parser. * If any object function is waiting for an input string, then * send it there. * Otherwise, send the string to the parser. * The player_parser() will find that current_object is 0, and * then set current_object to point to the object that defines * the command. This will enable such functions to be static. */ current_object = 0; current_interactive = command_giver; #ifdef DEBUG if (!command_giver->interactive) fatal("Non interactive player in main loop !\n"); #endif tracedepth = 0; if (buff[0] == '!' && command_giver->super) { if (command_giver->interactive->noecho) { add_message("%s\n", buff); } parse_command(buff+1, command_giver); } else if (command_giver->interactive->ed_buffer) ed_cmd(buff); else if (call_function_interactive(command_giver->interactive,buff)) ; /* Do nothing ! */ else parse_command(buff, command_giver); /* * Print a prompt if player is still here. */ if (command_giver && command_giver->interactive) print_prompt(); } if (time_to_call_heart_beat) call_heart_beat(); } /* end of main loop */ } /* * Despite the name, this routine takes care of several things. * It will loop through all objects once every 15 minutes. * * If an object is found in a state of not having done reset, and the * delay to next reset has passed, then reset() will be done. * * If the object has a existed more than the time limit given for swapping, * then 'clean_up' will first be called in the object, after which it will * be swapped out if it still exists. * * There are some problems if the object self-destructs in clean_up, so * special care has to be taken of how the linked list is used. */ static void look_for_objects_to_swap() { extern long time_to_swap; /* marion - for invocation parameter */ static int next_time; static struct object *next_ob; /* don't change back with longjmp() */ struct object *ob; struct error_recovery_info error_recovery_info; union { double dummy; /* force alignment */ char c[sizeof(struct program)+SCAN_SWAP_BUFSIZE]; } swapbuf; if (current_time < next_time) return; /* Not time to look yet */ #if TIME_TO_SWAP >= 15 * 60 next_time = current_time + 15 * 60; /* Next time is in 15 minutes */ #else next_time = current_time + TIME_TO_SWAP; #endif /* * Objects object can be destructed, which means that * next object to investigate is saved in next_ob. If very unlucky, * that object can be destructed too. In that case, the loop is simply * restarted. */ set_swapbuf(swapbuf.c); next_ob = obj_list; error_recovery_info.last = error_recovery_pointer; error_recovery_info.type = ERROR_RECOVERY_BACKEND; error_recovery_pointer = &error_recovery_info; if (setjmp(error_recovery_info.context)) { /* amylaar */ clear_state(); debug_message("Error in look_for_objects_to_swap.\n"); } for (; ob = next_ob; ) { int time_since_ref; if (ob->flags & O_DESTRUCTED) { ob = obj_list; /* restart */ } next_ob = ob->next_all; /* * Check reference time before reset() is called. */ time_since_ref = current_time - ob->time_of_ref; /* * Should this object have reset(1) called ? */ if (ob->next_reset < current_time && !(ob->flags & O_RESET_STATE)) { if (d_flag) fprintf(stderr, "RESET %s\n", ob->name); CLEAR_EVAL_COST; command_giver = 0; trace_level = 0; reset_object(ob, 1); } #if TIME_TO_CLEAN_UP > 0 /* * Has enough time passed, to give the object a chance * to self-destruct ? Save the O_RESET_STATE, which will be cleared. * * Only call clean_up in objects that has defined such a function. * * Only if the clean_up returns a non-zero value, will it be called * again. */ else if (time_since_ref > TIME_TO_CLEAN_UP && (ob->flags & O_WILL_CLEAN_UP)) { int was_swapped = ob->flags & O_SWAPPED ; int save_reset_state = ob->flags & O_RESET_STATE; struct svalue *svp; if (d_flag) fprintf(stderr, "clean up %s\n", ob->name); /* * Supply a flag to the object that says if this program * is inherited by other objects. Cloned objects might as well * believe they are not inherited. Swapped objects will not * have a ref count > 1 (and will have an invalid ob->prog * pointer). */ push_number(ob->flags & O_CLONE ? 0 : ( ob->flags & O_SWAPPED ? 1 : ob->prog->ref) ); CLEAR_EVAL_COST; command_giver = 0; trace_level = 0; svp = apply("clean_up", ob, 1); if (ob->flags & O_DESTRUCTED) continue; if ((!svp || (svp->type == T_NUMBER && svp->u.number == 0)) && was_swapped ) ob->flags &= ~O_WILL_CLEAN_UP; ob->flags |= save_reset_state; } #endif /* TIME_TO_CLEAN_UP > 0 */ #if TIME_TO_SWAP > 0 /* * At last, there is a possibility that the object can be swapped * out. */ if (ob->flags & O_SWAPPED || time_since_ref < time_to_swap) continue; if (ob->flags & O_HEART_BEAT) continue; if (d_flag) fprintf(stderr, "swap %s\n", ob->name); swap(ob); /* See if it is possible to swap out to disk */ #endif } set_swapbuf((char *)0); error_recovery_pointer = error_recovery_info.last; } /* * Call all heart_beat() functions in all objects. Also call the next reset, * and the call out. * We do heart beats by moving each object done to the end of the heart beat * list before we call its function, and always using the item at the head * of the list as our function to call. We keep calling heart beats until * a timeout or we have done num_heart_objs calls. It is done this way so * that objects can delete heart beating objects from the list from within * their heart beat without truncating the current round of heart beats. * * Set command_giver to current_object if it is a living object. If the object * is shadowed, check the shadowed object if living. There is no need to save * the value of the command_giver, as the caller resets it to 0 anyway. */ static struct object * hb_list = 0; /* head */ static struct object * hb_tail = 0; /* for sane wrap around */ static int num_hb_objs = 0; /* so we know when to stop! */ static int num_hb_calls = 0; /* stats */ static float perc_hb_probes = 100.0; /* decaying avge of how many complete */ void call_heart_beat() { struct object *ob, *hide_current = current_object; int num_done = 0; time_to_call_heart_beat = 0; /* interrupt loop if we take too long */ comm_time_to_call_heart_beat = 0; #ifndef MSDOS /* If the host is swapping madly, it can be too late here to reinstate * the SIGALRM handler here. */ alarm(2); #else start_timer(2); #endif current_time = get_current_time(); current_interactive = 0; if ((num_player > 0) && hb_list) { num_hb_calls++; while (hb_list && #ifndef MSDOS !comm_time_to_call_heart_beat #else !timer_expired() #endif && (num_done < num_hb_objs)) { num_done++; cycle_hb_list(); ob = hb_tail; /* now at end */ #ifdef DEBUG if (!(ob->flags & O_HEART_BEAT)) fatal("Heart beat not set in object on heart beat list!"); if (ob->flags & O_SWAPPED) fatal("Heart beat in swapped object.\n"); #endif /* move ob to end of list, do ob */ if (ob->prog->heart_beat == -1) continue; current_prog = ob->prog; current_object = ob; current_heart_beat = ob; command_giver = ob; while(command_giver->shadowing) command_giver = command_giver->shadowing; if (!(command_giver->flags & O_ENABLE_COMMANDS)) { command_giver = 0; trace_level = 0; } else trace_level = command_giver->interactive ? command_giver->interactive->trace_level : 0; if (ob->user) ob->user->heart_beats++; CLEAR_EVAL_COST; call_function(ob->prog, ob->prog->heart_beat); } if (num_hb_objs) perc_hb_probes = 100 * (float) num_done / num_hb_objs; else perc_hb_probes = 100.0; } current_heart_beat = 0; current_object = 0; look_for_objects_to_swap(); call_out(); /* some things depend on this, even without players! */ command_giver = 0; trace_level = 0; current_object = hide_current; flush_all_player_mess(); wiz_decay(); #ifdef MUDWHO sendmudwhoinfo(); #endif } /* * Take the first object off the heart beat list, place it at the end */ static void cycle_hb_list() { struct object * ob; if (!hb_list) fatal("Cycle heart beat list with empty list!"); if (hb_list == hb_tail) return; /* 1 object on list */ ob = hb_list; hb_list = hb_list -> next_heart_beat; hb_tail -> next_heart_beat = ob; hb_tail = ob; ob->next_heart_beat = 0; } /* * add or remove an object from the heart beat list; does the major check... * If an object removes something from the list from within a heart beat, * various pointers in call_heart_beat could be stuffed, so we must * check current_heart_beat and adjust pointers. */ int set_heart_beat(ob, to) struct object * ob; int to; { struct object * o = hb_list; struct object * oprev = 0; if (ob->flags & O_DESTRUCTED) return 0; if (to) to = 1; while (o && o != ob) { if (!(o->flags & O_HEART_BEAT)) fatal("Found disabled object in the active heart beat list!\n"); oprev = o; o = o->next_heart_beat; } if (!o && (ob->flags & O_HEART_BEAT)) fatal("Couldn't find enabled object in heart beat list!"); if (to == ((ob->flags & O_HEART_BEAT) != 0)) return(0); if (to) { ob->flags |= O_HEART_BEAT; if (ob->next_heart_beat) fatal("Dangling pointer to next_heart_beat in object!"); ob->next_heart_beat = hb_list; hb_list = ob; if (!hb_tail) hb_tail = ob; num_hb_objs++; cycle_hb_list(); /* Added by Linus. 911104 */ } else { /* remove all refs */ ob->flags &= ~O_HEART_BEAT; if (hb_list == ob) hb_list = ob->next_heart_beat; if (hb_tail == ob) hb_tail = oprev; if (oprev) oprev->next_heart_beat = ob->next_heart_beat; ob->next_heart_beat = 0; num_hb_objs--; } return(1); } /* * sigh. Another status function. */ int heart_beat_status(verbose) int verbose; { char buf[20]; if (verbose) { add_message("\nHeart beat information:\n"); add_message("-----------------------\n"); add_message("Number of objects with heart beat: %d, starts: %d\n", num_hb_objs, num_hb_calls); sprintf(buf, "%.2f", perc_hb_probes); add_message("Percentage of HB calls completed last time: %s\n", buf); } return 0; } /* * There is a file with a list of objects to be initialized at * start up. */ void load_first_objects() { /* Old version used when o_flag true /JnA */ FILE *f; char buff[1000]; char *p; extern int e_flag; #ifdef AMIGA #define tms_total(tms) (tms) #define times(tmsp) ( *(tmsp) = clock() ) clock_t tms1, tms2; #else #ifdef MSDOS #define tms_total(tms) (tms) #define times(tmsp) milliseconds(tmsp) long tms1 = 0L, tms2; #else #define tms_total(tms) ((tms).tms_utime + (tms).tms_stime) struct tms tms1, tms2; #endif #endif if (e_flag) return; (void)printf("Loading init file %s\n", INIT_FILE); f = fopen(INIT_FILE, "r"); if (f == 0) return; toplevel_error_recovery_info.type = ERROR_RECOVERY_BACKEND; if (setjmp(toplevel_error_recovery_info.context)) { clear_state(); add_message("Anomaly in the fabric of world space.\n"); } times(&tms1); current_object = master_ob; while(1) { if (fgets(buff, sizeof buff, f) == NULL) break; if (buff[0] == '#') continue; p = strchr(buff, '\n'); if (p != 0) *p = 0; if (buff[0] == '\0') continue; (void)printf("Preloading: %s", buff); fflush(stdout); CLEAR_EVAL_COST; (void)find_object(buff); #ifdef MALLOC_malloc resort_free_list(); #endif times(&tms2); (void)printf(" %.2f\n", ( tms_total(tms2) - tms_total(tms1) ) / (double)CLOCKS_PER_SEC); tms1 = tms2; fflush(stdout); } toplevel_error_recovery_info.type = ERROR_RECOVERY_NONE; fclose(f); } /* * New version used when not in -o mode. The epilog() in master.c is * supposed to return an array of files (castles in 2.4.5) to load. The array * returned by apply() will be freed at next call of apply(), which means that * the ref count has to be incremented to protect against deallocation. * * The master object is asked to do the actual loading. */ void preload_objects(eflag) int eflag; { struct vector *prefiles; struct svalue *ret; static int ix0; int ix; push_number(eflag); ret = apply_master_ob("epilog", 1); if ((ret == 0) || (ret->type != T_POINTER)) return; else prefiles = ret->u.vec; if ((prefiles == 0) || (prefiles->size < 1)) return; prefiles->ref++; ix0 = -1; toplevel_error_recovery_info.type = ERROR_RECOVERY_BACKEND; if (setjmp(toplevel_error_recovery_info.context)) { clear_state(); add_message("Anomaly in the fabric of world space.\n"); } while ((ix = ++ix0) < prefiles->size) { if (prefiles->item[ix].type != T_STRING) continue; CLEAR_EVAL_COST; push_string_malloced(prefiles->item[ix].u.string); (void)apply_master_ob("preload", 1); #ifdef MALLOC_malloc resort_free_list(); #endif } free_vector(prefiles); toplevel_error_recovery_info.type = ERROR_RECOVERY_NONE; } /* * catch alarm, set flag for comms code and heart_beat to catch. * comms code sets time_to_call_heart_beat for the backend when * it has completed the current round of player commands. */ void catch_alarm() { #ifndef MSDOS (void)signal(SIGALRM, catch_alarm); #endif comm_time_to_call_heart_beat = 1; } /* * All destructed objects are moved int a sperate linked list, * and deallocated after program execution. */ void remove_destructed_objects() { struct object *ob, *next; if (obj_list_replace) { extern void replace_programs(); replace_programs(); } for (ob=obj_list_destruct; ob; ob = next) { next = ob->next_all; destruct2(ob); } obj_list_destruct = 0; } /* * Append string to file. Return 0 for failure, otherwise 1. */ int write_file(file, str) char *file; char *str; { FILE *f; file = check_valid_path(file, current_object, "write_file", 1); if (!file) return 0; f = fopen(file, "a"); if (f == 0) { if (errno == EMFILE || errno == ENFILE) { extern void lex_close(); extern void push_apply_value(), pop_apply_value(); /* lex_close() calls lexerror(). lexerror() calls yyerror(). * yyerror calls smart_log(). smart_log calls apply_master_ob(). * This is why the value of file needs to be preserved. */ push_apply_value(); lex_close(); pop_apply_value(); f = fopen(file, "a"); } if (f == 0) { perror("write_file"); error("Wrong permissions for opening file %s for append.\n", file); } } fwrite(str, strlen(str), 1, f); fclose(f); return 1; } #if ( defined( atarist ) && !defined ( minix ) ) || defined( MSDOS ) #define MSDOS_FS #endif char *read_file(file,start,len) char *file; int start,len; { struct stat st; FILE *f; char *str,*p,*p2,*end,c; int size; if (len < 0 && len != -1) return 0; file = check_valid_path(file, current_object, "read_file", 0); if (!file) return 0; f = fopen(file, "r"); if (f == 0) return 0; if (fstat(fileno(f), &st) == -1) fatal("Could not stat an open file.\n"); size = st.st_size; if (size > READ_FILE_MAX_SIZE) { if ( start || len ) size = READ_FILE_MAX_SIZE; else { fclose(f); return 0; } } if (!start) start = 1; if (!len) len = READ_FILE_MAX_SIZE; str = xalloc(size + 2); if (!str) { fclose(f); return 0; } *str++ = ' '; /* this way, we can always read the 'previous' char... */ str[size] = '\0'; do { if (size > st.st_size) size = st.st_size; if (!size && start > 1 || fread(str, size, 1, f) != 1) { fclose(f); xfree(str-1); return 0; } st.st_size -= size; end = str+size; for (p=str; ( p2=memchr(p,'\n',end-p) ) && --start; ) p=p2+1; } while ( start > 1 ); for (p2=str; p != end; ) { c = *p++; if ( !isprint(c) && !isspace(c) ) c=' '; if ( c == '\n' ) { #ifdef MSDOS_FS if ( p2[-1] == '\r' ) p2--; #endif if (!--len) { *p2++=c; break; } } *p2++=c; } if ( len && st.st_size ) { size -= ( p2-str) ; if (size > st.st_size) size = st.st_size; if (fread(p2, size, 1, f) != 1) { fclose(f); xfree(str-1); return 0; } st.st_size -= size; end = p2+size; for (p=p2; p != end; ) { c = *p++; if ( !isprint(c) && !isspace(c) ) c=' '; if ( c == '\n' ) { #ifdef MSDOS_FS if ( p2[-1] == '\r' ) p2--; #endif if (!--len) { *p2++ = c; break; } } *p2++ = c; } if ( st.st_size && len > 0) { /* tried to read more than READ_MAX_FILE_SIZE */ fclose(f); xfree(str-1); return 0; } } *p2='\0'; fclose(f); #if 0 if ( st.st_size = (p2-str) ) return str; #endif p2=string_copy(str); xfree(str-1); return p2; } char *read_bytes(file,start,len) char *file; int start,len; { struct stat st; char *str,*p; int size, f; if (len < 0) return 0; if(len > MAX_BYTE_TRANSFER) return 0; file = check_valid_path(file, current_object, "read_bytes", 0); if (!file) return 0; f = ixopen(file, O_RDONLY); if (f < 0) return 0; if (fstat(f, &st) == -1) fatal("Could not stat an open file.\n"); size = st.st_size; if(start < 0) start = size + start; if (start >= size) { close(f); return 0; } if ((start+len) > size) len = (size - start); if ((size = lseek(f,start, 0)) < 0) { close(f); return 0; } str = xalloc(len + 1); if (!str) { close(f); return 0; } size = read(f, str, len); close(f); if (size <= 0) { xfree(str); return 0; } /* We want to allow all characters to pass untouched! for (il=0;il<size;il++) if (!isprint(str[il]) && !isspace(str[il])) str[il] = ' '; str[il] = 0; */ /* * The string has to end to '\0'!!! */ str[size] = '\0'; p = string_copy(str); xfree(str); return p; } int write_bytes(file,start,str) char *file, *str; int start; { struct stat st; mp_int size, len; int f; file = check_valid_path(file, current_object, "write_bytes", 1); if (!file) return 0; len = strlen(str); if(len > MAX_BYTE_TRANSFER) return 0; f = ixopen(file, O_WRONLY); if (f < 0) return 0; if (fstat(f, &st) == -1) fatal("Could not stat an open file.\n"); size = st.st_size; if(start < 0) start = size + start; if (start > size) { close(f); return 0; } if ((size = lseek(f,start, 0)) < 0) { close(f); return 0; } size = write(f, str, len); close(f); if (size <= 0) { return 0; } return 1; } int file_size(file) char *file; { struct stat st; file = check_valid_path(file, current_object, "file_size", 0); if (!file) return -1; if (ixstat(file, &st) == -1) return -1; if (S_IFDIR & st.st_mode) return -2; return st.st_size; } static double load_av = 0.0; void update_load_av() { extern double consts[5]; extern int current_time; static int last_time; int n; double c; static int acc = 0; acc++; if (current_time == last_time) return; n = current_time - last_time; if (n < sizeof consts / sizeof consts[0]) c = consts[n]; else c = exp(- n / 900.0); load_av = c * load_av + acc * (1 - c) / n; last_time = current_time; acc = 0; } static double compile_av = 0.0; void update_compile_av(lines) int lines; { extern double consts[5]; extern int current_time; static int last_time; int n; double c; static int acc = 0; acc += lines; if (current_time == last_time) return; n = current_time - last_time; if (n < sizeof consts / sizeof consts[0]) c = consts[n]; else c = exp(- n / 900.0); compile_av = c * compile_av + acc * (1 - c) / n; last_time = current_time; acc = 0; } char *query_load_av() { static char buff[100]; sprintf(buff, "%.2f cmds/s, %.2f comp lines/s", load_av, compile_av); return buff; } /* * Constructs an array of all objects that have a heart_beat. */ struct svalue *heart_beat_info(sp) struct svalue *sp; { int i; struct object *ob; struct vector *vec; struct svalue *v; for (i = 0, ob = hb_list; ob; i++, ob = ob->next_heart_beat); vec = allocate_array(i); for (v = vec->item, ob = hb_list; ob; v++, ob = ob->next_heart_beat) { v->type = T_OBJECT; v->u.ob = ob; add_ref(ob, "heart_beat_info"); } sp++; sp->type = T_POINTER; sp->u.vec = vec; return sp; }