2006-01-24   Thomas Kho <tkho at ucla.edu>, Dan Kegel <dank at kegel.com>
Patch to add self-monitoring capabilities to distccd, part 2/2.
--stats            Turn on the statistics HTTP server. (Daemon mode only.)
--stats-port PORT  Specify which port it should listen on.  Default 3633.
Signed-off-by: Thomas Kho <tkho at ucla.edu>

diff -Nuar distcc-2.18.3.load/Makefile.in distcc-2.18.3.stats/Makefile.in
--- distcc-2.18.3.load/Makefile.in	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/Makefile.in	2006-02-02 11:28:44.000000000 -0800
@@ -187,6 +187,7 @@
 	src/ncpus.o							\
 	src/prefork.o							\
 	src/serve.o src/setuid.o src/srvnet.o src/srvrpc.o src/state.o	\
+	src/stats.o							\
 	$(common_obj) @BUILD_POPT@
 
 # Objects that need to be linked in to build monitors
@@ -216,7 +217,8 @@
 h_ccvers_obj = src/h_ccvers.o $(common_obj) 
 
 # All source files, for the purposes of building the distribution
-SRC =	src/access.c src/arg.c src/argutil.c				\
+SRC =	src/stats.c							\
+	src/access.c src/arg.c src/argutil.c				\
 	src/backoff.c src/bulk.c					\
 	src/cleanup.c							\
 	src/climasq.c src/clinet.c src/clirpc.c src/compile.c		\
diff -Nuar distcc-2.18.3.load/configure distcc-2.18.3.stats/configure
--- distcc-2.18.3.load/configure	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/configure	2006-02-02 11:28:45.000000000 -0800
@@ -5833,6 +5833,108 @@
 # Checks for library functions, using libraries discovered above
 CPPFLAGS="$CPPFLAGS -I$srcdir/src"
 
+for ac_func in getpagesize
+do
+as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh`
+echo "$as_me:$LINENO: checking for $ac_func" >&5
+echo $ECHO_N "checking for $ac_func... $ECHO_C" >&6
+if eval "test \"\${$as_ac_var+set}\" = set"; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+/* Define $ac_func to an innocuous variant, in case <limits.h> declares $ac_func.
+   For example, HP-UX 11i <limits.h> declares gettimeofday.  */
+#define $ac_func innocuous_$ac_func
+
+/* System header to define __stub macros and hopefully few prototypes,
+    which can conflict with char $ac_func (); below.
+    Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+    <limits.h> exists even on freestanding compilers.  */
+
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+
+#undef $ac_func
+
+/* Override any gcc2 internal prototype to avoid an error.  */
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+/* We use char because int might match the return type of a gcc2
+   builtin and then its argument prototype would still apply.  */
+char $ac_func ();
+/* The GNU C library defines this for functions which it implements
+    to always fail with ENOSYS.  Some functions are actually named
+    something starting with __ and the normal name is an alias.  */
+#if defined (__stub_$ac_func) || defined (__stub___$ac_func)
+choke me
+#else
+char (*f) () = $ac_func;
+#endif
+#ifdef __cplusplus
+}
+#endif
+
+int
+main ()
+{
+return f != $ac_func;
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+  (eval $ac_link) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+	 { ac_try='test -z "$ac_c_werror_flag"
+			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest$ac_exeext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  eval "$as_ac_var=yes"
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+eval "$as_ac_var=no"
+fi
+rm -f conftest.err conftest.$ac_objext \
+      conftest$ac_exeext conftest.$ac_ext
+fi
+echo "$as_me:$LINENO: result: `eval echo '${'$as_ac_var'}'`" >&5
+echo "${ECHO_T}`eval echo '${'$as_ac_var'}'`" >&6
+if test `eval echo '${'$as_ac_var'}'` = yes; then
+  cat >>confdefs.h <<_ACEOF
+#define `echo "HAVE_$ac_func" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+done
+
+
 
 
 
diff -Nuar distcc-2.18.3.load/configure.ac distcc-2.18.3.stats/configure.ac
--- distcc-2.18.3.load/configure.ac	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/configure.ac	2006-02-02 11:28:45.000000000 -0800
@@ -297,6 +297,7 @@
 # Checks for library functions, using libraries discovered above
 CPPFLAGS="$CPPFLAGS -I$srcdir/src"
 
+AC_CHECK_FUNCS([getpagesize])
 AC_CHECK_FUNCS([sendfile setsid flock lockf hstrerror strerror setuid setreuid])
 AC_CHECK_FUNCS([getuid geteuid mcheck wait4 wait3 waitpid setgroups getcwd])
 AC_CHECK_FUNCS([snprintf vsnprintf vasprintf asprintf getcwd getwd])
diff -Nuar distcc-2.18.3.load/man/distccd.1 distcc-2.18.3.stats/man/distccd.1
--- distcc-2.18.3.load/man/distccd.1	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/man/distccd.1	2006-02-02 11:28:45.000000000 -0800
@@ -182,6 +182,14 @@
 a daemon, log verbosely to stderr, and do not detach or fork.  For
 wizards only.
 .TP
+.B --stats
+Turn on the statistics HTTP server. By default it is off.
+(Daemon mode only.)
+.TP
+.B --stats-port PORT
+Set the TCP port to listen on for HTTP requests, rather than the default of 3633.
+(Daemon mode only.)
+.TP
 .B --inetd
 Serve a client connected to stdin/stdout.  As the name
 suggests, this option should be used when distccd is run
