1 /******************************************/
2 /* WMTOP - Mini top in a dock app */
3 /******************************************/
6 * wmtop.c -- WindowMaker process view dock app
7 * Derived by Dan Piponi dan@tanelorn.demon.co.uk
8 * http://www.tanelorn.demon.co.uk
9 * http://wmtop.sourceforge.net
10 * from code originally contained in wmsysmon by Dave Clark (clarkd@skynet.ca)
11 * This software is licensed through the GNU General Public License.
15 * Ensure there's an operating system defined. There is *no* default
16 * because every OS has it's own way of revealing CPU/memory usage.
20 #endif /* defined(FREEBSD) */
24 #endif /* defined(LINUX) */
26 #if !defined(OS_DEFINED)
27 #error No operating system selected
28 #endif /* !defined(OS_DEFINED) */
32 /******************************************/
34 /******************************************/
51 #endif /* defined(PARANOID) */
55 #include <sys/param.h>
56 #include <sys/types.h>
57 #include <sys/ioctl.h>
62 #include <X11/extensions/shape.h>
63 #include <X11/keysym.h>
67 #include <libdockapp/wmgeneral.h>
68 #include <libdockapp/misc.h>
69 #include "xpm/wmtop-default.xpm"
70 #include "xpm/wmtop-lcd.xpm"
71 #include "xpm/wmtop-neon1.xpm"
72 #include "xpm/wmtop-neon2.xpm"
73 #include "xpm/wmtop-rainbow.xpm"
75 /******************************************/
77 /******************************************/
80 * XXX: I shouldn't really use this WMTOP_BUFLENGTH variable but scanf is so
81 * lame and it'll take me a while to write a replacement.
83 #define WMTOP_BUFLENGTH 1024
86 #define PROCFS_TEMPLATE "/proc/%d/stat"
87 #define PROCFS_CMDLINE_TEMPLATE "/proc/%d/cmdline"
88 #endif /* defined(LINUX) */
91 #define PROCFS_TEMPLATE "/proc/%d/status"
92 #endif /* defined(FREEBSD) */
94 /******************************************/
96 /******************************************/
98 regex_t
*exclusion_expression
= 0;
99 uid_t user
= (uid_t
) -1;
100 char *process_command
= 0;
102 * Default mode: zero=cpu one=memory
107 * Number and default artistic styles.
112 char wmtop_mask_bits
[64*64];
113 int wmtop_mask_width
= 64;
114 int wmtop_mask_height
= 64;
116 int update_rate
= 1000000;
117 int refresh_rate
= 100000;
119 extern char **environ
;
123 /******************************************/
125 /******************************************/
133 void *wmtop_malloc(int n
) {
134 int *p
= (int *)malloc(sizeof(int)+n
);
137 return (void *)(p
+1);
140 void wmtop_free(void *n
) {
147 fprintf(stderr
,"%d bytes allocated\n",g_malloced
);
149 #else /* defined(DEBUG) */
150 #define wmtop_malloc malloc
151 #define wmtop_free free
152 #endif /* defined(DEBUG) */
154 char *wmtop_strdup(const char *s
) {
155 return strcpy((char *)wmtop_malloc(strlen(s
)+1),s
);
158 /******************************************/
160 /******************************************/
166 { wmtop_default_xpm
, "Light emitting diode (default)" },
167 { wmtop_lcd_xpm
, "Liquid crystal display" },
168 { wmtop_rainbow_xpm
, "Rainbow display" },
169 { wmtop_neon1_xpm
, "Neon lights" },
170 { wmtop_neon2_xpm
, "More neon lights" },
174 #if defined(PARANOID)
176 #endif /* defined(PARANOID) */
178 * Store processes in a doubly linked list
180 struct process
*next
;
181 struct process
*previous
;
186 unsigned long user_time
;
187 unsigned long kernel_time
;
188 unsigned long previous_user_time
;
189 unsigned long previous_kernel_time
;
196 /******************************************/
198 /******************************************/
201 * Global pointer to head of process list
203 struct process
*first_process
= 0;
207 struct process
*find_process(pid_t pid
) {
208 struct process
*p
= first_process
;
218 * Create a new process object and insert it into the process list
220 struct process
*new_process(int p
) {
221 struct process
*process
;
222 process
= wmtop_malloc(sizeof(struct process
));
224 #if defined(PARANOID)
225 process
->id
= 0x0badfeed;
226 #endif /* defined(PARANOID) */
229 * Do stitching necessary for doubly linked list
232 process
->previous
= 0;
233 process
->next
= first_process
;
235 process
->next
->previous
= process
;
236 first_process
= process
;
239 process
->time_stamp
= 0;
240 process
->previous_user_time
= ULONG_MAX
;
241 process
->previous_kernel_time
= ULONG_MAX
;
242 process
->counted
= 1;
244 /* process_find_name(process);*/
249 /******************************************/
251 /******************************************/
253 void wmtop_routine(int, char **);
254 int process_parse_procfs(struct process
*);
255 int update_process_table(void);
256 int calculate_cpu(struct process
*);
257 void process_cleanup(void);
258 void delete_process(struct process
*);
259 void draw_processes(void);
260 unsigned long calc_cpu_total(void);
261 void calc_cpu_each(unsigned long total
);
263 unsigned long calc_mem_total(void);
264 void calc_mem_each(unsigned long total
);
266 int process_find_top_three(struct process
**);
267 void draw_bar(int, int, int, int, float, int, int);
268 void blit_string(char *, int, int);
270 void printversion(void);
272 /******************************************/
274 /******************************************/
276 int main(int argc
, char *argv
[]) {
281 * Make sure we have a /proc filesystem. No point in continuing if we
284 if (stat("/proc",&sbuf
)<0) {
286 "No /proc filesystem present. Unable to obtain processor info.\n");
295 if (strlen(ProgName
) >= 5)
296 ProgName
+= strlen(ProgName
) - 5;
298 for (i
= 1; i
<argc
; i
++) {
306 exclusion_expression
= ®
;
307 regcomp(exclusion_expression
,argv
[i
+1],REG_EXTENDED
);
316 process_command
= argv
[i
+1];
330 #endif /* defined(LINUX) */
332 if (strcmp(arg
+1, "display")) {
338 if (strcmp(arg
+1, "geometry")) {
352 update_rate
= (atoi(argv
[i
+1]) * 1000);
358 refresh_rate
= (atoi(argv
[i
+1]) * 1000);
364 if (atoi(argv
[i
+1]) < 1 || atoi(argv
[i
+1]) > nstyles
) {
368 style
= atoi(argv
[i
+1]) - 1;
380 wmtop_routine(argc
, argv
);
385 /******************************************/
387 /******************************************/
389 void wmtop_routine(int argc
, char **argv
) {
391 struct timeval tv
={0,0};
392 struct timeval last
={0,0};
393 int count
= update_rate
;
395 createXBMfromXPM(wmtop_mask_bits
, styles
[style
].pixmap
, wmtop_mask_width
, wmtop_mask_height
);
397 openXwindow(argc
, argv
, styles
[style
].pixmap
, wmtop_mask_bits
, wmtop_mask_width
, wmtop_mask_height
);
402 waitpid(0, NULL
, WNOHANG
);
404 if (count
>=update_rate
) {
405 memcpy(&last
,&tv
,sizeof(tv
));
419 while (XPending(display
)) {
420 XNextEvent(display
, &Event
);
421 switch (Event
.type
) {
426 XCloseDisplay(display
);
430 if (Event
.xbutton
.button
==1)
433 if (Event
.xbutton
.button
==2) {
439 if (Event
.xbutton
.button
==3 && process_command
)
440 execCommand(process_command
);
444 usleep(refresh_rate
);
445 count
= count
+ refresh_rate
;
449 /******************************************/
450 /* Extract information from /proc */
451 /******************************************/
454 * These are the guts that extract information out of /proc.
455 * Anyone hoping to port wmtop should look here first.
457 int process_parse_procfs(struct process
*process
) {
458 char line
[WMTOP_BUFLENGTH
],filename
[WMTOP_BUFLENGTH
],procname
[WMTOP_BUFLENGTH
];
461 unsigned long user_time
,kernel_time
;
465 char deparenthesised_name
[WMTOP_BUFLENGTH
];
467 #endif /* defined(LINUX) */
469 /* TODO: needs analysis. Probably needs same data type fix as LINUX (use
470 * long types). Need to check FreeBSD docs and test. -wbk */
472 #endif /* defined(FREEBSD) */
474 #if defined(PARANOID)
475 assert(process
->id
==0x0badfeed);
476 #endif /* defined(PARANOID) */
478 sprintf(filename
,PROCFS_TEMPLATE
,process
->pid
);
481 * Permissions of /proc filesystem are permissions of process too
483 if (user
!=(uid_t
)-1) {
484 stat(filename
,&sbuf
);
485 if (sbuf
.st_uid
!=user
)
489 ps
= open(filename
,O_RDONLY
);
492 * The process must have finished in the last few jiffies!
497 * Mark process as up-to-date.
499 process
->time_stamp
= g_time
;
501 rc
= read(ps
,line
,sizeof(line
));
508 * Extract cpu times from data in /proc filesystem.
509 * For conversion types see man proc(5).
511 rc
= sscanf(line
,"%*s %s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %lu %lu %*s %*s %*s %*s %*s %*s %*s %lu %ld",
513 &process
->user_time
,&process
->kernel_time
,
514 &process
->vsize
,&process
->rss
);
518 * Remove parentheses from the process name stored in /proc/ under Linux...
521 /* remove any "kdeinit: " */
522 if (r
== strstr(r
, "kdeinit"))
524 sprintf(filename
,PROCFS_CMDLINE_TEMPLATE
,process
->pid
);
527 * Permissions of /proc filesystem are permissions of process too
529 if (user
!=(uid_t
)-1) {
530 stat(filename
,&sbuf
);
531 if (sbuf
.st_uid
!=user
)
535 ps
= open(filename
,O_RDONLY
);
538 * The process must have finished in the last few jiffies!
542 endl
= read(ps
,line
,sizeof(line
));
545 /* null terminate the input */
547 /* account for "kdeinit: " */
548 if ((char*)line
== strstr(line
, "kdeinit: "))
553 q
= deparenthesised_name
;
555 while (*r
&& *r
!=' ')
561 q
= deparenthesised_name
;
562 while (*r
&& *r
!=')')
568 wmtop_free(process
->name
);
569 process
->name
= wmtop_strdup(deparenthesised_name
);
570 #endif /* defined(LINUX) */
574 * Extract cpu times from data in /proc/<pid>/stat
575 * XXX: Process name extractor for FreeBSD is untested right now.
577 * [TODO: FREEBSD code probably needs similar data type changes to
578 * those made for LINUX above. Need to check docs. -wbk]
580 rc
= sscanf(line
,"%s %*s %*s %*s %*s %*s %*s %*s %d,%d %d,%d",
586 wmtop_free(process
->name
);
587 process
->name
= wmtop_strdup(procname
);
588 process
->user_time
= us
*1000+um
/1000;
589 process
->kernel_time
= ks
*1000+km
/1000;
590 #endif /* defined(FREEBSD) */
592 /* not portable (especially unsuitable for redistributable executables.
593 * On some systems, getpagesize() is a preprocessor macro).
595 process
->rss
*= getpagesize();
597 if (process
->previous_user_time
==ULONG_MAX
)
598 process
->previous_user_time
= process
->user_time
;
599 if (process
->previous_kernel_time
==ULONG_MAX
)
600 process
->previous_kernel_time
= process
->kernel_time
;
602 user_time
= process
->user_time
-process
->previous_user_time
;
603 kernel_time
= process
->kernel_time
-process
->previous_kernel_time
;
605 process
->previous_user_time
= process
->user_time
;
606 process
->previous_kernel_time
= process
->kernel_time
;
608 process
->user_time
= user_time
;
609 process
->kernel_time
= kernel_time
;
614 /******************************************/
615 /* Update process table */
616 /******************************************/
618 int update_process_table() {
620 struct dirent
*entry
;
622 if (!(dir
= opendir("/proc")))
626 * Get list of processes from /proc directory
628 while ((entry
= readdir(dir
))) {
633 * Problem reading list of processes
639 if (sscanf(entry
->d_name
,"%d",&pid
)>0) {
641 p
= find_process(pid
);
643 p
= new_process(pid
);
654 /******************************************/
655 /* Get process structure for process pid */
656 /******************************************/
659 * This function seems to hog all of the CPU time. I can't figure out why - it
662 int calculate_cpu(struct process
*process
) {
665 #if defined(PARANOID)
666 assert(process
->id
==0x0badfeed);
667 #endif /* defined(PARANOID) */
669 rc
= process_parse_procfs(process
);
674 * Check name against the exclusion list
676 if (process
->counted
&& exclusion_expression
&& !regexec(exclusion_expression
,process
->name
,0,0,0))
677 process
->counted
= 0;
682 /******************************************/
683 /* Strip dead process entries */
684 /******************************************/
686 void process_cleanup() {
688 struct process
*p
= first_process
;
690 struct process
*current
= p
;
692 #if defined(PARANOID)
693 assert(p
->id
==0x0badfeed);
694 #endif /* defined(PARANOID) */
698 * Delete processes that have died
700 if (current
->time_stamp
!=g_time
)
701 delete_process(current
);
705 /******************************************/
706 /* Destroy and remove a process */
707 /******************************************/
709 void delete_process(struct process
*p
) {
710 #if defined(PARANOID)
711 assert(p
->id
==0x0badfeed);
714 * Ensure that deleted processes aren't reused.
717 #endif /* defined(PARANOID) */
720 * Maintain doubly linked list.
723 p
->next
->previous
= p
->previous
;
725 p
->previous
->next
= p
->next
;
727 first_process
= p
->next
;
734 /******************************************/
735 /* Generate display */
736 /******************************************/
738 void draw_processes() {
740 struct process
*best
[3] = { 0, 0, 0 };
744 * Invalidate time stamps
748 update_process_table();
752 total
= calc_cpu_total();
753 calc_cpu_each(total
);
757 total
= calc_mem_total();
758 calc_mem_each(total
);
766 * Find the top three!
768 n
= process_find_top_three(best
);
770 for (i
= 0; i
<3; ++i
) {
775 for (j
= 0; j
<9; ++j
) {
777 c
= best
[i
]->name
[j
];
783 draw_bar(0, 97, 55, 6, best
[i
]->amount
, 4, 13+i
*20);
785 draw_bar(0, 97, 55, 6, 0, 4, 13+i
*20);
786 blit_string(s
,4,4+i
*20);
794 /******************************************/
795 /* Calculate cpu total */
796 /******************************************/
798 unsigned long calc_cpu_total() {
799 unsigned long total
,t
;
800 static unsigned long previous_total
= ULONG_MAX
;
804 char line
[WMTOP_BUFLENGTH
];
805 unsigned long cpu
,nice
,system
,idle
;
807 ps
= open("/proc/stat",O_RDONLY
);
808 rc
= read(ps
,line
,sizeof(line
));
812 sscanf(line
,"%*s %lu %lu %lu %lu",&cpu
,&nice
,&system
,&idle
);
813 total
= cpu
+nice
+system
+idle
;
814 #endif /* defined(LINUX) */
820 total
= tv
.tv_sec
*1000+tv
.tv_usec
/1000;
821 #endif /* defined(FREEBSD) */
823 t
= total
-previous_total
;
824 previous_total
= total
;
831 /******************************************/
832 /* Calculate each processes cpu */
833 /******************************************/
835 void calc_cpu_each(unsigned long total
) {
836 struct process
*p
= first_process
;
839 #if defined(PARANOID)
840 assert(p
->id
==0x0badfeed);
841 #endif /* defined(PARANOID) */
843 p
->amount
= total
? 100*(float)(p
->user_time
+p
->kernel_time
)/total
: 0;
848 /******************************************/
849 /* Calculate total memory */
850 /******************************************/
853 /* INT_MAX won't always hold total system RAM, especially on a 64 bit system. */
854 unsigned long calc_mem_total() {
860 ps
= open("/proc/meminfo",O_RDONLY
);
861 rc
= read(ps
,line
,sizeof(line
));
866 if ((ptr
= strstr(line
, "Mem:")) != NULL
) {
869 } else if ((ptr
= strstr(line
, "MemTotal:")) != NULL
) {
870 /* The "Mem:" line has been removed in Linux 2.6 */
872 return atoi(ptr
) << 10; /* MemTotal is given in kiB */
877 #endif /* defined(LINUX) */
879 /******************************************/
880 /* Calculate each processes memory */
881 /******************************************/
884 void calc_mem_each(unsigned long total
) {
885 struct process
*p
= first_process
;
887 p
->amount
= 100*(double)p
->rss
/total
;
891 #endif /* defined(LINUX) */
893 /******************************************/
894 /* Find the top three processes */
895 /******************************************/
898 * Result is stored in decreasing order in best[0-2].
900 int process_find_top_three(struct process
**best
) {
901 struct process
*p
= first_process
;
905 * Insertion sort approach to skim top 3
908 if (p
->counted
&& p
->amount
>0 && (!best
[0] || p
->amount
>best
[0]->amount
)) {
913 } else if (p
->counted
&& p
->amount
>0 && (!best
[1] || p
->amount
>best
[1]->amount
)) {
917 } else if (p
->counted
&& p
->amount
>0 && (!best
[2] || p
->amount
>best
[2]->amount
)) {
928 /******************************************/
929 /* Blit bar at co-ordinates */
930 /******************************************/
932 void draw_bar(int sx
, int sy
, int w
, int h
, float percent
, int dx
, int dy
) {
936 tx
= w
* (float)percent
/ 100;
941 copyXPMArea(sx
, sy
, tx
, h
, dx
, dy
);
943 copyXPMArea(sx
+tx
, sy
+h
, w
-tx
, h
, dx
+tx
, dy
);
946 /******************************************/
947 /* Blit string at co-ordinates */
948 /******************************************/
950 void blit_string(char *name
, int x
, int y
) {
956 for ( i
= 0; name
[i
]; i
++) {
957 c
= toupper(name
[i
]);
958 if (c
>= 'A' && c
<= 'J') {
960 copyXPMArea(c
*6,73,6,7,k
,y
);
961 } else if (c
>='K' && c
<='T') {
963 copyXPMArea(c
*6,81,6,7,k
,y
);
964 } else if (c
>='U' && c
<='Z') {
966 copyXPMArea(c
*6,89,6,7,k
,y
);
967 } else if (c
>='0' && c
<='9') {
969 copyXPMArea(c
*6,65,6,7,k
,y
);
971 copyXPMArea(36,89,6,7,k
,y
);
977 /******************************************/
979 /******************************************/
983 fprintf(stderr
,"\nWMtop - Dan Piponi <dan@tanelorn.demon.co.uk> http://www.tanelorn.demon.co.uk\n\n");
984 fprintf(stderr
,"usage:\n");
985 fprintf(stderr
," -display <display name>\n");
986 fprintf(stderr
," -geometry +XPOS+YPOS initial window position\n");
987 fprintf(stderr
," -s <...> sample rate in milliseconds (default:%d)\n", update_rate
/1000);
988 fprintf(stderr
," -r <...> refresh rate in milliseconds (default:%d)\n", refresh_rate
/1000);
989 fprintf(stderr
," -U display user processes only\n");
990 fprintf(stderr
," -x <...> exclude matching processes\n");
991 fprintf(stderr
," -c <...> command\n");
993 fprintf(stderr
," -m display memory usage\n");
994 #endif /* defined(LINUX) */
995 fprintf(stderr
," -v print version number\n");
996 fprintf(stderr
," -a <1..%d> select artistic style\n", nstyles
);
997 fprintf(stderr
,"\n");
998 fprintf(stderr
,"The artistic style is one of:\n");
999 for (i
= 0; i
<nstyles
; ++i
)
1000 fprintf(stderr
," %d - %s\n",i
+1,styles
[i
].description
);
1003 /******************************************/
1005 /******************************************/
1007 void printversion(void) {
1008 fprintf(stderr
, "wmtop v%s\n",PACKAGE_VERSION
);