mirror of
https://github.com/topjohnwu/ndk-busybox.git
synced 2024-11-24 20:29:55 +00:00
Add a new top applet
-Erik
This commit is contained in:
parent
793c3b4a7d
commit
420b208a45
@ -473,6 +473,9 @@
|
||||
#ifdef CONFIG_TIME
|
||||
APPLET(time, time_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER)
|
||||
#endif
|
||||
#ifdef CONFIG_TOP
|
||||
APPLET(top, top_main, _BB_DIR_USR_BIN, _BB_SUID_NEVER)
|
||||
#endif
|
||||
#ifdef CONFIG_TOUCH
|
||||
APPLET(touch, touch_main, _BB_DIR_BIN, _BB_SUID_NEVER)
|
||||
#endif
|
||||
|
@ -1860,6 +1860,15 @@
|
||||
"Options:\n" \
|
||||
"\t-v\tDisplays verbose resource usage information."
|
||||
|
||||
#define top_trivial_usage \
|
||||
"[-d <seconds>]"
|
||||
#define top_full_usage \
|
||||
"top provides an view of processor activity in real time.\n" \
|
||||
"This utility reads the status for all processes in /proc each <seconds>\n" \
|
||||
"and shows the status for however many processes will fit on the screen.\n" \
|
||||
"This utility will not show processes that are started after program startup,\n" \
|
||||
"but it will show the EXIT status for and PIDs that exit while it is running."
|
||||
|
||||
#define touch_trivial_usage \
|
||||
"[-c] FILE [FILE ...]"
|
||||
#define touch_full_usage \
|
||||
|
@ -28,6 +28,7 @@ PROCPS-$(CONFIG_KILL) += kill.o
|
||||
PROCPS-$(CONFIG_PIDOF) += pidof.o
|
||||
PROCPS-$(CONFIG_PS) += ps.o
|
||||
PROCPS-$(CONFIG_RENICE) += renice.o
|
||||
PROCPS-$(CONFIG_TOP) += top.o
|
||||
PROCPS-$(CONFIG_UPTIME) += uptime.o
|
||||
|
||||
libraries-y+=$(PROCPS_DIR)$(PROCPS_AR)
|
||||
|
@ -18,6 +18,7 @@ if [ "$CONFIG_PS" = "y" ] ; then
|
||||
bool ' Use devps instead of /proc (needs a patched kernel)' CONFIG_FEATURE_USE_DEVPS_PATCH
|
||||
fi
|
||||
bool 'renice' CONFIG_RENICE
|
||||
bool 'top' CONFIG_TOP
|
||||
bool 'uptime' CONFIG_UPTIME
|
||||
endmenu
|
||||
|
||||
|
408
procps/top.c
Normal file
408
procps/top.c
Normal file
@ -0,0 +1,408 @@
|
||||
/*
|
||||
* A tiny 'top' utility.
|
||||
*
|
||||
* This is written specifically for the linux 2.4 /proc/<PID>/status
|
||||
* file format, but it checks that the file actually conforms to the
|
||||
* format that this utility expects.
|
||||
|
||||
* This reads the PIDs of all processes at startup and then shows the
|
||||
* status of those processes at given intervals. User can give
|
||||
* maximum number of processes to show. If a process exits, it's PID
|
||||
* is shown as 'EXIT'. If new processes are started while this works,
|
||||
* it doesn't add them to the list of shown processes.
|
||||
*
|
||||
* NOTES:
|
||||
* - At startup this changes to /proc, all the reads are then
|
||||
* relative to that.
|
||||
* - Includes code from the scandir() manual page.
|
||||
*
|
||||
* TODO:
|
||||
* - ppid, uid etc could be read only once when program starts
|
||||
* and rest of the information could be gotten from the
|
||||
* /proc/<PID>/statm file.
|
||||
* - Add process CPU and memory usage *percentages*.
|
||||
*
|
||||
* (C) Eero Tamminen <oak at welho dot com>
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include "busybox.h"
|
||||
|
||||
|
||||
/* process information taken from /proc,
|
||||
* The code takes into account how long the fields below are,
|
||||
* starting from copying the file from 'status' file to displaying it!
|
||||
*/
|
||||
typedef struct {
|
||||
char uid[6]; /* User ID */
|
||||
char pid[6]; /* Pid */
|
||||
char ppid[6]; /* Parent Pid */
|
||||
char name[12]; /* Name */
|
||||
char cmd[20]; /* command line[read/show size] */
|
||||
char state[2]; /* State: S, W... */
|
||||
char size[9]; /* VmSize */
|
||||
char lck[9]; /* VmLck */
|
||||
char rss[9]; /* VmRSS */
|
||||
char data[9]; /* VmData */
|
||||
char stk[9]; /* VmStk */
|
||||
char exe[9]; /* VmExe */
|
||||
char lib[9]; /* VmLib */
|
||||
} status_t;
|
||||
|
||||
/* display generic info (meminfo / loadavg) */
|
||||
static void display_generic(void)
|
||||
{
|
||||
FILE *fp;
|
||||
char buf[80];
|
||||
float avg1, avg2, avg3;
|
||||
unsigned long total, used, mfree, shared, buffers, cached;
|
||||
|
||||
/* read memory info */
|
||||
fp = fopen("meminfo", "r");
|
||||
if (!fp) {
|
||||
perror("fopen('meminfo')");
|
||||
return;
|
||||
}
|
||||
fgets(buf, sizeof(buf), fp); /* skip first line */
|
||||
|
||||
if (fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu",
|
||||
&total, &used, &mfree, &shared, &buffers, &cached) != 6) {
|
||||
fprintf(stderr, "Error: failed to read 'meminfo'");
|
||||
fclose(fp);
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
/* read load average */
|
||||
fp = fopen("loadavg", "r");
|
||||
if (!fp) {
|
||||
perror("fopen('loadavg')");
|
||||
return;
|
||||
}
|
||||
if (fscanf(fp, "%f %f %f", &avg1, &avg2, &avg3) != 3) {
|
||||
fprintf(stderr, "Error: failed to read 'loadavg'");
|
||||
fclose(fp);
|
||||
return;
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
/* convert to kilobytes */
|
||||
if (total) total /= 1024;
|
||||
if (used) used /= 1024;
|
||||
if (mfree) mfree /= 1024;
|
||||
if (shared) shared /= 1024;
|
||||
if (buffers) buffers /= 1024;
|
||||
if (cached) cached /= 1024;
|
||||
|
||||
/* output memory info and load average */
|
||||
printf("Mem: %ldK, %ldK used, %ldK free, %ldK shrd, %ldK buff, %ldK cached\n",
|
||||
total, used, mfree, shared, buffers, cached);
|
||||
printf("Load average: %.2f, %.2f, %.2f (State: S=sleeping R=running, W=waiting)\n",
|
||||
avg1, avg2, avg3);
|
||||
}
|
||||
|
||||
|
||||
/* display process statuses */
|
||||
static void display_status(int count, const status_t *s)
|
||||
{
|
||||
const char *fmt, *cmd;
|
||||
|
||||
/* clear screen & go to top */
|
||||
printf("\e[2J\e[1;1H");
|
||||
|
||||
display_generic();
|
||||
|
||||
/* what info of the processes is shown */
|
||||
printf("\n%*s %*s %*s %*s %*s %*s %-*s\n",
|
||||
sizeof(s->pid)-1, "Pid:",
|
||||
sizeof(s->state)-1, "",
|
||||
sizeof(s->ppid)-1, "PPid:",
|
||||
sizeof(s->uid)-1, "UID:",
|
||||
sizeof(s->size)-1, "WmSize:",
|
||||
sizeof(s->rss)-1, "WmRSS:",
|
||||
sizeof(s->cmd)-1, "command line:");
|
||||
|
||||
while (count--) {
|
||||
if (s->cmd[0]) {
|
||||
/* normal process, has command line */
|
||||
cmd = s->cmd;
|
||||
fmt = "%*s %*s %*s %*s %*s %*s %s\n";
|
||||
} else {
|
||||
/* no command line, show only process name */
|
||||
cmd = s->name;
|
||||
fmt = "%*s %*s %*s %*s %*s %*s [%s]\n";
|
||||
}
|
||||
printf(fmt,
|
||||
sizeof(s->pid)-1, s->pid,
|
||||
sizeof(s->state)-1, s->state,
|
||||
sizeof(s->ppid)-1, s->ppid,
|
||||
sizeof(s->uid)-1, s->uid,
|
||||
sizeof(s->size)-1, s->size,
|
||||
sizeof(s->rss)-1, s->rss,
|
||||
cmd);
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* checks if given 'buf' for process starts with 'id' + ':' + TAB
|
||||
* and stores rest of the buf to 'store' with max size 'size'
|
||||
*/
|
||||
static void process_status(const char *buf, const char *id, char *store, size_t size)
|
||||
{
|
||||
int len, i;
|
||||
|
||||
if (!store) {
|
||||
/* ignoring this field */
|
||||
return;
|
||||
}
|
||||
|
||||
/* check status field name */
|
||||
len = strlen(id);
|
||||
if (strncmp(buf, id, len) != 0) {
|
||||
error_msg_and_die("ERROR status: line doesn't start with '%s' in:\n%s\n", id, buf);
|
||||
}
|
||||
buf += len;
|
||||
|
||||
/* check status field format */
|
||||
if ((*buf++ != ':') || (*buf++ != '\t')) {
|
||||
error_msg_and_die("ERROR status: field '%s' not followed with ':' + TAB in:\n%s\n", id, buf);
|
||||
}
|
||||
|
||||
/* skip whitespace in Wm* fields */
|
||||
if (id[0] == 'V' && id[1] == 'm') {
|
||||
i = 3;
|
||||
while (i--) {
|
||||
if (*buf == ' ') {
|
||||
buf++;
|
||||
} else {
|
||||
error_msg_and_die("ERROR status: can't skip whitespace for "
|
||||
"'%s' field in:\n%s\n", id, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* copy at max (size-1) chars and force '\0' to the end */
|
||||
while (--size) {
|
||||
if (*buf < ' ') {
|
||||
break;
|
||||
}
|
||||
*store++ = *buf++;
|
||||
}
|
||||
*store = '\0';
|
||||
}
|
||||
|
||||
|
||||
/* read process statuses */
|
||||
static void read_status(int num, status_t *s)
|
||||
{
|
||||
char status[20];
|
||||
char buf[80];
|
||||
FILE *fp;
|
||||
|
||||
while (num--) {
|
||||
sprintf(status, "%s/status", s->pid);
|
||||
|
||||
/* read the command line from 'cmdline' in PID dir */
|
||||
fp = fopen(status, "r");
|
||||
if (!fp) {
|
||||
strncpy(s->pid, "EXIT", sizeof(s->pid));
|
||||
continue;
|
||||
}
|
||||
|
||||
/* get and process the information */
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "Name", s->name, sizeof(s->name));
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "State", s->state, sizeof(s->state));
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "Tgid", NULL, 0);
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "Pid", NULL, 0);
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "PPid", s->ppid, sizeof(s->ppid));
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "TracePid", NULL, 0);
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "Uid", s->uid, sizeof(s->uid));
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "Gid", NULL, 0);
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "FDSize", NULL, 0);
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "Groups", NULL, 0);
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
/* only user space processes have command line
|
||||
* and memory statistics
|
||||
*/
|
||||
if (s->cmd[0]) {
|
||||
process_status(buf, "VmSize", s->size, sizeof(s->size));
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "VmLck", s->lck, sizeof(s->lck));
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "VmRSS", s->rss, sizeof(s->rss));
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "VmData", s->data, sizeof(s->data));
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "VmStk", s->stk, sizeof(s->stk));
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "VmExe", s->exe, sizeof(s->exe));
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
process_status(buf, "VmLib", s->lib, sizeof(s->lib));
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
/* next process */
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* allocs statuslist and reads process command lines, frees namelist,
|
||||
* returns filled statuslist or NULL in case of error.
|
||||
*/
|
||||
static status_t *read_info(int num, struct dirent **namelist)
|
||||
{
|
||||
status_t *statuslist, *s;
|
||||
char cmdline[20];
|
||||
FILE *fp;
|
||||
int idx;
|
||||
|
||||
/* allocate & zero status for each of the processes */
|
||||
statuslist = calloc(num, sizeof(status_t));
|
||||
if (!statuslist) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* go through the processes */
|
||||
for (idx = 0; idx < num; idx++) {
|
||||
|
||||
/* copy PID string to status struct and free name */
|
||||
s = &(statuslist[idx]);
|
||||
if (strlen(namelist[idx]->d_name) > sizeof(s->pid)-1) {
|
||||
fprintf(stderr, "PID '%s' too long\n", namelist[idx]->d_name);
|
||||
return NULL;
|
||||
}
|
||||
strncpy(s->pid, namelist[idx]->d_name, sizeof(s->pid));
|
||||
s->pid[sizeof(s->pid)-1] = '\0';
|
||||
free(namelist[idx]);
|
||||
|
||||
/* read the command line from 'cmdline' in PID dir */
|
||||
sprintf(cmdline, "%s/cmdline", s->pid);
|
||||
fp = fopen(cmdline, "r");
|
||||
if (!fp) {
|
||||
perror("fopen('cmdline')");
|
||||
return NULL;
|
||||
}
|
||||
fgets(statuslist[idx].cmd, sizeof(statuslist[idx].cmd), fp);
|
||||
fclose(fp);
|
||||
}
|
||||
free(namelist);
|
||||
return statuslist;
|
||||
}
|
||||
|
||||
|
||||
/* returns true for file names which are PID dirs
|
||||
* (i.e. start with number)
|
||||
*/
|
||||
static int filter_pids(const struct dirent *dir)
|
||||
{
|
||||
status_t dummy;
|
||||
char *name = dir->d_name;
|
||||
|
||||
if (*name >= '0' && *name <= '9') {
|
||||
if (strlen(name) > sizeof(dummy.pid)-1) {
|
||||
fprintf(stderr, "PID name '%s' too long\n", name);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* compares two directory entry names as numeric strings
|
||||
*/
|
||||
static int num_sort(const void *a, const void *b)
|
||||
{
|
||||
int ia = atoi((*(struct dirent **)a)->d_name);
|
||||
int ib = atoi((*(struct dirent **)b)->d_name);
|
||||
|
||||
if (ia == ib) {
|
||||
return 0;
|
||||
}
|
||||
/* NOTE: by switching the check, you change the process sort order */
|
||||
if (ia < ib) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int top_main(int argc, char **argv)
|
||||
{
|
||||
status_t *statuslist;
|
||||
struct dirent **namelist;
|
||||
int opt, num, interval, lines;
|
||||
#if defined CONFIG_FEATURE_AUTOWIDTH && defined CONFIG_FEATURE_USE_TERMIOS
|
||||
struct winsize win = { 0, 0, 0, 0 };
|
||||
#endif
|
||||
/* Default update rate is 5 seconds */
|
||||
interval = 5;
|
||||
/* Default to 25 lines */
|
||||
lines = 25;
|
||||
|
||||
/* do normal option parsing */
|
||||
while ((opt = getopt(argc, argv, "d:")) > 0) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
interval = atoi(optarg);
|
||||
break;
|
||||
default:
|
||||
show_usage();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined CONFIG_FEATURE_AUTOWIDTH && defined CONFIG_FEATURE_USE_TERMIOS
|
||||
ioctl(fileno(stdout), TIOCGWINSZ, &win);
|
||||
if (win.ws_row > 4)
|
||||
lines = win.ws_row - 6;
|
||||
#endif
|
||||
|
||||
/* change to proc */
|
||||
if (chdir("/proc") < 0) {
|
||||
perror_msg_and_die("chdir('/proc')");
|
||||
}
|
||||
|
||||
/* read process IDs for all the processes from the procfs */
|
||||
num = scandir(".", &namelist, filter_pids, num_sort);
|
||||
if (num < 0) {
|
||||
perror_msg_and_die("scandir('/proc')");
|
||||
}
|
||||
if (lines > num) {
|
||||
lines = num;
|
||||
}
|
||||
|
||||
/* read command line for each of the processes */
|
||||
statuslist = read_info(num, namelist);
|
||||
if (!statuslist) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
/* read status for each of the processes */
|
||||
read_status(num, statuslist);
|
||||
|
||||
/* display status */
|
||||
display_status(lines, statuslist);
|
||||
|
||||
sleep(interval);
|
||||
}
|
||||
|
||||
free(statuslist);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
Loading…
Reference in New Issue
Block a user