diff -Nuar distcc-2.18.3.load/src/config.h.in distcc-2.18.3.stats/src/config.h.in
--- distcc-2.18.3.load/src/config.h.in	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/config.h.in	2006-02-02 11:28:45.000000000 -0800
@@ -71,6 +71,9 @@
 /* Define to 1 if you have the `getuid' function. */
 #undef HAVE_GETUID
 
+/* Define to 1 if you have the `getpagesize' function. */
+#undef HAVE_GETPAGESIZE
+
 /* Define to 1 if you have the `getwd' function. */
 #undef HAVE_GETWD
 
diff -Nuar distcc-2.18.3.load/src/daemon.c distcc-2.18.3.stats/src/daemon.c
--- distcc-2.18.3.load/src/daemon.c	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/daemon.c	2006-02-02 11:28:45.000000000 -0800
@@ -238,7 +238,7 @@
     
     if (opt_log_stderr) {
         rs_remove_all_loggers();
-        rs_add_logger(rs_logger_file, RS_LOG_DEBUG, 0, STDERR_FILENO);
+        rs_add_logger(rs_logger_file, opt_log_level_num, 0, STDERR_FILENO);
         return;
     }
     
@@ -251,14 +251,14 @@
             /* continue and use syslog */
         } else {
             rs_remove_all_loggers();
-            rs_add_logger(rs_logger_file, RS_LOG_DEBUG, NULL, fd);
+            rs_add_logger(rs_logger_file, opt_log_level_num, NULL, fd);
             return;
         }
     }
     
     rs_remove_all_loggers();
     openlog("distccd", LOG_PID, LOG_DAEMON);
-    rs_add_logger(rs_logger_syslog, RS_LOG_DEBUG, NULL, 0);
+    rs_add_logger(rs_logger_syslog, opt_log_level_num, NULL, 0);
 }
 
 
diff -Nuar distcc-2.18.3.load/src/distcc.h distcc-2.18.3.stats/src/distcc.h
--- distcc-2.18.3.load/src/distcc.h	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/distcc.h	2006-02-02 11:28:45.000000000 -0800
@@ -158,6 +158,8 @@
 
 
 #define DISTCC_DEFAULT_PORT 3632
+#define DISTCC_DEFAULT_STATS_ENABLED 0
+#define DISTCC_DEFAULT_STATS_PORT 3633
 
 
 
diff -Nuar distcc-2.18.3.load/src/dopt.c distcc-2.18.3.stats/src/dopt.c
--- distcc-2.18.3.load/src/dopt.c	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/dopt.c	2006-02-02 11:28:45.000000000 -0800
@@ -54,6 +54,8 @@
 int arg_max_jobs = 0;
 
 int arg_port = DISTCC_DEFAULT_PORT;
+int arg_stats = DISTCC_DEFAULT_STATS_ENABLED;
+int arg_stats_port = DISTCC_DEFAULT_STATS_PORT;
 
 /** If true, serve all requests directly from listening process
     without forking.  Better for debugging. **/
@@ -77,6 +79,8 @@
 
 int opt_log_stderr = 0;
 
