[MEDIUM] introduce the "stats" keyword in global section
Removed old unused MODE_LOG and MODE_STATS, and replaced the "stats"
keyword in the global section. The new "stats" keyword in the global
section is used to create a UNIX socket on which the statistics will
be accessed. The client must issue a "show stat\n" command in order
to get a CSV-formated output similar to the output on the HTTP socket
in CSV mode.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 97c427c..f72a95b 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -4,7 +4,7 @@
----------------------
version 1.3.13
willy tarreau
- 2007/10/15
+ 2007/10/18
This document covers the configuration language as implemented in the version
@@ -39,6 +39,7 @@
- uid
- ulimit-n
- user
+ - stats
* Performance tuning
- maxconn
@@ -52,7 +53,6 @@
* Debugging
- debug
- quiet
- - stats
1.1) Process management and security
@@ -111,6 +111,28 @@
the "-p" command line argument. The file must be accessible to the user
starting the process. See also "daemon".
+stats socket <path> [{uid | user} <uid>] [{gid | group} <gid>] [mode <mode>]
+ Creates a UNIX socket in stream mode at location <path>. Any previously
+ existing socket will be backed up then replaced. Connections to this socket
+ will get a CSV-formated output of the process statistics in response to the
+ "show stat" command followed by a line feed. On platforms which support it,
+ it is possible to restrict access to this socket by specifying numerical IDs
+ after "uid" and "gid", or valid user and group names after the "user" and
+ "group" keywords. It is also possible to restrict permissions on the socket
+ by passing an octal value after the "mode" keyword (same syntax as chmod).
+ Depending on the platform, the permissions on the socket will be inherited
+ from the directory which hosts it, or from the user the process is started
+ with.
+
+stats timeout <timeout, in milliseconds>
+ The default timeout on the stats socket is set to 10 seconds. It is possible
+ to change this value with "stats timeout". The value must be passed in
+ milliseconds.
+
+stats maxconn <connections>
+ By default, the stats socket is limited to 10 concurrent connections. It is
+ possible to change this value with "stats maxconn".
+
uid <number>
Changes the process' user ID to <number>. It is recommended that the user ID
is dedicated to HAProxy or to a small set of similar daemons. HAProxy must
@@ -186,10 +208,6 @@
Do not display any message during startup. It is equivalent to the command-
line argument "-q".
-stats
- Dump internal statistics to stdout at regular interval. It is available for
- development purposes only and should never be set.
-
2) Proxies
----------
diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
index bcf1540..efe5fa9 100644
--- a/include/proto/dumpstats.h
+++ b/include/proto/dumpstats.h
@@ -29,6 +29,7 @@
#define STAT_FMT_HTML 0x1
+int stats_parse_global(const char **args, char *err, int errlen);
int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags);
int stats_dump_http(struct session *s, struct uri_auth *uri, int flags);
int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri, int flags);
diff --git a/include/types/global.h b/include/types/global.h
index bf95ffd..bed62d8 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -29,15 +29,13 @@
#include <types/task.h>
/* modes of operation (global.mode) */
-#define MODE_DEBUG 1
-#define MODE_STATS 2
-#define MODE_LOG 4
-#define MODE_DAEMON 8
-#define MODE_QUIET 16
-#define MODE_CHECK 32
-#define MODE_VERBOSE 64
-#define MODE_STARTING 128
-#define MODE_FOREGROUND 256
+#define MODE_DEBUG 0x01
+#define MODE_DAEMON 0x02
+#define MODE_QUIET 0x04
+#define MODE_CHECK 0x08
+#define MODE_VERBOSE 0x10
+#define MODE_STARTING 0x20
+#define MODE_FOREGROUND 0x40
/* list of last checks to perform, depending on config options */
#define LSTCHK_CAP_BIND 0x00000001 /* check that we can bind to any port */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 53c900a..d9e01a7 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -41,6 +41,7 @@
#include <proto/backend.h>
#include <proto/buffers.h>
#include <proto/checks.h>
+#include <proto/dumpstats.h>
#include <proto/httperr.h>
#include <proto/log.h>
#include <proto/proxy.h>
@@ -279,7 +280,11 @@
global.mode |= MODE_QUIET;
}
else if (!strcmp(args[0], "stats")) {
- global.mode |= MODE_STATS;
+ memcpy(trash, "error near 'stats'", 19);
+ if (stats_parse_global((const char **)args + 1, trash, sizeof(trash)) < 0) {
+ Alert("parsing [%s:%d] : %s\n", file, linenum, trash);
+ return -1;
+ }
}
else if (!strcmp(args[0], "tune.maxpollevents")) {
if (global.tune.maxpollevents != 0) {
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 3fdac86..bba8c2c 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -17,6 +17,8 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
+#include <pwd.h>
+#include <grp.h>
#include <sys/socket.h>
#include <sys/stat.h>
@@ -42,9 +44,114 @@
#include <proto/buffers.h>
#include <proto/dumpstats.h>
#include <proto/fd.h>
+#include <proto/proto_uxst.h>
#include <proto/senddata.h>
#include <proto/session.h>
+/* This function parses a "stats" statement in the "global" section. It returns
+ * -1 if there is any error, otherwise zero. If it returns -1, it may write an
+ * error message into ther <err> buffer, for at most <errlen> bytes, trailing
+ * zero included. The trailing '\n' must not be written. The function must be
+ * called with <args> pointing to the first word after "stats".
+ */
+int stats_parse_global(const char **args, char *err, int errlen)
+{
+ if (!strcmp(args[0], "socket")) {
+ struct sockaddr_un su;
+ int cur_arg;
+
+ if (*args[1] == 0) {
+ snprintf(err, errlen, "'stats socket' in global section expects a path to a UNIX socket");
+ return -1;
+ }
+
+ if (global.stats_sock.state != LI_NEW) {
+ snprintf(err, errlen, "'stats socket' already specified in global section");
+ return -1;
+ }
+
+ su.sun_family = AF_UNIX;
+ strncpy(su.sun_path, args[1], sizeof(su.sun_path));
+ su.sun_path[sizeof(su.sun_path) - 1] = 0;
+ memcpy(&global.stats_sock.addr, &su, sizeof(su)); // guaranteed to fit
+
+ global.stats_sock.state = LI_INIT;
+ global.stats_sock.accept = uxst_event_accept;
+ global.stats_sock.handler = process_uxst_stats;
+ global.stats_sock.private = NULL;
+
+ cur_arg = 2;
+ while (*args[cur_arg]) {
+ if (!strcmp(args[cur_arg], "uid")) {
+ global.stats_sock.perm.ux.uid = atol(args[cur_arg + 1]);
+ cur_arg += 2;
+ }
+ else if (!strcmp(args[cur_arg], "gid")) {
+ global.stats_sock.perm.ux.gid = atol(args[cur_arg + 1]);
+ cur_arg += 2;
+ }
+ else if (!strcmp(args[cur_arg], "mode")) {
+ global.stats_sock.perm.ux.mode = strtol(args[cur_arg + 1], NULL, 8);
+ cur_arg += 2;
+ }
+ else if (!strcmp(args[cur_arg], "user")) {
+ struct passwd *user;
+ user = getpwnam(args[cur_arg + 1]);
+ if (!user) {
+ snprintf(err, errlen, "unknown user '%s' in 'global' section ('stats user')",
+ args[cur_arg + 1]);
+ return -1;
+ }
+ global.stats_sock.perm.ux.uid = user->pw_uid;
+ cur_arg += 2;
+ }
+ else if (!strcmp(args[cur_arg], "group")) {
+ struct group *group;
+ group = getgrnam(args[cur_arg + 1]);
+ if (!group) {
+ snprintf(err, errlen, "unknown group '%s' in 'global' section ('stats group')",
+ args[cur_arg + 1]);
+ return -1;
+ }
+ global.stats_sock.perm.ux.gid = group->gr_gid;
+ cur_arg += 2;
+ }
+ else {
+ snprintf(err, errlen, "'stats socket' only supports 'user', 'uid', 'group', 'gid', and 'mode'");
+ return -1;
+ }
+ }
+
+ uxst_add_listener(&global.stats_sock);
+ global.maxsock++;
+ }
+ else if (!strcmp(args[0], "timeout")) {
+ int timeout = atol(args[1]);
+
+ if (timeout <= 0) {
+ snprintf(err, errlen, "a positive value is expected for 'stats timeout' in 'global section'");
+ return -1;
+ }
+ __tv_from_ms(&global.stats_timeout, timeout);
+ }
+ else if (!strcmp(args[0], "maxconn")) {
+ int maxconn = atol(args[1]);
+
+ if (maxconn <= 0) {
+ snprintf(err, errlen, "a positive value is expected for 'stats maxconn' in 'global section'");
+ return -1;
+ }
+ global.maxsock -= global.stats_sock.maxconn;
+ global.stats_sock.maxconn = maxconn;
+ global.maxsock += global.stats_sock.maxconn;
+ }
+ else {
+ snprintf(err, errlen, "'stats' only supports 'socket', 'maxconn' and 'timeout' in 'global' section");
+ return -1;
+ }
+ return 0;
+}
+
/*
* Produces statistics data for the session <s>. Expects to be called with
* s->cli_state == CL_STSHUTR. It *may* make use of informations from <uri>
diff --git a/src/haproxy.c b/src/haproxy.c
index c13c392..c57e23f 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -116,6 +116,12 @@
logfac2 : -1,
loglev1 : 7, /* max syslog level : debug */
loglev2 : 7,
+ .stats_timeout = { .tv_sec = 10, .tv_usec = 0 }, /* stats timeout = 10 seconds */
+ .stats_sock.timeout = &global.stats_timeout,
+ .stats_sock.maxconn = 10, /* 10 concurrent stats connections */
+ .stats_sock.perm.ux.uid = -1,
+ .stats_sock.perm.ux.gid = -1,
+ .stats_sock.perm.ux.mode = 0,
/* others NULL OK */
};
@@ -542,7 +548,7 @@
global.mode &= ~(MODE_DAEMON | MODE_QUIET);
}
global.mode |= (arg_mode & (MODE_DAEMON | MODE_FOREGROUND | MODE_QUIET |
- MODE_VERBOSE | MODE_DEBUG | MODE_STATS | MODE_LOG));
+ MODE_VERBOSE | MODE_DEBUG ));
if ((global.mode & MODE_DEBUG) && (global.mode & (MODE_DAEMON | MODE_QUIET))) {
Warning("<debug> mode incompatible with <quiet> and <daemon>. Keeping <debug> only.\n");