xrestop should not try to build into a pure wayland platform.
[platform/upstream/xrestop.git] / xrestop.c
1 /*
2  *  XResTop - A 'top' like tool for monitoring X Client server resource
3  *            usage.
4  *
5  *  Copyright 2003 Matthew Allum
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2, or (at your option)
10  *  any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  */
17
18 /* 
19    TODO;
20
21    - autogubbinafy.
22    - reduce X traffic
23      - bad atm!
24      - grep window tree only once per stat collection.
25    - '--batch' option
26    - sort out clients[] array, avoid all the mallocing, use list 
27       find out max X connections ? 
28
29    possibles;
30
31    - handle term window resizes ? 
32    - more detailed mode ?
33    - key input for different sorting ?
34
35  */
36
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <unistd.h>
41
42 #include <X11/Xlib.h>
43 #include <X11/Xutil.h>
44 #include <X11/Xatom.h>
45 #include <X11/extensions/XRes.h>
46
47 #ifdef HAVE_LIBNCURSES
48 #include <ncurses.h>
49 #else
50 #include <curses.h>
51 #endif
52
53 #define DEBUG 1
54
55 #ifdef __GNUC__
56 #ifdef DEBUG
57 #define DBG(txt, args... ) fprintf(stderr, txt , ##args )
58 #else
59 #define DBG(txt, args... ) /* nothing */
60 #endif
61 #endif
62
63 enum {
64   ATOM_PIXMAP = 0,
65   ATOM_WINDOW,
66   ATOM_GC,
67   ATOM_FONT,
68   ATOM_GLYPHSET,
69   ATOM_PICTURE,
70   ATOM_COLORMAP_ENTRY,
71   ATOM_PASSIVE_GRAB,
72   ATOM_CURSOR,
73   ATOM_NET_CLIENT_LIST,
74   ATOM_NET_WM_PID,
75   ATOM_NET_WM_NAME,
76   ATOM_UTF8_STRING,
77   ATOM_COUNT
78 };
79
80 static char *AtomNames[] =
81   {
82     "PIXMAP",
83     "WINDOW",
84     "GC",
85     "FONT",
86     "GLYPHSET",
87     "PICTURE",
88     "COLORMAP ENTRY",
89     "PASSIVE GRAB",
90     "CURSOR",
91     "_NET_CLIENT_LIST",
92     "_NET_WM_PID",
93     "_NET_WM_NAME",
94     "UTF8_STRING"
95   };
96
97
98 typedef struct XResTopClient
99 {
100   XID            resource_base, resource_mask;
101   pid_t          pid;
102   char          *identifier;
103   unsigned long  pixmap_bytes;
104   unsigned long  other_bytes;
105
106   int            n_pixmaps;
107   int            n_windows; 
108   int            n_gcs;
109   int            n_pictures;
110   int            n_glyphsets; 
111   int            n_fonts;
112   int            n_colormaps;
113   int            n_passive_grabs;
114   int            n_cursors;
115   int            n_other;
116
117 } XResTopClient;
118
119 #define MAX_CLIENTS 1024  /* XXX find out max connections per server */
120
121 typedef struct XResTopApp 
122 {
123   Display    *dpy;
124   char       *dpy_name;
125   int         screen;
126   Window      win_root, win_dummy;
127   Atom        atoms[ATOM_COUNT];
128
129   XResTopClient *clients[MAX_CLIENTS];
130   int         n_clients;
131
132   Bool        want_batch_mode;
133   int         max_samples; 
134   int         delay;
135   int         n_xerrors;
136
137 } XResTopApp;
138
139
140 /* X Error trapping */
141
142 static int trapped_error_code = 0;
143 static int (*old_error_handler) (Display *d, XErrorEvent *e);
144
145 static int
146 error_handler(Display     *display,
147               XErrorEvent *error)
148 {
149    trapped_error_code = error->error_code;
150    return 0;
151 }
152
153 static void
154 trap_errors(void)
155 {
156    trapped_error_code = 0;
157    old_error_handler = XSetErrorHandler(error_handler);
158 }
159
160 static int
161 untrap_errors(void)
162 {
163    XSetErrorHandler(old_error_handler);
164    return trapped_error_code;
165 }
166
167
168 /* Misc util funcs */
169
170 pid_t
171 window_get_pid(XResTopApp *app, Window win)
172 {
173   Atom  type;
174   unsigned long  bytes_after, n_items;
175   long *data = NULL;
176   pid_t result = -1;
177   int   format;
178
179   if (XGetWindowProperty (app->dpy, win, 
180                           app->atoms[ATOM_NET_WM_PID],
181                           0, 2L,
182                           False, XA_CARDINAL,
183                           &type, &format, &n_items,
184                           &bytes_after, (unsigned char **)&data) == Success
185       && n_items && data != NULL)
186     {
187       result = *data;
188     }
189
190   if (data) XFree(data);
191
192   return result;
193 }
194
195 char*
196 window_get_utf8_name(XResTopApp *app, Window win)
197 {
198   Atom type;
199   int format;
200   unsigned long  bytes_after, n_items;
201   char *str = NULL;
202   int result;
203
204   result =  XGetWindowProperty (app->dpy, win, app->atoms[ATOM_NET_WM_NAME],
205                                 0, 1024L,
206                                 False, app->atoms[ATOM_UTF8_STRING],
207                                 &type, &format, &n_items,
208                                 &bytes_after, (unsigned char **)&str);
209
210   if (result != Success || str == NULL)
211     {
212       if (str) XFree (str);
213       return NULL;
214     }
215
216   if (type != app->atoms[ATOM_UTF8_STRING] || format != 8 || n_items == 0)
217     {
218       XFree (str);
219       return NULL;
220     }
221
222   /* XXX should probably utf8_validate this  */
223
224   return str;
225 }
226
227
228 void
229 nice_bytes(char *target, int target_size, unsigned long bytes)
230 {
231   char prefix = 'B';
232   unsigned long value = bytes;
233
234   if (bytes / 1024)
235     {
236       prefix = 'K';
237       value  = bytes / 1024;
238
239       /*
240       if (value / 1024)
241         {
242           prefix = 'M';
243           value  = value / 1024;
244         }
245       */
246     }
247
248   snprintf(target, target_size, "%li%c", value, prefix);
249 }
250
251 void 
252 usage(char *progname)
253 {
254   fprintf(stderr, 
255           "%s usage:\n"
256           "  -display,     -d        specify X Display to monitor.\n"
257           "  --delay-time, -t <int>  specify time in seconds between sampling.\n"
258           "  --batch,      -b        run in batch mode.\n"
259           "  --max-samples,-m <int>  Maximum overall readings to take.\n\n",
260           progname);
261
262   exit(1);
263 }
264
265
266 /* Client struct stuff */
267
268 XResTopClient*
269 xrestop_client_new(XResTopApp *app)
270 {
271   XResTopClient *client = NULL;
272
273   client = malloc(sizeof(XResTopClient));
274   memset(client, 0, sizeof(XResTopClient));
275
276   client->pid = -1;
277
278   return client;
279 }
280
281 void
282 xrestop_client_free(XResTopClient *client)
283 {
284   if (client->identifier) XFree (client->identifier);
285   free(client);
286 }
287
288 static Bool
289 check_win_for_info(XResTopApp *app, XResTopClient *client, Window win)
290 {
291   XTextProperty  text_prop;
292   XID            match_xid ;
293
294   /* 
295    *  Figure out if a window belongs in a XResClients resource range,
296    *  and if it does try and get a name for it.
297    *
298    *  XXX Should also check for CLASS and TRANSIENT props so we 
299    *      get the name for top level window. 
300    */
301
302   match_xid = (client->resource_base & ~client->resource_mask);
303
304   if ( (win & ~client->resource_mask) == match_xid )
305     {
306       trap_errors();
307
308       if ((client->identifier = window_get_utf8_name(app, win)) == NULL)
309         {
310           if (XGetWMName(app->dpy, win, &text_prop))
311             {
312               client->identifier = strdup((char *) text_prop.value);
313               XFree((char *) text_prop.value);
314             }
315           else
316             {
317               XFetchName(app->dpy, win, (char **)&client->identifier);
318             }
319         }
320
321       if (untrap_errors())
322         {
323           app->n_xerrors++;
324           return False;
325         }
326     }
327
328   if (client->identifier != NULL)
329     return True;
330
331   return False;
332 }
333
334 static XID
335 recurse_win_tree(XResTopApp *app, XResTopClient *client, Window win_top)
336 {
337   Window       *children, dummy;
338   unsigned int  nchildren;
339   int           i;
340   XID           w = 0;
341   Status        qtres;
342   
343   if (check_win_for_info(app, client, win_top))
344     return win_top;
345   
346   trap_errors();
347
348   qtres = XQueryTree(app->dpy, win_top, &dummy, &dummy, &children, &nchildren);
349
350   if (untrap_errors())
351     {
352       app->n_xerrors++;
353       return 0;
354     }
355
356   if (!qtres) return 0;
357
358   for (i=0; i<nchildren; i++) 
359     {
360       w = recurse_win_tree(app, client, children[i]);
361
362       if (w != None)
363         break;
364     }
365
366   if (children) XFree ((char *)children);
367
368   return w;
369 }
370
371 void 
372 xrestop_client_get_info(XResTopApp *app, XResTopClient *client)  
373 {
374   Window found = None;
375
376   /* 
377    * Try and find out some useful info about an XResClient so user
378    * can identify it to a window. 
379    * 
380    * XXX This uses a bucket load of X traffic - improve !
381    */
382
383   /* Check for our own connection */
384   if ( (client->resource_base & ~client->resource_mask) 
385           == (app->win_dummy & ~client->resource_mask) )
386     {
387       client->identifier = strdup("xrestop");
388       return;
389     }
390
391   found = recurse_win_tree(app, client, app->win_root);
392
393   if (found)
394     {
395        client->pid = window_get_pid(app, found);
396     }
397   else
398     {
399       client->identifier = strdup("<unknown>");
400     }
401 }
402
403 void
404 xrestop_client_get_stats(XResTopApp *app, XResTopClient *client)
405 {
406   int               j = 0;
407   XResType         *types = NULL;
408   int               n_types;
409
410   trap_errors();
411   
412   XResQueryClientResources (app->dpy, client->resource_base, &n_types, &types);
413   
414   XResQueryClientPixmapBytes (app->dpy, client->resource_base, 
415                               &client->pixmap_bytes);
416   
417   if (untrap_errors())
418     {
419       app->n_xerrors++;
420       goto cleanup;
421     }
422   
423   for (j=0; j < n_types; j++)
424     {
425       int this_type = types[j].resource_type;
426       
427       if (this_type == app->atoms[ATOM_PIXMAP])
428         client->n_pixmaps += types[j].count;
429       else if (this_type == app->atoms[ATOM_WINDOW])
430         client->n_windows += types[j].count;
431       else if (this_type == app->atoms[ATOM_GC])
432         client->n_gcs += types[j].count;
433       else if (this_type == app->atoms[ATOM_FONT])
434         client->n_fonts += types[j].count;
435       else if (this_type == app->atoms[ATOM_GLYPHSET])
436         client->n_glyphsets += types[j].count;
437       else if (this_type == app->atoms[ATOM_PICTURE])
438         client->n_pictures  += types[j].count;
439       else if (this_type == app->atoms[ATOM_COLORMAP_ENTRY])
440         client->n_colormaps += types[j].count;
441       else if (this_type == app->atoms[ATOM_PASSIVE_GRAB])
442         client->n_passive_grabs += types[j].count;
443       else if (this_type == app->atoms[ATOM_CURSOR])
444         client->n_cursors += types[j].count;
445       else client->n_other += types[j].count;
446     }
447
448   /* All approx currently - same as gnome system monitor */
449    client->other_bytes += client->n_windows * 24;
450    client->other_bytes += client->n_gcs * 24;
451    client->other_bytes += client->n_pictures * 24;
452    client->other_bytes += client->n_glyphsets * 24;
453    client->other_bytes += client->n_fonts * 1024;
454    client->other_bytes += client->n_colormaps * 24;
455    client->other_bytes += client->n_passive_grabs * 24;
456    client->other_bytes += client->n_cursors * 24;
457    client->other_bytes += client->n_other * 24;
458   
459  cleanup:
460    if (types) XFree(types);
461
462    return;
463 }
464
465 void
466 xrestop_populate_client_data(XResTopApp *app)
467 {
468   int         i;
469   XResClient *clients;
470
471   for (i=0; i < app->n_clients; i++)
472     xrestop_client_free(app->clients[i]);
473
474   trap_errors();
475
476   XResQueryClients(app->dpy, &app->n_clients, &clients); 
477
478   if (untrap_errors())
479     {
480       app->n_xerrors++;
481       goto cleanup;
482     }
483
484   for(i = 0; i < app->n_clients; i++) 
485     {
486       app->clients[i] = xrestop_client_new(app);
487
488       app->clients[i]->resource_base = clients[i].resource_base;
489       app->clients[i]->resource_mask = clients[i].resource_mask;
490
491       xrestop_client_get_info(app, app->clients[i]); 
492
493       xrestop_client_get_stats(app, app->clients[i]); 
494     }
495
496  cleanup:
497
498   if (clients) XFree(clients);
499 }
500
501 void
502 xrestop_display(XResTopApp *app)
503 {
504   int  i;
505   char pretty_pixmap_bytes[16] = { 0 };
506   char pretty_other_bytes[16]  = { 0 };
507   char pretty_total_bytes[16]  = { 0 };
508   char pretty_pid[16]          = { 0 };
509
510   if (!app->want_batch_mode)
511     {
512       int total_pixmap_bytes = 0, total_other_bytes = 0;
513
514       /* Calculate totals - batch doesn't have this */
515
516       for (i=0; i<app->n_clients; i++)
517         {
518           total_pixmap_bytes += app->clients[i]->pixmap_bytes;
519           total_other_bytes += app->clients[i]->other_bytes;
520         }
521
522       nice_bytes(pretty_pixmap_bytes, 16, total_pixmap_bytes);
523       nice_bytes(pretty_other_bytes, 16, total_other_bytes);
524       nice_bytes(pretty_total_bytes, 16, 
525                  total_pixmap_bytes + total_other_bytes);
526
527       /* Curses rendering  */
528
529       clear();
530
531       mvprintw(0, 0, "xrestop - Display: %s:%i", 
532                app->dpy_name ? app->dpy_name : "localhost", app->screen);
533
534       mvprintw(1, 0, "          Monitoring %i clients. XErrors: %i", app->n_clients, app->n_xerrors);
535       mvprintw(2, 0, "          Pixmaps: %8s total, Other: %8s total, All: %8s total", 
536                pretty_pixmap_bytes,
537                pretty_other_bytes,
538                pretty_total_bytes);
539
540
541       attron(A_BOLD|A_REVERSE);
542
543       mvprintw(4, 0, "res-base Wins  GCs Fnts Pxms Misc   Pxm mem  Other   Total   PID Identifier    ");
544       
545       attroff(A_BOLD|A_REVERSE);
546     }
547
548   for (i=0; i<app->n_clients; i++)
549     {
550       nice_bytes(pretty_pixmap_bytes, 16, app->clients[i]->pixmap_bytes);
551       nice_bytes(pretty_other_bytes, 16, app->clients[i]->other_bytes);
552       nice_bytes(pretty_total_bytes, 16, 
553                  app->clients[i]->pixmap_bytes + app->clients[i]->other_bytes);
554
555       if (app->clients[i]->pid > -1)
556         snprintf(pretty_pid, 16, "%5d", app->clients[i]->pid);
557       else
558         snprintf(pretty_pid, 16, "  ?  ");
559
560
561       if (!app->want_batch_mode)
562         {
563           mvprintw(i+5, 0, "%.7x  %4d %4d %4d %4d %4d   %7s %7s %7s %5s %s", 
564                    
565                    app->clients[i]->resource_base, 
566                    app->clients[i]->n_windows, 
567                    app->clients[i]->n_gcs, 
568                    app->clients[i]->n_fonts,
569                    app->clients[i]->n_pixmaps,  
570                    
571                    app->clients[i]->n_pictures 
572                    + app->clients[i]->n_glyphsets
573                    + app->clients[i]->n_colormaps
574                    + app->clients[i]->n_passive_grabs
575                    + app->clients[i]->n_cursors
576                    + app->clients[i]->n_other,
577                    
578                    pretty_pixmap_bytes,
579                    pretty_other_bytes,
580                    pretty_total_bytes,
581                    
582                    pretty_pid,     
583                    app->clients[i]->identifier
584                    
585                    );
586         }
587       else
588         {
589           printf("%i - %s ( PID:%s ):\n"
590                  "\tres_base      : ox%lx\n"
591                  "\tres_mask      : ox%lx\n"
592                  "\twindows       : %d\n"
593                  "\tGCs           : %d\n"
594                  "\tfonts         : %d\n"
595                  "\tpixmaps       : %d\n"
596                  "\tpictures      : %d\n"
597                  "\tglyphsets     : %d\n"
598                  "\tcolormaps     : %d\n"
599                  "\tpassive grabs : %d\n"
600                  "\tcursors       : %d\n"
601                  "\tunknowns      : %d\n"
602                  "\tpixmap bytes  : %ld\n"
603                  "\tother bytes   : ~%ld\n"
604                  "\ttotal bytes   : ~%ld\n",
605                  i, 
606                  app->clients[i]->identifier,
607                  pretty_pid,
608                  app->clients[i]->resource_base, 
609                  app->clients[i]->resource_mask, 
610                  app->clients[i]->n_windows, 
611                  app->clients[i]->n_gcs, 
612                  app->clients[i]->n_fonts,
613                  app->clients[i]->n_pixmaps,  
614                  app->clients[i]->n_pictures,
615                  app->clients[i]->n_glyphsets,
616                  app->clients[i]->n_colormaps,
617                  app->clients[i]->n_passive_grabs,
618                  app->clients[i]->n_cursors,
619                  app->clients[i]->n_other, 
620                  app->clients[i]->pixmap_bytes,
621                  app->clients[i]->other_bytes,
622                  app->clients[i]->pixmap_bytes + app->clients[i]->other_bytes);
623         }
624     }
625
626   if (!app->want_batch_mode)
627     refresh();
628 }
629
630 int 
631 xrestop_sort_compare(const void *a, const void *b)
632 {
633   XResTopClient *c1 = *(XResTopClient **)a;
634   XResTopClient *c2 = *(XResTopClient **)b;
635
636   if ((c1->pixmap_bytes + c1->other_bytes) > (c2->pixmap_bytes + c2->other_bytes))
637     return -1;
638
639   return 1;
640 }
641
642
643 void
644 xrestop_sort(XResTopApp *app)
645 {
646   qsort((void *)app->clients, app->n_clients, sizeof(app->clients[0]), xrestop_sort_compare);
647 }
648
649 int 
650 main(int argc, char **argv)
651 {
652   int      i, event, error, major, minor;
653   XResTopApp *app = NULL;
654
655   app = malloc(sizeof(XResTopApp));
656   memset(app, 0, sizeof(XResTopApp));
657
658   app->delay = 2;
659
660   for (i = 1; i < argc; i++) 
661     {
662       if (!strcmp ("-display", argv[i]) || !strcmp ("-d", argv[i])) 
663         {
664           if (++i>=argc) usage (argv[0]);
665           app->dpy_name = argv[i];
666           continue;
667         }
668
669       if (!strcmp ("-b", argv[i]) || !strcmp ("--batch", argv[i])) 
670         {
671           app->want_batch_mode = True;
672           continue;
673         }
674
675       if (!strcmp ("-t", argv[i]) || !strcmp ("--delay-time", argv[i])) 
676         {
677           if (++i>=argc) usage (argv[0]);
678           app->delay = atoi(argv[i]);
679           if (app->delay < 0) usage(argv[0]);
680           continue;
681         }
682
683       if (!strcmp ("-m", argv[i]) || !strcmp ("--max-samples", argv[i])) 
684         {
685           if (++i>=argc) usage (argv[0]);
686           app->max_samples = atoi(argv[i]);
687           if (app->max_samples < 0) usage(argv[0]);
688           continue;
689         }
690
691       if (!strcmp("--help", argv[i]) || !strcmp("-h", argv[i])) {
692         usage(argv[0]);
693     }
694
695     usage(argv[0]);
696   }
697
698   if ((app->dpy = XOpenDisplay(app->dpy_name)) == NULL)
699     {
700       fprintf(stderr, "%s: Unable to open display!\n", argv[0]);
701       exit(1);
702     }
703
704   app->screen = DefaultScreen(app->dpy);
705   app->win_root = RootWindow(app->dpy, app->screen); 
706     
707   XInternAtoms (app->dpy, AtomNames, ATOM_COUNT,False, app->atoms);
708
709   if(!XResQueryExtension(app->dpy, &event, &error)) {
710     fprintf(stderr, "%s: XResQueryExtension failed. Display Missing XRes extension ?\n", argv[0]);
711     return 1;
712   }
713
714   if(!XResQueryVersion(app->dpy, &major, &minor)) {
715     fprintf(stderr, "%s: XResQueryVersion failed, cannot continue.\n", argv[0]);
716     return 1;
717   }
718
719   app->n_clients = 0;
720
721   /* Create our own never mapped window so we can figure out this connection */
722   app->win_dummy = XCreateSimpleWindow(app->dpy, app->win_root, 
723                                        0, 0, 16, 16, 0, None, None); 
724
725
726   if (!app->want_batch_mode) 
727     {
728       /* Curses init */
729       initscr();
730       cbreak();
731       noecho();
732     }
733
734   i = app->max_samples;
735
736   for (;;)
737    {
738      xrestop_populate_client_data(app);
739      xrestop_sort(app);
740      xrestop_display(app);
741
742      if ((app->max_samples) && (--i < 1))
743        goto finish;
744
745      if (app->want_batch_mode) 
746        {
747          sleep(app->delay);
748        }
749      else
750        {
751          int delay;
752
753          /* Curses Curses! Handle 'q' key to quit */
754          for (delay = app->delay * 10; delay > 0; delay -= 255) 
755            {
756              if (delay > 255)
757                halfdelay(255);
758              else
759                halfdelay(delay);
760       
761              if (wgetch(stdscr) == 'q')
762                goto finish;
763            }
764        }
765
766    }
767
768  finish:
769   endwin();
770   exit(0);
771
772 }
773