[MINOR] Implement persistent id for proxies and servers

This patch adds a possibility to set a persistent id for a proxy/server.
Now, even if some proxies/servers are inserted/deleted/moved, iids and
sids can be still used reliable.

Some people add servers with tricky names (BACKEND or FRONTEND for example).
So I also added one more field ('type') to distinguish between a
backend (0), frontend (1) and server (2) without complicated logic:
if name==BACKEND and sid==0 then type is BACKEND else type is SERVER,
etc for a FRONTEND. It also makes possible to have one frontend with more
than one IP (a patch coming soon) with independed stats - for example to
differs between remote and local traffic.

Finally, I added documentation about the CSV format.

This patch depends on '[MEDIUM] Implement "track [<backend>/]<server>"'
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 504f8a9..a2c97e3 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1308,6 +1308,11 @@
   See also : "option httpchk"
 
 
+id <value>
+  Set a persistent value for proxy ID. Must be unique and larger than 1000, as
+  smaller values are reserved for auto-assigned ids.
+
+
 log global
 log <address> <facility> [<level>]
   Enable per-instance logging of events and traffic.
@@ -3778,6 +3783,10 @@
   <count> consecutive unsuccessful health checks. This value defaults to 3 if
   unspecified. See also the "check", "inter" and "rise" parameters.
 
+id <value>
+  Set a persistent value for server ID. Must be unique and larger than 1000, as
+  smaller values are reserved for auto-assigned ids.
+
 inter <delay>
 fastinter <delay>
 downinter <delay>
@@ -4012,6 +4021,42 @@
 
 [to do]
 
+2.7) CSV format
+
+  0. pxname: proxy name
+  1. svname: service name (FRONTEND for frontend, BACKEND for backend, any name
+    for server)
+  2. qcur: current queued requests
+  3. qmax: max queued requests
+  4. scur: current sessions
+  5. smax: max sessions
+  6. slim: sessions limit
+  7. stot: total sessions
+  8. bin: bytes in
+  9. bout: bytes out
+ 10. dreq: denied requests
+ 11. dresp: denied responces
+ 12. ereq: request errors
+ 13. econ: connection errors
+ 14. eresp: responce errors
+ 15. wretr: retries (warning)
+ 16. wredis: redispatches (warning)
+ 17. status: status (UP/DOWN/...)
+ 18. weight: server weight (server), total weight (backend)
+ 19. act: server is active (server), number of active servers (backend)
+ 20. bck: server is backup (server), number of backup servers (backend)
+ 21. chkfail: number of failed checks
+ 22. chkdown: number of UP->DOWN transitions
+ 23. lastchg: last status change (in seconds)
+ 24. downtime: total downtime (in seconds)
+ 25. qlimit: queue limit
+ 26. pid: process id (0 for first instance, 1 for second, ...)
+ 27. iid: unique proxy id
+ 28. sid: service id (unique inside a proxy)
+ 29. throttle: warm up status
+ 30. lbtot: total number of times a server was selected
+ 31. tracked: id of proxy/server if tracking is enabled
+ 32. type (0=frontend, 1=backend, 2=server)
 
 /*
  * Local variables:
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 25d954a..0b1a0ba 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -800,6 +800,36 @@
 			return -1;
 		}
 	}
+	else if (!strcmp(args[0], "id")) {
+		struct proxy *target;
+
+		if (curproxy == &defproxy) {
+			Alert("parsing [%s:%d]: '%s' not allowed in 'defaults' section.\n",
+				 file, linenum, args[0]);
+			return -1;
+		}
+
+		if (!*args[1]) {
+			Alert("parsing [%s:%d]: '%s' expects an integer argument.\n",
+				file, linenum, args[0]);
+			return -1;
+		}
+
+		curproxy->uuid = atol(args[1]);
+
+		if (curproxy->uuid < 1001) {
+			Alert("parsing [%s:%d]: custom id has to be > 1000",
+				file, linenum);
+			return -1;
+		}
+
+		for (target = proxy; target; target = target->next)
+			if (curproxy != target && curproxy->uuid == target->uuid) {
+				Alert("parsing [%s:%d]: custom id has to be unique but is duplicated in %s and %s.\n",
+					file, linenum, curproxy->id, target->id);
+				return -1;
+			}
+	}
 	else if (!strcmp(args[0], "disabled")) {  /* disables this proxy */
 		curproxy->state = PR_STSTOPPED;
 	}