+int opt_log_level_num = RS_LOG_NOTICE;
+
 /**
  * Daemon exits after this many seconds.  Intended mainly for testing, to make
  * sure daemons don't persist for too long.
@@ -110,11 +114,13 @@
     { "no-fifo", 0,      POPT_ARG_NONE, &opt_no_fifo, 0, 0, 0 },
     { "no-fork", 0,      POPT_ARG_NONE, &opt_no_fork, 0, 0, 0 },
     { "pid-file", 'P',   POPT_ARG_STRING, &arg_pid_file, 0, 0, 0 },
-    { "port", 'p',       POPT_ARG_INT, &arg_port,      0, 0, 0 },
+    { "port", 'p',       POPT_ARG_INT, &arg_port, 0, 0, 0 },
     { "user", 0,         POPT_ARG_STRING, &opt_user, 'u', 0, 0 },
     { "verbose", 0,      POPT_ARG_NONE, 0, 'v', 0, 0 },
     { "version", 0,      POPT_ARG_NONE, 0, 'V', 0, 0 },
     { "wizard", 'W',     POPT_ARG_NONE, 0, 'W', 0, 0 },
+    { "stats", 0,        POPT_ARG_NONE, &arg_stats, 0, 0, 0 },
+    { "stats-port", 0,   POPT_ARG_INT, &arg_stats_port, 0, 0, 0 },
     { 0, 0, 0, 0, 0, 0, 0 }
 };
 
@@ -137,6 +143,8 @@
 "    -p, --port PORT            TCP port to listen on\n"
 "    --listen ADDRESS           IP address to listen on\n"
 "    -a, --allow IP[/BITS]      client address access control\n"
+"    --stats                    enable statistics reporting via HTTP server\n"
+"    --stats-port PORT          TCP port to listen on for statistics requests\n"
 "  Debug and trace:\n"
 "    --log-level=LEVEL          set detail level for log file\n"
 "      levels: critical, error, warning, notice, info, debug\n"
@@ -220,6 +228,7 @@
                                    level_name);
                 } else {
                     rs_trace_set_level(level);
+                    opt_log_level_num = level;
                 }
             }
             break;
diff -Nuar distcc-2.18.3.load/src/dopt.h distcc-2.18.3.stats/src/dopt.h
--- distcc-2.18.3.load/src/dopt.h	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/dopt.h	2006-02-02 11:28:45.000000000 -0800
@@ -26,6 +26,9 @@
 int distccd_parse_options(int argc, const char *argv[]);
 
 extern int arg_port;
+extern int arg_stats;
+extern int arg_stats_port;
+extern int opt_log_level_num;
 extern int arg_max_jobs;
 extern const char *arg_pid_file;
 extern int opt_no_fork;
diff -Nuar distcc-2.18.3.load/src/prefork.c distcc-2.18.3.stats/src/prefork.c
--- distcc-2.18.3.load/src/prefork.c	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/prefork.c	2006-02-02 11:28:45.000000000 -0800
@@ -42,6 +42,7 @@
 #include <sys/wait.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
+#include <sys/select.h>
 
 #include "exitcode.h"
 #include "distcc.h"
@@ -52,8 +53,12 @@
 #include "srvnet.h"
 #include "types.h"
 #include "daemon.h"
+#include "netutil.h"
+#include "stats.h"
 
-
+void dcc_manage_kids(int listen_fd);
+static void dcc_sigchld_handler(int sig);
+static void dcc_create_kids(int listen_fd);
 static int dcc_preforked_child(int listen_fd);
 
 /*
@@ -76,33 +81,72 @@
  **/
 int dcc_preforking_parent(int listen_fd)
 {
-    while (1) {
-        pid_t kid;
+    int ret;
 
-        while (dcc_nkids < dcc_max_kids) {
-            if ((kid = fork()) == -1) {
-                rs_log_error("fork failed: %s", strerror(errno));
-                return EXIT_OUT_OF_MEMORY; /* probably */
-            } else if (kid == 0) {
-                dcc_exit(dcc_preforked_child(listen_fd));
-            } else {
-                /* in parent */
-                ++dcc_nkids;
-                rs_trace("up to %d children", dcc_nkids);
-            }
-            
-            /* Don't start them too quickly, or we might overwhelm a machine
-             * that's having trouble. */
-            sleep(1);
+    if (arg_stats) {
+	/* use sigaction instead of signal() because we need persistant handler, not oneshot */
+        struct sigaction act_child;
+        memset(&act_child, 0, sizeof act_child);
+        act_child.sa_handler = dcc_sigchld_handler;
+        sigaction(SIGCHLD, &act_child, NULL);
+
+        ret = dcc_stats_init();
+        if (ret) return ret;
+
+        dcc_create_kids(listen_fd);
+
+        /* Start the stats collection and web server */
+        return dcc_stats_server(listen_fd);
+    } else {
+        while (1) {
+            dcc_create_kids(listen_fd);
+
+            /* wait for any children to exit, and then start some more */
+            dcc_reap_kids(TRUE);
 
-            dcc_reap_kids(FALSE);
+            /* Another little safety brake here: since children should not exit
+             * too quickly, pausing before starting them should be harmless. */
+            sleep(1);
         }
+    }
+}
+
 
-        /* wait for any children to exit, and then start some more */
-        dcc_reap_kids(TRUE);
+static void dcc_sigchld_handler(int sig) {
+    /* Do nothing.  Only here to break out of select() in dcc_stats_server() */
+}
 
-        /* Another little safety brake here: since children should not exit
-         * too quickly, pausing before starting them should be harmless. */
+
+/**
+ * Functions in the parent can call this to clean up and maintain the pool of
+ * children
+ **/
+void dcc_manage_kids(int listen_fd) {
+    dcc_reap_kids(FALSE);
+    dcc_create_kids(listen_fd);
+}
+
+/**
+ * Fork children until we have dcc_max_kids of them
+ **/
+static void dcc_create_kids(int listen_fd) {
+    pid_t kid;
+
+    while (dcc_nkids < dcc_max_kids) {
+        if ((kid = fork()) == -1) {
+            rs_log_error("fork failed: %s", strerror(errno));
+            dcc_exit(EXIT_OUT_OF_MEMORY); /* probably */
+        } else if (kid == 0) {
+            dcc_stats_init_kid();
+            dcc_exit(dcc_preforked_child(listen_fd));
+        } else {
+            /* in parent */
+            ++dcc_nkids;
+            rs_trace("up to %d children", dcc_nkids);
+        }
+
+        /* Don't start them too quickly, or we might overwhelm a machine
+         * that's having trouble. */
         sleep(1);
     }
 }
@@ -137,8 +181,10 @@
             dcc_exit(EXIT_CONNECT_FAILED);
         }
         
+        dcc_stats_event(STATS_TCP_ACCEPT);
+
         dcc_service_job(acc_fd, acc_fd,
-                        (struct sockaddr *) &cli_addr, cli_len);
+                           (struct sockaddr *) &cli_addr, cli_len);
 
         dcc_close(acc_fd);
     }
diff -Nuar distcc-2.18.3.load/src/serve.c distcc-2.18.3.stats/src/serve.c
--- distcc-2.18.3.load/src/serve.c	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/serve.c	2006-02-02 11:28:45.000000000 -0800
@@ -61,6 +61,7 @@
 #include <fcntl.h>
 #include <errno.h>
 #include <signal.h>
+#include <time.h>
 
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -70,10 +71,12 @@
 #endif /* HAVE_SYS_SIGNAL_H */
 #include <sys/param.h>
 #include <sys/socket.h>
+#include <sys/time.h>
 
 #include "distcc.h"
 #include "trace.h"
 #include "util.h"
+#include "stats.h"
 #include "rpc.h"
 #include "exitcode.h"
 #include "snprintf.h"
