2 * XResTop - A 'top' like tool for monitoring X Client server resource
5 * Copyright 2003 Matthew Allum
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)
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.
24 - grep window tree only once per stat collection.
26 - sort out clients[] array, avoid all the mallocing, use list
27 find out max X connections ?
31 - handle term window resizes ?
32 - more detailed mode ?
33 - key input for different sorting ?
43 #include <X11/Xutil.h>
44 #include <X11/Xatom.h>
45 #include <X11/extensions/XRes.h>
47 #ifdef HAVE_LIBNCURSES
57 #define DBG(txt, args... ) fprintf(stderr, txt , ##args )
59 #define DBG(txt, args... ) /* nothing */
80 static char *AtomNames[] =
98 typedef struct XResTopClient
100 XID resource_base, resource_mask;
103 unsigned long pixmap_bytes;
104 unsigned long other_bytes;
119 #define MAX_CLIENTS 1024 /* XXX find out max connections per server */
121 typedef struct XResTopApp
126 Window win_root, win_dummy;
127 Atom atoms[ATOM_COUNT];
129 XResTopClient *clients[MAX_CLIENTS];
132 Bool want_batch_mode;
140 /* X Error trapping */
142 static int trapped_error_code = 0;
143 static int (*old_error_handler) (Display *d, XErrorEvent *e);
146 error_handler(Display *display,
149 trapped_error_code = error->error_code;
156 trapped_error_code = 0;
157 old_error_handler = XSetErrorHandler(error_handler);
163 XSetErrorHandler(old_error_handler);
164 return trapped_error_code;
168 /* Misc util funcs */
171 window_get_pid(XResTopApp *app, Window win)
174 unsigned long bytes_after, n_items;
179 if (XGetWindowProperty (app->dpy, win,
180 app->atoms[ATOM_NET_WM_PID],
183 &type, &format, &n_items,
184 &bytes_after, (unsigned char **)&data) == Success
185 && n_items && data != NULL)
190 if (data) XFree(data);
196 window_get_utf8_name(XResTopApp *app, Window win)
200 unsigned long bytes_after, n_items;
204 result = XGetWindowProperty (app->dpy, win, app->atoms[ATOM_NET_WM_NAME],
206 False, app->atoms[ATOM_UTF8_STRING],
207 &type, &format, &n_items,
208 &bytes_after, (unsigned char **)&str);
210 if (result != Success || str == NULL)
212 if (str) XFree (str);
216 if (type != app->atoms[ATOM_UTF8_STRING] || format != 8 || n_items == 0)
222 /* XXX should probably utf8_validate this */
229 nice_bytes(char *target, int target_size, unsigned long bytes)
232 unsigned long value = bytes;
237 value = bytes / 1024;
243 value = value / 1024;
248 snprintf(target, target_size, "%li%c", value, prefix);
252 usage(char *progname)
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",
266 /* Client struct stuff */
269 xrestop_client_new(XResTopApp *app)
271 XResTopClient *client = NULL;
273 client = malloc(sizeof(XResTopClient));
274 memset(client, 0, sizeof(XResTopClient));
282 xrestop_client_free(XResTopClient *client)
284 if (client->identifier) XFree (client->identifier);
289 check_win_for_info(XResTopApp *app, XResTopClient *client, Window win)
291 XTextProperty text_prop;
295 * Figure out if a window belongs in a XResClients resource range,
296 * and if it does try and get a name for it.
298 * XXX Should also check for CLASS and TRANSIENT props so we
299 * get the name for top level window.
302 match_xid = (client->resource_base & ~client->resource_mask);
304 if ( (win & ~client->resource_mask) == match_xid )
308 if ((client->identifier = window_get_utf8_name(app, win)) == NULL)
310 if (XGetWMName(app->dpy, win, &text_prop))
312 client->identifier = strdup((char *) text_prop.value);
313 XFree((char *) text_prop.value);
317 XFetchName(app->dpy, win, (char **)&client->identifier);
328 if (client->identifier != NULL)
335 recurse_win_tree(XResTopApp *app, XResTopClient *client, Window win_top)
337 Window *children, dummy;
338 unsigned int nchildren;
343 if (check_win_for_info(app, client, win_top))
348 qtres = XQueryTree(app->dpy, win_top, &dummy, &dummy, &children, &nchildren);
356 if (!qtres) return 0;
358 for (i=0; i<nchildren; i++)
360 w = recurse_win_tree(app, client, children[i]);
366 if (children) XFree ((char *)children);
372 xrestop_client_get_info(XResTopApp *app, XResTopClient *client)
377 * Try and find out some useful info about an XResClient so user
378 * can identify it to a window.
380 * XXX This uses a bucket load of X traffic - improve !
383 /* Check for our own connection */
384 if ( (client->resource_base & ~client->resource_mask)
385 == (app->win_dummy & ~client->resource_mask) )
387 client->identifier = strdup("xrestop");
391 found = recurse_win_tree(app, client, app->win_root);
395 client->pid = window_get_pid(app, found);
399 client->identifier = strdup("<unknown>");
404 xrestop_client_get_stats(XResTopApp *app, XResTopClient *client)
407 XResType *types = NULL;
412 XResQueryClientResources (app->dpy, client->resource_base, &n_types, &types);
414 XResQueryClientPixmapBytes (app->dpy, client->resource_base,
415 &client->pixmap_bytes);
423 for (j=0; j < n_types; j++)
425 int this_type = types[j].resource_type;
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;
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;
460 if (types) XFree(types);
466 xrestop_populate_client_data(XResTopApp *app)
471 for (i=0; i < app->n_clients; i++)
472 xrestop_client_free(app->clients[i]);
476 XResQueryClients(app->dpy, &app->n_clients, &clients);
484 for(i = 0; i < app->n_clients; i++)
486 app->clients[i] = xrestop_client_new(app);
488 app->clients[i]->resource_base = clients[i].resource_base;
489 app->clients[i]->resource_mask = clients[i].resource_mask;
491 xrestop_client_get_info(app, app->clients[i]);
493 xrestop_client_get_stats(app, app->clients[i]);
498 if (clients) XFree(clients);
502 xrestop_display(XResTopApp *app)
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 };
510 if (!app->want_batch_mode)
512 int total_pixmap_bytes = 0, total_other_bytes = 0;
514 /* Calculate totals - batch doesn't have this */
516 for (i=0; i<app->n_clients; i++)
518 total_pixmap_bytes += app->clients[i]->pixmap_bytes;
519 total_other_bytes += app->clients[i]->other_bytes;
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);
527 /* Curses rendering */
531 mvprintw(0, 0, "xrestop - Display: %s:%i",
532 app->dpy_name ? app->dpy_name : "localhost", app->screen);
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",
541 attron(A_BOLD|A_REVERSE);
543 mvprintw(4, 0, "res-base Wins GCs Fnts Pxms Misc Pxm mem Other Total PID Identifier ");
545 attroff(A_BOLD|A_REVERSE);
548 for (i=0; i<app->n_clients; i++)
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);
555 if (app->clients[i]->pid > -1)
556 snprintf(pretty_pid, 16, "%5d", app->clients[i]->pid);
558 snprintf(pretty_pid, 16, " ? ");
561 if (!app->want_batch_mode)
563 mvprintw(i+5, 0, "%.7x %4d %4d %4d %4d %4d %7s %7s %7s %5s %s",
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,
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,
583 app->clients[i]->identifier
589 printf("%i - %s ( PID:%s ):\n"
590 "\tres_base : ox%lx\n"
591 "\tres_mask : ox%lx\n"
599 "\tpassive grabs : %d\n"
602 "\tpixmap bytes : %ld\n"
603 "\tother bytes : ~%ld\n"
604 "\ttotal bytes : ~%ld\n",
606 app->clients[i]->identifier,
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);
626 if (!app->want_batch_mode)
631 xrestop_sort_compare(const void *a, const void *b)
633 XResTopClient *c1 = *(XResTopClient **)a;
634 XResTopClient *c2 = *(XResTopClient **)b;
636 if ((c1->pixmap_bytes + c1->other_bytes) > (c2->pixmap_bytes + c2->other_bytes))
644 xrestop_sort(XResTopApp *app)
646 qsort((void *)app->clients, app->n_clients, sizeof(app->clients[0]), xrestop_sort_compare);
650 main(int argc, char **argv)
652 int i, event, error, major, minor;
653 XResTopApp *app = NULL;
655 app = malloc(sizeof(XResTopApp));
656 memset(app, 0, sizeof(XResTopApp));
660 for (i = 1; i < argc; i++)
662 if (!strcmp ("-display", argv[i]) || !strcmp ("-d", argv[i]))
664 if (++i>=argc) usage (argv[0]);
665 app->dpy_name = argv[i];
669 if (!strcmp ("-b", argv[i]) || !strcmp ("--batch", argv[i]))
671 app->want_batch_mode = True;
675 if (!strcmp ("-t", argv[i]) || !strcmp ("--delay-time", argv[i]))
677 if (++i>=argc) usage (argv[0]);
678 app->delay = atoi(argv[i]);
679 if (app->delay < 0) usage(argv[0]);
683 if (!strcmp ("-m", argv[i]) || !strcmp ("--max-samples", argv[i]))
685 if (++i>=argc) usage (argv[0]);
686 app->max_samples = atoi(argv[i]);
687 if (app->max_samples < 0) usage(argv[0]);
691 if (!strcmp("--help", argv[i]) || !strcmp("-h", argv[i])) {
698 if ((app->dpy = XOpenDisplay(app->dpy_name)) == NULL)
700 fprintf(stderr, "%s: Unable to open display!\n", argv[0]);
704 app->screen = DefaultScreen(app->dpy);
705 app->win_root = RootWindow(app->dpy, app->screen);
707 XInternAtoms (app->dpy, AtomNames, ATOM_COUNT,False, app->atoms);
709 if(!XResQueryExtension(app->dpy, &event, &error)) {
710 fprintf(stderr, "%s: XResQueryExtension failed. Display Missing XRes extension ?\n", argv[0]);
714 if(!XResQueryVersion(app->dpy, &major, &minor)) {
715 fprintf(stderr, "%s: XResQueryVersion failed, cannot continue.\n", argv[0]);
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);
726 if (!app->want_batch_mode)
734 i = app->max_samples;
738 xrestop_populate_client_data(app);
740 xrestop_display(app);
742 if ((app->max_samples) && (--i < 1))
745 if (app->want_batch_mode)
753 /* Curses Curses! Handle 'q' key to quit */
754 for (delay = app->delay * 10; delay > 0; delay -= 255)
761 if (wgetch(stdscr) == 'q')