@@ -1530,7 +1560,32 @@
 
 		cur_arg = 3;
 		while (*args[cur_arg]) {
-			if (!strcmp(args[cur_arg], "cookie")) {
+			if (!strcmp(args[cur_arg], "id")) {
+				struct server *target;
+
+				if (!*args[cur_arg + 1]) {
+					Alert("parsing [%s:%d]: '%s' expects an integer argument.\n",
+						file, linenum, args[cur_arg]);
+					return -1;
+				}
+
+				newsrv->puid = atol(args[cur_arg + 1]);
+
+				if (newsrv->puid< 1001) {
+					Alert("parsing [%s:%d]: custom id has to be > 1000",
+						file, linenum);
+					return -1;
+				}
+
+				for (target = proxy->srv; target; target = target->next)
+					if (newsrv != target && newsrv->puid == target->puid) {
+						Alert("parsing [%s:%d]: custom id has to be unique but is duplicated in %s and %s.\n",
+							file, linenum, newsrv->id, target->id);
+						return -1;
+					}
+				cur_arg += 2;
+			}
+			else if (!strcmp(args[cur_arg], "cookie")) {
 				newsrv->cookie = strdup(args[cur_arg + 1]);
 				newsrv->cklen = strlen(args[cur_arg + 1]);
 				cur_arg += 2;
@@ -1696,7 +1751,7 @@
 				return -1;
 			}
 			else {
-				Alert("parsing [%s:%d] : server %s only supports options 'backup', 'cookie', 'redir', 'check', 'track', 'inter', 'fastinter', 'downinter', 'rise', 'fall', 'addr', 'port', 'source', 'minconn', 'maxconn', 'maxqueue', 'slowstart' and 'weight'.\n",
+				Alert("parsing [%s:%d] : server %s only supports options 'backup', 'cookie', 'redir', 'check', 'track', 'id', 'inter', 'fastinter', 'downinter', 'rise', 'fall', 'addr', 'port', 'source', 'minconn', 'maxconn', 'maxqueue', 'slowstart' and 'weight'.\n",
 				      file, linenum, newsrv->id);
 				return -1;
 			}
diff --git a/src/dumpstats.c b/src/dumpstats.c
index 71e382a..c132246 100644
--- a/src/dumpstats.c
+++ b/src/dumpstats.c
@@ -171,7 +171,7 @@
 			    "wretr,wredis,"
 			    "status,weight,act,bck,"
 			    "chkfail,chkdown,lastchg,downtime,qlimit,"
-			    "pid,iid,sid,throttle,lbtot,tracked,"
+			    "pid,iid,sid,throttle,lbtot,tracked,type,"
 			    "\n");
 }
 
@@ -706,8 +706,8 @@
 				     "%s,"
 				     /* rest of server: nothing */
 				     ",,,,,,,,"
-				     /* pid, iid, sid, throttle, lbtot, tracked*/
-				     "%d,%d,0,,,,"
+				     /* pid, iid, sid, throttle, lbtot, tracked, type (0=server)*/
+				     "%d,%d,0,,,,0,"
 				     "\n",
 				     px->id,
 				     px->feconn, px->feconn_max, px->maxconn, px->cum_feconn,
@@ -911,21 +911,21 @@
 				    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)
-					chunk_printf(&msg, sizeof(trash), ",%s/%s",
+					chunk_printf(&msg, sizeof(trash), "%s/%s,",
 						sv->tracked->proxy->id, sv->tracked->id);
 				else
 					chunk_printf(&msg, sizeof(trash), ",");
 
-				/* ',' then EOL */
-				chunk_printf(&msg, sizeof(trash), ",\n");
+				/* type (2=server), then EOL */
+				chunk_printf(&msg, sizeof(trash), "2,\n");
 			}
 			if (buffer_write_chunk(rep, &msg) != 0)
 				return 0;
@@ -1007,8 +1007,8 @@
 				     "%d,%d,%d,"
 				     /* rest of backend: nothing, down transitions, last change, total downtime */
 				     ",%d,%d,%d,,"
-				     /* pid, iid, sid, throttle, lbtot, tracked,*/
-				     "%d,%d,0,,%d,,"
+				     /* pid, iid, sid, throttle, lbtot, tracked, type (1=backend) */
+				     "%d,%d,0,,%d,,1,"
 				     "\n",
 				     px->id,
 				     px->nbpend /* or px->totpend ? */, px->nbpend_max,