xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_stats.c (revision 4f364e7c95ee7fd9d5bbeddc1940e92405bb0e72)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  * Copyright 2012 Milan Jurik. All rights reserved.
26  */
27 
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <strings.h>
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <sys/sysmacros.h>
35 #include <sys/note.h>
36 #include <fcntl.h>
37 #include <errno.h>
38 #include <assert.h>
39 #include <libgen.h>
40 #include <kstat.h>
41 #include <ofmt.h>
42 #include <libilb.h>
43 #include "ilbadm.h"
44 
45 #define	ILBST_TIMESTAMP_HEADER	0x01	/* a timestamp w. every header */
46 #define	ILBST_DELTA_INTERVAL	0x02	/* delta over specified interval */
47 #define	ILBST_ABS_NUMBERS	0x04	/* print absolute numbers, no d's */
48 #define	ILBST_ITEMIZE		0x08	/* itemize */
49 #define	ILBST_VERBOSE		0x10	/* verbose error info */
50 
51 #define	ILBST_OLD_VALUES	0x20	/* for internal processing */
52 #define	ILBST_RULES_CHANGED	0x40
53 
54 typedef struct {
55 	char		is_name[KSTAT_STRLEN];
56 	uint64_t	is_value;
57 } ilbst_stat_t;
58 
59 static ilbst_stat_t rulestats[] = {
60 	{"num_servers", 0},
61 	{"bytes_not_processed", 0},
62 	{"pkt_not_processed", 0},
63 	{"bytes_dropped", 0},
64 	{"pkt_dropped", 0},
65 	{"nomem_bytes_dropped", 0},
66 	{"nomem_pkt_dropped", 0},
67 	{"noport_bytes_dropped", 0},
68 	{"noport_pkt_dropped", 0},
69 	{"icmp_echo_processed", 0},
70 	{"icmp_dropped", 0},
71 	{"icmp_too_big_processed", 0},
72 	{"icmp_too_big_dropped", 0}
73 };
74 
75 /* indices into array above, to avoid searching */
76 #define	RLSTA_NUM_SRV		0
77 #define	RLSTA_BYTES_U		1
78 #define	RLSTA_PKT_U		2
79 #define	RLSTA_BYTES_D		3
80 #define	RLSTA_PKT_D		4
81 #define	RLSTA_NOMEMBYTES_D	5
82 #define	RLSTA_NOMEMPKT_D	6
83 #define	RLSTA_NOPORTBYTES_D	7
84 #define	RLSTA_NOPORTPKT_D	8
85 #define	RLSTA_ICMP_P		9
86 #define	RLSTA_ICMP_D		10
87 #define	RLSTA_ICMP2BIG_P	11
88 #define	RLSTA_ICMP2BIG_D	12
89 
90 static ilbst_stat_t servstats[] = {
91 	{"bytes_processed", 0},
92 	{"pkt_processed", 0}
93 };
94 /* indices into array above, to avoid searching */
95 #define	SRVST_BYTES_P	0
96 #define	SRVST_PKT_P	1
97 
98 /* values used for of_* commands as id */
99 #define	ILBST_PKT_P		0
100 #define	ILBST_BYTES_P		1
101 #define	ILBST_PKT_U		2
102 #define	ILBST_BYTES_U		3
103 #define	ILBST_PKT_D		4
104 #define	ILBST_BYTES_D		5
105 #define	ILBST_ICMP_P		6
106 #define	ILBST_ICMP_D		7
107 #define	ILBST_ICMP2BIG_P	8
108 #define	ILBST_ICMP2BIG_D	9
109 #define	ILBST_NOMEMP_D		10
110 #define	ILBST_NOPORTP_D		11
111 #define	ILBST_NOMEMB_D		12
112 #define	ILBST_NOPORTB_D		13
113 
114 #define	ILBST_ITEMIZE_SNAME	97
115 #define	ILBST_ITEMIZE_RNAME	98
116 #define	ILBST_TIMESTAMP		99
117 
118 /* approx field widths */
119 #define	ILBST_PKTCTR_W		8
120 #define	ILBST_BYTECTR_W		10
121 #define	ILBST_TIME_W		15
122 
123 static boolean_t of_rule_stats(ofmt_arg_t *, char *, uint_t);
124 static boolean_t of_server_stats(ofmt_arg_t *, char *, uint_t);
125 static boolean_t of_itemize_stats(ofmt_arg_t *, char *, uint_t);
126 static boolean_t of_timestamp(ofmt_arg_t *, char *, uint_t);
127 
128 static ofmt_field_t stat_itemize_fields[] = {
129 	{"RULENAME", ILB_NAMESZ,	ILBST_ITEMIZE_RNAME, of_itemize_stats},
130 	{"SERVERNAME", ILB_NAMESZ,	ILBST_ITEMIZE_SNAME, of_itemize_stats},
131 	{"PKT_P",   ILBST_PKTCTR_W,	ILBST_PKT_P, of_itemize_stats},
132 	{"BYTES_P", ILBST_BYTECTR_W,	ILBST_BYTES_P, of_itemize_stats},
133 	{"TIME",    ILBST_TIME_W,	ILBST_TIMESTAMP, of_timestamp},
134 	{NULL,	    0, 0, NULL}
135 };
136 static ofmt_field_t stat_stdfields[] = {
137 	{"PKT_P",   ILBST_PKTCTR_W,	ILBST_PKT_P, of_server_stats},
138 	{"BYTES_P", ILBST_BYTECTR_W,	ILBST_BYTES_P, of_server_stats},
139 	{"PKT_U",   ILBST_PKTCTR_W,	ILBST_PKT_U, of_rule_stats},
140 	{"BYTES_U", ILBST_BYTECTR_W,	ILBST_BYTES_U, of_rule_stats},
141 	{"PKT_D",   ILBST_PKTCTR_W,	ILBST_PKT_D, of_rule_stats},
142 	{"BYTES_D", ILBST_BYTECTR_W,	ILBST_BYTES_D, of_rule_stats},
143 	{"ICMP_P",  ILBST_PKTCTR_W,	ILBST_ICMP_P, of_rule_stats},
144 	{"ICMP_D",  ILBST_PKTCTR_W,	ILBST_ICMP_D, of_rule_stats},
145 	{"ICMP2BIG_P", 11,		ILBST_ICMP2BIG_P, of_rule_stats},
146 	{"ICMP2BIG_D", 11,		ILBST_ICMP2BIG_D, of_rule_stats},
147 	{"NOMEMP_D", ILBST_PKTCTR_W,	ILBST_NOMEMP_D, of_rule_stats},
148 	{"NOPORTP_D", ILBST_PKTCTR_W,	ILBST_NOPORTP_D, of_rule_stats},
149 	{"NOMEMB_D", ILBST_PKTCTR_W,	ILBST_NOMEMB_D, of_rule_stats},
150 	{"NOPORTB_D", ILBST_PKTCTR_W,	ILBST_NOPORTB_D, of_rule_stats},
151 	{"TIME",    ILBST_TIME_W,	ILBST_TIMESTAMP, of_timestamp},
152 	{NULL,	    0, 0, NULL}
153 };
154 
155 static char stat_stdhdrs[] = "PKT_P,BYTES_P,PKT_U,BYTES_U,PKT_D,BYTES_D";
156 static char stat_stdv_hdrs[] = "PKT_P,BYTES_P,PKT_U,BYTES_U,PKT_D,BYTES_D,"
157 	"ICMP_P,ICMP_D,ICMP2BIG_P,ICMP2BIG_D,NOMEMP_D,NOPORTP_D";
158 static char stat_itemize_rule_hdrs[] = "SERVERNAME,PKT_P,BYTES_P";
159 static char stat_itemize_server_hdrs[] = "RULENAME,PKT_P,BYTES_P";
160 
161 #define	RSTAT_SZ	(sizeof (rulestats)/sizeof (rulestats[0]))
162 #define	SSTAT_SZ	(sizeof (servstats)/sizeof (servstats[0]))
163 
164 typedef struct {
165 	char		isd_servername[KSTAT_STRLEN]; /* serverID */
166 	ilbst_stat_t	isd_serverstats[SSTAT_SZ];
167 	hrtime_t	isd_crtime;	/* save for comparison purpose */
168 } ilbst_srv_desc_t;
169 
170 /*
171  * this data structure stores statistics for a rule - both an old set
172  * and a current/new set. we use pointers to the actual stores and switch
173  * the pointers for every round. old_is_old in ilbst_arg_t indicates
174  * which pointer points to the "old" data struct (ie, if true, _o pointer
175  * points to old)
176  */
177 typedef struct {
178 	char			ird_rulename[KSTAT_STRLEN];
179 	int			ird_num_servers;
180 	int			ird_num_servers_o;
181 	int			ird_srv_ind;
182 	hrtime_t		ird_crtime;	/* save for comparison */
183 	hrtime_t		ird_crtime_o;	/* save for comparison */
184 	ilbst_srv_desc_t	*ird_srvlist;
185 	ilbst_srv_desc_t	*ird_srvlist_o;
186 	ilbst_stat_t		ird_rstats[RSTAT_SZ];
187 	ilbst_stat_t		ird_rstats_o[RSTAT_SZ];
188 	ilbst_stat_t		*ird_rulestats;
189 	ilbst_stat_t		*ird_rulestats_o;
190 } ilbst_rule_desc_t;
191 
192 /*
193  * overall "container" for information pertaining to statistics, and
194  * how to display them.
195  */
196 typedef struct {
197 	int			ilbst_flags;
198 	/* fields representing user input */
199 	char			*ilbst_rulename;	/* optional */
200 	char 			*ilbst_server;	/* optional */
201 	int			ilbst_interval;
202 	int			ilbst_count;
203 	/* "internal" fields for data and data presentation */
204 	ofmt_handle_t		ilbst_oh;
205 	boolean_t		ilbst_old_is_old;
206 	ilbst_rule_desc_t	*ilbst_rlist;
207 	int			ilbst_rcount;	  /* current list count */
208 	int			ilbst_rcount_prev; /* prev (different) count */
209 	int			ilbst_rlist_sz; /* number of alloc'ed rules */
210 	int			ilbst_rule_index; /* for itemizes display */
211 } ilbst_arg_t;
212 
213 /* ARGSUSED */
214 static boolean_t
215 of_timestamp(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
216 {
217 	time_t		now;
218 	struct tm	*now_tm;
219 
220 	now = time(NULL);
221 	now_tm = localtime(&now);
222 
223 	(void) strftime(buf, bufsize, "%F:%H.%M.%S", now_tm);
224 	return (B_TRUE);
225 }
226 
227 static boolean_t
228 i_sum_per_rule_processed(ilbst_rule_desc_t *rp, uint64_t *resp, int index,
229     int flags)
230 {
231 	int			i, num_servers;
232 	ilbst_srv_desc_t	*srv, *o_srv, *n_srv;
233 	uint64_t		res = 0;
234 	boolean_t		valid = B_TRUE;
235 	boolean_t		old = flags & ILBST_OLD_VALUES;
236 	boolean_t		check_valid;
237 
238 	/* if we do abs. numbers, we never look at the _o fields */
239 	assert((old && (flags & ILBST_ABS_NUMBERS)) == B_FALSE);
240 
241 	/* we only check for validity under certain conditions */
242 	check_valid = !(old || (flags & ILBST_ABS_NUMBERS));
243 
244 	if (check_valid && rp->ird_num_servers != rp->ird_num_servers_o)
245 		valid = B_FALSE;
246 
247 	num_servers = old ? rp->ird_num_servers_o : rp->ird_num_servers;
248 
249 	for (i = 0; i < num_servers; i++) {
250 		n_srv = &rp->ird_srvlist[i];
251 		o_srv = &rp->ird_srvlist_o[i];
252 
253 		if (old)
254 			srv = o_srv;
255 		else
256 			srv = n_srv;
257 
258 		res += srv->isd_serverstats[index].is_value;
259 		/*
260 		 * if creation times don't match, comparison is wrong; if
261 		 * if we already know something is invalid, we don't
262 		 * need to compare again.
263 		 */
264 		if (check_valid && valid == B_TRUE &&
265 		    o_srv->isd_crtime != n_srv->isd_crtime) {
266 			valid = B_FALSE;
267 			break;
268 		}
269 	}
270 	/*
271 	 * save the result even though it may be imprecise  - let the
272 	 * caller decide what to do
273 	 */
274 	*resp = res;
275 
276 	return (valid);
277 }
278 
279 typedef boolean_t (*sumfunc_t)(ilbst_rule_desc_t *, uint64_t *, int);
280 
281 static boolean_t
282 i_sum_per_rule_pkt_p(ilbst_rule_desc_t *rp, uint64_t *resp, int flags)
283 {
284 	return (i_sum_per_rule_processed(rp, resp, SRVST_PKT_P, flags));
285 }
286 
287 static boolean_t
288 i_sum_per_rule_bytes_p(ilbst_rule_desc_t *rp, uint64_t *resp, int flags)
289 {
290 	return (i_sum_per_rule_processed(rp, resp, SRVST_BYTES_P, flags));
291 }
292 
293 static boolean_t
294 of_server_stats(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
295 {
296 	ilbst_arg_t	*sta = (ilbst_arg_t *)of_arg->ofmt_cbarg;
297 	uint64_t	count = 0, val;
298 	int		i;
299 	boolean_t	valid = B_TRUE;
300 	sumfunc_t	sumfunc;
301 
302 	switch (of_arg->ofmt_id) {
303 	case ILBST_PKT_P: sumfunc = i_sum_per_rule_pkt_p;
304 		break;
305 	case ILBST_BYTES_P: sumfunc = i_sum_per_rule_bytes_p;
306 		break;
307 	}
308 
309 	for (i = 0; i < sta->ilbst_rcount; i++) {
310 		valid = sumfunc(&sta->ilbst_rlist[i], &val, sta->ilbst_flags);
311 		if (!valid)
312 			return (valid);
313 		count += val;
314 	}
315 
316 	if ((sta->ilbst_flags & ILBST_ABS_NUMBERS) != 0)
317 		goto out;
318 
319 	for (i = 0; i < sta->ilbst_rcount; i++) {
320 		(void) sumfunc(&sta->ilbst_rlist[i], &val,
321 		    sta->ilbst_flags | ILBST_OLD_VALUES);
322 		count -= val;
323 	}
324 
325 out:
326 	/*
327 	 * normally, we print "change per second", which we calculate
328 	 * here. otherwise, we print "change over interval"
329 	 */
330 	if ((sta->ilbst_flags & (ILBST_DELTA_INTERVAL|ILBST_ABS_NUMBERS)) == 0)
331 		count /= sta->ilbst_interval;
332 
333 	(void) snprintf(buf, bufsize, "%llu", count);
334 	return (B_TRUE);
335 }
336 
337 /*
338  * this function is called when user wants itemized stats of every
339  * server for a named rule, or vice vera.
340  * i_do_print sets sta->rule_index and the proper ird_srv_ind so
341  * we don't have to differentiate between these two cases here.
342  */
343 static boolean_t
344 of_itemize_stats(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
345 {
346 	ilbst_arg_t	*sta = (ilbst_arg_t *)of_arg->ofmt_cbarg;
347 	int		stat_ind;
348 	uint64_t	count;
349 	int		rule_index = sta->ilbst_rule_index;
350 	int		srv_ind = sta->ilbst_rlist[rule_index].ird_srv_ind;
351 	boolean_t	ret = B_TRUE;
352 	ilbst_srv_desc_t *srv, *osrv;
353 
354 	srv = &sta->ilbst_rlist[rule_index].ird_srvlist[srv_ind];
355 
356 	switch (of_arg->ofmt_id) {
357 	case ILBST_PKT_P: stat_ind = SRVST_PKT_P;
358 		break;
359 	case ILBST_BYTES_P: stat_ind = SRVST_BYTES_P;
360 		break;
361 	case ILBST_ITEMIZE_RNAME:
362 		(void) snprintf(buf, bufsize, "%s",
363 		    sta->ilbst_rlist[rule_index].ird_rulename);
364 		return (B_TRUE);
365 	case ILBST_ITEMIZE_SNAME:
366 		(void) snprintf(buf, bufsize, "%s", srv->isd_servername);
367 		return (B_TRUE);
368 	}
369 
370 	count = srv->isd_serverstats[stat_ind].is_value;
371 
372 	if ((sta->ilbst_flags & ILBST_ABS_NUMBERS) != 0)
373 		goto out;
374 
375 	osrv = &sta->ilbst_rlist[rule_index].ird_srvlist_o[srv_ind];
376 	if (srv->isd_crtime != osrv->isd_crtime)
377 		ret = B_FALSE;
378 
379 	count -= osrv->isd_serverstats[stat_ind].is_value;
380 out:
381 	/*
382 	 * normally, we print "change per second", which we calculate
383 	 * here. otherwise, we print "change over interval" or absolute
384 	 * values.
385 	 */
386 	if ((sta->ilbst_flags & (ILBST_DELTA_INTERVAL|ILBST_ABS_NUMBERS)) == 0)
387 		count /= sta->ilbst_interval;
388 
389 	(void) snprintf(buf, bufsize, "%llu", count);
390 	return (ret);
391 
392 }
393 
394 static boolean_t
395 of_rule_stats(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
396 {
397 	ilbst_arg_t	*sta = (ilbst_arg_t *)of_arg->ofmt_cbarg;
398 	int		i, ind;
399 	uint64_t	count = 0;
400 
401 	switch (of_arg->ofmt_id) {
402 	case ILBST_PKT_U: ind = RLSTA_PKT_U;
403 		break;
404 	case ILBST_BYTES_U: ind = RLSTA_BYTES_U;
405 		break;
406 	case ILBST_PKT_D: ind = RLSTA_PKT_D;
407 		break;
408 	case ILBST_BYTES_D: ind = RLSTA_BYTES_D;
409 		break;
410 	case ILBST_ICMP_P: ind = RLSTA_ICMP_P;
411 		break;
412 	case ILBST_ICMP_D: ind = RLSTA_ICMP_D;
413 		break;
414 	case ILBST_ICMP2BIG_P: ind = RLSTA_ICMP2BIG_P;
415 		break;
416 	case ILBST_ICMP2BIG_D: ind = RLSTA_ICMP2BIG_D;
417 		break;
418 	case ILBST_NOMEMP_D: ind  = RLSTA_NOMEMPKT_D;
419 		break;
420 	case ILBST_NOPORTP_D: ind = RLSTA_NOPORTPKT_D;
421 		break;
422 	case ILBST_NOMEMB_D: ind = RLSTA_NOMEMBYTES_D;
423 		break;
424 	case ILBST_NOPORTB_D: ind = RLSTA_NOPORTBYTES_D;
425 		break;
426 	}
427 
428 	for (i = 0; i < sta->ilbst_rcount; i++)
429 		count += sta->ilbst_rlist[i].ird_rulestats[ind].is_value;
430 
431 	if ((sta->ilbst_flags & ILBST_ABS_NUMBERS) != 0)
432 		goto out;
433 
434 	/*
435 	 * the purist approach: if we can't say 100% that what we
436 	 * calculate is correct, don't.
437 	 */
438 	if (sta->ilbst_flags & ILBST_RULES_CHANGED)
439 		return (B_FALSE);
440 
441 	for (i = 0; i < sta->ilbst_rcount; i++) {
442 		if (sta->ilbst_rlist[i].ird_crtime_o != 0 &&
443 		    sta->ilbst_rlist[i].ird_crtime !=
444 		    sta->ilbst_rlist[i].ird_crtime_o)
445 			return (B_FALSE);
446 
447 		count -= sta->ilbst_rlist[i].ird_rulestats_o[ind].is_value;
448 	}
449 out:
450 	/*
451 	 * normally, we print "change per second", which we calculate
452 	 * here. otherwise, we print "change over interval"
453 	 */
454 	if ((sta->ilbst_flags & (ILBST_DELTA_INTERVAL|ILBST_ABS_NUMBERS)) == 0)
455 		count /= sta->ilbst_interval;
456 
457 	(void) snprintf(buf, bufsize, "%llu", count);
458 	return (B_TRUE);
459 }
460 
461 /*
462  * Get the number of kstat instances. Note that when rules are being
463  * drained the number of kstats instances may be different than the
464  * kstat counter num_rules (ilb:0:global:num_rules").
465  *
466  * Also there can be multiple instances of a rule in the following
467  * scenario:
468  *
469  * A rule named rule A has been deleted but remains in kstats because
470  * its undergoing connection draining. During this time, the user adds
471  * a new rule with the same name(rule A). In this case, there would
472  * be two kstats instances for rule A. Currently ilbadm's aggregate
473  * results will include data from both instances of rule A. In,
474  * future we should have ilbadm stats only consider the latest instance
475  * of the rule (ie only consider the the instance that corresponds
476  * to the rule that was just added).
477  *
478  */
479 static int
480 i_get_num_kinstances(kstat_ctl_t *kctl)
481 {
482 	kstat_t		*kp;
483 	int		num_instances = 0; /* nothing found, 0 rules */
484 
485 	for (kp = kctl->kc_chain; kp != NULL; kp = kp->ks_next) {
486 		if (strncmp("rulestat", kp->ks_class, 8) == 0 &&
487 		    strncmp("ilb", kp->ks_module, 3) == 0) {
488 			num_instances++;
489 		}
490 	}
491 
492 	return (num_instances);
493 }
494 
495 
496 /*
497  * since server stat's classname is made up of <rulename>-sstat,
498  * we walk the rule list to construct the comparison
499  * Return:	pointer to rule whose name matches the class
500  *		NULL if no match
501  */
502 static ilbst_rule_desc_t *
503 match_2_rnames(char *class, ilbst_rule_desc_t *rlist, int rcount)
504 {
505 	int i;
506 	char	classname[KSTAT_STRLEN];
507 
508 	for (i = 0; i < rcount; i++) {
509 		(void) snprintf(classname, sizeof (classname), "%s-sstat",
510 		    rlist[i].ird_rulename);
511 		if (strncmp(classname, class, sizeof (classname)) == 0)
512 			return (&rlist[i]);
513 	}
514 	return (NULL);
515 }
516 
517 static int
518 i_stat_index(kstat_named_t *knp, ilbst_stat_t *stats, int count)
519 {
520 	int	i;
521 
522 	for (i = 0; i < count; i++) {
523 		if (strcasecmp(stats[i].is_name, knp->name) == 0)
524 			return (i);
525 	}
526 
527 	return (-1);
528 }
529 
530 static void
531 i_copy_sstats(ilbst_srv_desc_t *sp, kstat_t *kp)
532 {
533 	kstat_named_t	*knp;
534 	int		i, ind;
535 
536 	knp = KSTAT_NAMED_PTR(kp);
537 	for (i = 0; i < kp->ks_ndata; i++, knp++) {
538 		ind = i_stat_index(knp, servstats, SSTAT_SZ);
539 		if (ind == -1)
540 			continue;
541 		(void) strlcpy(sp->isd_serverstats[ind].is_name, knp->name,
542 		    sizeof (sp->isd_serverstats[ind].is_name));
543 		sp->isd_serverstats[ind].is_value = knp->value.ui64;
544 		sp->isd_crtime = kp->ks_crtime;
545 	}
546 }
547 
548 
549 static ilbadm_status_t
550 i_get_server_descs(ilbst_arg_t *sta, kstat_ctl_t *kctl)
551 {
552 	ilbadm_status_t	rc = ILBADM_OK;
553 	kstat_t		*kp;
554 	int		i = -1;
555 	ilbst_rule_desc_t	*rp;
556 	ilbst_rule_desc_t	*rlist = sta->ilbst_rlist;
557 	int			rcount = sta->ilbst_rcount;
558 
559 	/*
560 	 * find all "server" kstats, or the one specified in
561 	 * sta->server
562 	 */
563 	for (kp = kctl->kc_chain; kp != NULL; kp = kp->ks_next) {
564 		if (strncmp("ilb", kp->ks_module, 3) != 0)
565 			continue;
566 		if (sta->ilbst_server != NULL &&
567 		    strcasecmp(sta->ilbst_server, kp->ks_name) != 0)
568 			continue;
569 		rp = match_2_rnames(kp->ks_class, rlist, rcount);
570 		if (rp == NULL)
571 			continue;
572 
573 		(void) kstat_read(kctl, kp, NULL);
574 		i = rp->ird_srv_ind++;
575 
576 		rc = ILBADM_OK;
577 		/*
578 		 * This means that a server is added after we check last
579 		 * time...  Just make the array bigger.
580 		 */
581 		if (i+1 > rp->ird_num_servers) {
582 			ilbst_srv_desc_t  *srvlist;
583 
584 			if ((srvlist = realloc(rp->ird_srvlist, (i+1) *
585 			    sizeof (*srvlist))) == NULL) {
586 				rc = ILBADM_ENOMEM;
587 				break;
588 			}
589 			rp->ird_srvlist = srvlist;
590 			rp->ird_num_servers = i;
591 		}
592 
593 		(void) strlcpy(rp->ird_srvlist[i].isd_servername, kp->ks_name,
594 		    sizeof (rp->ird_srvlist[i].isd_servername));
595 		i_copy_sstats(&rp->ird_srvlist[i], kp);
596 	}
597 
598 	for (i = 0; i < rcount; i++)
599 		rlist[i].ird_srv_ind = 0;
600 
601 	if (sta->ilbst_server != NULL && i == -1)
602 		rc = ILBADM_ENOSERVER;
603 	return (rc);
604 }
605 
606 static void
607 i_copy_rstats(ilbst_rule_desc_t *rp, kstat_t *kp)
608 {
609 	kstat_named_t	*knp;
610 	int		i, ind;
611 
612 	knp = KSTAT_NAMED_PTR(kp);
613 	for (i = 0; i < kp->ks_ndata; i++, knp++) {
614 		ind = i_stat_index(knp, rulestats, RSTAT_SZ);
615 		if (ind == -1)
616 			continue;
617 
618 		(void) strlcpy(rp->ird_rulestats[ind].is_name, knp->name,
619 		    sizeof (rp->ird_rulestats[ind].is_name));
620 		rp->ird_rulestats[ind].is_value = knp->value.ui64;
621 	}
622 }
623 
624 static void
625 i_set_rlstats_ptr(ilbst_rule_desc_t *rp, boolean_t old_is_old)
626 {
627 	if (old_is_old) {
628 		rp->ird_rulestats = rp->ird_rstats;
629 		rp->ird_rulestats_o = rp->ird_rstats_o;
630 	} else {
631 		rp->ird_rulestats = rp->ird_rstats_o;
632 		rp->ird_rulestats_o = rp->ird_rstats;
633 	}
634 }
635 /*
636  * this function walks the array of rules and switches pointer to old
637  * and new stats as well as serverlists.
638  */
639 static void
640 i_swap_rl_pointers(ilbst_arg_t *sta, int rcount)
641 {
642 	int			i, tmp_num;
643 	ilbst_rule_desc_t	*rlist = sta->ilbst_rlist;
644 	ilbst_srv_desc_t	*tmp_srv;
645 
646 	for (i = 0; i < rcount; i++) {
647 		/* swap srvlist pointers */
648 		tmp_srv = rlist[i].ird_srvlist;
649 		rlist[i].ird_srvlist = rlist[i].ird_srvlist_o;
650 		rlist[i].ird_srvlist_o = tmp_srv;
651 
652 		/*
653 		 * swap server counts - we need the old one to
654 		 * save reallocation calls
655 		 */
656 		tmp_num = rlist[i].ird_num_servers_o;
657 		rlist[i].ird_num_servers_o = rlist[i].ird_num_servers;
658 		rlist[i].ird_num_servers = tmp_num;
659 
660 		/* preserve creation time */
661 		rlist[i].ird_crtime_o = rlist[i].ird_crtime;
662 
663 		i_set_rlstats_ptr(&rlist[i], sta->ilbst_old_is_old);
664 		rlist[i].ird_srv_ind = 0;
665 	}
666 }
667 
668 static void
669 i_init_rulelist(ilbst_arg_t *sta, int rcount)
670 {
671 	int			 i;
672 	ilbst_rule_desc_t	*rlist = sta->ilbst_rlist;
673 
674 	for (i = 0; i < rcount; i++) {
675 		rlist[i].ird_rulestats = rlist[i].ird_rstats;
676 		rlist[i].ird_rulestats_o = rlist[i].ird_rstats_o;
677 		rlist[i].ird_srv_ind = 0;
678 	}
679 }
680 
681 
682 /*
683  * this function searches for kstats describing individual rules and
684  * saves name, # of servers, and the kstat_t * describing them (this is
685  * for sta->rulename == NULL);
686  * if sta->rulename != NULL, it names the rule we're looking for
687  * and this function will fill in the other data (like the all_rules case)
688  * Returns:	ILBADM_ENORULE	named rule not found
689  *		ILBADM_ENOMEM	no mem. available
690  */
691 static ilbadm_status_t
692 i_get_rule_descs(ilbst_arg_t *sta, kstat_ctl_t *kctl)
693 {
694 	ilbadm_status_t	rc = ILBADM_OK;
695 	kstat_t		*kp;
696 	kstat_named_t	*knp;
697 	int		i;
698 	int		num_servers;
699 	ilbst_rule_desc_t	*rlist = sta->ilbst_rlist;
700 	int		rcount = sta->ilbst_rcount;
701 
702 	/*
703 	 * find all "rule" kstats, or the one specified in
704 	 * sta->ilbst_rulename.
705 	 */
706 	for (i = 0, kp = kctl->kc_chain; i < rcount && kp != NULL;
707 	    kp = kp->ks_next) {
708 		if (strncmp("rulestat", kp->ks_class, 8) != 0 ||
709 		    strncmp("ilb", kp->ks_module, 3) != 0)
710 			continue;
711 
712 		(void) kstat_read(kctl, kp, NULL);
713 
714 		knp = kstat_data_lookup(kp, "num_servers");
715 		if (knp == NULL) {
716 			ilbadm_err(gettext("kstat_data_lookup() failed: %s"),
717 			    strerror(errno));
718 			rc = ILBADM_LIBERR;
719 			break;
720 		}
721 		if (sta->ilbst_rulename != NULL) {
722 			if (strcasecmp(kp->ks_name, sta->ilbst_rulename)
723 			    != 0)
724 				continue;
725 		}
726 		(void) strlcpy(rlist[i].ird_rulename, kp->ks_name,
727 		    sizeof (rlist[i].ird_rulename));
728 
729 		/* only alloc the space we need, set counter here ... */
730 		if (sta->ilbst_server != NULL)
731 			num_servers = 1;
732 		else
733 			num_servers = (int)knp->value.ui64;
734 
735 		/* ... furthermore, only reallocate if necessary */
736 		if (num_servers != rlist[i].ird_num_servers) {
737 			ilbst_srv_desc_t  *srvlist;
738 
739 			rlist[i].ird_num_servers = num_servers;
740 
741 			if (rlist[i].ird_srvlist == NULL)
742 				srvlist = calloc(num_servers,
743 				    sizeof (*srvlist));
744 			else
745 				srvlist = realloc(rlist[i].ird_srvlist,
746 				    sizeof (*srvlist) * num_servers);
747 			if (srvlist == NULL) {
748 				rc = ILBADM_ENOMEM;
749 				break;
750 			}
751 			rlist[i].ird_srvlist = srvlist;
752 		}
753 		rlist[i].ird_srv_ind = 0;
754 		rlist[i].ird_crtime = kp->ks_crtime;
755 
756 		i_copy_rstats(&rlist[i], kp);
757 		i++;
758 
759 		/* if we know we're done, return */
760 		if (sta->ilbst_rulename != NULL || i == rcount) {
761 			rc = ILBADM_OK;
762 			break;
763 		}
764 	}
765 
766 	if (sta->ilbst_rulename != NULL && i == 0)
767 		rc = ILBADM_ENORULE;
768 	return (rc);
769 }
770 
771 static void
772 i_do_print(ilbst_arg_t *sta)
773 {
774 	int	i;
775 
776 	/* non-itemized display can go right ahead */
777 	if ((sta->ilbst_flags & ILBST_ITEMIZE) == 0) {
778 		ofmt_print(sta->ilbst_oh, sta);
779 		return;
780 	}
781 
782 	/*
783 	 * rulename is given, list a line per server
784 	 * here's how we do it:
785 	 *	the _ITEMIZE flag indicates to the print function (called
786 	 *	from ofmt_print()) to look at server [ird_srv_ind] only.
787 	 */
788 	if (sta->ilbst_rulename != NULL) {
789 		sta->ilbst_rule_index = 0;
790 		for (i = 0; i < sta->ilbst_rlist->ird_num_servers; i++) {
791 			sta->ilbst_rlist->ird_srv_ind = i;
792 			ofmt_print(sta->ilbst_oh, sta);
793 		}
794 		sta->ilbst_rlist->ird_srv_ind = 0;
795 		return;
796 	}
797 
798 	/* list one line for every rule for a given server */
799 	for (i = 0; i < sta->ilbst_rcount; i++) {
800 		/*
801 		 * if a rule doesn't contain a given server, there's no
802 		 * need to print it. Luckily, we can check that
803 		 * fairly easily
804 		 */
805 		if (sta->ilbst_rlist[i].ird_srvlist[0].isd_servername[0] ==
806 		    '\0')
807 			continue;
808 
809 		sta->ilbst_rule_index = i;
810 		sta->ilbst_rlist[i].ird_srv_ind = 0;
811 		ofmt_print(sta->ilbst_oh, sta);
812 	}
813 	sta->ilbst_rule_index = 0;
814 }
815 
816 static ilbadm_status_t
817 i_do_show_stats(ilbst_arg_t *sta)
818 {
819 	kstat_ctl_t	*kctl;
820 	kid_t		nkid;
821 	int		rcount = 1, i;
822 	ilbadm_status_t	rc = ILBADM_OK;
823 	ilbst_rule_desc_t	*rlist, *rp;
824 	boolean_t	pseudo_abs = B_FALSE; /* for first pass */
825 
826 	if ((kctl = kstat_open()) == NULL) {
827 		ilbadm_err(gettext("kstat_open() failed: %s"), strerror(errno));
828 		return (ILBADM_LIBERR);
829 	}
830 
831 
832 	if (sta->ilbst_rulename == NULL)
833 		rcount = i_get_num_kinstances(kctl);
834 
835 	rlist = calloc(sizeof (*rlist), rcount);
836 	if (rlist == NULL) {
837 		rc = ILBADM_ENOMEM;
838 		goto out;
839 	}
840 
841 	sta->ilbst_old_is_old = B_TRUE;
842 	sta->ilbst_rlist = rlist;
843 	sta->ilbst_rcount = sta->ilbst_rcount_prev = rcount;
844 	sta->ilbst_rlist_sz = rcount;
845 
846 	/*
847 	 * in the first pass, we always print absolute numbers. We
848 	 * need to remember whether we wanted abs. numbers for
849 	 * other samples as well
850 	 */
851 	if ((sta->ilbst_flags & ILBST_ABS_NUMBERS) == 0) {
852 		sta->ilbst_flags |= ILBST_ABS_NUMBERS;
853 		pseudo_abs = B_TRUE;
854 	}
855 
856 	i_init_rulelist(sta, rcount);
857 	do {
858 		rc = i_get_rule_descs(sta, kctl);
859 		if (rc != ILBADM_OK)
860 			goto out;
861 
862 		rc = i_get_server_descs(sta, kctl);
863 		if (rc != ILBADM_OK)
864 			goto out;
865 
866 		i_do_print(sta);
867 
868 		if (sta->ilbst_count == -1 || --(sta->ilbst_count) > 0)
869 			(void) sleep(sta->ilbst_interval);
870 		else
871 			break;
872 
873 		nkid = kstat_chain_update(kctl);
874 		sta->ilbst_flags &= ~ILBST_RULES_CHANGED;
875 		/*
876 		 * we only need to continue with most of the rest of this if
877 		 * the kstat chain id has changed
878 		 */
879 		if (nkid == 0)
880 			goto swap_old_new;
881 		if (nkid == -1) {
882 			ilbadm_err(gettext("kstat_chain_update() failed: %s"),
883 			    strerror(errno));
884 			rc = ILBADM_LIBERR;
885 			break;
886 		}
887 
888 		/*
889 		 * find out whether the number of rules has changed.
890 		 * if so, adjust rcount and _o; if number has increased,
891 		 * expand array to hold all rules.
892 		 * we only shrink if rlist_sz is larger than both rcount and
893 		 * rcount_prev;
894 		 */
895 		if (sta->ilbst_rulename == NULL)
896 			rcount = i_get_num_kinstances(kctl);
897 		if (rcount != sta->ilbst_rcount) {
898 			sta->ilbst_flags |= ILBST_RULES_CHANGED;
899 			sta->ilbst_rcount_prev = sta->ilbst_rcount;
900 			sta->ilbst_rcount = rcount;
901 
902 			if (rcount > sta->ilbst_rcount_prev) {
903 				rlist = realloc(sta->ilbst_rlist,
904 				    sizeof (*sta->ilbst_rlist) * rcount);
905 				if (rlist == NULL) {
906 					rc = ILBADM_ENOMEM;
907 					break;
908 				}
909 				sta->ilbst_rlist = rlist;
910 				/* realloc doesn't zero out memory */
911 				for (i = sta->ilbst_rcount_prev;
912 				    i < rcount; i++) {
913 					rp = &sta->ilbst_rlist[i];
914 					bzero(rp, sizeof (*rp));
915 					i_set_rlstats_ptr(rp,
916 					    sta->ilbst_old_is_old);
917 				}
918 				/*
919 				 * even if rlist_sz was > rcount, it's now
920 				 * shrunk to rcount
921 				 */
922 				sta->ilbst_rlist_sz = sta->ilbst_rcount;
923 			}
924 		}
925 
926 		/*
927 		 * we may need to shrink the allocated slots down to the
928 		 * actually required number - we need to make sure we
929 		 * don't delete old or new stats.
930 		 */
931 		if (sta->ilbst_rlist_sz > MAX(sta->ilbst_rcount,
932 		    sta->ilbst_rcount_prev)) {
933 			sta->ilbst_rlist_sz =
934 			    MAX(sta->ilbst_rcount, sta->ilbst_rcount_prev);
935 			rlist = realloc(sta->ilbst_rlist,
936 			    sizeof (*sta->ilbst_rlist) * sta->ilbst_rlist_sz);
937 			if (rlist == NULL) {
938 				rc = ILBADM_ENOMEM;
939 				break;
940 			}
941 			sta->ilbst_rlist = rlist;
942 		}
943 
944 		/*
945 		 * move pointers around so what used to point to "old"
946 		 * stats now points to new, and vice versa
947 		 * if we're printing absolute numbers, this rigmarole is
948 		 * not necessary.
949 		 */
950 swap_old_new:
951 		if (pseudo_abs)
952 			sta->ilbst_flags &= ~ILBST_ABS_NUMBERS;
953 
954 		if ((sta->ilbst_flags & ILBST_ABS_NUMBERS) == 0) {
955 			sta->ilbst_old_is_old = !sta->ilbst_old_is_old;
956 			i_swap_rl_pointers(sta, rcount);
957 		}
958 		_NOTE(CONSTCOND)
959 	} while (B_TRUE);
960 
961 out:
962 	(void) kstat_close(kctl);
963 	if ((rc != ILBADM_OK) && (rc != ILBADM_LIBERR))
964 		ilbadm_err(ilbadm_errstr(rc));
965 
966 	if (sta->ilbst_rlist != NULL)
967 		free(sta->ilbst_rlist);
968 
969 	return (rc);
970 }
971 
972 /*
973  * read ilb's kernel statistics and (periodically) display
974  * them.
975  */
976 /* ARGSUSED */
977 ilbadm_status_t
978 ilbadm_show_stats(int argc, char *argv[])
979 {
980 	ilbadm_status_t	rc;
981 	int		c;
982 	ilbst_arg_t	sta;
983 	int		oflags = 0;
984 	char		*fieldnames = stat_stdhdrs;
985 	ofmt_field_t	*fields = stat_stdfields;
986 	boolean_t	r_opt = B_FALSE, s_opt = B_FALSE, i_opt = B_FALSE;
987 	boolean_t	o_opt = B_FALSE, p_opt = B_FALSE, t_opt = B_FALSE;
988 	boolean_t	v_opt = B_FALSE, A_opt = B_FALSE, d_opt = B_FALSE;
989 	ofmt_status_t	oerr;
990 	ofmt_handle_t	oh = NULL;
991 
992 	bzero(&sta, sizeof (sta));
993 	sta.ilbst_interval = 1;
994 	sta.ilbst_count = 1;
995 
996 	while ((c = getopt(argc, argv, ":tdAr:s:ivo:p")) != -1) {
997 		switch ((char)c) {
998 		case 't': sta.ilbst_flags |= ILBST_TIMESTAMP_HEADER;
999 			t_opt = B_TRUE;
1000 			break;
1001 		case 'd': sta.ilbst_flags |= ILBST_DELTA_INTERVAL;
1002 			d_opt = B_TRUE;
1003 			break;
1004 		case 'A': sta.ilbst_flags |= ILBST_ABS_NUMBERS;
1005 			A_opt = B_TRUE;
1006 			break;
1007 		case 'r': sta.ilbst_rulename = optarg;
1008 			r_opt = B_TRUE;
1009 			break;
1010 		case 's': sta.ilbst_server = optarg;
1011 			s_opt = B_TRUE;
1012 			break;
1013 		case 'i': sta.ilbst_flags |= ILBST_ITEMIZE;
1014 			i_opt = B_TRUE;
1015 			break;
1016 		case 'o': fieldnames = optarg;
1017 			o_opt = B_TRUE;
1018 			break;
1019 		case 'p': oflags |= OFMT_PARSABLE;
1020 			p_opt = B_TRUE;
1021 			break;
1022 		case 'v': sta.ilbst_flags |= ILBST_VERBOSE;
1023 			v_opt = B_TRUE;
1024 			fieldnames = stat_stdv_hdrs;
1025 			break;
1026 		case ':': ilbadm_err(gettext("missing option-argument"
1027 			    " detected for %c"), (char)optopt);
1028 			exit(1);
1029 			/* not reached */
1030 			break;
1031 		case '?': /* fallthrough */
1032 		default:
1033 			unknown_opt(argv, optind-1);
1034 			/* not reached */
1035 			break;
1036 		}
1037 	}
1038 
1039 	if (s_opt && r_opt) {
1040 		ilbadm_err(gettext("options -s and -r are mutually exclusive"));
1041 		exit(1);
1042 	}
1043 
1044 	if (i_opt) {
1045 		if (!(s_opt || r_opt)) {
1046 			ilbadm_err(gettext("option -i requires"
1047 			    " either -r or -s"));
1048 			exit(1);
1049 		}
1050 		if (v_opt) {
1051 			ilbadm_err(gettext("option -i and -v are mutually"
1052 			    " exclusive"));
1053 			exit(1);
1054 		}
1055 		/* only use "std" headers if none are specified */
1056 		if (!o_opt)
1057 			if (r_opt)
1058 				fieldnames = stat_itemize_rule_hdrs;
1059 			else /* must be s_opt */
1060 				fieldnames = stat_itemize_server_hdrs;
1061 		fields = stat_itemize_fields;
1062 	}
1063 
1064 	if (p_opt) {
1065 		if (!o_opt) {
1066 			ilbadm_err(gettext("option -p requires -o"));
1067 			exit(1);
1068 		}
1069 		if (v_opt) {
1070 			ilbadm_err(gettext("option -o and -v are mutually"
1071 			    " exclusive"));
1072 			exit(1);
1073 		}
1074 		if (strcasecmp(fieldnames, "all") == 0) {
1075 			ilbadm_err(gettext("option -p requires"
1076 			    " explicit field names"));
1077 			exit(1);
1078 		}
1079 	}
1080 
1081 	if (t_opt) {
1082 		if (v_opt) {
1083 			fieldnames = "all";
1084 		} else {
1085 			int  len = strlen(fieldnames) + 6;
1086 			char *fnames;
1087 
1088 			fnames = malloc(len);
1089 			if (fnames == NULL) {
1090 				rc = ILBADM_ENOMEM;
1091 				return (rc);
1092 			}
1093 			(void) snprintf(fnames, len, "%s,TIME", fieldnames);
1094 			fieldnames = fnames;
1095 		}
1096 	}
1097 
1098 	if (A_opt && d_opt) {
1099 		ilbadm_err(gettext("options -d and -A are mutually exclusive"));
1100 		exit(1);
1101 	}
1102 
1103 	/* find and parse interval and count arguments if present */
1104 	if (optind < argc) {
1105 		sta.ilbst_interval = atoi(argv[optind]);
1106 		if (sta.ilbst_interval < 1) {
1107 			ilbadm_err(gettext("illegal interval spec %s"),
1108 			    argv[optind]);
1109 			exit(1);
1110 		}
1111 		sta.ilbst_count = -1;
1112 		if (++optind < argc) {
1113 			sta.ilbst_count = atoi(argv[optind]);
1114 			if (sta.ilbst_count < 1) {
1115 				ilbadm_err(gettext("illegal count spec %s"),
1116 				    argv[optind]);
1117 				exit(1);
1118 			}
1119 		}
1120 	}
1121 
1122 	oerr = ofmt_open(fieldnames, fields, oflags, 80, &oh);
1123 	if (oerr != OFMT_SUCCESS) {
1124 		char	e[80];
1125 
1126 		ilbadm_err(gettext("ofmt_open failed: %s"),
1127 		    ofmt_strerror(oh, oerr, e, sizeof (e)));
1128 		return (ILBADM_LIBERR);
1129 	}
1130 
1131 	sta.ilbst_oh = oh;
1132 
1133 	rc = i_do_show_stats(&sta);
1134 
1135 	ofmt_close(oh);
1136 	return (rc);
1137 }
1138