2 * A tiny 'top' utility.
4 * This is written specifically for the linux /proc/<PID>/status
5 * file format, but it checks that the file actually conforms to the
6 * format that this utility expects.
8 * This reads the PIDs of all processes at startup and then shows the
9 * status of those processes at given intervals. User can give
10 * maximum number of processes to show. If a process exits, it's PID
11 * is shown as 'EXIT'. If new processes are started while this works,
12 * it doesn't add them to the list of shown processes.
15 * - At startup this changes to /proc, all the reads are then
17 * - Includes code from the scandir() manual page.
20 * - ppid, uid etc could be read only once when program starts
21 * and rest of the information could be gotten from the
22 * /proc/<PID>/statm file.
23 * - Add process CPU and memory usage *percentages*.
25 * (C) Eero Tamminen <oak at welho dot com>
32 #include <sys/ioctl.h>
36 /* process information taken from /proc,
37 * The code takes into account how long the fields below are,
38 * starting from copying the file from 'status' file to displaying it!
41 char uid[6]; /* User ID */
42 char pid[6]; /* Pid */
43 char ppid[6]; /* Parent Pid */
44 char name[12]; /* Name */
45 char cmd[20]; /* command line[read/show size] */
46 char state[2]; /* State: S, W... */
47 char size[9]; /* VmSize */
48 char lck[9]; /* VmLck */
49 char rss[9]; /* VmRSS */
50 char data[9]; /* VmData */
51 char stk[9]; /* VmStk */
52 char exe[9]; /* VmExe */
53 char lib[9]; /* VmLib */
56 /* display generic info (meminfo / loadavg) */
57 static void display_generic(void)
61 float avg1, avg2, avg3;
62 unsigned long total, used, mfree, shared, buffers, cached;
64 /* read memory info */
65 fp = fopen("meminfo", "r");
67 perror("fopen('meminfo')");
70 fgets(buf, sizeof(buf), fp); /* skip first line */
72 if (fscanf(fp, "Mem: %lu %lu %lu %lu %lu %lu",
73 &total, &used, &mfree, &shared, &buffers, &cached) != 6) {
74 fprintf(stderr, "Error: failed to read 'meminfo'");
79 /* read load average */
80 fp = fopen("loadavg", "r");
82 perror("fopen('loadavg')");
85 if (fscanf(fp, "%f %f %f", &avg1, &avg2, &avg3) != 3) {
86 fprintf(stderr, "Error: failed to read 'loadavg'");
92 /* convert to kilobytes */
93 if (total) total /= 1024;
94 if (used) used /= 1024;
95 if (mfree) mfree /= 1024;
96 if (shared) shared /= 1024;
97 if (buffers) buffers /= 1024;
98 if (cached) cached /= 1024;
100 /* output memory info and load average */
101 printf("Mem: %ldK, %ldK used, %ldK free, %ldK shrd, %ldK buff, %ldK cached\n",
102 total, used, mfree, shared, buffers, cached);
103 printf("Load average: %.2f, %.2f, %.2f (State: S=sleeping R=running, W=waiting)\n",
108 /* display process statuses */
109 static void display_status(int count, const status_t *s)
111 const char *fmt, *cmd;
113 /* clear screen & go to top */
114 printf("\e[2J\e[1;1H");
118 /* what info of the processes is shown */
119 printf("\n%*s %*s %*s %*s %*s %*s %-*s\n",
120 sizeof(s->pid)-1, "Pid:",
121 sizeof(s->state)-1, "",
122 sizeof(s->ppid)-1, "PPid:",
123 sizeof(s->uid)-1, "UID:",
124 sizeof(s->size)-1, "WmSize:",
125 sizeof(s->rss)-1, "WmRSS:",
126 sizeof(s->cmd)-1, "command line:");
130 /* normal process, has command line */
132 fmt = "%*s %*s %*s %*s %*s %*s %s\n";
134 /* no command line, show only process name */
136 fmt = "%*s %*s %*s %*s %*s %*s [%s]\n";
139 sizeof(s->pid)-1, s->pid,
140 sizeof(s->state)-1, s->state,
141 sizeof(s->ppid)-1, s->ppid,
142 sizeof(s->uid)-1, s->uid,
143 sizeof(s->size)-1, s->size,
144 sizeof(s->rss)-1, s->rss,
151 /* checks if given 'buf' for process starts with 'id' + ':' + TAB
152 * and stores rest of the buf to 'store' with max size 'size'
154 static int process_status(const char *buf, const char *id, char *store, size_t size)
158 /* check status field name */
160 if (strncmp(buf, id, len) != 0) {
162 error_msg_and_die("ERROR status: line doesn't start with '%s' in:\n%s\n", id, buf);
167 /* ignoring this field */
172 /* check status field format */
173 if ((*buf++ != ':') || (*buf++ != '\t')) {
174 error_msg_and_die("ERROR status: field '%s' not followed with ':' + TAB in:\n%s\n", id, buf);
177 /* skip whitespace in Wm* fields */
178 if (id[0] == 'V' && id[1] == 'm') {
184 error_msg_and_die("ERROR status: can't skip whitespace for "
185 "'%s' field in:\n%s\n", id, buf);
190 /* copy at max (size-1) chars and force '\0' to the end */
201 /* read process statuses */
202 static void read_status(int num, status_t *s)
209 sprintf(status, "%s/status", s->pid);
211 /* read the command line from 'cmdline' in PID dir */
212 fp = fopen(status, "r");
214 strncpy(s->pid, "EXIT", sizeof(s->pid));
215 s->pid[sizeof(s->pid)-1] = '\0';
220 /* get and process the information */
221 fgets(buf, sizeof(buf), fp);
222 process_status(buf, "Name", s->name, sizeof(s->name));
223 fgets(buf, sizeof(buf), fp);
224 process_status(buf, "State", s->state, sizeof(s->state));
225 fgets(buf, sizeof(buf), fp);
226 if(process_status(buf, "Tgid", NULL, 0))
227 fgets(buf, sizeof(buf), fp);
228 process_status(buf, "Pid", NULL, 0);
229 fgets(buf, sizeof(buf), fp);
230 process_status(buf, "PPid", s->ppid, sizeof(s->ppid));
231 fgets(buf, sizeof(buf), fp);
232 if(process_status(buf, "TracerPid", NULL, 0))
233 fgets(buf, sizeof(buf), fp);
234 process_status(buf, "Uid", s->uid, sizeof(s->uid));
235 fgets(buf, sizeof(buf), fp);
236 process_status(buf, "Gid", NULL, 0);
237 fgets(buf, sizeof(buf), fp);
238 if(process_status(buf, "FDSize", NULL, 0))
239 fgets(buf, sizeof(buf), fp);
240 process_status(buf, "Groups", NULL, 0);
241 fgets(buf, sizeof(buf), fp);
242 /* only user space processes have command line
243 * and memory statistics
246 process_status(buf, "VmSize", s->size, sizeof(s->size));
247 fgets(buf, sizeof(buf), fp);
248 process_status(buf, "VmLck", s->lck, sizeof(s->lck));
249 fgets(buf, sizeof(buf), fp);
250 process_status(buf, "VmRSS", s->rss, sizeof(s->rss));
251 fgets(buf, sizeof(buf), fp);
252 process_status(buf, "VmData", s->data, sizeof(s->data));
253 fgets(buf, sizeof(buf), fp);
254 process_status(buf, "VmStk", s->stk, sizeof(s->stk));
255 fgets(buf, sizeof(buf), fp);
256 process_status(buf, "VmExe", s->exe, sizeof(s->exe));
257 fgets(buf, sizeof(buf), fp);
258 process_status(buf, "VmLib", s->lib, sizeof(s->lib));
268 /* allocs statuslist and reads process command lines, frees namelist,
269 * returns filled statuslist or NULL in case of error.
271 static status_t *read_info(int num, struct dirent **namelist)
273 status_t *statuslist, *s;
278 /* allocate & zero status for each of the processes */
279 statuslist = calloc(num, sizeof(status_t));
284 /* go through the processes */
285 for (idx = 0; idx < num; idx++) {
287 /* copy PID string to status struct and free name */
288 s = &(statuslist[idx]);
289 if (strlen(namelist[idx]->d_name) > sizeof(s->pid)-1) {
290 fprintf(stderr, "PID '%s' too long\n", namelist[idx]->d_name);
293 strncpy(s->pid, namelist[idx]->d_name, sizeof(s->pid));
294 s->pid[sizeof(s->pid)-1] = '\0';
297 /* read the command line from 'cmdline' in PID dir */
298 sprintf(cmdline, "%s/cmdline", s->pid);
299 fp = fopen(cmdline, "r");
302 perror("fopen('cmdline')");
305 fgets(statuslist[idx].cmd, sizeof(statuslist[idx].cmd), fp);
313 /* returns true for file names which are PID dirs
314 * (i.e. start with number)
316 static int filter_pids(const struct dirent *dir)
319 char *name = dir->d_name;
321 if (*name >= '0' && *name <= '9') {
322 if (strlen(name) > sizeof(dummy.pid)-1) {
323 fprintf(stderr, "PID name '%s' too long\n", name);
332 /* compares two directory entry names as numeric strings
334 static int num_sort(const void *a, const void *b)
336 int ia = atoi((*(struct dirent **)a)->d_name);
337 int ib = atoi((*(struct dirent **)b)->d_name);
342 /* NOTE: by switching the check, you change the process sort order */
350 int top_main(int argc, char **argv)
352 status_t *statuslist;
353 struct dirent **namelist;
354 int opt, num, interval, lines;
355 #if defined CONFIG_FEATURE_AUTOWIDTH && defined CONFIG_FEATURE_USE_TERMIOS
356 struct winsize win = { 0, 0, 0, 0 };
358 /* Default update rate is 5 seconds */
360 /* Default to 25 lines - 5 lines for status */
363 /* do normal option parsing */
364 while ((opt = getopt(argc, argv, "d:")) > 0) {
367 interval = atoi(optarg);
374 #if defined CONFIG_FEATURE_AUTOWIDTH && defined CONFIG_FEATURE_USE_TERMIOS
375 ioctl(fileno(stdout), TIOCGWINSZ, &win);
377 lines = win.ws_row - 5;
381 if (chdir("/proc") < 0) {
382 perror_msg_and_die("chdir('/proc')");
385 /* read process IDs for all the processes from the procfs */
386 num = scandir(".", &namelist, filter_pids, num_sort);
388 perror_msg_and_die("scandir('/proc')");
394 /* read command line for each of the processes */
395 statuslist = read_info(num, namelist);
401 /* read status for each of the processes */
402 read_status(num, statuslist);
405 display_status(lines, statuslist);