@@ -150,6 +153,8 @@
 {
     int ret;
 
+    dcc_job_summary_clear();
+
     /* Log client name and check access if appropriate.  For ssh connections
      * the client comes from a unix-domain socket and that's always
      * allowed. */
@@ -158,6 +163,8 @@
 
     ret = dcc_run_job(in_fd, out_fd);
 
+    dcc_job_summary();
+
 out:
     return ret;
 }
@@ -216,7 +223,7 @@
             n = p + len;
         }
         if (asprintf(&buf, "%.*s/%s", len, p, compiler_name) == -1) {
-            rs_log_crit("asnprintf failed");
+            rs_log_crit("asprintf failed");
             return EXIT_DISTCC_FAILED;
         }
 
@@ -260,6 +267,12 @@
     pid_t cc_pid;
     enum dcc_protover protover;
     enum dcc_compress compr;
+    struct timeval start, end;
+    int time_ms;
+    char *time_str;
+    int job_result = -1;
+
+    gettimeofday(&start, NULL);
     
     if ((ret = dcc_make_tmpnam("distcc", ".stderr", &err_fname)))
         goto out_cleanup;
@@ -319,8 +332,13 @@
         || WEXITSTATUS(status)) {
         /* Something went wrong, so send DOTO 0 */
         dcc_x_token_int(out_fd, "DOTO", 0);
+
+        job_result = STATS_COMPILE_ERROR;
     } else {
-        ret = dcc_x_file(out_fd, temp_o, "DOTO", compr, NULL);
+        if ((ret = dcc_x_file(out_fd, temp_o, "DOTO", compr, NULL)))
+            goto out_cleanup;
+
+        job_result = STATS_COMPILE_OK;
     }
 
     dcc_critique_status(status, argv[0], orig_input, dcc_hostdef_local, 0);
@@ -329,6 +347,49 @@
     rs_log(RS_LOG_INFO|RS_LOG_NONAME, "job complete");
 
     out_cleanup:
+
+    switch (ret) {
+    case EXIT_BUSY: /* overloaded */
+        job_result = STATS_REJ_OVERLOAD;
+        break;
+    case EXIT_IO_ERROR: /* probably client disconnected */
+        job_result = STATS_CLI_DISCONN;
+        break;
+    case EXIT_PROTOCOL_ERROR:
+        job_result = STATS_REJ_BAD_REQ;
+        break;
+    default:
+        if (job_result != STATS_COMPILE_ERROR
+            && job_result != STATS_COMPILE_OK) {
+            job_result = STATS_OTHER;
+        }
+    }
+
+    gettimeofday(&end, NULL);
+    time_ms = (end.tv_sec - start.tv_sec) * 1000 + (end.tv_usec - start.tv_usec) / 1000;
+
+    dcc_job_summary_append(" ");
+    dcc_job_summary_append(stats_text[job_result]);
+
+    if (job_result == STATS_COMPILE_OK) {
+        /* special case, also log compiler, file and time */
+        dcc_stats_compile_ok(argv[0], orig_input, time_ms);
+    } else {
+        dcc_stats_event(job_result);
+    }
+
+    asprintf(&time_str, " exit:%d sig:%d core:%d ret:%d time:%dms ", WEXITSTATUS(status), WTERMSIG(status), WCOREDUMP(status), ret, time_ms);
+    dcc_job_summary_append(time_str);
+    free(time_str);
+
+    /* append compiler and input file info */
+    if (job_result == STATS_COMPILE_ERROR
+        || job_result == STATS_COMPILE_OK) {
+        dcc_job_summary_append(argv[0]);
+        dcc_job_summary_append(" ");
+        dcc_job_summary_append(orig_input);
+    }
+
     dcc_remove_log_to_file();
     dcc_cleanup_tempfiles();
 
diff -Nuar distcc-2.18.3.load/src/srvnet.c distcc-2.18.3.stats/src/srvnet.c
--- distcc-2.18.3.load/src/srvnet.c	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/srvnet.c	2006-02-02 11:28:45.000000000 -0800
@@ -235,6 +235,8 @@
         return ret;
     
     rs_log_info("connection from %s", client_ip);
+    dcc_job_summary_append("client: ");
+    dcc_job_summary_append(client_ip);
     free(client_ip);
 
     if (!psa)
