dockapps/wmtop/wmtop.c
Tilmann Hentze 51132ae7ee wmtop: patch for process name extraction on Linux
I received the following patch from Tilmann Hentze <0xcafe@directbox.com>
in an email:

> Hello,
> there are processes, that have space characters in their names, for example
> Firefoxe's "Web Content" process.
> The current sscanf line for Linux would only considers the first part of
> the process name, e.g. "Web" and cut off the rest.
> Attached patch should consider the complete process name between parentheses.

> Further I did not touch the handling of removal of "kdeinit", since
> I am not using KDE and am not sure if the assumptions in the source code
> are still valid.

> Best Regards,
> Tilmann.

Signed-off-by: Doug Torrance <dtorrance@piedmont.edu>
2020-03-31 08:16:24 +01:00

1007 lines
23 KiB
C

/******************************************/
/* WMTOP - Mini top in a dock app */
/******************************************/
/*
* wmtop.c -- WindowMaker process view dock app
* Derived by Dan Piponi dan@tanelorn.demon.co.uk
* http://www.tanelorn.demon.co.uk
* http://wmtop.sourceforge.net
* from code originally contained in wmsysmon by Dave Clark (clarkd@skynet.ca)
* This software is licensed through the GNU General Public License.
*/
/*
* Ensure there's an operating system defined. There is *no* default
* because every OS has it's own way of revealing CPU/memory usage.
*/
#if defined(FREEBSD)
#define OS_DEFINED
#endif /* defined(FREEBSD) */
#if defined(LINUX)
#define OS_DEFINED
#endif /* defined(LINUX) */
#if !defined(OS_DEFINED)
#error No operating system selected
#endif /* !defined(OS_DEFINED) */
#define _BSD_SOURCE
/******************************************/
/* Includes */
/******************************************/
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <math.h>
#include <limits.h>
#include <errno.h>
#include <signal.h>
#if defined(PARANOID)
#include <assert.h>
#endif /* defined(PARANOID) */
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/xpm.h>
#include <X11/extensions/shape.h>
#include <X11/keysym.h>
#include <regex.h>
#include <libdockapp/wmgeneral.h>
#include <libdockapp/misc.h>
#include "xpm/wmtop-default.xpm"
#include "xpm/wmtop-lcd.xpm"
#include "xpm/wmtop-neon1.xpm"
#include "xpm/wmtop-neon2.xpm"
#include "xpm/wmtop-rainbow.xpm"
/******************************************/
/* Defines */
/******************************************/
/*
* XXX: I shouldn't really use this WMTOP_BUFLENGTH variable but scanf is so
* lame and it'll take me a while to write a replacement.
*/
#define WMTOP_BUFLENGTH 1024
#if defined(LINUX)
#define PROCFS_TEMPLATE "/proc/%d/stat"
#define PROCFS_CMDLINE_TEMPLATE "/proc/%d/cmdline"
#endif /* defined(LINUX) */
#if defined(FREEBSD)
#define PROCFS_TEMPLATE "/proc/%d/status"
#endif /* defined(FREEBSD) */
/******************************************/
/* Globals */
/******************************************/
regex_t *exclusion_expression = 0;
uid_t user = (uid_t) -1;
char *process_command = 0;
/*
* Default mode: zero=cpu one=memory
*/
int mode = 0;
/*
* Number and default artistic styles.
*/
int nstyles = 5;
int style = 0;
char wmtop_mask_bits[64*64];
int wmtop_mask_width = 64;
int wmtop_mask_height = 64;
int update_rate = 1000000;
int refresh_rate = 100000;
extern char **environ;
char *ProgName;
/******************************************/
/* Debug */
/******************************************/
#if defined(DEBUG)
/*
* Memory handler
*/
int g_malloced = 0;
void *wmtop_malloc(int n) {
int *p = (int *)malloc(sizeof(int)+n);
p[0] = n;
g_malloced += n;
return (void *)(p+1);
}
void wmtop_free(void *n) {
int *p = (int *)n;
g_malloced -= p[-1];
free(p-1);
}
void show_memory() {
fprintf(stderr,"%d bytes allocated\n",g_malloced);
}
#else /* defined(DEBUG) */
#define wmtop_malloc malloc
#define wmtop_free free
#endif /* defined(DEBUG) */
char *wmtop_strdup(const char *s) {
return strcpy((char *)wmtop_malloc(strlen(s)+1),s);
}
/******************************************/
/* Structures */
/******************************************/
struct {
char **pixmap;
char *description;
} styles[] = {
{ wmtop_default_xpm, "Light emitting diode (default)" },
{ wmtop_lcd_xpm, "Liquid crystal display" },
{ wmtop_rainbow_xpm, "Rainbow display" },
{ wmtop_neon1_xpm, "Neon lights" },
{ wmtop_neon2_xpm, "More neon lights" },
};
struct process {
#if defined(PARANOID)
long id;
#endif /* defined(PARANOID) */
/*
* Store processes in a doubly linked list
*/
struct process *next;
struct process *previous;
pid_t pid;
char *name;
float amount;
unsigned long user_time;
unsigned long kernel_time;
unsigned long previous_user_time;
unsigned long previous_kernel_time;
unsigned long vsize;
long rss;
int time_stamp;
int counted;
};
/******************************************/
/* Process class */
/******************************************/
/*
* Global pointer to head of process list
*/
struct process *first_process = 0;
int g_time = 0;
struct process *find_process(pid_t pid) {
struct process *p = first_process;
while (p) {
if (p->pid==pid)
return p;
p = p->next;
}
return 0;
}
/*
* Create a new process object and insert it into the process list
*/
struct process *new_process(int p) {
struct process *process;
process = wmtop_malloc(sizeof(struct process));
#if defined(PARANOID)
process->id = 0x0badfeed;
#endif /* defined(PARANOID) */
/*
* Do stitching necessary for doubly linked list
*/
process->name = 0;
process->previous = 0;
process->next = first_process;
if (process->next)
process->next->previous = process;
first_process = process;
process->pid = p;
process->time_stamp = 0;
process->previous_user_time = ULONG_MAX;
process->previous_kernel_time = ULONG_MAX;
process->counted = 1;
/* process_find_name(process);*/
return process;
}
/******************************************/
/* Functions */
/******************************************/
void wmtop_routine(int, char **);
int process_parse_procfs(struct process *);
int update_process_table(void);
int calculate_cpu(struct process *);
void process_cleanup(void);
void delete_process(struct process *);
void draw_processes(void);
unsigned long calc_cpu_total(void);
void calc_cpu_each(unsigned long total);
#if defined(LINUX)
unsigned long calc_mem_total(void);
void calc_mem_each(unsigned long total);
#endif
int process_find_top_three(struct process **);
void draw_bar(int, int, int, int, float, int, int);
void blit_string(char *, int, int);
void usage(void);
void printversion(void);
/******************************************/
/* Main */
/******************************************/
int main(int argc, char *argv[]) {
int i;
struct stat sbuf;
/*
* Make sure we have a /proc filesystem. No point in continuing if we
* haven't!
*/
if (stat("/proc",&sbuf)<0) {
fprintf(stderr,
"No /proc filesystem present. Unable to obtain processor info.\n");
exit(1);
}
/*
* Parse Command Line
*/
ProgName = argv[0];
if (strlen(ProgName) >= 5)
ProgName += strlen(ProgName) - 5;
for (i = 1; i<argc; i++) {
char *arg = argv[i];
if (*arg=='-') {
switch (arg[1]) {
case 'x' :
if (argc>i+1) {
static regex_t reg;
exclusion_expression = &reg;
regcomp(exclusion_expression,argv[i+1],REG_EXTENDED);
i++;
} else {
usage();
exit(1);
}
break;
case 'c' :
if (argc>i+1) {
process_command = argv[i+1];
i++;
break;
} else {
usage();
exit(1);
}
#if defined(LINUX)
case 'm':
/*
* Display memory
*/
mode = 1;
break;
#endif /* defined(LINUX) */
case 'd' :
if (strcmp(arg+1, "display")) {
usage();
exit(1);
}
break;
case 'g' :
if (strcmp(arg+1, "geometry")) {
usage();
exit(1);
}
break;
case 'v' :
printversion();
exit(0);
break;
case 'U' :
user = getuid();
break;
case 's':
if (argc > (i+1)) {
update_rate = (atoi(argv[i+1]) * 1000);
i++;
}
break;
case 'r':
if (argc > (i+1)) {
refresh_rate = (atoi(argv[i+1]) * 1000);
i++;
}
break;
case 'a':
if (argc > (i+1)) {
if (atoi(argv[i+1]) < 1 || atoi(argv[i+1]) > nstyles) {
usage();
exit(1);
}
style = atoi(argv[i+1]) - 1;
i++;
}
break;
default:
usage();
exit(0);
break;
}
}
}
wmtop_routine(argc, argv);
return 0;
}
/******************************************/
/* Main routine */
/******************************************/
void wmtop_routine(int argc, char **argv) {
XEvent Event;
struct timeval tv={0,0};
struct timeval last={0,0};
int count = update_rate;
createXBMfromXPM(wmtop_mask_bits, styles[style].pixmap, wmtop_mask_width, wmtop_mask_height);
openXwindow(argc, argv, styles[style].pixmap, wmtop_mask_bits, wmtop_mask_width, wmtop_mask_height);
while (1) {
waitpid(0, NULL, WNOHANG);
if (count>=update_rate) {
memcpy(&last,&tv,sizeof(tv));
/*
* Update display
*/
draw_processes();
RedrawWindow();
count = 0;
}
/*
* X Events
*/
while (XPending(display)) {
XNextEvent(display, &Event);
switch (Event.type) {
case Expose:
RedrawWindow();
break;
case DestroyNotify:
XCloseDisplay(display);
exit(0);
case ButtonPress:
#if defined(LINUX)
if (Event.xbutton.button==1)
mode = !mode;
#endif
if (Event.xbutton.button==2) {
if (user==(uid_t)-1)
user=getuid();
else
user=-1;
}
if (Event.xbutton.button==3 && process_command)
execCommand(process_command);
break;
}
}
usleep(refresh_rate);
count = count + refresh_rate;
}
}
/******************************************/
/* Extract information from /proc */
/******************************************/
/*
* These are the guts that extract information out of /proc.
* Anyone hoping to port wmtop should look here first.
*/
int process_parse_procfs(struct process *process) {
char line[WMTOP_BUFLENGTH],filename[WMTOP_BUFLENGTH],procname[WMTOP_BUFLENGTH];
int ps;
struct stat sbuf;
unsigned long user_time,kernel_time;
int rc;
#if defined(LINUX)
char *r,*q;
char deparenthesised_name[WMTOP_BUFLENGTH];
int endl;
#endif /* defined(LINUX) */
#if defined(FREEBSD)
/* TODO: needs analysis. Probably needs same data type fix as LINUX (use
* long types). Need to check FreeBSD docs and test. -wbk */
int us,um,ks,km;
#endif /* defined(FREEBSD) */
#if defined(PARANOID)
assert(process->id==0x0badfeed);
#endif /* defined(PARANOID) */
sprintf(filename,PROCFS_TEMPLATE,process->pid);
/*
* Permissions of /proc filesystem are permissions of process too
*/
if (user!=(uid_t)-1) {
stat(filename,&sbuf);
if (sbuf.st_uid!=user)
return 1;
}
ps = open(filename,O_RDONLY);
if (ps<0)
/*
* The process must have finished in the last few jiffies!
*/
return 1;
/*
* Mark process as up-to-date.
*/
process->time_stamp = g_time;
rc = read(ps,line,sizeof(line));
close(ps);
if (rc<0)
return 1;
#if defined(LINUX)
/*
* Extract cpu times from data in /proc filesystem.
* For conversion types see man proc(5).
*/
rc = sscanf(line,"%*s (%[^)]) %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %lu %lu %*s %*s %*s %*s %*s %*s %*s %lu %ld",
procname,
&process->user_time,&process->kernel_time,
&process->vsize,&process->rss);
if (rc<5)
return 1;
r = procname;
/* remove any "kdeinit: " */
if (r == strstr(r, "kdeinit"))
{
sprintf(filename,PROCFS_CMDLINE_TEMPLATE,process->pid);
/*
* Permissions of /proc filesystem are permissions of process too
*/
if (user!=(uid_t)-1) {
stat(filename,&sbuf);
if (sbuf.st_uid!=user)
return 1;
}
ps = open(filename,O_RDONLY);
if (ps<0)
/*
* The process must have finished in the last few jiffies!
*/
return 1;
endl = read(ps,line,sizeof(line));
close(ps);
/* null terminate the input */
line[endl]=0;
/* account for "kdeinit: " */
if ((char*)line == strstr(line, "kdeinit: "))
r = ((char*)line)+9;
else
r = (char*)line;
q = deparenthesised_name;
/* stop at space */
while (*r && *r!=' ')
*q++ = *r++;
*q = 0;
}
else
{
q = deparenthesised_name;
while (*r)
*q++ = *r++;
*q = 0;
}
if (process->name)
wmtop_free(process->name);
process->name = wmtop_strdup(deparenthesised_name);
#endif /* defined(LINUX) */
#if defined(FREEBSD)
/*
* Extract cpu times from data in /proc/<pid>/stat
* XXX: Process name extractor for FreeBSD is untested right now.
*
* [TODO: FREEBSD code probably needs similar data type changes to
* those made for LINUX above. Need to check docs. -wbk]
*/
rc = sscanf(line,"%s %*s %*s %*s %*s %*s %*s %*s %d,%d %d,%d",
procname,
&us,&um,&ks,&km);
if (rc<5)
return 1;
if (process->name)
wmtop_free(process->name);
process->name = wmtop_strdup(procname);
process->user_time = us*1000+um/1000;
process->kernel_time = ks*1000+km/1000;
#endif /* defined(FREEBSD) */
/* not portable (especially unsuitable for redistributable executables.
* On some systems, getpagesize() is a preprocessor macro).
*/
process->rss *= getpagesize();
if (process->previous_user_time==ULONG_MAX)
process->previous_user_time = process->user_time;
if (process->previous_kernel_time==ULONG_MAX)
process->previous_kernel_time = process->kernel_time;
user_time = process->user_time-process->previous_user_time;
kernel_time = process->kernel_time-process->previous_kernel_time;
process->previous_user_time = process->user_time;
process->previous_kernel_time = process->kernel_time;
process->user_time = user_time;
process->kernel_time = kernel_time;
return 0;
}
/******************************************/
/* Update process table */
/******************************************/
int update_process_table() {
DIR *dir;
struct dirent *entry;
if (!(dir = opendir("/proc")))
return 1;
/*
* Get list of processes from /proc directory
*/
while ((entry = readdir(dir))) {
pid_t pid;
if (!entry) {
/*
* Problem reading list of processes
*/
closedir(dir);
return 1;
}
if (sscanf(entry->d_name,"%d",&pid)>0) {
struct process *p;
p = find_process(pid);
if (!p)
p = new_process(pid);
calculate_cpu(p);
}
}
closedir(dir);
return 0;
}
/******************************************/
/* Get process structure for process pid */
/******************************************/
/*
* This function seems to hog all of the CPU time. I can't figure out why - it
* doesn't do much.
*/
int calculate_cpu(struct process *process) {
int rc;
#if defined(PARANOID)
assert(process->id==0x0badfeed);
#endif /* defined(PARANOID) */
rc = process_parse_procfs(process);
if (rc)
return 1;
/*
* Check name against the exclusion list
*/
if (process->counted && exclusion_expression && !regexec(exclusion_expression,process->name,0,0,0))
process->counted = 0;
return 0;
}
/******************************************/
/* Strip dead process entries */
/******************************************/
void process_cleanup() {
struct process *p = first_process;
while (p) {
struct process *current = p;
#if defined(PARANOID)
assert(p->id==0x0badfeed);
#endif /* defined(PARANOID) */
p = p->next;
/*
* Delete processes that have died
*/
if (current->time_stamp!=g_time)
delete_process(current);
}
}
/******************************************/
/* Destroy and remove a process */
/******************************************/
void delete_process(struct process *p) {
#if defined(PARANOID)
assert(p->id==0x0badfeed);
/*
* Ensure that deleted processes aren't reused.
*/
p->id = 0x007babe;
#endif /* defined(PARANOID) */
/*
* Maintain doubly linked list.
*/
if (p->next)
p->next->previous = p->previous;
if (p->previous)
p->previous->next = p->next;
else
first_process = p->next;
if (p->name)
wmtop_free(p->name);
wmtop_free(p);
}
/******************************************/
/* Generate display */
/******************************************/
void draw_processes() {
int i,n;
struct process *best[3] = { 0, 0, 0 };
unsigned long total;
/*
* Invalidate time stamps
*/
++g_time;
update_process_table();
switch (mode) {
case 0:
total = calc_cpu_total();
calc_cpu_each(total);
break;
#if defined(LINUX)
case 1:
total = calc_mem_total();
calc_mem_each(total);
break;
#endif
}
process_cleanup();
/*
* Find the top three!
*/
n = process_find_top_three(best);
for (i = 0; i<3; ++i) {
int j;
char s[10];
strcpy(s," ");
if (i<n) {
for (j = 0; j<9; ++j) {
char c;
c = best[i]->name[j];
if (c)
s[j] = c;
else
break;
}
draw_bar(0, 97, 55, 6, best[i]->amount, 4, 13+i*20);
} else
draw_bar(0, 97, 55, 6, 0, 4, 13+i*20);
blit_string(s,4,4+i*20);
}
#if defined(DEBUG)
show_memory();
#endif
}
/******************************************/
/* Calculate cpu total */
/******************************************/
unsigned long calc_cpu_total() {
unsigned long total,t;
static unsigned long previous_total = ULONG_MAX;
#if defined(LINUX)
int rc;
int ps;
char line[WMTOP_BUFLENGTH];
unsigned long cpu,nice,system,idle;
ps = open("/proc/stat",O_RDONLY);
rc = read(ps,line,sizeof(line));
close(ps);
if (rc<0)
return 0;
sscanf(line,"%*s %lu %lu %lu %lu",&cpu,&nice,&system,&idle);
total = cpu+nice+system+idle;
#endif /* defined(LINUX) */
#if defined(FREEBSD)
struct timeval tv;
gettimeofday(&tv,0);
total = tv.tv_sec*1000+tv.tv_usec/1000;
#endif /* defined(FREEBSD) */
t = total-previous_total;
previous_total = total;
if (t<0)
t = 0;
return t;
}
/******************************************/
/* Calculate each processes cpu */
/******************************************/
void calc_cpu_each(unsigned long total) {
struct process *p = first_process;
while (p) {
#if defined(PARANOID)
assert(p->id==0x0badfeed);
#endif /* defined(PARANOID) */
p->amount = total ? 100*(float)(p->user_time+p->kernel_time)/total : 0;
p = p->next;
}
}
/******************************************/
/* Calculate total memory */
/******************************************/
#if defined(LINUX)
/* INT_MAX won't always hold total system RAM, especially on a 64 bit system. */
unsigned long calc_mem_total() {
int ps;
char line[1024];
char *ptr;
int rc;
ps = open("/proc/meminfo",O_RDONLY);
rc = read(ps,line,sizeof(line));
close(ps);
if (rc<0)
return 0;
if ((ptr = strstr(line, "Mem:")) != NULL) {
ptr += 4;
return atoi(ptr);
} else if ((ptr = strstr(line, "MemTotal:")) != NULL) {
/* The "Mem:" line has been removed in Linux 2.6 */
ptr += 9;
return atoi(ptr) << 10; /* MemTotal is given in kiB */
} else {
return 0;
}
}
#endif /* defined(LINUX) */
/******************************************/
/* Calculate each processes memory */
/******************************************/
#if defined(LINUX)
void calc_mem_each(unsigned long total) {
struct process *p = first_process;
while (p) {
p->amount = 100*(double)p->rss/total;
p = p->next;
}
}
#endif /* defined(LINUX) */
/******************************************/
/* Find the top three processes */
/******************************************/
/*
* Result is stored in decreasing order in best[0-2].
*/
int process_find_top_three(struct process **best) {
struct process *p = first_process;
int n = 0;
/*
* Insertion sort approach to skim top 3
*/
while (p) {
if (p->counted && p->amount>0 && (!best[0] || p->amount>best[0]->amount)) {
best[2] = best[1];
best[1] = best[0];
best[0] = p;
++n;
} else if (p->counted && p->amount>0 && (!best[1] || p->amount>best[1]->amount)) {
best[2] = best[1];
best[1] = p;
++n;
} else if (p->counted && p->amount>0 && (!best[2] || p->amount>best[2]->amount)) {
++n;
best[2] = p;
}
p = p->next;
}
return n>3 ? 3 : n;
}
/******************************************/
/* Blit bar at co-ordinates */
/******************************************/
void draw_bar(int sx, int sy, int w, int h, float percent, int dx, int dy) {
int tx;
if (percent<=100)
tx = w * (float)percent / 100;
else
tx = w;
if (tx>0)
copyXPMArea(sx, sy, tx, h, dx, dy);
if (tx<w)
copyXPMArea(sx+tx, sy+h, w-tx, h, dx+tx, dy);
}
/******************************************/
/* Blit string at co-ordinates */
/******************************************/
void blit_string(char *name, int x, int y) {
int i;
int c;
int k;
k = x;
for ( i = 0; name[i]; i++) {
c = toupper(name[i]);
if (c >= 'A' && c <= 'J') {
c -= 'A';
copyXPMArea(c*6,73,6,7,k,y);
} else if (c>='K' && c<='T') {
c -= 'K';
copyXPMArea(c*6,81,6,7,k,y);
} else if (c>='U' && c<='Z') {
c -= 'U';
copyXPMArea(c*6,89,6,7,k,y);
} else if (c>='0' && c<='9') {
c -= '0';
copyXPMArea(c*6,65,6,7,k,y);
} else {
copyXPMArea(36,89,6,7,k,y);
}
k += 6;
}
}
/******************************************/
/* Usage */
/******************************************/
void usage(void) {
int i;
fprintf(stderr,"\nWMtop - Dan Piponi <dan@tanelorn.demon.co.uk> http://www.tanelorn.demon.co.uk\n\n");
fprintf(stderr,"usage:\n");
fprintf(stderr," -display <display name>\n");
fprintf(stderr," -geometry +XPOS+YPOS initial window position\n");
fprintf(stderr," -s <...> sample rate in milliseconds (default:%d)\n", update_rate/1000);
fprintf(stderr," -r <...> refresh rate in milliseconds (default:%d)\n", refresh_rate/1000);
fprintf(stderr," -U display user processes only\n");
fprintf(stderr," -x <...> exclude matching processes\n");
fprintf(stderr," -c <...> command\n");
#if defined(LINUX)
fprintf(stderr," -m display memory usage\n");
#endif /* defined(LINUX) */
fprintf(stderr," -v print version number\n");
fprintf(stderr," -a <1..%d> select artistic style\n", nstyles);
fprintf(stderr,"\n");
fprintf(stderr,"The artistic style is one of:\n");
for (i = 0; i<nstyles; ++i)
fprintf(stderr," %d - %s\n",i+1,styles[i].description);
}
/******************************************/
/* Print version */
/******************************************/
void printversion(void) {
fprintf(stderr, "wmtop v%s\n",PACKAGE_VERSION);
}