/* ps.c: a program to print MiNT process statuses Coded: 3/24/1991 by Tony Reynolds, cctony@sgisci1.ocis.olemiss.edu For use with: MiNT release 0.7 Updated: 3/29/1991 by Eric Smith for MiNT version 0.8 Updated and completely revised from 'ps' into 'top' by AKP 8/91. Link with -lcurses and -ltermcap. Beware! Bammi's GNU curses makefile actually links the termcap object files into curses.olb, so changing them and recompiling only termcap.olb will have no effect! Updated some more 11/91 by Allan Pratt for MiNT version 0.9 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef PCTXTSIZE #define PCTXTSIZE (('P'<<8)|3) #endif #define MINWIDTH 36 /* this is the width of everything before the command */ #define DEF_PTABSIZ 99 /* this is how many process' worth of table to have */ struct status { /* defines values for internal->external process states */ int mint; /* what MiNT knows */ char desc[6]; /* a textual description for above */ } proc_stat[] = { 0, "Run ", /* really "run" but "top" is always running! */ 0x01, "Run ", 0x20, "Wait ", 0x21, "Sleep", 0x22, "Exit ", 0x02, "TSR ", 0x24, "Stop ", -1, "?????" }; typedef struct _context { long regs[15]; /* registers d0-d7, a0-a6 */ long usp; /* user stack pointer (a7) */ short sr; /* status register */ long pc; /* program counter */ long ssp; /* supervisor stack pointer */ long term_vec; /* GEMDOS terminate vector (0x102) */ /* these fields were added in MiNT 0.9 -- ERS */ char fstate[216]; /* FPU internal state */ char fregs[12*8]; /* registers fp0-fp7 */ long fctrl[3]; /* FPCR/FPSR/FPIAR */ short sfmt; /* stack frame format identifier */ long iar; /* instruction address */ short internal[4]; /* four words of internal info */ } CONTEXT; struct pinfo { long magic; char *base; short pid, ppid, pgrp; short ruid, rgid; short euid, egid; short flags; short pri; short wait_q; long wait_cond; unsigned long systime, usrtime, chldstime, chldutime; unsigned long maxmem, maxdata, maxcore, maxcpu; short domain; short curpri; }; /* open the process named "name" */ int open_proc(name) char *name; { static char pname[32] = "u:\\proc\\"; strcpy(pname+8, name); return open(pname, 0); } struct ptab { short pid; short used; long lasttime; char line[2]; /* make sure sizeof(struct ptab) is even */ } *ptab; /* an array of these gets malloc'ed */ #define INCPTAB(p) ((struct ptab *)(((char *)p) + sizeof(struct ptab) + width)) int garbage = 0; /* when set, screen needs total update */ void quit(sig) long sig; { /* move to bottom of screen */ mvcur(0, COLS - 1, LINES - 1, 0); endwin(); exit(0); } void usage() { puts("Usage: top [-s sleeptime] [-w width] [-n nlines] [-r]"); puts("-r sorts procs in decreasing PID order; usually increasing PID."); puts("TOP ONLY RUNS UNDER MiNT. Use '?' to get help."); exit(1); } void dokill(s) char *s; { int signum = SIGTERM; int pid; char *p; char hold; while (isspace(*s)) s++; if (*s == '-') { p = ++s; while (*p && !isspace(*p)) p++; hold = *p; *p = '\0'; if (p != s) signum = atoi(s); *p = hold; if (!signum) signum = SIGTERM; s = p; } while (isspace(*s)) s++; while (*s) { p = s; while (*p && !isspace(*p)) p++; hold = *p; *p = '\0'; pid = atoi(s); *p = hold; s = p; if (pid) (void)Pkill(pid,signum); while (isspace(*s)) s++; } } void dorenice(s) char *s; { int niceval; int pid; char *p; char hold; while (isspace(*s)) s++; p = s; while (*p && !isspace(*p)) p++; hold = *p; *p = '\0'; niceval = atoi(s); *p = hold; s = p; while (isspace(*s)) s++; while (*s) { p = s; while (*p && !isspace(*p)) p++; hold = *p; *p = '\0'; pid = atoi(s); *p = hold; s = p; if (pid) (void)Prenice(pid,niceval); while (isspace(*s)) s++; } } int cmp_incr_pid(void *p1, void *p2) { struct ptab *pt1 = p1, *pt2 = p2; int pid1 = pt1->pid, pid2 = pt2->pid; if (pid1 == -1 && pid2 == -1) return 0; if (pid1 == -1) return 1; /* send invalid procs to bottom */ if (pid2 == -1) return -1; return pid1 - pid2; } int cmp_decr_pid(void *p1, void *p2) { struct ptab *pt1 = p1, *pt2 = p2; int pid1 = pt1->pid, pid2 = pt2->pid; if (pid1 == -1 && pid2 == -1) return 0; if (pid1 == -1) return 1; /* send invalid procs to bottom */ if (pid2 == -1) return -1; return pid2 - pid1; } char helpstr[] = " s[leeptime] w[idth] n[lines] k[ill] r[enice] h[elp] p[riority] q[uit]\r"; void main(argc, argv) int argc; char **argv; { int repeat_time = 0; _DTA mydta; int fd; int fserror=0; long place, procaddr; int pid, ppid; char *cmdline; struct status *statp; char bpage[sizeof(BASEPAGE)]; struct pinfo proc; int thispid; struct ptab *pt; char *c; unsigned long ptime, hour, min, sec, frac; long realtime; long lastclock = clock(); long thisclock; int i; long deltime; long ctxtsize; int nprocs, nrunning; long cummemory, idletime; int width = 0; long readfds; char ibuf[80]; int ichar; int taillen, cmdlen; int tailstart; int nlines = 0; int helpline = 0; int ptabsiz = DEF_PTABSIZ; int len; int (*cmpfunc)(); extern int __mint; int ppid_mode = 1; cmpfunc = cmp_incr_pid; if (!__mint) usage(); --argc, ++argv; while (argc) { if (**argv != '-') usage(); switch(argv[0][1]) { case 'r': cmpfunc = cmp_decr_pid; break; case 's': if (argc == 0) usage(); if ((repeat_time = atoi(argv[1])) == 0) usage(); ++argv, --argc; break; case 'w': if (argc == 0) usage(); if ((width = atoi(argv[1])) <= 0) usage(); if (width < MINWIDTH) width = MINWIDTH; ++argv, --argc; break; case 'n': if (argc == 0) usage(); if ((nlines = atoi(argv[1])) <= 0) usage(); ++argv, --argc; break; default: usage(); } --argc, ++argv; } Fsetdta(&mydta); /* give the OS my new DTA */ initscr(); if (!width) width = COLS-1; /* default if it's unset */ width &= ~1; /* make sure width is even, too */ if (!repeat_time) repeat_time = 5; if (LINES < 4) { puts("top: can't run on a screen with < 4 lines"); exit(1); } if (nlines == 0 || nlines > (LINES-3)) nlines = LINES-3; /* struct ptab ends with char line[2] so there's already padding for '\0' */ if ((ptab = malloc((sizeof(struct ptab) + width) * ptabsiz)) == NULL) { puts("top: can't get memory for process structures"); exit(1); } cbreak(); noecho(); signal(SIGINT,quit); signal(SIGQUIT,quit); signal(SIGHUP,quit); signal(SIGTERM,quit); for (i = ptabsiz, pt = ptab; i; i--, pt = INCPTAB(pt)) { pt->pid = -1; pt->used = 0; } while (1) { fserror=Fsfirst("u:\\proc\\*.*", -1); if (fserror < 0) { fprintf(stderr, "top: cannot list processes ?!"); exit(1); } erase(); /* top line is printed later */ if (ppid_mode) { printw("\n\nPID PPID STATUS SIZE TIME %% COMMAND\n"); } else { printw("\n\nPID PRI CUR STATUS SIZE TIME %% COMMAND\n"); } nprocs = 0; nrunning = 0; idletime = 0; cummemory = 0; thisclock = clock(); realtime = (thisclock - lastclock) * (1000/CLOCKS_PER_SEC); lastclock = thisclock; /* okay, now run the Fsnext bit through the wringer */ fserror = E_OK; /* set up for the loop */ while (fserror == E_OK ) { fd = open_proc(mydta.dta_name); if (fd < 0) goto getnext; /* cut name off at '.' */ for (cmdline = mydta.dta_name; *cmdline && *cmdline != '.'; cmdline++) /* do nothing */ ; *cmdline = 0; ioctl(fd, PPROCADDR, &procaddr); if (ioctl(fd, PCTXTSIZE, &ctxtsize) < 0) lseek(fd, procaddr+4+2*sizeof(CONTEXT), 0); else lseek(fd, procaddr, 0); read(fd, &proc, sizeof(proc)); pid = proc.pid; ppid = proc.ppid; ioctl(fd, PBASEADDR, &place); lseek(fd, place, 0); read(fd, bpage, sizeof(bpage)); close(fd); cmdline = bpage+128; if (*cmdline) *cmdline = ' '; /* span cmdline turning control chars into question marks */ for (c = cmdline; *c; c++) if (*c < ' ') *c = '?'; taillen = c - cmdline; thispid = proc.pid; if (ppid < 0) ppid = 0; statp = proc_stat; /* hunt down string referring to process status */ while (statp->mint != mydta.dta_attribute && statp->mint >= 0) statp++; ptime = proc.systime + proc.usrtime; /* set deltime to the time used since last time we checked */ for (i = ptabsiz, pt = ptab; i; i--, pt = INCPTAB(pt)) { if (pt->pid == thispid) { pt->used = 1; deltime = ptime - pt->lasttime; pt->lasttime = ptime; goto found; } } /* fell out: didn't find the process */ for (i = ptabsiz, pt = ptab; i; i--, pt = INCPTAB(pt)) { if (pt->pid == -1) { /* use this slot */ pt->used = 1; pt->pid = thispid; pt->lasttime = ptime; deltime = (ptime > realtime ? realtime : ptime); goto found; } } /* fell out again - can't keep track of this process (sorry!) */ deltime = 0; pt = NULL; found: if (thispid == 0) { /* idle time is charged to process zero */ idletime = deltime; strcpy(mydta.dta_name,"(idle)"); } hour = (ptime/1000/60/60); min = (ptime/1000/60)%60; sec = (ptime/1000)%60; frac = (ptime%1000) / 10; /* (never anything in .001 digit) */ if (ppid_mode) { len = sprintf(pt->line,"%03d %03d %-5s %8ld ", pid, ppid, statp->desc, mydta.dta_size); } else { len = sprintf(pt->line,"%03d %3d %3d %-5s %8ld ", pid, proc.pri, proc.curpri, statp->desc, mydta.dta_size); } if (hour) len += sprintf(pt->line + len,"%2ld:%02ld:%02ld",hour,min,sec); else len += sprintf(pt->line + len,"%2ld:%02ld.%02ld",min,sec,frac); len += sprintf(pt->line + len," %3ld ",(deltime * 100) / realtime); /* if the line's too long, cut off the tail so the total is width */ cmdlen = strlen(mydta.dta_name); tailstart = len+cmdlen; if (width < tailstart) cmdline[0] = 0; else if (tailstart+taillen > width) { cmdline[width-tailstart] = '\0'; } sprintf(pt->line + len,"%s%s",mydta.dta_name,cmdline); nprocs++; cummemory += mydta.dta_size; if (deltime) nrunning++; getnext: fserror = Fsnext(); } /* * Now zero out those slots which weren't used in the last pass, and zero * all "used" fields */ for (i = nlines, pt = ptab; i; i--, pt = INCPTAB(pt)) { if (!pt->used) pt->pid = -1; else pt->used = 0; } /* * Now all processes are in the ptab array, their lines stored. Sort them * into printing order and print nlines of them. */ qsort(ptab,nlines,sizeof(struct ptab) + width,cmpfunc); if (nprocs < nlines) i = nprocs; else i = nlines; for (pt = ptab; i; i--, pt = INCPTAB(pt)) { printw("%s\n",pt->line); } /* compute idle percentage into idletime */ idletime = (idletime * 100) / realtime; move(0,0); printw("Sample time: %5ld; %3ld%% idle; %3d processes, %3d running" "; %ld bytes used\n", realtime,idletime,nprocs,nrunning,cummemory); if (helpline) printw(helpstr); if (garbage) wrefresh(curscr); else refresh(); garbage = 0; /* Fall asleep for repeat_time seconds or until a key comes in. */ /* We want zero to mean "no time" not "forever" so add one. */ readfds = 1; if (Fselect(repeat_time * 1000 + 1,&readfds,0L,0L)) { /* input waiting */ if (helpline) { clrtoeol(); refresh(); helpline = 0; } ichar = getch(); switch(ichar) { case 'L'-'@': garbage = 1; break; case 's': printw("sleep time: "); refresh(); echo(); getstr(ibuf); noecho(); if (*ibuf) repeat_time = atoi(ibuf); if (repeat_time < 0) repeat_time = 0; break; case 'w': printw("width: "); refresh(); echo(); getstr(ibuf); noecho(); if (*ibuf) width = atoi(ibuf); if (width < MINWIDTH) width = MINWIDTH; width &= ~1; /* make sure width is even */ break; case 'n': printw("nlines: "); refresh(); echo(); getstr(ibuf); noecho(); if (*ibuf) nlines = atoi(ibuf); if (nlines == 0 || nlines > (LINES-3)) nlines = (LINES-3); break; case 'k': printw("kill "); refresh(); echo(); getstr(ibuf); noecho(); if (*ibuf) dokill(ibuf); break; case 'r': printw("renice "); refresh(); echo(); getstr(ibuf); noecho(); if (*ibuf) dorenice(ibuf); break; case 'p': /* toggle ppid_mode */ ppid_mode ^= 1; break; case 'h': case '?': helpline = 1; break; case 'q': quit(); default: ; } } } }