diff -Nuar distcc-2.18.3.load/src/stats.c distcc-2.18.3.stats/src/stats.c
--- distcc-2.18.3.load/src/stats.c	1969-12-31 16:00:00.000000000 -0800
+++ distcc-2.18.3.stats/src/stats.c	2006-02-02 11:36:01.000000000 -0800
@@ -0,0 +1,412 @@
+/* -*- c-file-style: "java"; indent-tabs-mode: nil -*-
+ * 
+ * distcc -- A simple distributed compiler system
+ *
+ * Copyright (C) 2005 by Google (Thomas Kho <tkho at google.com>)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <time.h>
+
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/vfs.h>
+
+#include "exitcode.h"
+#include "distcc.h"
+#include "trace.h"
+#include "dopt.h"
+#include "stats.h"
+#include "srvnet.h"
+#include "util.h"
+#include "netutil.h"
+#include "fcntl.h"
+#include "daemon.h"
+
+int dcc_statspipe[2];
+
+#define MAX_FILENAME_LEN 1024
+
+/* in prefork.c */
+void dcc_manage_kids(int listen_fd);
+
+struct stats_s {
+    int counters[STATS_ENUM_MAX];
+    int longest_job_time;
+    char longest_job_name[MAX_FILENAME_LEN];
+    char longest_job_compiler[MAX_FILENAME_LEN];
+    int io_rate; /* read/write sectors per second */
+
+    int compile_timeseries[300]; /* 300 3-sec time intervals */
+} dcc_stats;
+
+struct statsdata {
+    enum stats_e type;
+
+    /* used only for STATS_COMPILE_OK */
+    int time;
+    char filename[MAX_FILENAME_LEN];
+    char compiler[MAX_FILENAME_LEN];
+};
+
+const char *stats_text[20] = { "TCP_ACCEPT", "REJ_BAD_REQ", "REJ_OVERLOAD",
+    "COMPILE_OK", "COMPILE_ERROR", "COMPILE_TIMEOUT", "CLI_DISCONN",
+    "OTHER" };
+
+/* Call this to initialize stats */
+int dcc_stats_init() {
+    if (arg_stats) {
+        if (pipe(dcc_statspipe) == -1) {
+            return -1;
+        }
+    }
+    memset(&dcc_stats, 0, sizeof(dcc_stats));
+    return 0;
+}
+
+
+/* In the preforking model call this to initialize stats for forked children */
+void dcc_stats_init_kid() {
+    if (arg_stats) {
+        close(dcc_statspipe[0]);
+    }
+}
+
+
+/**
+ * Logs countable event of type e to stats server
+ **/
+void dcc_stats_event(enum stats_e e) {
+    if (arg_stats) {
+        struct statsdata sd;
+        memset(&sd, 0, sizeof(sd));
+        sd.type = e;
+        write(dcc_statspipe[1], &sd, sizeof(sd));
+    }
+}
+
+
+/**
+ * Logs a completed job to stats server
+ **/
+void dcc_stats_compile_ok(char *compiler, char *filename, int time_usec) {
+    if (arg_stats) {
+        struct statsdata sd;
+        memset(&sd, 0, sizeof(sd));
+
+        sd.type = STATS_COMPILE_OK;
+        /* also send compiler, filename & runtime */
+        sd.time = time_usec;
+        strncpy(sd.filename, filename, MAX_FILENAME_LEN);
+        strncpy(sd.compiler, compiler, MAX_FILENAME_LEN);
+        write(dcc_statspipe[1], &sd, sizeof(sd));
+    }
+}
+
+
+/*
+ * Updates a running total of compiles in the last 1, 5, 15 minutes
+ *
+ * Returns the oldest slot
+ */
+static int dcc_stats_update_running_total(int increment) {
+    static int prev_slot;
+    static time_t last = 0;
+    static int total = 0;
+    int i;
+    int cur_slot;
+    int *const cts = dcc_stats.compile_timeseries;
+    time_t now = time(NULL);
+
+    cur_slot = (now / 3) % 300;
+
+    if (last + 900 < now) {
+        /* reset all stats; last call was >15 min ago */
+        for (i = 0; i < 300; i++)
+            cts[i] = total;
+        prev_slot = cur_slot;
+    }
+
+    if (prev_slot != cur_slot) {
+        /* different timeslot, so set the interval [prev, cur) */
+        for (i = (prev_slot)%300; i != cur_slot; i = (i+1)%300) {
+            cts[i] = total;
+        }
+        prev_slot = cur_slot;
+    }
+
+    total += increment;
+    last = now;
+
+    return cur_slot;
+}
+
+
+static int *dcc_stats_get_compile_totals(void) {
+    int cur_slot;
+    static int ct[3]; /* 1, 5, 15 min compile totals */
+    int *const cts = dcc_stats.compile_timeseries;
+
+    cur_slot = dcc_stats_update_running_total(0);
+
+    ct[0] = cts[(cur_slot + 299)%300] - cts[(cur_slot+280)%300];
+    ct[1] = cts[(cur_slot + 299)%300] - cts[(cur_slot+200)%300];
+    ct[2] = cts[(cur_slot + 299)%300] - cts[(cur_slot+1)%300];
+
+    return ct;
+}
+
+
+/* Sets dcc_stats.io_rate at most 50 secs */
+static void dcc_stats_minutely_update(void) {
+    static int prev_io_tot = -1;
+    static time_t last = 0;
+    int n_reads, n_writes;
+    time_t now = time(NULL);
+
+    if (last + 50 < now) {
+        dcc_get_disk_io_stats(&n_reads, &n_writes);
+
+        if (prev_io_tot == -1)
+            dcc_stats.io_rate = -1;
+        else
+            dcc_stats.io_rate = (n_reads + n_writes - prev_io_tot) / (now - last);
+
+        prev_io_tot = n_reads + n_writes;
+        last = now;
+    }
+}
+
+static long dcc_get_tmpdirinfo(void) {
+    char *tmp_dir;
+    struct statfs buf;
+
+    if (dcc_get_tmp_top(&tmp_dir) != 0)
+	return -1;
+
+    if (statfs(tmp_dir, &buf) != 0)
+        return -1;
+
+    if (buf.f_bsize >= 1024)
+        return buf.f_bavail * (buf.f_bsize / 1024) / 1024;
+    else
+        return (buf.f_bavail * buf.f_bsize) / (1024 * 1024);
+}
+
+/**
+ * Accept a connection on the stats port and send the reply, regardless of data
+ * that the client sends us.
+ **/
+static void dcc_service_stats_request(int http_fd) {
+    int acc_fd;
+    int *ct;
+    int num_D;
+    int max_RSS;
+    char *max_RSS_name;
+    size_t reply_len;
+    char challenge[1024];
+    char reply[2048];
+    struct dcc_sockaddr_storage cli_addr;
+    socklen_t cli_len = sizeof(cli_addr);
+    double loadavg[3];
+    int free_space_mb;
+
+    const char replytemplate[] = "\
+HTTP/1.0 200 OK\n\
+Content-Type: text/plain\n\
+Connection: close\n\n\
+argv /distccd\n\
+<distccstats>\n\
+dcc_tcp_accept %d\n\
+dcc_rej_bad_req %d\n\
+dcc_rej_overload %d\n\
+dcc_compile_ok %d\n\
+dcc_compile_error %d\n\
+dcc_compile_timeout %d\n\
+dcc_cli_disconnect %d\n\
+dcc_other %d\n\
+dcc_longest_job %s\n\
+dcc_longest_job_compiler %s\n\
+dcc_longest_job_time_msecs %d\n\
+dcc_max_kids %d\n\
+dcc_current_load %d\n\
+dcc_load1 %1.2lf\n\
+dcc_load2 %1.2lf\n\
+dcc_load3 %1.2lf\n\
+dcc_num_compiles1 %d\n\
+dcc_num_compiles2 %d\n\
+dcc_num_compiles3 %d\n\
+dcc_num_procstate_D %d\n\
+dcc_max_RSS %d\n\
+dcc_max_RSS_name %s\n\
+dcc_io_rate %d\n\
+dcc_free_space %d MB\n\
+</distccstats>\n";
+
+    dcc_stats_minutely_update(); /* force update to get fresh disk io data */
+    ct = dcc_stats_get_compile_totals();
+    dcc_getloadavg(loadavg);
+
+    free_space_mb = dcc_get_tmpdirinfo();
+    dcc_get_proc_stats(&num_D, &max_RSS, &max_RSS_name);
+
+    if (dcc_stats.longest_job_name[0] == 0)
+        strcpy(dcc_stats.longest_job_name, "none");
+    if (dcc_stats.longest_job_compiler[0] == 0)
+        strcpy(dcc_stats.longest_job_compiler, "none");
+
+    acc_fd = accept(http_fd, (struct sockaddr *) &cli_addr, &cli_len);
+    if (dcc_check_client(&cli_addr, (int) cli_len, opt_allowed) == 0) {
+        reply_len = snprintf(reply, 2048, replytemplate,
+                               dcc_stats.counters[STATS_TCP_ACCEPT],
+                               dcc_stats.counters[STATS_REJ_BAD_REQ],
+                               dcc_stats.counters[STATS_REJ_OVERLOAD],
+                               dcc_stats.counters[STATS_COMPILE_OK],
+                               dcc_stats.counters[STATS_COMPILE_ERROR],
+                               dcc_stats.counters[STATS_COMPILE_TIMEOUT],
+                               dcc_stats.counters[STATS_CLI_DISCONN],
+                               dcc_stats.counters[STATS_OTHER],
+                               dcc_stats.longest_job_name,
+                               dcc_stats.longest_job_compiler,
+                               dcc_stats.longest_job_time,
+                               dcc_max_kids,
+                               dcc_getcurrentload(),
+                               loadavg[0], loadavg[1], loadavg[2],
+                               ct[0], ct[1], ct[2],
+                               num_D, max_RSS, max_RSS_name,
+                               dcc_stats.io_rate,
+                               free_space_mb);
+        dcc_set_nonblocking(acc_fd);
+        read(acc_fd, challenge, 1024); /* empty the receive queue */
+        write(acc_fd, reply, reply_len);
+    }
+
+    /* Don't think we need this to prevent RST anymore, since we read() now */
+    //shutdown(acc_fd, SHUT_WR); /* prevent connection reset */
+    dcc_close(acc_fd);
+}
+
+
+/**
+ * Process a packet of stats data
+ **/
+static void dcc_stats_process(struct statsdata *sd) {
+    if (sd->type > STATS_ENUM_MAX) {
+        /* Got a bad message */
+        return;
+    }
+
+    switch (sd->type) {
+
+    case STATS_TCP_ACCEPT:
+    case STATS_REJ_BAD_REQ:
+    case STATS_REJ_OVERLOAD:
+        break;
+    case STATS_COMPILE_OK:
+        /* Record file with longest runtime */
+        if (dcc_stats.longest_job_time < sd->time) {
+            dcc_stats.longest_job_time = sd->time;
+            strncpy(dcc_stats.longest_job_name, sd->filename,
+                    MAX_FILENAME_LEN);
+            strncpy(dcc_stats.longest_job_compiler, sd->compiler,
+                    MAX_FILENAME_LEN);
+        }
+    case STATS_COMPILE_ERROR:
+    case STATS_COMPILE_TIMEOUT:
+    case STATS_CLI_DISCONN:
+        /* We want to update the running compile total for all jobs that
+         * non-trivially tax the CPU */
+        dcc_stats_update_running_total(1);
+    default: ;
+    }
+
+    dcc_stats.counters[sd->type]++;
+}
+
+
+/**
+ * Collect runtime statistics from kids and serve them via HTTP
+ * Also, maintains the pool of kids.
+ **/
+int dcc_stats_server(int listen_fd)
+{
+    int http_fd, max_fd;
+    int i, ret;
+    fd_set fds, fds_master;
+    struct statsdata sd;
+    struct timeval timeout;
+
+    /* clear stats data */
+    for (i = 0; i < STATS_ENUM_MAX; i++)
+        dcc_stats.counters[i] = 0;
+    dcc_stats.longest_job_time = -1;
+    dcc_stats.longest_job_name[0] = 0;
+    dcc_stats.io_rate = -1;
+
+    if ((ret = dcc_socket_listen(arg_stats_port, &http_fd, 
+                                    opt_listen_addr)) != 0) {
+        return ret;
+    }
+    rs_log_info("HTTP server started on port %d\n", arg_stats_port);
+
+    /* We don't want children to inherit this FD */
+    fcntl(http_fd, F_SETFD, FD_CLOEXEC);
+
+    max_fd = (http_fd > dcc_statspipe[0]) ? (http_fd + 1) 
+                                           : (dcc_statspipe[0] + 1);
+
+    FD_ZERO(&fds_master);
+    FD_SET(dcc_statspipe[0], &fds_master);
+    FD_SET(http_fd, &fds_master);
+
+    while (1) {
+        dcc_stats_minutely_update();
+
+        timeout.tv_sec = 60;
+        timeout.tv_usec = 0;
+        fds = fds_master;
+        ret = select(max_fd, &fds, NULL, NULL, &timeout);
+        if (ret != -1) {
+            if (FD_ISSET(dcc_statspipe[0], &fds)) {
+                /* Received stats report from a child */
+                if (read(dcc_statspipe[0], &sd, sizeof(sd)) != -1) {
+                    dcc_stats_process(&sd);
+                }
+            }
+
+            if (FD_ISSET(http_fd, &fds)) {
+                /* Received request on stats reporting port */
+                dcc_service_stats_request(http_fd);
+            }
+        } else {
+            if (errno == EINTR) {
+                /* Interrupted -- SIGCHLD? */
+            }
+        }
+
+        dcc_manage_kids(listen_fd);
+    }
+}
+
diff -Nuar distcc-2.18.3.load/src/stats.h distcc-2.18.3.stats/src/stats.h
--- distcc-2.18.3.load/src/stats.h	1969-12-31 16:00:00.000000000 -0800
+++ distcc-2.18.3.stats/src/stats.h	2006-02-02 11:28:45.000000000 -0800
@@ -0,0 +1,46 @@
+/* -*- c-file-style: "java"; indent-tabs-mode: nil -*-
+ * 
+ * distcc -- A simple distributed compiler system
+ *
+ * Copyright (C) 2005 by Google (Thomas Kho <tkho at google.com>)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifndef _DISTCC_STATS_H
+#define _DISTCC_STATS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum stats_e { STATS_TCP_ACCEPT, STATS_REJ_BAD_REQ, STATS_REJ_OVERLOAD,
+                STATS_COMPILE_OK, STATS_COMPILE_ERROR, STATS_COMPILE_TIMEOUT,
+                STATS_CLI_DISCONN, STATS_OTHER, STATS_ENUM_MAX };
+
+const char *stats_text[20];
+
+int  dcc_stats_init(void);
+void dcc_stats_init_kid(void);
+int  dcc_stats_server(int listen_fd);
+void dcc_stats_event(enum stats_e e);
+void dcc_stats_compile_ok(char *compiler, char *filename, int time_usec);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DISTCC_STATS_H */
diff -Nuar distcc-2.18.3.load/src/trace.c distcc-2.18.3.stats/src/trace.c
--- distcc-2.18.3.load/src/trace.c	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/trace.c	2006-02-02 11:28:45.000000000 -0800
@@ -52,7 +52,7 @@
 static struct rs_logger_list *logger_list = NULL;
 
 
