[MAJOR] proto_uxst rework -> SNMP support

Currently there is a ~16KB limit for a data size passed via unix socket.
It is caused by a trivial bug ttat is going to fixed soon, 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 stat [<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 throttling 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).

29385 is my PEN (Private Enterprise Number) and I'm willing to donate
the SNMPv2-SMI::enterprises.29385.106.* OIDs for HAProxy if there
is nothing assigned already.
diff --git a/contrib/netsnmp-perl/README b/contrib/netsnmp-perl/README
new file mode 100644
index 0000000..f44eb5b
--- /dev/null
+++ b/contrib/netsnmp-perl/README
@@ -0,0 +1,111 @@
+SNMP support for HAProxy
+Copyright 2007-2008 Krzysztof Piotr Oledzki <ole@ans.pl>
+
+Root OID: 1.3.6.1.4.1.29385.106
+
+Files:
+ - README: this file
+ - haproxy.pl: Net-SNMP embedded perl module
+ - haproxy_backend.xml: Cacti snmp-query definition for backends
+ - haproxy_frontend.xml: Cacti snmp-query definition for frontends
+
+Install:
+ cp haproxy.pl /etc/snmp/
+ grep -q "disablePerl false" /etc/snmp/snmpd.conf || echo "disablePerl false" >> /etc/snmp/snmpd.conf
+ echo "perl do '/etc/snmp/haproxy.pl';" >> /etc/snmp/snmpd.conf
+
+Supported commands:
+ - GET (snmpget, snmpbulkget): quite fast.
+ - GETNEXT (snmpwalk, snmpbulkwalk): not so fast as requires to transfer
+    and parse a lot of data during each step. Always use "get" instead of "walk"
+    if that's possible.
+
+Supported OIDs:
+ - 1.3.6.1.4.1.29385.106.1: get a variable from stats
+    Usage: 1.3.6.1.4.1.29385.106.1.$type.$field.$iid.$sid
+
+     - type is one of:
+       0) frontend
+       1) backend
+       2) server
+
+     - field is one of:
+       0..32) CSV format variable
+       10001) index
+       10002) unique name
+
+     - iid is a proxy id 
+
+     - sid is a service id (sid): 0 for frontends and backends, >= 1 for servers
+
+ - 1.3.6.1.4.1.29385.106.2: get a variable from info
+    Usage: 1.3.6.1.4.1.29385.106.2.$req.$varnr
+
+      - req is one of:
+        0) get variable name
+        1) gat variable value
+
+Examples:
+
+- Get a list of frontends (type: 0) with status (field: 17):
+$ snmpbulkwalk -c public -v2c 192.168.0.1 1.3.6.1.4.1.29385.106.1.0.17
+SNMPv2-SMI::enterprises.29385.106.1.0.17.1.0 = STRING: "OPEN"
+SNMPv2-SMI::enterprises.29385.106.1.0.17.47.0 = STRING: "OPEN"
+
+- Get a list of backends (type: 1) with index (field: 10001):
+$ snmpbulkwalk -c public -v2c 192.168.0.1 1.3.6.1.4.1.29385.106.1.1.10001
+SNMPv2-SMI::enterprises.29385.106.1.1.10001.1.0 = STRING: "1.0"
+SNMPv2-SMI::enterprises.29385.106.1.1.10001.1100.0 = STRING: "1100.0"
+SNMPv2-SMI::enterprises.29385.106.1.1.10001.1101.0 = STRING: "1101.0"
+SNMPv2-SMI::enterprises.29385.106.1.1.10001.1200.0 = STRING: "1200.0"
+SNMPv2-SMI::enterprises.29385.106.1.1.10001.1201.0 = STRING: "1201.0"
+SNMPv2-SMI::enterprises.29385.106.1.1.10001.1300.0 = STRING: "1300.0"
+SNMPv2-SMI::enterprises.29385.106.1.1.10001.1400.0 = STRING: "1400.0"
+SNMPv2-SMI::enterprises.29385.106.1.1.10001.1401.0 = STRING: "1401.0"
+SNMPv2-SMI::enterprises.29385.106.1.1.10001.1500.0 = STRING: "1500.0"
+(...)
+
+- Get a list of servers (type: 2) with unique name (field: 10002):
+$ snmpbulkwalk -c public -v2c 192.168.0.1 1.3.6.1.4.1.29385.106.1.2.10002
+SNMPv2-SMI::enterprises.29385.106.1.2.10002.1100.1001 = STRING: "backend1/s2"
+SNMPv2-SMI::enterprises.29385.106.1.2.10002.1100.1002 = STRING: "backend1/s5"
+SNMPv2-SMI::enterprises.29385.106.1.2.10002.1100.1003 = STRING: "backend1/s6"
+SNMPv2-SMI::enterprises.29385.106.1.2.10002.1100.1012 = STRING: "backend1/s7"
+SNMPv2-SMI::enterprises.29385.106.1.2.10002.1101.1001 = STRING: "backend2/s9"
+SNMPv2-SMI::enterprises.29385.106.1.2.10002.1101.1002 = STRING: "backend2/s10"
+SNMPv2-SMI::enterprises.29385.106.1.2.10002.1101.1003 = STRING: "backend2/s11"
+SNMPv2-SMI::enterprises.29385.106.1.2.10002.1101.1012 = STRING: "backend2/s12"
+SNMPv2-SMI::enterprises.29385.106.1.2.10002.1200.1001 = STRING: "backend3/s8"
+(...)
+
+- Get a list of servers (type: 2) with weight (field: 18) in proxy 4300:
+$ snmpbulkwalk -c public -v2c 192.168.0.1 1.3.6.1.4.1.29385.106.1.2.18.4300
+SNMPv2-SMI::enterprises.29385.106.1.2.18.4300.1001 = STRING: "40"
+SNMPv2-SMI::enterprises.29385.106.1.2.18.4300.1002 = STRING: "25"
+SNMPv2-SMI::enterprises.29385.106.1.2.18.4300.1003 = STRING: "40"
+SNMPv2-SMI::enterprises.29385.106.1.2.18.4300.1012 = STRING: "80"
+
+- Get total sessions count (field: 7) in frontend (type: 1), sid.iid: 47.0 (proxy #47):
+snmpget -c public -v2c 192.168.0.1 enterprises.29385.106.1.0.7.47.0
+SNMPv2-SMI::enterprises.29385.106.1.0.7.47.0 = STRING: "1014019"
+
+- Get a list of available variables (req: 0):
+$ snmpbulkwalk -c public -v2c 192.168.0.1 1.3.6.1.4.1.29385.106.2.0
+SNMPv2-SMI::enterprises.29385.106.2.0.0 = STRING: "Name"
+SNMPv2-SMI::enterprises.29385.106.2.0.1 = STRING: "Version"
+SNMPv2-SMI::enterprises.29385.106.2.0.2 = STRING: "Release_date"
+SNMPv2-SMI::enterprises.29385.106.2.0.3 = STRING: "Nbproc"
+SNMPv2-SMI::enterprises.29385.106.2.0.4 = STRING: "Process_num"
+SNMPv2-SMI::enterprises.29385.106.2.0.5 = STRING: "Pid"
+SNMPv2-SMI::enterprises.29385.106.2.0.6 = STRING: "Uptime"
+SNMPv2-SMI::enterprises.29385.106.2.0.7 = STRING: "Uptime_sec"
+SNMPv2-SMI::enterprises.29385.106.2.0.8 = STRING: "Memmax_MB"
+SNMPv2-SMI::enterprises.29385.106.2.0.9 = STRING: "Ulimit-n"
+SNMPv2-SMI::enterprises.29385.106.2.0.10 = STRING: "Maxsock"
+SNMPv2-SMI::enterprises.29385.106.2.0.11 = STRING: "Maxconn"
+SNMPv2-SMI::enterprises.29385.106.2.0.12 = STRING: "CurrConns"
+
+- Get a variable (req: 1), varnr: 7 (Uptime_sec):
+$ snmpget -c public -v2c 192.168.0.1 1.3.6.1.4.1.29385.106.2.1.7
+SNMPv2-SMI::enterprises.29385.106.2.1.7 = STRING: "18761"
+ 
diff --git a/contrib/netsnmp-perl/haproxy.pl b/contrib/netsnmp-perl/haproxy.pl
new file mode 100644
index 0000000..61b693a
--- /dev/null
+++ b/contrib/netsnmp-perl/haproxy.pl
@@ -0,0 +1,242 @@
+#
+# Net-SNMP perl plugin for Haproxy
+# Version 0.27
+#
+# Copyright 2007-2008 Krzysztof Piotr Oledzki <ole@ans.pl>
+#
+# 1. get a variable from stat:
+#  1.3.6.1.4.1.29385.106.1.$type.$field.$iid.$sid
+#  type: 0->frontend, 1->backend, 2->server
+#
+# 2. get a variable from info
+#  1.3.6.1.4.1.29385.106.2.$req.$varnr
+#
+# TODO:
+# - implement read timeout
+#
+
+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 OID_HAPROXY => '1.3.6.1.4.1.29385.106';
+use constant OID_HAPROXY_STATS => OID_HAPROXY . '.1';
+use constant OID_HAPROXY_INFO => OID_HAPROXY . '.2';
+
+my $oid_stat = new NetSNMP::OID(OID_HAPROXY_STATS);
+my $oid_info = new NetSNMP::OID(OID_HAPROXY_INFO);
+
+use constant STATS_PXNAME => 0;
+use constant STATS_SVNAME => 1;
+use constant STATS_IID => 27;
+use constant STATS_SID => 28;
+use constant STATS_TYPE => 32;
+
+use constant FIELD_INDEX => 10001;
+use constant FIELD_NAME => 10002;
+
+my %info_vars = (
+	0	=> 'Name',
+	1	=> 'Version',
+	2	=> 'Release_date',
+	3	=> 'Nbproc',
+	4	=> 'Process_num',
+	5	=> 'Pid',
+	6	=> 'Uptime',
+	7	=> 'Uptime_sec',
+	8	=> 'Memmax_MB',
+	9	=> 'Ulimit-n',
+	10	=> 'Maxsock',
+	11	=> 'Maxconn',
+	12	=> 'CurrConns',
+);
+
+sub find_next_stat_id {
+	my($type, $field, $proxyid, $sid) = @_;
+
+	my $obj = 1 << $type;
+
+	my $np = -1;
+	my $nl = -1;
+
+	my $sock = new IO::Socket::UNIX (Peer => $sa, Type => SOCK_STREAM, Timeout => 1);
+	next if !$sock;
+
+	print $sock "show stat -1 $obj -1\n";
+
+	while(<$sock>) {
+		chomp;
+		my @d = split(',');
+
+		last if !$d[$field] && $field != FIELD_INDEX && $field != FIELD_NAME && /^#/;
+		next if /^#/;
+
+		next if $d[STATS_TYPE] != $type;
+
+		next if ($d[STATS_IID] < $proxyid) || ($d[STATS_IID] == $proxyid && $d[STATS_SID] <= $sid);
+
+		if ($np == -1 || $d[STATS_IID] < $np || ($d[STATS_IID] == $np && $d[STATS_SID] < $nl)) {
+			$np = $d[STATS_IID];
+			$nl = $d[STATS_SID];
+			next;
+		}
+	}
+
+	close($sock);
+
+	return 0 if ($np == -1);
+
+	return "$type.$field.$np.$nl"
+}
+
+sub haproxy_stat {
+	my($handler, $registration_info, $request_info, $requests) = @_;
+
+	for(my $request = $requests; $request; $request = $request->next()) {
+		my $oid = $request->getOID();
+
+		$oid =~ s/$oid_stat//;
+		$oid =~ s/^\.//;
+
+		my $mode = $request_info->getMode();
+
+		my($type, $field, $proxyid, $sid, $or) = split('\.', $oid, 5);
+
+		next if $type > 2 || defined($or);
+
+		if ($mode == MODE_GETNEXT) {
+
+			$type = 0 if !$type;
+			$field = 0 if !$field;
+			$proxyid = 0 if !$proxyid;
+			$sid = 0 if !$sid;
+
+			my $nextid = find_next_stat_id($type, $field, $proxyid, $sid);
+			$nextid = find_next_stat_id($type, $field+1, 0, 0) if !$nextid;
+			$nextid = find_next_stat_id($type+1, 0, 0, 0) if !$nextid;
+
+			if ($nextid) {
+				($type, $field, $proxyid, $sid) = split('\.', $nextid);
+				$request->setOID(sprintf("%s.%s", OID_HAPROXY_STATS, $nextid));
+				$mode = MODE_GET;
+			}
+		}
+
+		if ($mode == MODE_GET) {
+				next if !defined($proxyid) || !defined($type) || !defined($sid) || !defined($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 $sid\n";
+
+				while(<$sock>) {
+					chomp;
+					my @data = split(',');
+
+					last if !defined($data[$field]) && $field != FIELD_INDEX && $field != FIELD_NAME;
+
+					if ($proxyid) {
+						next if $data[STATS_IID] ne $proxyid;
+						next if $data[STATS_SID] ne $sid;
+						next if $data[STATS_TYPE] ne $type;
+					}
+
+					if ($field == FIELD_INDEX) {
+						$request->setValue(ASN_OCTET_STR,
+							sprintf("%s.%s", $data[STATS_IID],
+								$data[STATS_SID]));
+					} elsif ($field == FIELD_NAME) {
+						$request->setValue(ASN_OCTET_STR,
+							sprintf("%s/%s", $data[STATS_PXNAME],
+								$data[STATS_SVNAME]));
+					} else {
+						$request->setValue(ASN_OCTET_STR, $data[$field]);
+					}
+
+					close($sock);
+					last;
+				}
+
+				close($sock);
+				next;
+		}
+
+  	}
+}
+
+sub haproxy_info {
+	my($handler, $registration_info, $request_info, $requests) = @_;
+
+	for(my $request = $requests; $request; $request = $request->next()) {
+		my $oid = $request->getOID();
+
+		$oid =~ s/$oid_info//;
+		$oid =~ s/^\.//;
+
+		my $mode = $request_info->getMode();
+
+		my($req, $nr, $or) = split('\.', $oid, 3);
+
+		next if $req >= 2 || defined($or);
+
+		if ($mode == MODE_GETNEXT) {
+			$req = 0 if !defined($req);
+			$nr  = -1 if !defined($nr);
+
+			if (!defined($info_vars{$nr+1})) {
+				$req++;
+				$nr = -1;
+			}
+
+			next if $req >= 2;
+
+			$request->setOID(sprintf("%s.%s.%s", OID_HAPROXY_INFO, $req, ++$nr));
+			$mode = MODE_GET;
+			
+		}
+
+		if ($mode == MODE_GET) {
+
+			next if !defined($req) || !defined($nr);
+
+			if ($req == 0) {
+				next if !defined($info_vars{$nr});
+				$request->setValue(ASN_OCTET_STR, $info_vars{$nr});
+				next;
+			}
+
+			if ($req == 1) {
+				next if !defined($info_vars{$nr});
+
+				my $sock = new IO::Socket::UNIX (Peer => $sa, Type => SOCK_STREAM, Timeout => 1);
+				next if !$sock;
+
+				print $sock "show info\n";
+
+				while(<$sock>) {
+					chomp;
+					my ($key, $val) = /(.*):\s*(.*)/;
+
+					next if $info_vars{$nr} ne $key;
+
+					$request->setValue(ASN_OCTET_STR, $val);
+					last;
+				}
+
+				close($sock);
+			}
+		}
+	}
+}
+
+$agent->register('Haproxy stat', OID_HAPROXY_STATS, \&haproxy_stat);
+$agent->register('Haproxy info', OID_HAPROXY_INFO, \&haproxy_info);
+
diff --git a/contrib/netsnmp-perl/haproxy_backend.xml b/contrib/netsnmp-perl/haproxy_backend.xml
new file mode 100644
index 0000000..9bbde2f
--- /dev/null
+++ b/contrib/netsnmp-perl/haproxy_backend.xml
@@ -0,0 +1,62 @@
+<interface>
+	<name>Haproxy - backend</name>
+	<oid_index>.1.3.6.1.4.1.29385.106.1.1.10001</oid_index>
+	<fields>
+		<beIID>
+			<name>Proxy ID</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>input</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.1.27</oid>
+		</beIID>
+		<beSID>
+			<name>Service ID</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>input</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.1.28</oid>
+		</beSID>
+		<bePxName>
+			<name>Proxy Name</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>input</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.1.0</oid>
+		</bePxName>
+		<beSvName>
+			<name>Service Name</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>input</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.1.1</oid>
+		</beSvName>
+		<beSTot>
+			<name>Total Sessions</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>output</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.1.7</oid>
+		</beSTot>
+		<beEResp>
+			<name>Response Errors</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>output</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.1.14</oid>
+		</beEResp>
+		<beBIn>
+			<name>Bytes In</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>output</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.1.8</oid>
+		</beBIn>
+		<beBOut>
+			<name>Bytes Out</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>output</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.1.9</oid>
+		</beBOut>
+	</fields>
+</interface>
diff --git a/contrib/netsnmp-perl/haproxy_frontend.xml b/contrib/netsnmp-perl/haproxy_frontend.xml
new file mode 100644
index 0000000..09789c8
--- /dev/null
+++ b/contrib/netsnmp-perl/haproxy_frontend.xml
@@ -0,0 +1,62 @@
+<interface>
+	<name>Haproxy - frontend</name>
+	<oid_index>.1.3.6.1.4.1.29385.106.1.0.10001</oid_index>
+	<fields>
+		<feIID>
+			<name>Proxy ID</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>input</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.0.27</oid>
+		</feIID>
+		<feSID>
+			<name>Service ID</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>input</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.0.28</oid>
+		</feSID>
+		<fePxName>
+			<name>Proxy Name</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>input</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.0.0</oid>
+		</fePxName>
+		<feSvName>
+			<name>Service Name</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>input</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.0.1</oid>
+		</feSvName>
+		<feSTot>
+			<name>Total Sessions</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>output</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.0.7</oid>
+		</feSTot>
+		<feEReq>
+			<name>Request Errors</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>output</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.0.12</oid>
+		</feEReq>
+		<feBIn>
+			<name>Bytes In</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>output</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.0.8</oid>
+		</feBIn>
+		<feBOut>
+			<name>Bytes Out</name>
+			<method>get</method>
+			<source>value</source>
+			<direction>output</direction>
+			<oid>.1.3.6.1.4.1.29385.106.1.0.9</oid>
+		</feBOut>
+	</fields>
+</interface>