Re: [PATCH] proto_uxst rework -> SNMP support

From: Willy Tarreau <w#1wt.eu>
Date: Wed, 27 Feb 2008 05:55:32 +0100


Hi Krzysztof,

Cool stuff. I'll *try* to process all your pending mails today.

Concerning the 16kB limit on unix, it's certainly a bug, I will have to check. For the show stats, that's fine. I would initially have liked something with the names instead of numbers, but this is just the stats socket, designed exactly for this use, and not a CLI (there's a reason why it was called 'stats'), so in the end, numbers are the right method :-)

Concerning the SNMP support, that looks fine. At Exosec, we have developped a net-snmp plugin to check the stats (which we use in our appliance), and decided to set it free as a contrib. It's just a matter of time to package it and release it as a distinct package.

Since yours is probably simpler and smaller, there's no problem merging it into haproxy. You should probably create a "contrib" directory with another sub-directory inside for your program. We will then progressively move all other contribs there and leave only examples in the examples directory.

Thanks!
Willy

On Tue, Feb 26, 2008 at 02:07:56AM +0100, Krzysztof Oledzki wrote:
> >From 422ec2e879b6c1f5577d1602394d5bd654b41187 Mon Sep 17 00:00:00 2001
> From: Krzysztof Piotr Oledzki <ole#ans.pl>
> Date: Tue, 26 Feb 2008 01:37:10 +0100
> Subject: [MAJOR] proto_uxst rework -> SNMP support
>
> Currently there is a ~16KB limit for data size passed via unix socket.
> It is probably caused by a trivial bug that can be easily fixed, however
> in most cases there is no need to dump a full stats.
>
> This patch makes possible to select a scope of dumped data by extending
> current "show stat" to "show stats [<iid> <type> <sid>]":
> - iid is a proxy id, -1 to dump all proxies
> - type selects type of dumpable objects: 1 for frontend, 2 for backend, 4 for
> server, -1 for all types. Values can be ORed, for example:
> 1+2=3 -> frontend+backend.
> 1+2+4=7 -> frontend+backend+server.
> - sid is a service id, -1 to dump everything from the selected proxy.
>
> To do this I implemented a new session flag (SN_STAT_BOUND), added three
> variables in data_ctx.stats (iid, type, sid), modified dumpstats.c and
> completely revorked the process_uxst_stats: now it waits for a "\n"
> terminated string, splits args and uses them. BTW: It should be quite easy
> to add new commands, for example to enable/disable servers, the only problem
> I can see is a not very lucky config name (*stats* socket). :|
>
> During the work I also fixed two bug:
> - s->flags were not initialized for proto_uxst
> - missing comma if throttle not enabled (caused by a stupid change in
> "Implement persistent id for proxies and servers")
>
> Other changes:
> - No more magic type valuse, use STATS_TYPE_FE/STATS_TYPE_BE/STATS_TYPE_SV
> - Don't memset full s->data_ctx (it was clearing s->data_ctx.stats.{iid/type/sid},
> instead initialize stats.sv & stats.sv_st (stats.px and stats.px_st were already
> initialized)
>
> With all that changes it was extremely easy to write a short perl plugin
> for a perl-enabled net-snmp (also included in this patch). To use it
> you need a perl enabled net-snmpd and:
> cp examples/haproxy.pl /etc/snmp/
> echo "perl do '/etc/snmp/haproxy.pl';" >> /etc/snmp/snmpd.conf
>
> Examples:
> - get var #7 (stot: total sessions) for proxy id 3000 - backend:
> snmpget -v 1 -c public 127.1 1.3.6.1.4.1.29385.106.1.3000.1.0.7
>
> - get vars #7, #8 (bin: bytes in, bout: bytes out) for proxy id 3000 - server id 1001
> snmpget -v 1 -c public 127.1 1.3.6.1.4.1.29385.106.1.3000.2.1001.8 1.3.6.1.4.1.29385.106.1.3000.2.1001.9
>
> 29385 is my PEN (Private Enterprise Number) and I'm willing to donate
> the SNMPv2-SMI::enterprises.29385.106.* OIDs for the Haproxy if there
> is nothing assigned already.
>
> In the future I'm going to add support for snmpwalk and access to all
> "show info" variables.
> ---
> doc/configuration.txt | 13 +++++++
> examples/haproxy.pl | 80 +++++++++++++++++++++++++++++++++++++++++++++
> include/common/defaults.h | 3 ++
> include/proto/dumpstats.h | 4 ++
> include/types/session.h | 2 +
> src/dumpstats.c | 48 +++++++++++++++++---------
> src/proto_uxst.c | 80 +++++++++++++++++++++++++++++++++++---------
> 7 files changed, 196 insertions(+), 34 deletions(-)
> create mode 100644 examples/haproxy.pl
>
> diff --git a/doc/configuration.txt b/doc/configuration.txt
> index 8621f4e..6a8f9a0 100644
> --- a/doc/configuration.txt
> +++ b/doc/configuration.txt
> @@ -4058,6 +4058,19 @@ Notes related to these keywords :
> 31. tracked: id of proxy/server if tracking is enabled
> 32. type (0=frontend, 1=backend, 2=server)
>
> +2.8) Unix Socket commands
> +
> + - "show stats [<iid> <type> <sid>]": dump statistics in the cvs format. By
> + passing id, type and sid it is possible to dump only selected items:
> + - iid is a proxy id, -1 to dump everything
> + - type selects type of dumpable objects: 1 for frontend, 2 for backend, 4 for
> + server, -1 for everything. Values can be ORed, for example:
> + 1+2=3 -> frontend+backend.
> + 1+2+4=7 -> frontend+backend+server.
> + - sid is a service id, -1 to dump everything from the selected proxy.
> +
> + - "show info": dump info about current haproxy status.
> +
> /*
> * Local variables:
> * fill-column: 79
> diff --git a/examples/haproxy.pl b/examples/haproxy.pl
> new file mode 100644
> index 0000000..bc0cfb0
> --- /dev/null
> +++ b/examples/haproxy.pl
> @@ -0,0 +1,80 @@
> +#
> +# Net-SNMP perl plugin for Haproxy
> +# Verision 0.03
> +#
> +# Copyright 2007-2008 Krzysztof Piotr Oledzki <ole#ans.pl>
> +#
> +# 1. get a variable from statistics:
> +# 1.3.6.1.4.1.29385.106.1.$proxy.$type.$lid.$field
> +# type: 0->frontend, 1->backend, 2->server
> +#
> +
> +use NetSNMP::agent (':all');
> +use NetSNMP::ASN qw(:all);
> +use IO::Socket::UNIX;
> +
> +use strict;
> +
> +my $agent = new NetSNMP::agent('Name' => 'Haproxy');
> +my $sa = "/var/run/haproxy.stat";
> +
> +use constant HAPROXYOID => '1.3.6.1.4.1.29385.106';
> +
> +my $myoid = new NetSNMP::OID(HAPROXYOID);
> +
> +sub myroutine {
> + my($handler, $registration_info, $request_info, $requests) = @_;
> +
> + for(my $request = $requests; $request; $request = $request->next()) {
> + my $oid = $request->getOID();
> +
> + $oid =~ s/$myoid//;
> +
> + return if (!$oid);
> +
> + $oid =~ s/^.//;
> +
> + if ($request_info->getMode() == MODE_GET) {
> +
> + my($cmd, $or1) = split('\.', $oid, 2);
> +
> + if ($cmd == 1) {
> +
> + my($proxyid, $type, $lid, $field, $or2) = split('\.', $or1, 5);
> +
> + next if defined($or2);
> + next if !defined($proxyid) || !defined($type) || !defined($lid) || !defined($field);
> +
> + # FIXME: check/verify input values ($proxyid, $type, $lid, $field)
> +
> + my $obj = 1 << $type;
> +
> + my $sock = new IO::Socket::UNIX (Peer => $sa, Type => SOCK_STREAM, Timeout => 1);
> + next if !$sock;
> +
> + print $sock "show stat $proxyid $obj $lid\n";
> +
> + while(<$sock>) {
> + chomp;
> +
> + my @data = split(',');
> +
> + if ($proxyid) {
> + next if $data[27] ne $proxyid;
> + next if $data[28] ne $lid;
> + next if $data[32] ne $type;
> + }
> +
> + $request->setValue(ASN_OCTET_STR, $data[$field]);
> + close($sock);
> + last;
> + }
> +
> + close($sock);
> + next;
> + }
> + }
> + }
> +}
> +
> +$agent->register('Haproxy', HAPROXYOID, \&myroutine);
> diff --git a/include/common/defaults.h b/include/common/defaults.h
> index 1a69f40..da9bdd8 100644
> --- a/include/common/defaults.h
> +++ b/include/common/defaults.h
> @@ -51,6 +51,9 @@
> // max # args on a configuration line
> #define MAX_LINE_ARGS 64
>
> +// max # args on a uxts socket
> +#define MAX_UXST_ARGS 16
> +
> // max # of added headers per request
> #define MAX_NEWHDR 10
>
> diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h
> index 198f0d7..3626505 100644
> --- a/include/proto/dumpstats.h
> +++ b/include/proto/dumpstats.h
> @@ -31,6 +31,10 @@
> #define STAT_SHOW_STAT 0x2
> #define STAT_SHOW_INFO 0x4
>
> +#define STATS_TYPE_FE 0
> +#define STATS_TYPE_BE 1
> +#define STATS_TYPE_SV 2
> +
> 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);
> diff --git a/include/types/session.h b/include/types/session.h
> index 9437198..18d5115 100644
> --- a/include/types/session.h
> +++ b/include/types/session.h
> @@ -82,6 +82,7 @@
> #define SN_STAT_HIDEDWN 0x00100000 /* hide 'down' servers in the stats page */
> #define SN_STAT_NORFRSH 0x00200000 /* do not automatically refresh the stats page */
> #define SN_STAT_FMTCSV 0x00400000 /* dump the stats in CSV format instead of HTML */
> +#define SN_STAT_BOUND 0x00800000 /* bound statistics to selected proxies/types/services */
>
>
> /* WARNING: if new fields are added, they must be initialized in event_accept()
> @@ -126,6 +127,7 @@ struct session {
> struct proxy *px;
> struct server *sv;
> short px_st, sv_st; /* DATA_ST_INIT or DATA_ST_DATA */
> + int iid, type, sid; /* proxy id, type and service id if bounding of stats is enabled */
> } stats;
> } data_ctx; /* used by produce_content to dump the stats right now */
> unsigned int uniq_id; /* unique ID used for the traces */
> diff --git a/src/dumpstats.c b/src/dumpstats.c
> index c132246..99ac770 100644
> --- a/src/dumpstats.c
> +++ b/src/dumpstats.c
> @@ -213,7 +213,6 @@ int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags)
>
> case DATA_ST_INFO:
> up = (now.tv_sec - start_date.tv_sec);
> - memset(&s->data_ctx, 0, sizeof(s->data_ctx));
>
> if (flags & STAT_SHOW_INFO) {
> chunk_printf(&msg, sizeof(trash),
> @@ -248,6 +247,10 @@ int stats_dump_raw(struct session *s, struct uri_auth *uri, int flags)
>
> s->data_ctx.stats.px = proxy;
> s->data_ctx.stats.px_st = DATA_ST_PX_INIT;
> +
> + s->data_ctx.stats.sv = NULL;
> + s->data_ctx.stats.sv_st = 0;
> +
> s->data_state = DATA_ST_LIST;
> /* fall through */
>
> @@ -621,6 +624,10 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
> return 1;
> }
>
> + if ((s->flags & SN_STAT_BOUND) && (s->data_ctx.stats.iid != -1) &&
> + (px->uuid != s->data_ctx.stats.iid))
> + return 1;
> +
> s->data_ctx.stats.px_st = DATA_ST_PX_TH;
> /* fall through */
>
> @@ -660,7 +667,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
>
> case DATA_ST_PX_FE:
> /* print the frontend */
> - if (px->cap & PR_CAP_FE) {
> + if ((px->cap & PR_CAP_FE) && (!(s->flags & SN_STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_FE)))) {
> if (flags & STAT_FMT_HTML) {
> chunk_printf(&msg, sizeof(trash),
> /* name, queue */
> @@ -706,8 +713,8 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
> "%s,"
> /* rest of server: nothing */
> ",,,,,,,,"
> - /* pid, iid, sid, throttle, lbtot, tracked, type (0=server)*/
> - "%d,%d,0,,,,0,"
> + /* pid, iid, sid, throttle, lbtot, tracked, type */
> + "%d,%d,0,,,,%d,"
> "\n",
> px->id,
> px->feconn, px->feconn_max, px->maxconn, px->cum_feconn,
> @@ -716,7 +723,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
> px->failed_req,
> px->state == PR_STRUN ? "OPEN" :
> px->state == PR_STIDLE ? "FULL" : "STOP",
> - relative_pid, px->uuid);
> + relative_pid, px->uuid, STATS_TYPE_FE);
> }
>
> if (buffer_write_chunk(rep, &msg) != 0)
> @@ -729,11 +736,20 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
>
> case DATA_ST_PX_SV:
> /* stats.sv has been initialized above */
> - while (s->data_ctx.stats.sv != NULL) {
> + for (; s->data_ctx.stats.sv != NULL; s->data_ctx.stats.sv = sv->next) {
> +
> int sv_state; /* 0=DOWN, 1=going up, 2=going down, 3=UP, 4,5=NOLB, 6=unchecked */
>
> sv = s->data_ctx.stats.sv;
>
> + if (s->flags & SN_STAT_BOUND) {
> + if (!(s->data_ctx.stats.type & (1 << STATS_TYPE_SV)))
> + break;
> +
> + if (s->data_ctx.stats.sid != -1 && sv->puid != s->data_ctx.stats.sid)
> + continue;
> + }
> +
> if (sv->tracked)
> svs = sv->tracked;
> else
> @@ -911,11 +927,11 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
> now.tv_sec >= sv->last_change) {
> unsigned int ratio;
> ratio = MAX(1, 100 * (now.tv_sec - sv->last_change) / sv->slowstart);
> - chunk_printf(&msg, sizeof(trash), "%d,", ratio);
> + chunk_printf(&msg, sizeof(trash), "%d", ratio);
> }
>
> /* sessions: lbtot */
> - chunk_printf(&msg, sizeof(trash), "%d,", sv->cum_lbconn);
> + chunk_printf(&msg, sizeof(trash), ",%d,", sv->cum_lbconn);
>
> /* tracked */
> if (sv->tracked)
> @@ -924,21 +940,19 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
> else
> chunk_printf(&msg, sizeof(trash), ",");
>
> - /* type (2=server), then EOL */
> - chunk_printf(&msg, sizeof(trash), "2,\n");
> + /* type, then EOL */
> + chunk_printf(&msg, sizeof(trash), "%d,\n", STATS_TYPE_SV);
> }
> if (buffer_write_chunk(rep, &msg) != 0)
> return 0;
> -
> - s->data_ctx.stats.sv = sv->next;
> - } /* while sv */
> + } /* for sv */
>
> s->data_ctx.stats.px_st = DATA_ST_PX_BE;
> /* fall through */
>
> case DATA_ST_PX_BE:
> /* print the backend */
> - if (px->cap & PR_CAP_BE) {
> + if ((px->cap & PR_CAP_BE) && (!(s->flags & SN_STAT_BOUND) || (s->data_ctx.stats.type & (1 << STATS_TYPE_BE)))) {
> if (flags & STAT_FMT_HTML) {
> chunk_printf(&msg, sizeof(trash),
> /* name */
> @@ -1007,8 +1021,8 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
> "%d,%d,%d,"
> /* rest of backend: nothing, down transitions, last change, total downtime */
> ",%d,%d,%d,,"
> - /* pid, iid, sid, throttle, lbtot, tracked, type (1=backend) */
> - "%d,%d,0,,%d,,1,"
> + /* pid, iid, sid, throttle, lbtot, tracked, type */
> + "%d,%d,0,,%d,,%d,"
> "\n",
> px->id,
> px->nbpend /* or px->totpend ? */, px->nbpend_max,
> @@ -1023,7 +1037,7 @@ int stats_dump_proxy(struct session *s, struct proxy *px, struct uri_auth *uri,
> px->down_trans, now.tv_sec - px->last_change,
> px->srv?be_downtime(px):0,
> relative_pid, px->uuid,
> - px->cum_lbconn);
> + px->cum_lbconn, STATS_TYPE_BE);
> }
> if (buffer_write_chunk(rep, &msg) != 0)
> return 0;
> diff --git a/src/proto_uxst.c b/src/proto_uxst.c
> index 9054722..76709c1 100644
> --- a/src/proto_uxst.c
> +++ b/src/proto_uxst.c
> @@ -416,6 +416,8 @@ int uxst_event_accept(int fd) {
> return 0;
> }
>
> + s->flags = 0;
> +
> if ((t = pool_alloc2(pool2_task)) == NULL) {
> Alert("out of memory in uxst_event_accept().\n");
> close(cfd);
> @@ -1381,31 +1383,75 @@ void process_uxst_stats(struct task *t, struct timeval *next)
> }
>
> if (s->data_state == DATA_ST_INIT) {
> - if ((s->req->l >= 10) && (memcmp(s->req->data, "show stat\n", 10) == 0)) {
> - /* send the stats, and changes the data_state */
> - if (stats_dump_raw(s, NULL, STAT_SHOW_STAT) != 0) {
> - s->srv_state = SV_STCLOSE;
> - fsm_resync |= 1;
> +
> + char *args[MAX_UXST_ARGS + 1];
> + char *line, *p;
> + int arg;
> +
> + line = s->req->data;
> + p = memchr(line, '\n', s->req->l);
> +
> + if (!p)
> + continue;
> +
> + *p = '\0';
> +
> + while (isspace((unsigned char)*line))
> + line++;
> +
> + arg = 0;
> + args[arg] = line;
> +
> + while (*line && arg < MAX_UXST_ARGS) {
> + if (isspace((unsigned char)*line)) {
> + *line++ = '\0';
> +
> + while (isspace((unsigned char)*line))
> + line++;
> +
> + args[++arg] = line;
> continue;
> }
> +
> + line++;
> }
> - if ((s->req->l >= 10) && (memcmp(s->req->data, "show info\n", 10) == 0)) {
> - /* send the stats, and changes the data_state */
> - if (stats_dump_raw(s, NULL, STAT_SHOW_INFO) != 0) {
> - s->srv_state = SV_STCLOSE;
> - fsm_resync |= 1;
> +
> + while (++arg <= MAX_UXST_ARGS)
> + args[arg] = line;
> +
> + if (!strcmp(args[0], "show")) {
> + if (!strcmp(args[1], "stat")) {
> + if (*args[2] && *args[3] && *args[4]) {
> + s->flags |= SN_STAT_BOUND;
> + s->data_ctx.stats.iid = atoi(args[2]);
> + s->data_ctx.stats.type = atoi(args[3]);
> + s->data_ctx.stats.sid = atoi(args[4]);
> + }
> +
> + /* send the stats, and changes the data_state */
> + if (stats_dump_raw(s, NULL, STAT_SHOW_STAT) != 0) {
> + s->srv_state = SV_STCLOSE;
> + fsm_resync |= 1;
> + }
> +
> + continue;
> + }
> +
> + if (!strcmp(args[1], "info")) {
> + /* send the stats, and changes the data_state */
> + if (stats_dump_raw(s, NULL, STAT_SHOW_INFO) != 0) {
> + s->srv_state = SV_STCLOSE;
> + fsm_resync |= 1;
> + }
> +
> continue;
> }
> }
> - else if (s->cli_state == CL_STSHUTR || (s->req->l >= s->req->rlim - s->req->data)) {
> - s->srv_state = SV_STCLOSE;
> - fsm_resync |= 1;
> - continue;
> - }
> - }
>
> - if (s->data_state == DATA_ST_INIT)
> + s->srv_state = SV_STCLOSE;
> + fsm_resync |= 1;
> continue;
> + }
>
> /* OK we have some remaining data to process. Just for the
> * sake of an exercice, we copy the req into the resp,
> --
> 1.5.3.7
>
Received on 2008/02/27 05:55

This archive was generated by hypermail 2.2.0 : 2008/02/27 06:00 CET