-int rs_trace_level = RS_LOG_INFO;
+int rs_trace_level = RS_LOG_NOTICE;
 
 #ifdef UNUSED
 /* nothing */
@@ -78,7 +78,7 @@
  */
 static const char *rs_severities[] = {
     "EMERGENCY! ", "ALERT! ", "CRITICAL! ", "ERROR: ", "Warning: ",
-    "Notice: ", "", ""
+    "", "", ""
 };
 
 
@@ -432,3 +432,15 @@
 }
 
 
+static char job_summary[4096];
+void dcc_job_summary_clear(void) {
+    job_summary[0] = 0;
+}
+
+void dcc_job_summary(void) {
+    rs_log_notice("%s", job_summary);
+}
+
+void dcc_job_summary_append(const char *s) {
+    strncat(job_summary, s, 4096-strlen(job_summary));
+}
diff -Nuar distcc-2.18.3.load/src/trace.h distcc-2.18.3.stats/src/trace.h
--- distcc-2.18.3.load/src/trace.h	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/trace.h	2006-02-02 11:28:45.000000000 -0800
@@ -224,3 +224,7 @@
  * trace.c
  **/
 extern const char *rs_program_name;
+
+void dcc_job_summary_clear(void);
+void dcc_job_summary(void);
+void dcc_job_summary_append(const char *s);
diff -Nuar distcc-2.18.3.load/src/util.c distcc-2.18.3.stats/src/util.c
--- distcc-2.18.3.load/src/util.c	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/util.c	2006-02-02 11:28:45.000000000 -0800
@@ -22,6 +22,7 @@
 
 #include "config.h"
 
