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 %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 any "kdeinit: " */
519 if (r
== strstr(r
, "kdeinit"))
521 sprintf(filename
,PROCFS_CMDLINE_TEMPLATE
,process
->pid
);
524 * Permissions of /proc filesystem are permissions of process too
526 if (user
!=(uid_t
)-1) {
527 stat(filename
,&sbuf
);
528 if (sbuf
.st_uid
!=user
)
532 ps
= open(filename
,O_RDONLY
);
535 * The process must have finished in the last few jiffies!
539 endl
= read(ps
,line
,sizeof(line
));
542 /* null terminate the input */
544 /* account for "kdeinit: " */
545 if ((char*)line
== strstr(line
, "kdeinit: "))
550 q
= deparenthesised_name
;
552 while (*r
&& *r
!=' ')
558 q
= deparenthesised_name
;
565 wmtop_free(process
->name
);
566 process
->name
= wmtop_strdup(deparenthesised_name
);
567 #endif /* defined(LINUX) */
571 * Extract cpu times from data in /proc/<pid>/stat
572 * XXX: Process name extractor for FreeBSD is untested right now.
574 * [TODO: FREEBSD code probably needs similar data type changes to
575 * those made for LINUX above. Need to check docs. -wbk]
577 rc
= sscanf(line
,"%s %*s %*s %*s %*s %*s %*s %*s %d,%d %d,%d",
583 wmtop_free(process
->name
);
584 process
->name
= wmtop_strdup(procname
);
585 process
->user_time
= us
*1000+um
/1000;
586 process
->kernel_time
= ks
*1000+km
/1000;
587 #endif /* defined(FREEBSD) */
589 /* not portable (especially unsuitable for redistributable executables.
590 * On some systems, getpagesize() is a preprocessor macro).
592 process
->rss
*= getpagesize();
594 if (process
->previous_user_time
==ULONG_MAX
)
595 process
->previous_user_time
= process
->user_time
;
596 if (process
->previous_kernel_time
==ULONG_MAX
)
597 process
->previous_kernel_time
= process
->kernel_time
;
599 user_time
= process
->user_time
-process
->previous_user_time
;
600 kernel_time
= process
->kernel_time
-process
->previous_kernel_time
;
602 process
->previous_user_time
= process
->user_time
;
603 process
->previous_kernel_time
= process
->kernel_time
;
605 process
->user_time
= user_time
;
606 process
->kernel_time
= kernel_time
;
611 /******************************************/
612 /* Update process table */
613 /******************************************/
615 int update_process_table() {
617 struct dirent
*entry
;
619 if (!(dir
= opendir("/proc")))
623 * Get list of processes from /proc directory
625 while ((entry
= readdir(dir
))) {
630 * Problem reading list of processes
636 if (sscanf(entry
->d_name
,"%d",&pid
)>0) {
638 p
= find_process(pid
);
640 p
= new_process(pid
);
651 /******************************************/
652 /* Get process structure for process pid */
653 /******************************************/
656 * This function seems to hog all of the CPU time. I can't figure out why - it
659 int calculate_cpu(struct process
*process
) {
662 #if defined(PARANOID)
663 assert(process
->id
==0x0badfeed);
664 #endif /* defined(PARANOID) */
666 rc
= process_parse_procfs(process
);
671 * Check name against the exclusion list
673 if (process
->counted
&& exclusion_expression
&& !regexec(exclusion_expression
,process
->name
,0,0,0))
674 process
->counted
= 0;
679 /******************************************/
680 /* Strip dead process entries */
681 /******************************************/
683 void process_cleanup() {
685 struct process
*p
= first_process
;
687 struct process
*current
= p
;
689 #if defined(PARANOID)
690 assert(p
->id
==0x0badfeed);
691 #endif /* defined(PARANOID) */
695 * Delete processes that have died
697 if (current
->time_stamp
!=g_time
)
698 delete_process(current
);
702 /******************************************/
703 /* Destroy and remove a process */
704 /******************************************/
706 void delete_process(struct process
*p
) {
707 #if defined(PARANOID)
708 assert(p
->id
==0x0badfeed);
711 * Ensure that deleted processes aren't reused.
714 #endif /* defined(PARANOID) */
717 * Maintain doubly linked list.
720 p
->next
->previous
= p
->previous
;
722 p
->previous
->next
= p
->next
;
724 first_process
= p
->next
;
731 /******************************************/
732 /* Generate display */
733 /******************************************/
735 void draw_processes() {
737 struct process
*best
[3] = { 0, 0, 0 };
741 * Invalidate time stamps
745 update_process_table();
749 total
= calc_cpu_total();
750 calc_cpu_each(total
);
754 total
= calc_mem_total();
755 calc_mem_each(total
);
763 * Find the top three!
765 n
= process_find_top_three(best
);
767 for (i
= 0; i
<3; ++i
) {
772 for (j
= 0; j
<9; ++j
) {
774 c
= best
[i
]->name
[j
];
780 draw_bar(0, 97, 55, 6, best
[i
]->amount
, 4, 13+i
*20);
782 draw_bar(0, 97, 55, 6, 0, 4, 13+i
*20);
783 blit_string(s
,4,4+i
*20);
791 /******************************************/
792 /* Calculate cpu total */
793 /******************************************/
795 unsigned long calc_cpu_total() {
796 unsigned long total
,t
;
797 static unsigned long previous_total
= ULONG_MAX
;
801 char line
[WMTOP_BUFLENGTH
];
802 unsigned long cpu
,nice
,system
,idle
;
804 ps
= open("/proc/stat",O_RDONLY
);
805 rc
= read(ps
,line
,sizeof(line
));
809 sscanf(line
,"%*s %lu %lu %lu %lu",&cpu
,&nice
,&system
,&idle
);
810 total
= cpu
+nice
+system
+idle
;
811 #endif /* defined(LINUX) */
817 total
= tv
.tv_sec
*1000+tv
.tv_usec
/1000;
818 #endif /* defined(FREEBSD) */
820 t
= total
-previous_total
;
821 previous_total
= total
;
828 /******************************************/
829 /* Calculate each processes cpu */
830 /******************************************/
832 void calc_cpu_each(unsigned long total
) {
833 struct process
*p
= first_process
;
836 #if defined(PARANOID)
837 assert(p
->id
==0x0badfeed);
838 #endif /* defined(PARANOID) */
840 p
->amount
= total
? 100*(float)(p
->user_time
+p
->kernel_time
)/total
: 0;
845 /******************************************/
846 /* Calculate total memory */
847 /******************************************/
850 /* INT_MAX won't always hold total system RAM, especially on a 64 bit system. */
851 unsigned long calc_mem_total() {
857 ps
= open("/proc/meminfo",O_RDONLY
);
858 rc
= read(ps
,line
,sizeof(line
));
863 if ((ptr
= strstr(line
, "Mem:")) != NULL
) {
866 } else if ((ptr
= strstr(line
, "MemTotal:")) != NULL
) {
867 /* The "Mem:" line has been removed in Linux 2.6 */
869 return atoi(ptr
) << 10; /* MemTotal is given in kiB */
874 #endif /* defined(LINUX) */
876 /******************************************/
877 /* Calculate each processes memory */
878 /******************************************/
881 void calc_mem_each(unsigned long total
) {
882 struct process
*p
= first_process
;
884 p
->amount
= 100*(double)p
->rss
/total
;
888 #endif /* defined(LINUX) */
890 /******************************************/
891 /* Find the top three processes */
892 /******************************************/
895 * Result is stored in decreasing order in best[0-2].
897 int process_find_top_three(struct process
**best
) {
898 struct process
*p
= first_process
;
902 * Insertion sort approach to skim top 3
905 if (p
->counted
&& p
->amount
>0 && (!best
[0] || p
->amount
>best
[0]->amount
)) {
910 } else if (p
->counted
&& p
->amount
>0 && (!best
[1] || p
->amount
>best
[1]->amount
)) {
914 } else if (p
->counted
&& p
->amount
>0 && (!best
[2] || p
->amount
>best
[2]->amount
)) {
925 /******************************************/
926 /* Blit bar at co-ordinates */
927 /******************************************/
929 void draw_bar(int sx
, int sy
, int w
, int h
, float percent
, int dx
, int dy
) {
933 tx
= w
* (float)percent
/ 100;
938 copyXPMArea(sx
, sy
, tx
, h
, dx
, dy
);
940 copyXPMArea(sx
+tx
, sy
+h
, w
-tx
, h
, dx
+tx
, dy
);
943 /******************************************/
944 /* Blit string at co-ordinates */
945 /******************************************/
947 void blit_string(char *name
, int x
, int y
) {
953 for ( i
= 0; name
[i
]; i
++) {
954 c
= toupper(name
[i
]);
955 if (c
>= 'A' && c
<= 'J') {
957 copyXPMArea(c
*6,73,6,7,k
,y
);
958 } else if (c
>='K' && c
<='T') {
960 copyXPMArea(c
*6,81,6,7,k
,y
);
961 } else if (c
>='U' && c
<='Z') {
963 copyXPMArea(c
*6,89,6,7,k
,y
);
964 } else if (c
>='0' && c
<='9') {
966 copyXPMArea(c
*6,65,6,7,k
,y
);
968 copyXPMArea(36,89,6,7,k
,y
);
974 /******************************************/
976 /******************************************/
980 fprintf(stderr
,"\nWMtop - Dan Piponi <dan@tanelorn.demon.co.uk> http://www.tanelorn.demon.co.uk\n\n");
981 fprintf(stderr
,"usage:\n");
982 fprintf(stderr
," -display <display name>\n");
983 fprintf(stderr
," -geometry +XPOS+YPOS initial window position\n");
984 fprintf(stderr
," -s <...> sample rate in milliseconds (default:%d)\n", update_rate
/1000);
985 fprintf(stderr
," -r <...> refresh rate in milliseconds (default:%d)\n", refresh_rate
/1000);
986 fprintf(stderr
," -U display user processes only\n");
987 fprintf(stderr
," -x <...> exclude matching processes\n");
988 fprintf(stderr
," -c <...> command\n");
990 fprintf(stderr
," -m display memory usage\n");
991 #endif /* defined(LINUX) */
992 fprintf(stderr
," -v print version number\n");
993 fprintf(stderr
," -a <1..%d> select artistic style\n", nstyles
);
994 fprintf(stderr
,"\n");
995 fprintf(stderr
,"The artistic style is one of:\n");
996 for (i
= 0; i
<nstyles
; ++i
)
997 fprintf(stderr
," %d - %s\n",i
+1,styles
[i
].description
);
1000 /******************************************/
1002 /******************************************/
1004 void printversion(void) {
1005 fprintf(stderr
, "wmtop v%s\n",PACKAGE_VERSION
);