1 /* Monolith statistics/debugging application.
2 * - by Richard W.M. Jones <rich@annexia.org>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the Free
16 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 * $Id: stats.c,v 1.10 2003/02/22 15:34:24 rich Exp $
26 #ifdef HAVE_SYS_TYPES_H
27 #include <sys/types.h>
38 #ifdef HAVE_NETINET_IN_H
39 #include <netinet/in.h>
42 #ifdef HAVE_SYS_SOCKET_H
43 #include <sys/socket.h>
46 #ifdef HAVE_ARPA_INET_H
47 #include <arpa/inet.h>
57 #include <pthr_pseudothread.h>
62 #include "ml_window.h"
63 #include "ml_widget.h"
64 #include "ml_form_layout.h"
65 #include "ml_table_layout.h"
66 #include "ml_multicol_layout.h"
67 #include "ml_flow_layout.h"
69 #include "ml_form_select.h"
70 #include "ml_form_submit.h"
71 #include "ml_text_label.h"
72 #include "ml_button.h"
73 #include "ml_dialog.h"
75 /*----- The following standard boilerplate code must appear -----*/
77 /* Main entry point to the app. */
78 static void app_main (ml_session);
81 handle_request (rws_request rq)
83 return ml_entry_point (rq, app_main);
86 /*----- End of standard boilerplate code -----*/
88 /* Per-session data structure. */
91 pool pool; /* Session pool for allocations. */
93 /* This is the top-level window. */
95 ml_table_layout std_tbl;
96 ml_form std_form; /* Area selection form. */
97 ml_flow_layout std_flow;
98 ml_form_select std_choices; /* Top-level choices. */
99 ml_form_submit std_submit;
102 /* Arguments to the show_session function. */
103 struct show_session_args
105 const char *sessionid;
109 static void show_reactor (ml_session session, struct data *data);
110 static void list_sessions (ml_session session, struct data *data);
111 static void show_session (ml_session session, void *vargs);
112 static void list_threads (ml_session session, struct data *data);
113 static void jump_to (ml_session session, void *vdata);
114 static void pack (struct data *data, ml_widget widget);
115 static const char *resolve_addr (pool pool, unsigned long addr);
116 static const char *pr_time (pool pool, reactor_time_t time);
120 void (*fn) (ml_session, struct data *data);
123 { "Reactor", show_reactor, 0 },
124 { "Sessions", list_sessions, 1 },
125 { "Threads", list_threads, 0 },
128 #define nr_choices (sizeof choices / sizeof choices[0])
130 /* If this variable is set, then sessionids are not revealed in full. Set
131 * this using the 'monolith stats restricted: 1' flag in the configuration
134 static int restricted = 1;
136 /* If this variable is set, then the application is available to everyone.
137 * If not set, then it is only available to localhost. Set this using the
138 * 'monolith stats world readable: 1' flag in the configuration file.
140 static int world_readable = 0;
143 app_main (ml_session session)
145 pool pool = ml_session_pool (session);
149 /* Read configuration settings. */
150 restricted = ml_cfg_get_bool (session, "monolith stats restricted", 1);
152 ml_cfg_get_bool (session, "monolith stats world readable", 0);
154 /* Allocate per-session data structure. */
155 data = pmalloc (pool, sizeof *data);
158 /* Lay out the window. */
159 data->std_win = new_ml_window (session, pool);
160 data->std_tbl = new_ml_table_layout (pool, 2, 1);
161 data->std_form = new_ml_form (pool);
162 data->std_flow = new_ml_flow_layout (pool);
163 data->std_choices = new_ml_form_select (pool, data->std_form);
164 ml_flow_layout_pack (data->std_flow, data->std_choices);
165 data->std_submit = new_ml_form_submit (pool, data->std_form, "Go");
166 ml_flow_layout_pack (data->std_flow, data->std_submit);
167 ml_form_pack (data->std_form, data->std_flow);
168 ml_table_layout_pack (data->std_tbl, data->std_form, 0, 0);
169 ml_window_pack (data->std_win, data->std_tbl);
171 /* Populate the list of choices. */
172 for (i = 0; i < nr_choices; ++i)
174 ml_form_select_push_back (data->std_choices, choices[i].choice);
175 if (choices[i].is_default)
176 ml_form_select_set_selection (data->std_choices, i);
179 /* Set the form callback so that we jump to the right page. */
180 ml_form_set_callback (data->std_form, jump_to, session, data);
182 /* Set the window title. */
183 ml_window_set_title (data->std_win,
184 "Monolith statistics and debugging application");
186 /* Jump to the initial page (default choice). */
187 jump_to (session, data);
191 jump_to (ml_session session, void *vdata)
193 struct data *data = (struct data *) vdata;
196 /* Find out which choice is selected. */
197 i = ml_form_select_get_selection (data->std_choices);
198 if (i >= 0 && i < nr_choices)
199 /* Run that function to update the page. */
200 choices[i].fn (session, data);
204 pack (struct data *data, ml_widget widget)
206 ml_table_layout_pack (data->std_tbl, widget, 1, 0);
210 disguise_sessionid (pool pool, const char *sessionid)
212 if (!restricted) return sessionid;
215 char *s = pstrndup (pool, sessionid, 6);
216 s = pstrcat (pool, s, "[...]");
222 show_reactor (ml_session session, struct data *data)
228 list_sessions (ml_session session, struct data *data)
230 pool pool = data->pool;
232 ml_multicol_layout tbl;
236 /* Pull out the list of session IDs and turn them into session objects. */
237 sessionids = _ml_get_sessions (pool);
239 tbl = new_ml_multicol_layout (pool, 5);
240 ml_widget_set_property (tbl, "class", "ml_stats_table");
243 lbl = new_ml_text_label (pool, "sessionid");
244 ml_multicol_layout_set_header (tbl, 1);
245 ml_multicol_layout_pack (tbl, lbl);
246 lbl = new_ml_text_label (pool, "last access");
247 ml_multicol_layout_set_header (tbl, 1);
248 ml_multicol_layout_pack (tbl, lbl);
249 lbl = new_ml_text_label (pool, "address");
250 ml_multicol_layout_set_header (tbl, 1);
251 ml_multicol_layout_pack (tbl, lbl);
252 lbl = new_ml_text_label (pool, "size");
253 ml_multicol_layout_set_header (tbl, 1);
254 ml_multicol_layout_pack (tbl, lbl);
255 lbl = new_ml_text_label (pool, "path");
256 ml_multicol_layout_set_header (tbl, 1);
257 ml_multicol_layout_pack (tbl, lbl);
259 /* The sessions themselves. */
260 for (i = 0; i < vector_size (sessionids); ++i)
262 const char *sessionid;
265 struct sockaddr_in addr;
266 const char *host_header, *canonical_path;
268 struct pool_stats pool_stats;
269 struct show_session_args *args;
271 vector_get (sessionids, i, sessionid);
272 s = _ml_get_session (sessionid);
274 b = new_ml_button (pool, disguise_sessionid (pool, sessionid));
275 ml_widget_set_property (b, "button.style", "link");
276 args = pmalloc (pool, sizeof *args);
277 args->sessionid = sessionid;
279 ml_button_set_callback (b, show_session, session, args);
280 ml_button_set_popup (b, psprintf (pool, "session_%s", sessionid));
281 ml_button_set_popup_size (b, 640, 480);
282 ml_multicol_layout_pack (tbl, b);
284 lbl = new_ml_text_label (pool,
286 _ml_session_get_last_access (s)));
287 ml_multicol_layout_pack (tbl, lbl);
289 addr = _ml_session_get_original_ip (s);
290 lbl = new_ml_text_label (pool, pstrdup (pool,
291 inet_ntoa (addr.sin_addr)));
292 ml_multicol_layout_pack (tbl, lbl);
294 sp = ml_session_pool (s);
295 pool_get_stats (sp, &pool_stats, sizeof (pool_stats));
296 lbl = new_ml_text_label (pool, pitoa (pool, pool_stats.struct_size));
297 ml_multicol_layout_pack (tbl, lbl);
299 host_header = ml_session_host_header (s);
300 canonical_path = ml_session_canonical_path (s);
301 lbl = new_ml_text_label (pool,
302 psprintf (pool, "http://%s%s",
303 host_header, canonical_path));
304 ml_multicol_layout_pack (tbl, lbl);
311 show_session (ml_session session, void *vargs)
313 struct show_session_args *args = (struct show_session_args *) vargs;
314 struct data *data = args->data;
315 pool pool = data->pool;
316 const char *sessionid = args->sessionid;
322 win = new_ml_window (session, pool);
323 tbl = new_ml_form_layout (pool);
324 ml_widget_set_property (tbl, "class", "ml_stats_table");
326 /* Get the session. */
327 s = _ml_get_session (sessionid);
330 ml_error_window (pool, session,
331 "Session not found. It is likely that the session "
332 "has timed out and been deleted.",
333 ML_DIALOG_CLOSE_BUTTON);
337 /* Get the fields from the session and display them. */
339 ml_form_layout_pack (tbl, "sessionid",
340 new_ml_text_label (pool,
341 disguise_sessionid (pool, sessionid)));
342 ml_form_layout_pack (tbl, "path",
343 new_ml_text_label (pool,
344 psprintf (pool, "http://%s%s",
345 ml_session_host_header (s),
346 ml_session_canonical_path (s))));
348 /* Pool and pool stats. */
351 struct pool_stats pool_stats;
353 sp = ml_session_pool (s);
354 pool_get_stats (sp, &pool_stats, sizeof (pool_stats));
356 ml_form_layout_pack (tbl, "session pool",
357 new_ml_text_label (pool, psprintf (pool, "%p", sp)));
358 ml_form_layout_pack (tbl, "session pool size",
359 new_ml_text_label (pool,
360 pitoa (pool, pool_stats.struct_size)));
363 ml_form_layout_pack (tbl, "access count",
364 new_ml_text_label (pool,
365 pitoa (pool, _ml_session_get_hits (s))));
366 ml_form_layout_pack (tbl, "last access",
367 new_ml_text_label (pool,
368 pr_time (pool, _ml_session_get_last_access (s))));
369 ml_form_layout_pack (tbl, "creation time",
370 new_ml_text_label (pool,
371 pr_time (pool, _ml_session_get_created (s))));
373 /* Original IP address. */
375 struct sockaddr_in addr;
377 addr = _ml_session_get_original_ip (s);
378 ml_form_layout_pack (tbl, "original ip",
379 new_ml_text_label (pool,
380 pstrdup (pool, inet_ntoa (addr.sin_addr))));
383 /* User-Agent header. */
385 const char *ua = ml_session_user_agent (s);
387 ml_form_layout_pack (tbl, "user agent",
388 new_ml_text_label (pool, ua ? pstrdup (pool, ua)
395 ml_multicol_layout args_tbl;
399 args = ml_session_args (s);
400 /* XXX cgi_params_in_pool function */
401 keys = cgi_params (args);
403 args_tbl = new_ml_multicol_layout (pool, 2);
404 ml_widget_set_property (args_tbl, "class", "ml_stats_table");
406 lbl = new_ml_text_label (pool, "name");
407 ml_multicol_layout_set_header (args_tbl, 1);
408 ml_multicol_layout_pack (args_tbl, lbl);
410 lbl = new_ml_text_label (pool, "value");
411 ml_multicol_layout_set_header (args_tbl, 1);
412 ml_multicol_layout_pack (args_tbl, lbl);
414 for (i = 0; i < vector_size (keys); ++i)
416 const char *key, *value;
418 vector_get (keys, i, key);
419 value = cgi_param (args, key);
421 lbl = new_ml_text_label (pool, pstrdup (pool, key));
422 ml_multicol_layout_pack (args_tbl, lbl);
423 lbl = new_ml_text_label (pool, pstrdup (pool, value));
424 ml_multicol_layout_pack (args_tbl, lbl);
427 ml_form_layout_pack (tbl, "initial parameters", args_tbl);
430 ml_form_layout_pack (tbl, "app_main function",
431 new_ml_text_label (pool,
433 (unsigned long) _ml_session_get_app_main (s))));
438 ml_multicol_layout win_tbl;
441 windows = _ml_session_get_windows (s, pool);
443 win_tbl = new_ml_multicol_layout (pool, 2);
444 ml_widget_set_property (win_tbl, "class", "ml_stats_table");
446 lbl = new_ml_text_label (pool, "windowid");
447 ml_multicol_layout_set_header (win_tbl, 1);
448 ml_multicol_layout_pack (win_tbl, lbl);
450 lbl = new_ml_text_label (pool, "window");
451 ml_multicol_layout_set_header (win_tbl, 1);
452 ml_multicol_layout_pack (win_tbl, lbl);
454 for (i = 0; i < vector_size (windows); ++i)
456 const char *windowid;
459 vector_get (windows, i, windowid);
460 window = _ml_session_get_window (s, windowid);
462 lbl = new_ml_text_label (pool, windowid);
463 ml_multicol_layout_pack (win_tbl, lbl);
464 lbl = new_ml_text_label (pool, psprintf (pool, "%p", window));
465 ml_multicol_layout_pack (win_tbl, lbl);
468 ml_form_layout_pack (tbl, "windows", win_tbl);
474 ml_multicol_layout act_tbl;
477 actions = _ml_session_get_actions (s, pool);
479 act_tbl = new_ml_multicol_layout (pool, 3);
480 ml_widget_set_property (act_tbl, "class", "ml_stats_table");
482 lbl = new_ml_text_label (pool, "actionid");
483 ml_multicol_layout_set_header (act_tbl, 1);
484 ml_multicol_layout_pack (act_tbl, lbl);
486 lbl = new_ml_text_label (pool, "function");
487 ml_multicol_layout_set_header (act_tbl, 1);
488 ml_multicol_layout_pack (act_tbl, lbl);
490 lbl = new_ml_text_label (pool, "data");
491 ml_multicol_layout_set_header (act_tbl, 1);
492 ml_multicol_layout_pack (act_tbl, lbl);
494 for (i = 0; i < vector_size (actions); ++i)
496 const char *actionid;
499 vector_get (actions, i, actionid);
500 _ml_session_get_action (s, actionid, &fn, &data);
502 lbl = new_ml_text_label (pool, actionid);
503 ml_multicol_layout_pack (act_tbl, lbl);
504 lbl = new_ml_text_label (pool, resolve_addr (pool,
505 (unsigned long) fn));
506 ml_multicol_layout_pack (act_tbl, lbl);
507 lbl = new_ml_text_label (pool, psprintf (pool, "%p", data));
508 ml_multicol_layout_pack (act_tbl, lbl);
511 ml_form_layout_pack (tbl, "actions", act_tbl);
514 ml_form_layout_pack (tbl, "userid",
515 new_ml_text_label (pool,
516 pitoa (pool, ml_session_userid (s))));
518 ml_window_pack (win, tbl);
520 /* Set window title. */
521 ml_window_set_title (win, psprintf (pool, "Session %s",
522 disguise_sessionid (pool, sessionid)));
526 list_threads (ml_session session, struct data *data)
528 pool pool = data->pool;
531 ml_multicol_layout tbl;
534 /* Get the threads. */
535 threads = pseudothread_get_threads (pool);
537 tbl = new_ml_multicol_layout (pool, 4);
538 ml_widget_set_property (tbl, "class", "ml_stats_table");
541 lbl = new_ml_text_label (pool, "id");
542 ml_multicol_layout_set_header (tbl, 1);
543 ml_multicol_layout_pack (tbl, lbl);
544 lbl = new_ml_text_label (pool, "program counter");
545 ml_multicol_layout_set_header (tbl, 1);
546 ml_multicol_layout_pack (tbl, lbl);
547 lbl = new_ml_text_label (pool, "size");
548 ml_multicol_layout_set_header (tbl, 1);
549 ml_multicol_layout_pack (tbl, lbl);
550 lbl = new_ml_text_label (pool, "name");
551 ml_multicol_layout_set_header (tbl, 1);
552 ml_multicol_layout_pack (tbl, lbl);
554 for (i = 0; i < vector_size (threads); ++i)
559 struct pool_stats pool_stats;
561 const char *pc_symbol;
564 vector_get_ptr (threads, i, pth);
566 lbl = new_ml_text_label (pool, pitoa (pool, pth_get_thread_num (pth)));
567 ml_multicol_layout_pack (tbl, lbl);
569 pc = pth_get_PC (pth);
570 pc_symbol = resolve_addr (pool, pc);
571 lbl = new_ml_text_label (pool, pc_symbol);
572 ml_multicol_layout_pack (tbl, lbl);
574 tp = pth_get_pool (pth);
575 pool_get_stats (tp, &pool_stats, sizeof (pool_stats));
576 lbl = new_ml_text_label (pool, pitoa (pool, pool_stats.struct_size));
577 ml_multicol_layout_pack (tbl, lbl);
579 name = pstrdup (pool, pth_get_name (pth));
580 b = new_ml_button (pool, name);
581 ml_widget_set_property (b, "button.style", "link");
582 ml_multicol_layout_pack (tbl, b);
589 pr_time (pool pool, reactor_time_t time)
591 int secs = (reactor_time - time) / 1000LL;
594 return psprintf (pool, "%ds", secs);
595 else if (secs < 3600)
596 return psprintf (pool, "%dm %ds", secs / 60, secs % 60);
598 return psprintf (pool, "%dh %dm %ds", secs / 3600, (secs / 60) % 60,
602 /*----- Address to symbol resolution code. -----*/
604 /* This code is highly Linux-dependent.
606 * The strategy taken is as follows:
608 * (1) Look at /proc/$$/maps (if it exists) to try and work out which
609 * executable or shared library the symbol exists in.
610 * (2) Look for a corresponding symbol table file in /usr/share/rws/symtabs/
611 * (3) If the symbol table file is found, then we can resolve the symbol
612 * immediately with that file.
613 * (4) If no symbol table file is found, use the dladdr(3) function from
615 * (5) If there is no dladdr(3) function, just print the address.
617 * All of this must be done without blocking.
622 static const char *resolve_addr_in_maps (pool, unsigned long);
623 static const char *resolve_addr_in_symtab (pool, unsigned long, const char *,
624 unsigned long, unsigned long);
625 static const char *resolve_addr_with_dladdr (pool, unsigned long);
628 resolve_addr (pool session_pool, unsigned long addr)
634 fprintf (stderr, "trying to resolve address %lx\n", addr);
637 /* Give the address resolution code its own scratch pool, but copy
638 * the result into the more permanent storage of the session pool.
640 pool = new_subpool (session_pool);
641 s = pstrdup (session_pool, resolve_addr_in_maps (pool, addr));
645 fprintf (stderr, "resolved address %lx as %s\n", addr, s);
652 resolve_addr_in_maps (pool pool, unsigned long addr)
656 #define RES_BUF_SIZE 2048
659 unsigned long low, high, offset;
661 if (addr == 0) return "NULL";
663 maps_filename = psprintf (pool, "/proc/%d/maps", (int) getpid ());
665 fp = fopen (maps_filename, "r");
666 if (fp == 0) return resolve_addr_with_dladdr (pool, addr);
668 buffer = pmalloc (pool, RES_BUF_SIZE);
670 /* Read the maps file until we find the appropriate address range. */
671 while (fgets (buffer, RES_BUF_SIZE, fp) != 0)
673 if (buffer[strlen(buffer)-1] == '\n')
674 buffer[strlen(buffer)-1] = '\0';
676 if (sscanf (buffer, "%lx-%lx r-xp %lx", &low, &high, &offset) != 3)
680 fprintf (stderr, "\t%lx-%lx offset %lx\n", low, high, offset);
682 if (!(low <= addr && addr < high))
685 /* Found the address, so we can close the file. */
688 /* Find the filename (just the basename). */
689 filename = strrchr (buffer, '/');
690 if (!filename) return resolve_addr_with_dladdr (pool, addr);
693 t = strchr (filename, '.');
697 fprintf (stderr, "\tbase filename = %s\n", filename);
700 return resolve_addr_in_symtab (pool, addr, filename,
704 /* Address not found. Go to the back-up approach. */
707 return resolve_addr_with_dladdr (pool, addr);
712 resolve_addr_in_symtab (pool pool, unsigned long addr,
713 const char *filename, unsigned long low,
714 unsigned long offset)
717 #define RES_BUF_SIZE 2048
718 char *symtabfile, *buffer, *symname, *lastsymname = "(none)";
719 unsigned long lastsymaddr = 0, symaddr, diff;
721 /* Look for a matching symtab file. */
722 symtabfile = psprintf (pool, SYMTABSDIR "/%s.syms", filename);
724 fp = fopen (symtabfile, "r");
725 if (!fp) return resolve_addr_with_dladdr (pool, addr);
727 buffer = pmalloc (pool, RES_BUF_SIZE);
729 /* Read the symbols from the symtab file until one matches. */
730 while (fgets (buffer, RES_BUF_SIZE, fp) != 0)
732 if (buffer[strlen(buffer)-1] == '\n')
733 buffer[strlen(buffer)-1] = '\0';
735 symname = strchr (buffer, ' ');
736 if (!symname) continue;
740 if (sscanf (buffer, "%lx", &symaddr) != 1) continue;
743 fprintf (stderr, "\t%s: %lx %s\n", symtabfile, symaddr, symname);
746 /* Work out the in-memory address of this symbol. */
747 symaddr += low - offset;
749 /* If we've gone past the address, then the symbol must be the
755 lastsymaddr = symaddr;
756 lastsymname = pstrdup (pool, symname);
759 found_it: /* It's the previous symbol. */
762 diff = addr - lastsymaddr;
764 return psprintf (pool, "%s+0x%lx (%s)", lastsymname, diff, filename);
766 return psprintf (pool, "%s (%s)", lastsymname, filename);
772 resolve_addr_with_dladdr (pool pool, unsigned long addr)
774 #if defined(HAVE_DLFCN_H) && defined(HAVE_DLADDR)
778 r = dladdr ((void *) addr, &info);
779 if (r != 0 && info.dli_saddr != 0)
781 unsigned long real_addr = (unsigned long) info.dli_saddr;
782 unsigned long diff = addr - real_addr;
785 return psprintf (pool, "%s+0x%lx (%s)",
786 info.dli_sname, diff, info.dli_fname);
788 return psprintf (pool, "%s (%s)", info.dli_sname, info.dli_fname);
791 return psprintf (pool, "%p", (void *) addr);
793 return psprintf (pool, "%p", (void *) addr);
797 /*----- End of symbol resolution code. -----*/