1112 lines
26 KiB
C
1112 lines
26 KiB
C
/*
|
|
* Copyright (c) 1984 through 2008, William LeFebvre
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
*
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
*
|
|
* * Neither the name of William LeFebvre nor the names of other
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* top - a top users display for Unix
|
|
*
|
|
* SYNOPSIS: 2.x with thread eliding
|
|
*
|
|
* DESCRIPTION:
|
|
* This is the machine-dependent module for Linux 2.x that elides threads
|
|
* from the output.
|
|
*
|
|
* CFLAGS: -DHAVE_GETOPT -DHAVE_STRERROR -DORDER
|
|
*
|
|
* TERMCAP: -lcurses
|
|
*
|
|
* AUTHOR: Richard Henderson <rth@tamu.edu>
|
|
* Order support added by Alexey Klimkin <kad@klon.tme.mcst.ru>
|
|
* Ported to 2.4 by William LeFebvre
|
|
* Thread eliding by William LeFebvre
|
|
*/
|
|
|
|
#include "config.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 */
|
|
|
|
#if 0
|
|
#include <linux/proc_fs.h> /* for PROC_SUPER_MAGIC */
|
|
#else
|
|
#define PROC_SUPER_MAGIC 0x9fa0
|
|
#endif
|
|
|
|
#include "top.h"
|
|
#include "machine.h"
|
|
#include "utils.h"
|
|
|
|
#define PROCFS "/proc"
|
|
extern char *myname;
|
|
|
|
/*=PROCESS INFORMATION==================================================*/
|
|
|
|
struct top_proc
|
|
{
|
|
pid_t pid;
|
|
pid_t ppid;
|
|
uid_t uid;
|
|
char *name;
|
|
int pri, nice;
|
|
unsigned long size, rss; /* in k */
|
|
int state;
|
|
unsigned long time;
|
|
unsigned long start_time;
|
|
unsigned long otime;
|
|
unsigned long start_code;
|
|
unsigned long end_code;
|
|
unsigned long start_stack;
|
|
unsigned int threads;
|
|
double pcpu, wcpu;
|
|
struct top_proc *next;
|
|
};
|
|
|
|
|
|
/*=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 MEMUSED 0
|
|
#define MEMFREE 1
|
|
#define MEMSHARED 2
|
|
#define MEMBUFFERS 3
|
|
#define MEMCACHED 4
|
|
#define NMEMSTATS 5
|
|
static char *memorynames[NMEMSTATS+1] =
|
|
{
|
|
"K used, ", "K free, ", "K shared, ", "K buffers, ", "K cached",
|
|
NULL
|
|
};
|
|
|
|
#define SWAPUSED 0
|
|
#define SWAPFREE 1
|
|
#define SWAPCACHED 2
|
|
#define NSWAPSTATS 3
|
|
static char *swapnames[NSWAPSTATS+1] =
|
|
{
|
|
"K used, ", "K free, ", "K cached",
|
|
NULL
|
|
};
|
|
|
|
static char fmt_header[] =
|
|
" PID X THR PRI NICE SIZE RES STATE TIME CPU COMMAND";
|
|
|
|
/* these are names given to allowed sorting orders -- first is default */
|
|
char *ordernames[] =
|
|
{"cpu", "size", "res", "time", "command", NULL};
|
|
|
|
/* forward definitions for comparison functions */
|
|
int compare_cpu();
|
|
int compare_size();
|
|
int compare_res();
|
|
int compare_time();
|
|
int compare_cmd();
|
|
|
|
int (*proc_compares[])() = {
|
|
compare_cpu,
|
|
compare_size,
|
|
compare_res,
|
|
compare_time,
|
|
compare_cmd,
|
|
NULL };
|
|
|
|
/*=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 (1003)
|
|
#define INITIAL_ACTIVE_SIZE (256)
|
|
#define PROCBLOCK_SIZE (32)
|
|
static struct top_proc *ptable[HASH_SIZE];
|
|
static struct top_proc **pactive;
|
|
static struct top_proc **nextactive;
|
|
static unsigned int activesize = 0;
|
|
static time_t boottime = -1;
|
|
|
|
/* these are for passing data back to the machine independant portion */
|
|
|
|
static int cpu_states[NCPUSTATES];
|
|
static int process_states[NPROCSTATES];
|
|
static long memory_stats[NMEMSTATS];
|
|
static long swap_stats[NSWAPSTATS];
|
|
|
|
/* 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;
|
|
}
|
|
|
|
static void
|
|
xfrm_cmdline(char *p, int len)
|
|
{
|
|
while (--len > 0)
|
|
{
|
|
if (*p == '\0')
|
|
{
|
|
*p = ' ';
|
|
}
|
|
p++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
update_procname(struct top_proc *proc, char *cmd)
|
|
|
|
{
|
|
printable(cmd);
|
|
|
|
if (proc->name == NULL)
|
|
{
|
|
proc->name = strdup(cmd);
|
|
}
|
|
else if (strcmp(proc->name, cmd) != 0)
|
|
{
|
|
free(proc->name);
|
|
proc->name = strdup(cmd);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* Process structures are allocated and freed as needed. Here we
|
|
* keep big pools of them, adding more pool as needed. When a
|
|
* top_proc structure is freed, it is added to a freelist and reused.
|
|
*/
|
|
|
|
static struct top_proc *freelist = NULL;
|
|
static struct top_proc *procblock = NULL;
|
|
static struct top_proc *procmax = NULL;
|
|
|
|
static struct top_proc *
|
|
new_proc()
|
|
{
|
|
struct top_proc *p;
|
|
|
|
if (freelist)
|
|
{
|
|
p = freelist;
|
|
freelist = freelist->next;
|
|
}
|
|
else if (procblock)
|
|
{
|
|
p = procblock;
|
|
if (++procblock >= procmax)
|
|
{
|
|
procblock = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
p = procblock = (struct top_proc *)calloc(PROCBLOCK_SIZE,
|
|
sizeof(struct top_proc));
|
|
procmax = procblock++ + PROCBLOCK_SIZE;
|
|
}
|
|
|
|
/* initialization */
|
|
if (p->name != NULL)
|
|
{
|
|
free(p->name);
|
|
p->name = NULL;
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
static void
|
|
free_proc(struct top_proc *proc)
|
|
{
|
|
proc->next = freelist;
|
|
freelist = proc;
|
|
}
|
|
|
|
|
|
int
|
|
machine_init(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);
|
|
|
|
/* get a boottime */
|
|
{
|
|
int fd;
|
|
char buff[64];
|
|
char *p;
|
|
unsigned long uptime;
|
|
struct timeval tv;
|
|
|
|
if ((fd = open("uptime", 0)) != -1)
|
|
{
|
|
if (read(fd, buff, sizeof(buff)) > 0)
|
|
{
|
|
uptime = strtoul(buff, &p, 10);
|
|
gettimeofday(&tv, 0);
|
|
boottime = tv.tv_sec - uptime;
|
|
}
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
/* fill in the statics information */
|
|
statics->procstate_names = procstatenames;
|
|
statics->cpustate_names = cpustatenames;
|
|
statics->memory_names = memorynames;
|
|
statics->swap_names = swapnames;
|
|
statics->order_names = ordernames;
|
|
statics->boottime = boottime;
|
|
statics->flags.fullcmds = 1;
|
|
statics->flags.warmup = 1;
|
|
|
|
/* allocate needed space */
|
|
pactive = (struct top_proc **)malloc(sizeof(struct top_proc *) * INITIAL_ACTIVE_SIZE);
|
|
activesize = INITIAL_ACTIVE_SIZE;
|
|
|
|
/* make sure the hash table is empty */
|
|
memset(ptable, 0, HASH_SIZE * sizeof(struct top_proc *));
|
|
|
|
/* all done! */
|
|
return 0;
|
|
}
|
|
|
|
|
|
void
|
|
get_system_info(struct system_info *info)
|
|
|
|
{
|
|
char buffer[4096+1];
|
|
int fd, len;
|
|
char *p;
|
|
|
|
/* get load averages */
|
|
|
|
if ((fd = open("loadavg", O_RDONLY)) != -1)
|
|
{
|
|
if ((len = read(fd, buffer, sizeof(buffer)-1)) > 0)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
/* get the cpu time info */
|
|
if ((fd = open("stat", O_RDONLY)) != -1)
|
|
{
|
|
if ((len = read(fd, buffer, sizeof(buffer)-1)) > 0)
|
|
{
|
|
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);
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
/* get system wide memory usage */
|
|
if ((fd = open("meminfo", O_RDONLY)) != -1)
|
|
{
|
|
char *p;
|
|
int mem = 0;
|
|
int swap = 0;
|
|
unsigned long memtotal = 0;
|
|
unsigned long memfree = 0;
|
|
unsigned long swaptotal = 0;
|
|
|
|
if ((len = read(fd, buffer, sizeof(buffer)-1)) > 0)
|
|
{
|
|
buffer[len] = '\0';
|
|
p = buffer-1;
|
|
|
|
/* iterate thru the lines */
|
|
while (p != NULL)
|
|
{
|
|
p++;
|
|
if (p[0] == ' ' || p[0] == '\t')
|
|
{
|
|
/* skip */
|
|
}
|
|
else if (strncmp(p, "Mem:", 4) == 0)
|
|
{
|
|
p = skip_token(p); /* "Mem:" */
|
|
p = skip_token(p); /* total memory */
|
|
memory_stats[MEMUSED] = strtoul(p, &p, 10);
|
|
memory_stats[MEMFREE] = strtoul(p, &p, 10);
|
|
memory_stats[MEMSHARED] = strtoul(p, &p, 10);
|
|
memory_stats[MEMBUFFERS] = strtoul(p, &p, 10);
|
|
memory_stats[MEMCACHED] = strtoul(p, &p, 10);
|
|
memory_stats[MEMUSED] = bytetok(memory_stats[MEMUSED]);
|
|
memory_stats[MEMFREE] = bytetok(memory_stats[MEMFREE]);
|
|
memory_stats[MEMSHARED] = bytetok(memory_stats[MEMSHARED]);
|
|
memory_stats[MEMBUFFERS] = bytetok(memory_stats[MEMBUFFERS]);
|
|
memory_stats[MEMCACHED] = bytetok(memory_stats[MEMCACHED]);
|
|
mem = 1;
|
|
}
|
|
else if (strncmp(p, "Swap:", 5) == 0)
|
|
{
|
|
p = skip_token(p); /* "Swap:" */
|
|
p = skip_token(p); /* total swap */
|
|
swap_stats[SWAPUSED] = strtoul(p, &p, 10);
|
|
swap_stats[SWAPFREE] = strtoul(p, &p, 10);
|
|
swap_stats[SWAPUSED] = bytetok(swap_stats[SWAPUSED]);
|
|
swap_stats[SWAPFREE] = bytetok(swap_stats[SWAPFREE]);
|
|
swap = 1;
|
|
}
|
|
else if (!mem && strncmp(p, "MemTotal:", 9) == 0)
|
|
{
|
|
p = skip_token(p);
|
|
memtotal = strtoul(p, &p, 10);
|
|
}
|
|
else if (!mem && memtotal > 0 && strncmp(p, "MemFree:", 8) == 0)
|
|
{
|
|
p = skip_token(p);
|
|
memfree = strtoul(p, &p, 10);
|
|
memory_stats[MEMUSED] = memtotal - memfree;
|
|
memory_stats[MEMFREE] = memfree;
|
|
}
|
|
else if (!mem && strncmp(p, "MemShared:", 10) == 0)
|
|
{
|
|
p = skip_token(p);
|
|
memory_stats[MEMSHARED] = strtoul(p, &p, 10);
|
|
}
|
|
else if (!mem && strncmp(p, "Buffers:", 8) == 0)
|
|
{
|
|
p = skip_token(p);
|
|
memory_stats[MEMBUFFERS] = strtoul(p, &p, 10);
|
|
}
|
|
else if (!mem && strncmp(p, "Cached:", 7) == 0)
|
|
{
|
|
p = skip_token(p);
|
|
memory_stats[MEMCACHED] = strtoul(p, &p, 10);
|
|
}
|
|
else if (!swap && strncmp(p, "SwapTotal:", 10) == 0)
|
|
{
|
|
p = skip_token(p);
|
|
swaptotal = strtoul(p, &p, 10);
|
|
}
|
|
else if (!swap && swaptotal > 0 && strncmp(p, "SwapFree:", 9) == 0)
|
|
{
|
|
p = skip_token(p);
|
|
memfree = strtoul(p, &p, 10);
|
|
swap_stats[SWAPUSED] = swaptotal - memfree;
|
|
swap_stats[SWAPFREE] = memfree;
|
|
}
|
|
else if (!mem && strncmp(p, "SwapCached:", 11) == 0)
|
|
{
|
|
p = skip_token(p);
|
|
swap_stats[SWAPCACHED] = strtoul(p, &p, 10);
|
|
}
|
|
|
|
/* move to the next line */
|
|
p = strchr(p, '\n');
|
|
}
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
/* set arrays and strings */
|
|
info->cpustates = cpu_states;
|
|
info->memory = memory_stats;
|
|
info->swap = swap_stats;
|
|
}
|
|
|
|
|
|
static void
|
|
read_one_proc_stat(pid_t pid, struct top_proc *proc, struct process_select *sel)
|
|
{
|
|
char buffer[4096], *p, *q;
|
|
int fd, len;
|
|
int fullcmd;
|
|
|
|
/* if anything goes wrong, we return with proc->state == 0 */
|
|
proc->state = 0;
|
|
|
|
/* full cmd handling */
|
|
fullcmd = sel->fullcmd;
|
|
if (fullcmd)
|
|
{
|
|
sprintf(buffer, "%d/cmdline", pid);
|
|
if ((fd = open(buffer, O_RDONLY)) != -1)
|
|
{
|
|
/* read command line data */
|
|
/* (theres no sense in reading more than we can fit) */
|
|
if ((len = read(fd, buffer, MAX_COLS)) > 1)
|
|
{
|
|
buffer[len] = '\0';
|
|
xfrm_cmdline(buffer, len);
|
|
update_procname(proc, buffer);
|
|
}
|
|
else
|
|
{
|
|
fullcmd = 0;
|
|
}
|
|
close(fd);
|
|
}
|
|
else
|
|
{
|
|
fullcmd = 0;
|
|
}
|
|
}
|
|
|
|
/* grab the proc stat info in one go */
|
|
sprintf(buffer, "%d/stat", pid);
|
|
|
|
fd = open(buffer, O_RDONLY);
|
|
len = read(fd, buffer, sizeof(buffer)-1);
|
|
close(fd);
|
|
|
|
buffer[len] = '\0';
|
|
|
|
proc->uid = (uid_t)proc_owner((int)pid);
|
|
|
|
/* parse out the status */
|
|
|
|
/* skip pid and locate command, which is in parentheses */
|
|
if ((p = strchr(buffer, '(')) == NULL)
|
|
{
|
|
return;
|
|
}
|
|
if ((q = strrchr(++p, ')')) == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* set the procname */
|
|
*q = '\0';
|
|
if (!fullcmd)
|
|
{
|
|
update_procname(proc, p);
|
|
}
|
|
|
|
/* scan the rest of the line */
|
|
p = q+1;
|
|
p = skip_ws(p);
|
|
switch (*p++) /* state */
|
|
{
|
|
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;
|
|
case '\0': return;
|
|
}
|
|
|
|
proc->ppid = strtoul(p, &p, 10); /* 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 */
|
|
proc->start_time = strtoul(p, &p, 10); /* start_time */
|
|
|
|
proc->size = bytetok(strtoul(p, &p, 10)); /* vsize */
|
|
proc->rss = pagetok(strtoul(p, &p, 10)); /* rss */
|
|
|
|
p = skip_token(p); /* skip rlim */
|
|
proc->start_code = strtoul(p, &p, 10); /* start_code */
|
|
proc->end_code = strtoul(p, &p, 10); /* end_code */
|
|
proc->start_stack = strtoul(p, &p, 10); /* start_stack */
|
|
|
|
/* for the record, here are the rest of the fields */
|
|
#if 0
|
|
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_index)
|
|
{
|
|
struct timeval thistime;
|
|
double timediff, alpha, beta;
|
|
struct top_proc *proc;
|
|
pid_t pid;
|
|
unsigned long now;
|
|
unsigned long elapsed;
|
|
int i;
|
|
|
|
/* 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 = 0;
|
|
}
|
|
lasttime = thistime;
|
|
|
|
/* round current time to a second */
|
|
now = (unsigned long)thistime.tv_sec;
|
|
if (thistime.tv_usec >= 500000)
|
|
{
|
|
now++;
|
|
}
|
|
|
|
/* calculate constants for the exponental average */
|
|
if (timediff > 0.0 && 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 */
|
|
for (i = 0; i < HASH_SIZE; ++i)
|
|
{
|
|
for (proc = ptable[i]; proc; proc = proc->next)
|
|
{
|
|
proc->state = 0;
|
|
}
|
|
}
|
|
|
|
/* read the process information */
|
|
{
|
|
DIR *dir = opendir(".");
|
|
struct dirent *ent;
|
|
int total_procs = 0;
|
|
struct top_proc **active;
|
|
|
|
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 *pp;
|
|
|
|
if (!isdigit(ent->d_name[0]))
|
|
continue;
|
|
|
|
pid = atoi(ent->d_name);
|
|
|
|
/* look up hash table entry */
|
|
proc = pp = ptable[HASH(pid)];
|
|
while (proc && proc->pid != pid)
|
|
{
|
|
proc = proc->next;
|
|
}
|
|
|
|
/* if we came up empty, create a new entry */
|
|
if (proc == NULL)
|
|
{
|
|
proc = new_proc();
|
|
proc->pid = pid;
|
|
proc->next = pp;
|
|
ptable[HASH(pid)] = proc;
|
|
proc->time = proc->otime = 0;
|
|
}
|
|
|
|
read_one_proc_stat(pid, proc, sel);
|
|
proc->threads = 1;
|
|
|
|
if (proc->state == 0)
|
|
continue;
|
|
|
|
total_procs++;
|
|
process_states[proc->state]++;
|
|
|
|
/* calculate cpu percentage */
|
|
if (timediff > 0.0)
|
|
{
|
|
if ((proc->pcpu = (proc->time - proc->otime) / timediff) < 0.0001)
|
|
{
|
|
proc->pcpu = 0;
|
|
}
|
|
}
|
|
else if ((elapsed = (now - boottime)*HZ - proc->start_time) > 0)
|
|
{
|
|
if ((proc->pcpu = (double)proc->time / (double)elapsed) < 0.0001)
|
|
{
|
|
proc->pcpu;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
proc->pcpu = 0.0;
|
|
}
|
|
|
|
/* remember time for next time */
|
|
proc->otime = proc->time;
|
|
}
|
|
closedir(dir);
|
|
|
|
/* make sure we have enough slots for the active procs */
|
|
if (activesize < total_procs)
|
|
{
|
|
pactive = (struct top_proc **)realloc(pactive,
|
|
sizeof(struct top_proc *) * total_procs);
|
|
activesize = total_procs;
|
|
}
|
|
|
|
/* set up the active procs and flush dead entries */
|
|
/* also coalesce threads */
|
|
active = pactive;
|
|
for (i = 0; i < HASH_SIZE; i++)
|
|
{
|
|
struct top_proc *last;
|
|
struct top_proc *ptmp;
|
|
struct top_proc *parent;
|
|
|
|
last = NULL;
|
|
proc = ptable[i];
|
|
while (proc != NULL)
|
|
{
|
|
if (proc->state == 0)
|
|
{
|
|
ptmp = proc;
|
|
if (last)
|
|
{
|
|
proc = last->next = proc->next;
|
|
}
|
|
else
|
|
{
|
|
proc = ptable[i] = proc->next;
|
|
}
|
|
free_proc(ptmp);
|
|
}
|
|
else
|
|
{
|
|
/* look up hash table entry for parent */
|
|
parent = proc;
|
|
do {
|
|
pid = parent->ppid;
|
|
parent = ptable[HASH(pid)];
|
|
while (parent && parent->pid != pid)
|
|
{
|
|
parent = parent->next;
|
|
}
|
|
} while (parent && parent->state == 0);
|
|
|
|
/* does this look like a thread of its parent? */
|
|
if (parent && proc->size == parent->size &&
|
|
proc->rss == parent->rss &&
|
|
proc->start_code == parent->start_code &&
|
|
proc->end_code == parent->end_code &&
|
|
proc->start_stack == parent->start_stack)
|
|
{
|
|
/* yes it does: roll up the cumulative numbers */
|
|
parent->threads += proc->threads;
|
|
parent->time += proc->time;
|
|
parent->pcpu += proc->pcpu;
|
|
|
|
/* mark this process as dead (undisplayable) */
|
|
proc->state = 0;
|
|
}
|
|
else if ((show_idle || proc->state == 1 || proc->pcpu) &&
|
|
(!show_uid || proc->uid == sel->uid))
|
|
{
|
|
*active++ = proc;
|
|
last = proc;
|
|
}
|
|
proc = proc->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
si->p_active = active - pactive;
|
|
si->p_total = total_procs;
|
|
si->procstates = process_states;
|
|
}
|
|
|
|
/* if requested, sort the "active" procs */
|
|
if (si->p_active)
|
|
qsort(pactive, si->p_active, sizeof(struct top_proc *),
|
|
proc_compares[compare_index]);
|
|
|
|
/* don't even pretend that the return value thing here isn't bogus */
|
|
nextactive = pactive;
|
|
return (caddr_t)0;
|
|
}
|
|
|
|
|
|
char *
|
|
format_header(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(caddr_t handle, char *(*get_userid)(int))
|
|
|
|
{
|
|
static char fmt[MAX_COLS]; /* static area where result is built */
|
|
struct top_proc *p = *nextactive++;
|
|
|
|
snprintf(fmt, sizeof(fmt),
|
|
"%5d %-8.8s %3d %3d %4d %5s %5s %-5s %6s %5.2f%% %s",
|
|
p->pid,
|
|
(*get_userid)(p->uid),
|
|
p->threads,
|
|
p->pri < -99 ? -99 : p->pri,
|
|
p->nice,
|
|
format_k(p->size),
|
|
format_k(p->rss),
|
|
state_abbrev[p->state],
|
|
format_time(p->time / HZ),
|
|
p->pcpu * 100.0,
|
|
p->name);
|
|
|
|
/* return the result */
|
|
return (fmt);
|
|
}
|
|
|
|
/* comparison routines for qsort */
|
|
|
|
/*
|
|
* There are currently four possible comparison routines. main selects
|
|
* one of these by indexing in to the array proc_compares.
|
|
*
|
|
* Possible keys are defined as macros below. Currently these keys are
|
|
* defined: percent cpu, cpu ticks, process 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.
|
|
*/
|
|
|
|
/* First, the possible comparison keys. These are defined in such a way
|
|
that they can be merely listed in the source code to define the actual
|
|
desired ordering.
|
|
*/
|
|
|
|
#define ORDERKEY_PCTCPU if (dresult = p2->pcpu - p1->pcpu,\
|
|
(result = dresult > 0.0 ? 1 : dresult < 0.0 ? -1 : 0) == 0)
|
|
#define ORDERKEY_CPTICKS if ((result = (long)p2->time - (long)p1->time) == 0)
|
|
#define ORDERKEY_STATE if ((result = (sort_state[p2->state] - \
|
|
sort_state[p1->state])) == 0)
|
|
#define ORDERKEY_PRIO if ((result = p2->pri - p1->pri) == 0)
|
|
#define ORDERKEY_RSSIZE if ((result = p2->rss - p1->rss) == 0)
|
|
#define ORDERKEY_MEM if ((result = p2->size - p1->size) == 0)
|
|
#define ORDERKEY_NAME if ((result = strcmp(p1->name, p2->name)) == 0)
|
|
|
|
/* Now the array that maps process state to a weight */
|
|
|
|
unsigned char sort_state[] =
|
|
{
|
|
0, /* empty */
|
|
6, /* run */
|
|
3, /* sleep */
|
|
5, /* disk wait */
|
|
1, /* zombie */
|
|
2, /* stop */
|
|
4 /* swap */
|
|
};
|
|
|
|
|
|
/* compare_cpu - the comparison function for sorting by cpu percentage */
|
|
|
|
int
|
|
compare_cpu (
|
|
struct top_proc **pp1,
|
|
struct top_proc **pp2)
|
|
{
|
|
register struct top_proc *p1;
|
|
register struct top_proc *p2;
|
|
register long result;
|
|
double dresult;
|
|
|
|
/* remove one level of indirection */
|
|
p1 = *pp1;
|
|
p2 = *pp2;
|
|
|
|
ORDERKEY_PCTCPU
|
|
ORDERKEY_CPTICKS
|
|
ORDERKEY_STATE
|
|
ORDERKEY_PRIO
|
|
ORDERKEY_RSSIZE
|
|
ORDERKEY_MEM
|
|
;
|
|
|
|
return result == 0 ? 0 : result < 0 ? -1 : 1;
|
|
}
|
|
|
|
/* compare_size - the comparison function for sorting by total memory usage */
|
|
|
|
int
|
|
compare_size (
|
|
struct top_proc **pp1,
|
|
struct top_proc **pp2)
|
|
{
|
|
register struct top_proc *p1;
|
|
register struct top_proc *p2;
|
|
register long result;
|
|
double dresult;
|
|
|
|
/* remove one level of indirection */
|
|
p1 = *pp1;
|
|
p2 = *pp2;
|
|
|
|
ORDERKEY_MEM
|
|
ORDERKEY_RSSIZE
|
|
ORDERKEY_PCTCPU
|
|
ORDERKEY_CPTICKS
|
|
ORDERKEY_STATE
|
|
ORDERKEY_PRIO
|
|
;
|
|
|
|
return result == 0 ? 0 : result < 0 ? -1 : 1;
|
|
}
|
|
|
|
/* compare_res - the comparison function for sorting by resident set size */
|
|
|
|
int
|
|
compare_res (
|
|
struct top_proc **pp1,
|
|
struct top_proc **pp2)
|
|
{
|
|
register struct top_proc *p1;
|
|
register struct top_proc *p2;
|
|
register long result;
|
|
double dresult;
|
|
|
|
/* remove one level of indirection */
|
|
p1 = *pp1;
|
|
p2 = *pp2;
|
|
|
|
ORDERKEY_RSSIZE
|
|
ORDERKEY_MEM
|
|
ORDERKEY_PCTCPU
|
|
ORDERKEY_CPTICKS
|
|
ORDERKEY_STATE
|
|
ORDERKEY_PRIO
|
|
;
|
|
|
|
return result == 0 ? 0 : result < 0 ? -1 : 1;
|
|
}
|
|
|
|
/* compare_time - the comparison function for sorting by total cpu time */
|
|
|
|
int
|
|
compare_time (
|
|
struct top_proc **pp1,
|
|
struct top_proc **pp2)
|
|
{
|
|
register struct top_proc *p1;
|
|
register struct top_proc *p2;
|
|
register long result;
|
|
double dresult;
|
|
|
|
/* remove one level of indirection */
|
|
p1 = *pp1;
|
|
p2 = *pp2;
|
|
|
|
ORDERKEY_CPTICKS
|
|
ORDERKEY_PCTCPU
|
|
ORDERKEY_STATE
|
|
ORDERKEY_PRIO
|
|
ORDERKEY_MEM
|
|
ORDERKEY_RSSIZE
|
|
;
|
|
|
|
return result == 0 ? 0 : result < 0 ? -1 : 1;
|
|
}
|
|
|
|
/* compare_cmd - the comparison function for sorting by command name */
|
|
|
|
int
|
|
compare_cmd (
|
|
struct top_proc **pp1,
|
|
struct top_proc **pp2)
|
|
{
|
|
register struct top_proc *p1;
|
|
register struct top_proc *p2;
|
|
register long result;
|
|
double dresult;
|
|
|
|
/* remove one level of indirection */
|
|
p1 = *pp1;
|
|
p2 = *pp2;
|
|
|
|
ORDERKEY_NAME
|
|
ORDERKEY_PCTCPU
|
|
ORDERKEY_CPTICKS
|
|
ORDERKEY_STATE
|
|
ORDERKEY_PRIO
|
|
ORDERKEY_RSSIZE
|
|
ORDERKEY_MEM
|
|
;
|
|
|
|
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.
|
|
*/
|
|
|
|
int
|
|
proc_owner(int pid)
|
|
|
|
{
|
|
struct stat sb;
|
|
char buffer[32];
|
|
sprintf(buffer, "%d", pid);
|
|
|
|
if (stat(buffer, &sb) < 0)
|
|
return -1;
|
|
else
|
|
return (int)sb.st_uid;
|
|
}
|