+#include <dirent.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -442,6 +443,154 @@
 }
 
 
+/* Returns the number of processes in state D, the max non-cc/c++ RSS in kb and
+ * the max RSS program's name */
+void dcc_get_proc_stats(int *num_D, int *max_RSS, char **max_RSS_name) {
+#if defined(linux)
+    DIR *proc = opendir("/proc");
+    struct dirent *procsubdir;
+    static int pagesize = -1;
+    static char RSS_name[1024];
+    char statfile[1024];
+    FILE *f;
+    char name[1024];
+    char state;
+    int pid;
+    int rss_size;
+    int l;
+    char *c;
+    int isCC;
+
+    /* If this doesn't cut it for you, see how CVS does it:
+     * http://savannah.nongnu.org/cgi-bin/viewcvs/cvs/ccvs/lib/getpagesize.h */
+    if (pagesize == -1) {
+#if HAVE_GETPAGESIZE
+        pagesize = getpagesize();
+#else
+        pagesize = 8192;
+#endif
+    }
+
+    *num_D = 0;
+    *max_RSS = 0;
+    *max_RSS_name = RSS_name;
+    RSS_name[0] = 0;
+
+    while ((procsubdir = readdir(proc)) != NULL) {
+        if (sscanf(procsubdir->d_name, "%d", &pid) != 1)
+            continue;
+
+        strcpy(statfile, "/proc/");
+        strcat(statfile, procsubdir->d_name);
+        strcat(statfile, "/stat");
+
+        f = fopen(statfile, "r");
+        if (f == NULL)
+            continue;
+
+        if (fscanf(f, "%*d %s %c %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %d", 
+                        &name, &state, &rss_size) != 3) {
+            fclose(f);
+            continue;
+        }
+
+        rss_size = (rss_size * pagesize) / 1024; /* get rss_size in KB */
+
+        if (state == 'D') {
+            (*num_D)++;
+        }
+
+        l = strlen(RSS_name);
+        c = RSS_name;
+        
+        /* check for .*{++,cc} */
+        isCC = (l >= 2) && ((c[l-1] == 'c' && c[l-2] == 'c')
+                                || (c[l-1] == '+' && c[l-2] == '+'));
+        if ((rss_size > *max_RSS) && !isCC) {
+            *max_RSS = rss_size;
+            strncpy(RSS_name, name, 1024);
+        }
+
+        fclose(f);
+    }
+
+    closedir(proc);
+#else
+    *num_D = -1;
+    *max_RSS = -1;
+    *max_RSS_name = "none";
+#endif
+}
+
+
+/* Returns the number of sector read/writes since boot */
+void dcc_get_disk_io_stats(int *n_reads, int *n_writes) {
+#if defined(linux)
+    int retval;
+    int kernel26 = 1;
+    FILE *f;
+    int reads, writes, minor;
+    char dev[100];
+    char tmp[1024];
+
+    *n_reads = 0;
+    *n_writes = 0;
+
+    f = fopen("/proc/diskstats", "r");
+    if (f == NULL) {
+        if (errno != ENOENT)
+            return;
+
+        /* /proc/diskstats does not exist. probably a 2.4 kernel, so try reading
+         * /proc/partitions */
+        f = fopen("/proc/partitions", "r");
+        if (f == NULL)
+            return;
+        kernel26 = 0;
+    }
+
+    if (!kernel26) /* blast away 2 header lines in /proc/partitions */
+        fscanf(f, "%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s");
+
+    while (1) {
+        if (kernel26)
+            retval = fscanf(f, " %*d %d %s", &minor, &dev);
+        else
+            retval = fscanf(f, " %*d %d %*d %s", &minor, &dev);
+
+        if (retval == EOF || retval != 2)
+            break;
+
+        if (minor % 64 == 0
+                && ((dev[0] == 'h' && dev[1] == 'd' && dev[2] == 'a')
+                    || (dev[0] == 's' && dev[1] == 'd' && dev[2] == 'a'))) {
+            /* disk stats */
+            retval = fscanf(f, " %*d %*d %d %*d %*d %*d %d %*d %*d %*d %*d",
+                            &reads, &writes);
+            if (retval == EOF || retval != 2)
+                break;
+
+            /* only add stats for disks, so we don't double count */
+            *n_reads += reads;
+            *n_writes += writes;
+        } else {
+            /* individual parition stats */
+            //retval = fscanf(f, " %*d %d %*d %d", &reads, &writes);
+            //if (retval == EOF || retval != 2)
+            //    break;
+            /* assume the lines aren't longer that 1024 characters */
+            fgets(tmp, 1024, f);
+        }
+    }
+
+    fclose(f);
+#else
+    *n_reads = 0;
+    *n_writes = 0;
+#endif
+}
+
+
 #ifndef HAVE_STRLCPY
 /* like strncpy but does not 0 fill the buffer and always null 
    terminates. bufsize is the size of the destination buffer */
diff -Nuar distcc-2.18.3.load/src/util.h distcc-2.18.3.stats/src/util.h
--- distcc-2.18.3.load/src/util.h	2006-02-02 11:25:34.000000000 -0800
+++ distcc-2.18.3.stats/src/util.h	2006-02-02 11:28:45.000000000 -0800
@@ -41,6 +41,9 @@
 #define str_equal(a, b) (!strcmp((a), (b)))
 
 
+void dcc_get_proc_stats(int *num_D, int *max_RSS, char **max_RSS_name);
+void dcc_get_disk_io_stats(int *n_reads, int *n_writes);
+
 
 int dcc_dup_part(const char **psrc, char **pdst, const char *sep);
 
