599 lines
14 KiB
C
599 lines
14 KiB
C
/*
|
|
* top - a top users display for Unix
|
|
*
|
|
* SYNOPSIS: Linux 1.2.x, 1.3.x, using the /proc filesystem
|
|
*
|
|
* DESCRIPTION:
|
|
* This is the machine-dependent module for Linux 1.2.x or 1.3.x.
|
|
*
|
|
* LIBS:
|
|
*
|
|
* CFLAGS: -DHAVE_GETOPT
|
|
*
|
|
* AUTHOR: Richard Henderson <rth@tamu.edu>
|
|
*/
|
|
|
|
#include "top.h"
|
|
#include "machine.h"
|
|
#include "utils.h"
|
|
|
|
#include <sys/types.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <dirent.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
#include <sys/time.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/vfs.h>
|
|
|
|
#include <sys/param.h> /* for HZ */
|
|
#include <asm/page.h> /* for PAGE_SHIFT */
|
|
#include <linux/tasks.h> /* for NR_TASKS */
|
|
|
|
#if 0
|
|
#include <linux/proc_fs.h> /* for PROC_SUPER_MAGIC */
|
|
#else
|
|
#define PROC_SUPER_MAGIC 0x9fa0
|
|
#endif
|
|
|
|
#define PROCFS "/proc"
|
|
extern char *myname;
|
|
extern uid_t proc_owner(pid_t pid);
|
|
|
|
/*=PROCESS INFORMATION==================================================*/
|
|
|
|
struct top_proc
|
|
{
|
|
pid_t pid;
|
|
uid_t uid;
|
|
char name[64];
|
|
int pri, nice;
|
|
unsigned long size, rss; /* in k */
|
|
int state;
|
|
unsigned long time;
|
|
double pcpu, wcpu;
|
|
};
|
|
|
|
|
|
/*=STATE IDENT STRINGS==================================================*/
|
|
|
|
#define NPROCSTATES 7
|
|
static char *state_abbrev[NPROCSTATES+1] =
|
|
{
|
|
"", "run", "sleep", "disk", "zomb", "stop", "swap",
|
|
NULL
|
|
};
|
|
|
|
static char *procstatenames[NPROCSTATES+1] =
|
|
{
|
|
"", " running, ", " sleeping, ", " uninterruptable, ",
|
|
" zombie, ", " stopped, ", " swapping, ",
|
|
NULL
|
|
};
|
|
|
|
#define NCPUSTATES 4
|
|
static char *cpustatenames[NCPUSTATES+1] =
|
|
{
|
|
"user", "nice", "system", "idle",
|
|
NULL
|
|
};
|
|
|
|
#define NMEMSTATS 6
|
|
static char *memorynames[NMEMSTATS+1] =
|
|
{
|
|
"K used, ", "K free, ", "K shd, ", "K buf Swap: ",
|
|
"K used, ", "K free",
|
|
NULL
|
|
};
|
|
|
|
static char fmt_header[] =
|
|
" PID X PRI NICE SIZE RES STATE TIME WCPU CPU COMMAND";
|
|
|
|
|
|
/*=SYSTEM STATE INFO====================================================*/
|
|
|
|
/* these are for calculating cpu state percentages */
|
|
|
|
static long cp_time[NCPUSTATES];
|
|
static long cp_old[NCPUSTATES];
|
|
static long cp_diff[NCPUSTATES];
|
|
|
|
/* for calculating the exponential average */
|
|
|
|
static struct timeval lasttime;
|
|
|
|
/* these are for keeping track of processes */
|
|
|
|
#define HASH_SIZE (NR_TASKS * 3 / 2)
|
|
static struct top_proc ptable[HASH_SIZE];
|
|
static struct top_proc *pactive[NR_TASKS];
|
|
static struct top_proc **nextactive;
|
|
|
|
/* these are for passing data back to the machine independant portion */
|
|
|
|
static int cpu_states[NCPUSTATES];
|
|
static int process_states[NPROCSTATES];
|
|
static int memory_stats[NMEMSTATS];
|
|
|
|
/* usefull macros */
|
|
#define bytetok(x) (((x) + 512) >> 10)
|
|
#define pagetok(x) ((x) << (PAGE_SHIFT - 10))
|
|
#define HASH(x) (((x) * 1686629713U) % HASH_SIZE)
|
|
|
|
/*======================================================================*/
|
|
|
|
static inline char *
|
|
skip_ws(const char *p)
|
|
{
|
|
while (isspace(*p)) p++;
|
|
return (char *)p;
|
|
}
|
|
|
|
static inline char *
|
|
skip_token(const char *p)
|
|
{
|
|
while (isspace(*p)) p++;
|
|
while (*p && !isspace(*p)) p++;
|
|
return (char *)p;
|
|
}
|
|
|
|
|
|
int
|
|
machine_init(statics)
|
|
struct statics *statics;
|
|
{
|
|
/* make sure the proc filesystem is mounted */
|
|
{
|
|
struct statfs sb;
|
|
if (statfs(PROCFS, &sb) < 0 || sb.f_type != PROC_SUPER_MAGIC)
|
|
{
|
|
fprintf(stderr, "%s: proc filesystem not mounted on " PROCFS "\n",
|
|
myname);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* chdir to the proc filesystem to make things easier */
|
|
chdir(PROCFS);
|
|
|
|
/* initialize the process hash table */
|
|
{
|
|
int i;
|
|
for (i = 0; i < HASH_SIZE; ++i)
|
|
ptable[i].pid = -1;
|
|
}
|
|
|
|
/* fill in the statics information */
|
|
statics->procstate_names = procstatenames;
|
|
statics->cpustate_names = cpustatenames;
|
|
statics->memory_names = memorynames;
|
|
|
|
/* all done! */
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
get_system_info(info)
|
|
struct system_info *info;
|
|
{
|
|
char buffer[4096+1];
|
|
int fd, len;
|
|
char *p;
|
|
|
|
/* get load averages */
|
|
{
|
|
fd = open("loadavg", O_RDONLY);
|
|
len = read(fd, buffer, sizeof(buffer)-1);
|
|
close(fd);
|
|
buffer[len] = '\0';
|
|
|
|
info->load_avg[0] = strtod(buffer, &p);
|
|
info->load_avg[1] = strtod(p, &p);
|
|
info->load_avg[2] = strtod(p, &p);
|
|
p = skip_token(p); /* skip running/tasks */
|
|
p = skip_ws(p);
|
|
if (*p)
|
|
info->last_pid = atoi(p);
|
|
else
|
|
info->last_pid = -1;
|
|
}
|
|
|
|
/* get the cpu time info */
|
|
{
|
|
fd = open("stat", O_RDONLY);
|
|
len = read(fd, buffer, sizeof(buffer)-1);
|
|
close(fd);
|
|
buffer[len] = '\0';
|
|
|
|
p = skip_token(buffer); /* "cpu" */
|
|
cp_time[0] = strtoul(p, &p, 0);
|
|
cp_time[1] = strtoul(p, &p, 0);
|
|
cp_time[2] = strtoul(p, &p, 0);
|
|
cp_time[3] = strtoul(p, &p, 0);
|
|
|
|
/* convert cp_time counts to percentages */
|
|
percentages(4, cpu_states, cp_time, cp_old, cp_diff);
|
|
}
|
|
|
|
/* get system wide memory usage */
|
|
{
|
|
char *p;
|
|
|
|
fd = open("meminfo", O_RDONLY);
|
|
len = read(fd, buffer, sizeof(buffer)-1);
|
|
close(fd);
|
|
buffer[len] = '\0';
|
|
|
|
/* be prepared for extra columns to appear be seeking
|
|
to ends of lines */
|
|
|
|
p = strchr(buffer, '\n');
|
|
p = skip_token(p); /* "Mem:" */
|
|
p = skip_token(p); /* total memory */
|
|
memory_stats[0] = strtoul(p, &p, 10);
|
|
memory_stats[1] = strtoul(p, &p, 10);
|
|
memory_stats[2] = strtoul(p, &p, 10);
|
|
memory_stats[3] = strtoul(p, &p, 10);
|
|
|
|
p = strchr(p, '\n');
|
|
p = skip_token(p); /* "Swap:" */
|
|
p = skip_token(p); /* total swap */
|
|
memory_stats[4] = strtoul(p, &p, 10);
|
|
memory_stats[5] = strtoul(p, &p, 10);
|
|
|
|
memory_stats[0] = bytetok(memory_stats[0]);
|
|
memory_stats[1] = bytetok(memory_stats[1]);
|
|
memory_stats[2] = bytetok(memory_stats[2]);
|
|
memory_stats[3] = bytetok(memory_stats[3]);
|
|
memory_stats[4] = bytetok(memory_stats[4]);
|
|
memory_stats[5] = bytetok(memory_stats[5]);
|
|
}
|
|
|
|
/* set arrays and strings */
|
|
info->cpustates = cpu_states;
|
|
info->memory = memory_stats;
|
|
}
|
|
|
|
|
|
static void
|
|
read_one_proc_stat(pid_t pid, struct top_proc *proc)
|
|
{
|
|
char buffer[4096], *p;
|
|
|
|
/* grab the proc stat info in one go */
|
|
{
|
|
int fd, len;
|
|
|
|
sprintf(buffer, "%d/stat", pid);
|
|
|
|
fd = open(buffer, O_RDONLY);
|
|
len = read(fd, buffer, sizeof(buffer)-1);
|
|
close(fd);
|
|
|
|
buffer[len] = '\0';
|
|
}
|
|
|
|
proc->uid = proc_owner(pid);
|
|
|
|
/* parse out the status */
|
|
|
|
p = buffer;
|
|
p = strchr(p, '(')+1; /* skip pid */
|
|
{
|
|
char *q = strrchr(p, ')');
|
|
int len = q-p;
|
|
if (len >= sizeof(proc->name))
|
|
len = sizeof(proc->name)-1;
|
|
memcpy(proc->name, p, len);
|
|
proc->name[len] = 0;
|
|
p = q+1;
|
|
}
|
|
|
|
p = skip_ws(p);
|
|
switch (*p++)
|
|
{
|
|
case 'R': proc->state = 1; break;
|
|
case 'S': proc->state = 2; break;
|
|
case 'D': proc->state = 3; break;
|
|
case 'Z': proc->state = 4; break;
|
|
case 'T': proc->state = 5; break;
|
|
case 'W': proc->state = 6; break;
|
|
}
|
|
|
|
p = skip_token(p); /* skip ppid */
|
|
p = skip_token(p); /* skip pgrp */
|
|
p = skip_token(p); /* skip session */
|
|
p = skip_token(p); /* skip tty */
|
|
p = skip_token(p); /* skip tty pgrp */
|
|
p = skip_token(p); /* skip flags */
|
|
p = skip_token(p); /* skip min flt */
|
|
p = skip_token(p); /* skip cmin flt */
|
|
p = skip_token(p); /* skip maj flt */
|
|
p = skip_token(p); /* skip cmaj flt */
|
|
|
|
proc->time = strtoul(p, &p, 10); /* utime */
|
|
proc->time += strtoul(p, &p, 10); /* stime */
|
|
|
|
p = skip_token(p); /* skip cutime */
|
|
p = skip_token(p); /* skip cstime */
|
|
|
|
proc->pri = strtol(p, &p, 10); /* priority */
|
|
proc->nice = strtol(p, &p, 10); /* nice */
|
|
|
|
p = skip_token(p); /* skip timeout */
|
|
p = skip_token(p); /* skip it_real_val */
|
|
p = skip_token(p); /* skip start_time */
|
|
|
|
proc->size = bytetok(strtoul(p, &p, 10)); /* vsize */
|
|
proc->rss = pagetok(strtoul(p, &p, 10)); /* rss */
|
|
|
|
#if 0
|
|
/* for the record, here are the rest of the fields */
|
|
p = skip_token(p); /* skip rlim */
|
|
p = skip_token(p); /* skip start_code */
|
|
p = skip_token(p); /* skip end_code */
|
|
p = skip_token(p); /* skip start_stack */
|
|
p = skip_token(p); /* skip sp */
|
|
p = skip_token(p); /* skip pc */
|
|
p = skip_token(p); /* skip signal */
|
|
p = skip_token(p); /* skip sigblocked */
|
|
p = skip_token(p); /* skip sigignore */
|
|
p = skip_token(p); /* skip sigcatch */
|
|
p = skip_token(p); /* skip wchan */
|
|
#endif
|
|
}
|
|
|
|
|
|
caddr_t
|
|
get_process_info(struct system_info *si,
|
|
struct process_select *sel,
|
|
int (*compare)())
|
|
{
|
|
struct timeval thistime;
|
|
double timediff, alpha, beta;
|
|
|
|
/* calculate the time difference since our last check */
|
|
gettimeofday(&thistime, 0);
|
|
if (lasttime.tv_sec)
|
|
{
|
|
timediff = ((thistime.tv_sec - lasttime.tv_sec) +
|
|
(thistime.tv_usec - lasttime.tv_usec) * 1e-6);
|
|
}
|
|
else
|
|
timediff = 1e9;
|
|
lasttime = thistime;
|
|
|
|
/* calculate constants for the exponental average */
|
|
if (timediff < 30.0)
|
|
{
|
|
alpha = 0.5 * (timediff / 30.0);
|
|
beta = 1.0 - alpha;
|
|
}
|
|
else
|
|
alpha = beta = 0.5;
|
|
timediff *= HZ; /* convert to ticks */
|
|
|
|
/* mark all hash table entries as not seen */
|
|
{
|
|
int i;
|
|
for (i = 0; i < HASH_SIZE; ++i)
|
|
ptable[i].state = 0;
|
|
}
|
|
|
|
/* read the process information */
|
|
{
|
|
DIR *dir = opendir(".");
|
|
struct dirent *ent;
|
|
int total_procs = 0;
|
|
struct top_proc **active = pactive;
|
|
|
|
int show_idle = sel->idle;
|
|
int show_uid = sel->uid != -1;
|
|
|
|
memset(process_states, 0, sizeof(process_states));
|
|
|
|
while ((ent = readdir(dir)) != NULL)
|
|
{
|
|
struct top_proc *proc;
|
|
pid_t pid;
|
|
unsigned long otime;
|
|
|
|
if (!isdigit(ent->d_name[0]))
|
|
continue;
|
|
|
|
pid = atoi(ent->d_name);
|
|
|
|
/* look up hash table entry */
|
|
proc = &ptable[HASH(pid)];
|
|
while (proc->pid != pid && proc->pid != -1)
|
|
{
|
|
if (++proc == ptable+HASH_SIZE)
|
|
proc = ptable;
|
|
}
|
|
|
|
otime = proc->time;
|
|
|
|
read_one_proc_stat(pid, proc);
|
|
|
|
if (proc->state == 0)
|
|
continue;
|
|
|
|
total_procs++;
|
|
process_states[proc->state]++;
|
|
|
|
if (proc->pid == -1)
|
|
{
|
|
proc->pid = pid;
|
|
proc->wcpu = proc->pcpu = proc->time / timediff;
|
|
}
|
|
else
|
|
{
|
|
proc->pcpu = (proc->time - otime) / timediff;
|
|
proc->wcpu = proc->pcpu * alpha + proc->wcpu * beta;
|
|
}
|
|
|
|
if ((show_idle || proc->state == 1 || proc->pcpu) &&
|
|
(!show_uid || proc->uid == sel->uid))
|
|
{
|
|
*active++ = proc;
|
|
}
|
|
}
|
|
closedir(dir);
|
|
|
|
si->p_active = active - pactive;
|
|
si->p_total = total_procs;
|
|
si->procstates = process_states;
|
|
}
|
|
|
|
/* flush old hash table entries */
|
|
{
|
|
int i;
|
|
for (i = 0; i < HASH_SIZE; ++i)
|
|
if (ptable[i].state == 0)
|
|
ptable[i].pid = -1;
|
|
}
|
|
|
|
/* if requested, sort the "active" procs */
|
|
if (compare && si->p_active)
|
|
qsort(pactive, si->p_active, sizeof(struct top_proc *), compare);
|
|
|
|
/* don't even pretend that the return value thing here isn't bogus */
|
|
nextactive = pactive;
|
|
return (caddr_t)0;
|
|
}
|
|
|
|
|
|
char *
|
|
format_header(uname_field)
|
|
char *uname_field;
|
|
{
|
|
int uname_len = strlen(uname_field);
|
|
if (uname_len > 8)
|
|
uname_len = 8;
|
|
|
|
memcpy(strchr(fmt_header, 'X'), uname_field, uname_len);
|
|
|
|
return fmt_header;
|
|
}
|
|
|
|
|
|
char *
|
|
format_next_process(handle, get_userid)
|
|
caddr_t handle;
|
|
char *(*get_userid)();
|
|
{
|
|
static char fmt[128]; /* static area where result is built */
|
|
struct top_proc *p = *nextactive++;
|
|
|
|
sprintf(fmt,
|
|
"%5d %-8.8s %3d %4d %5s %5s %-5s %6s %5.2f%% %5.2f%% %.14s",
|
|
p->pid,
|
|
(*get_userid)(p->uid),
|
|
p->pri,
|
|
p->nice,
|
|
format_k(p->size),
|
|
format_k(p->rss),
|
|
state_abbrev[p->state],
|
|
format_time(p->time / HZ),
|
|
p->wcpu * 100.0,
|
|
p->pcpu * 100.0,
|
|
p->name);
|
|
|
|
/* return the result */
|
|
return (fmt);
|
|
}
|
|
|
|
|
|
/*
|
|
* proc_compare - comparison function for "qsort"
|
|
* Compares the resource consumption of two processes using five
|
|
* distinct keys. The keys (in descending order of importance) are:
|
|
* percent cpu, cpu ticks, state, resident set size, total virtual
|
|
* memory usage. The process states are ordered as follows (from least
|
|
* to most important): WAIT, zombie, sleep, stop, start, run. The
|
|
* array declaration below maps a process state index into a number
|
|
* that reflects this ordering.
|
|
*/
|
|
|
|
|
|
int
|
|
proc_compare (pp1, pp2)
|
|
struct top_proc **pp1, **pp2;
|
|
{
|
|
static unsigned char sort_state[] =
|
|
{
|
|
0, /* empty */
|
|
6, /* run */
|
|
3, /* sleep */
|
|
5, /* disk wait */
|
|
1, /* zombie */
|
|
2, /* stop */
|
|
4 /* swap */
|
|
};
|
|
|
|
struct top_proc *p1, *p2;
|
|
int result;
|
|
double dresult;
|
|
|
|
/* remove one level of indirection */
|
|
p1 = *pp1;
|
|
p2 = *pp2;
|
|
|
|
/* compare percent cpu (pctcpu) */
|
|
dresult = p2->pcpu - p1->pcpu;
|
|
if (dresult != 0.0)
|
|
return dresult > 0.0 ? 1 : -1;
|
|
|
|
/* use cputicks to break the tie */
|
|
if ((result = p2->time - p1->time) == 0)
|
|
{
|
|
/* use process state to break the tie */
|
|
if ((result = (sort_state[p2->state] - sort_state[p1->state])) == 0)
|
|
{
|
|
/* use priority to break the tie */
|
|
if ((result = p2->pri - p1->pri) == 0)
|
|
{
|
|
/* use resident set size (rssize) to break the tie */
|
|
if ((result = p2->rss - p1->rss) == 0)
|
|
{
|
|
/* use total memory to break the tie */
|
|
result = (p2->size - p1->size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result == 0 ? 0 : result < 0 ? -1 : 1;
|
|
}
|
|
|
|
|
|
/*
|
|
* proc_owner(pid) - returns the uid that owns process "pid", or -1 if
|
|
* the process does not exist.
|
|
* It is EXTREMLY IMPORTANT that this function work correctly.
|
|
* If top runs setuid root (as in SVR4), then this function
|
|
* is the only thing that stands in the way of a serious
|
|
* security problem. It validates requests for the "kill"
|
|
* and "renice" commands.
|
|
*/
|
|
|
|
uid_t
|
|
proc_owner(pid)
|
|
pid_t pid;
|
|
{
|
|
struct stat sb;
|
|
char buffer[32];
|
|
sprintf(buffer, "%d", pid);
|
|
|
|
if (stat(buffer, &sb) < 0)
|
|
return -1;
|
|
else
|
|
return sb.st_uid;
|
|
}
|