xref: /illumos-gate/usr/src/cmd/flowstat/flowstat.c (revision d0b12b660e0741581d18f1f3a7d5268e0a5f1806)
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  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 /*
27  * Copyright 2017 Joyent, Inc.
28  */
29 
30 #include <stdio.h>
31 #include <locale.h>
32 #include <stdarg.h>
33 #include <stdlib.h>
34 #include <fcntl.h>
35 #include <string.h>
36 #include <stropts.h>
37 #include <errno.h>
38 #include <strings.h>
39 #include <getopt.h>
40 #include <unistd.h>
41 #include <priv.h>
42 #include <netdb.h>
43 #include <libintl.h>
44 #include <libdlflow.h>
45 #include <libdllink.h>
46 #include <libdlstat.h>
47 #include <sys/types.h>
48 #include <sys/socket.h>
49 #include <netinet/in.h>
50 #include <arpa/inet.h>
51 #include <sys/ethernet.h>
52 #include <inet/ip.h>
53 #include <inet/ip6.h>
54 #include <stddef.h>
55 #include <ofmt.h>
56 
57 typedef struct flow_chain_s {
58 	char			fc_flowname[MAXFLOWNAMELEN];
59 	boolean_t		fc_visited;
60 	flow_stat_t		*fc_stat;
61 	struct flow_chain_s	*fc_next;
62 } flow_chain_t;
63 
64 typedef struct show_flow_state {
65 	flow_chain_t	*fs_flowchain;
66 	ofmt_handle_t	fs_ofmt;
67 	char		fs_unit;
68 	boolean_t	fs_parsable;
69 } show_flow_state_t;
70 
71 typedef struct show_history_state_s {
72 	boolean_t	us_plot;
73 	boolean_t	us_parsable;
74 	boolean_t	us_printheader;
75 	boolean_t	us_first;
76 	boolean_t	us_showall;
77 	ofmt_handle_t	us_ofmt;
78 } show_history_state_t;
79 
80 static void	do_show_history(int, char **);
81 
82 static int	query_flow_stats(dladm_handle_t, dladm_flow_attr_t *, void *);
83 static int	query_link_flow_stats(dladm_handle_t, datalink_id_t, void *);
84 
85 static void	die(const char *, ...);
86 static void	die_optdup(int);
87 static void	die_opterr(int, int, const char *);
88 static void	die_dlerr(dladm_status_t, const char *, ...);
89 static void	warn(const char *, ...);
90 
91 /* callback functions for printing output */
92 static ofmt_cb_t print_default_cb, print_flow_stats_cb;
93 
94 #define	NULL_OFMT		{NULL, 0, 0, NULL}
95 
96 /*
97  * structures for flowstat (printing live statistics)
98  */
99 typedef enum {
100 	FLOW_S_FLOW,
101 	FLOW_S_IPKTS,
102 	FLOW_S_RBYTES,
103 	FLOW_S_IERRORS,
104 	FLOW_S_OPKTS,
105 	FLOW_S_OBYTES,
106 	FLOW_S_OERRORS
107 } flow_s_field_index_t;
108 
109 static ofmt_field_t flow_s_fields[] = {
110 /* name,	field width,	index,		callback */
111 { "FLOW",	15,	FLOW_S_FLOW,	print_flow_stats_cb},
112 { "IPKTS",	8,	FLOW_S_IPKTS,	print_flow_stats_cb},
113 { "RBYTES",	8,	FLOW_S_RBYTES,	print_flow_stats_cb},
114 { "IERRS",	8,	FLOW_S_IERRORS,	print_flow_stats_cb},
115 { "OPKTS",	8,	FLOW_S_OPKTS,	print_flow_stats_cb},
116 { "OBYTES",	8,	FLOW_S_OBYTES,	print_flow_stats_cb},
117 { "OERRS",	8,	FLOW_S_OERRORS,	print_flow_stats_cb},
118 NULL_OFMT}
119 ;
120 
121 typedef struct flow_args_s {
122 	char		*flow_s_flow;
123 	flow_stat_t	*flow_s_stat;
124 	char		flow_s_unit;
125 	boolean_t	flow_s_parsable;
126 } flow_args_t;
127 
128 /*
129  * structures for 'flowstat -h'
130  */
131 typedef struct  history_fields_buf_s {
132 	char	history_flow[12];
133 	char	history_duration[10];
134 	char	history_ipackets[9];
135 	char	history_rbytes[10];
136 	char	history_opackets[9];
137 	char	history_obytes[10];
138 	char	history_bandwidth[14];
139 } history_fields_buf_t;
140 
141 static ofmt_field_t history_fields[] = {
142 /* name,	field width,	offset */
143 { "FLOW",	13,
144 	offsetof(history_fields_buf_t, history_flow), print_default_cb},
145 { "DURATION",	11,
146 	offsetof(history_fields_buf_t, history_duration), print_default_cb},
147 { "IPACKETS",	10,
148 	offsetof(history_fields_buf_t, history_ipackets), print_default_cb},
149 { "RBYTES",	11,
150 	offsetof(history_fields_buf_t, history_rbytes), print_default_cb},
151 { "OPACKETS",	10,
152 	offsetof(history_fields_buf_t, history_opackets), print_default_cb},
153 { "OBYTES",	11,
154 	offsetof(history_fields_buf_t, history_obytes), print_default_cb},
155 { "BANDWIDTH",	15,
156 	offsetof(history_fields_buf_t, history_bandwidth), print_default_cb},
157 NULL_OFMT}
158 ;
159 
160 typedef struct  history_l_fields_buf_s {
161 	char	history_l_flow[12];
162 	char	history_l_stime[13];
163 	char	history_l_etime[13];
164 	char	history_l_rbytes[8];
165 	char	history_l_obytes[8];
166 	char	history_l_bandwidth[14];
167 } history_l_fields_buf_t;
168 
169 static ofmt_field_t history_l_fields[] = {
170 /* name,	field width,	offset */
171 { "FLOW",	13,
172 	offsetof(history_l_fields_buf_t, history_l_flow), print_default_cb},
173 { "START",	14,
174 	offsetof(history_l_fields_buf_t, history_l_stime), print_default_cb},
175 { "END",	14,
176 	offsetof(history_l_fields_buf_t, history_l_etime), print_default_cb},
177 { "RBYTES",	9,
178 	offsetof(history_l_fields_buf_t, history_l_rbytes), print_default_cb},
179 { "OBYTES",	9,
180 	offsetof(history_l_fields_buf_t, history_l_obytes), print_default_cb},
181 { "BANDWIDTH",	15,
182 	offsetof(history_l_fields_buf_t, history_l_bandwidth),
183 	    print_default_cb},
184 NULL_OFMT}
185 ;
186 
187 static char *progname;
188 
189 /*
190  * Handle to libdladm.  Opened in main() before the sub-command
191  * specific function is called.
192  */
193 static dladm_handle_t handle = NULL;
194 
195 const char *usage_ermsg = "flowstat [-r | -t] [-i interval] "
196 	    "[-l link] [flow]\n"
197 	    "       flowstat [-A] [-i interval] [-p] [ -o field[,...]]\n"
198 	    "                [-u R|K|M|G|T|P] [-l link] [flow]\n"
199 	    "       flowstat -h [-a] [-d] [-F format]"
200 	    " [-s <DD/MM/YYYY,HH:MM:SS>]\n"
201 	    "                [-e <DD/MM/YYYY,HH:MM:SS>] -f <logfile> "
202 	    "[<flow>]";
203 
204 static void
205 usage(void)
206 {
207 	(void) fprintf(stderr, "%s\n", gettext(usage_ermsg));
208 
209 	/* close dladm handle if it was opened */
210 	if (handle != NULL)
211 		dladm_close(handle);
212 
213 	exit(1);
214 }
215 
216 boolean_t
217 flowstat_unit(char *oarg, char *unit)
218 {
219 	if ((strcmp(oarg, "R") == 0) || (strcmp(oarg, "K") == 0) ||
220 	    (strcmp(oarg, "M") == 0) || (strcmp(oarg, "G") == 0) ||
221 	    (strcmp(oarg, "T") == 0) || (strcmp(oarg, "P") == 0)) {
222 		*unit = oarg[0];
223 		return (B_TRUE);
224 	}
225 
226 	return (B_FALSE);
227 }
228 
229 void
230 map_to_units(char *buf, uint_t bufsize, double num, char unit,
231     boolean_t parsable)
232 {
233 	if (parsable) {
234 		(void) snprintf(buf, bufsize, "%.0lf", num);
235 		return;
236 	}
237 
238 	if (unit == '\0') {
239 		int index;
240 
241 		for (index = 0; (int)(num/1000) != 0; index++, num /= 1000)
242 			;
243 
244 		switch (index) {
245 			case 0:
246 				unit = '\0';
247 				break;
248 			case 1:
249 				unit = 'K';
250 				break;
251 			case 2:
252 				unit = 'M';
253 				break;
254 			case 3:
255 				unit = 'G';
256 				break;
257 			case 4:
258 				unit = 'T';
259 				break;
260 			case 5:
261 				/* Largest unit supported */
262 			default:
263 				unit = 'P';
264 				break;
265 		}
266 	} else  {
267 		switch (unit) {
268 			case 'R':
269 				/* Already raw numbers */
270 				unit = '\0';
271 				break;
272 			case 'K':
273 				num /= 1000;
274 				break;
275 			case 'M':
276 				num /= (1000*1000);
277 				break;
278 			case 'G':
279 				num /= (1000*1000*1000);
280 				break;
281 			case 'T':
282 				num /= (1000.0*1000.0*1000.0*1000.0);
283 				break;
284 			case 'P':
285 				/* Largest unit supported */
286 			default:
287 				num /= (1000.0*1000.0*1000.0*1000.0*1000.0);
288 				break;
289 		}
290 	}
291 
292 	if (unit == '\0')
293 		(void) snprintf(buf, bufsize, " %7.0lf%c", num, unit);
294 	else
295 		(void) snprintf(buf, bufsize, " %6.2lf%c", num, unit);
296 }
297 
298 flow_chain_t *
299 get_flow_prev_stat(const char *flowname, void *arg)
300 {
301 	show_flow_state_t	*state = arg;
302 	flow_chain_t		*flow_curr = NULL;
303 
304 	/* Scan prev flowname list and look for entry matching this entry */
305 	for (flow_curr = state->fs_flowchain; flow_curr;
306 	    flow_curr = flow_curr->fc_next) {
307 		if (strcmp(flow_curr->fc_flowname, flowname) == 0)
308 			break;
309 	}
310 
311 	/* New flow, add it */
312 	if (flow_curr == NULL) {
313 		flow_curr = (flow_chain_t *)malloc(sizeof (flow_chain_t));
314 		if (flow_curr == NULL)
315 			goto done;
316 		(void) strncpy(flow_curr->fc_flowname, flowname,
317 		    MAXFLOWNAMELEN);
318 		flow_curr->fc_stat = NULL;
319 		flow_curr->fc_next = state->fs_flowchain;
320 		state->fs_flowchain = flow_curr;
321 	}
322 done:
323 	return (flow_curr);
324 }
325 
326 /*
327  * Number of flows may change while flowstat -i is executing.
328  * Free memory allocated for flows that are no longer there.
329  * Prepare for next iteration by marking visited = false for
330  * existing stat entries.
331  */
332 static void
333 cleanup_removed_flows(show_flow_state_t *state)
334 {
335 	flow_chain_t	*fcurr;
336 	flow_chain_t	*fprev;
337 	flow_chain_t	*tofree;
338 
339 	/* Delete all nodes from the list that have fc_visited marked false */
340 	fcurr = state->fs_flowchain;
341 	while (fcurr != NULL) {
342 		if (fcurr->fc_visited) {
343 			fcurr->fc_visited = B_FALSE;
344 			fprev = fcurr;
345 			fcurr = fcurr->fc_next;
346 			continue;
347 		}
348 
349 		/* Is it head of the list? */
350 		if (fcurr == state->fs_flowchain)
351 			state->fs_flowchain = fcurr->fc_next;
352 		else
353 			fprev->fc_next = fcurr->fc_next;
354 
355 		/* fprev remains the same */
356 		tofree = fcurr;
357 		fcurr = fcurr->fc_next;
358 
359 		/* Free stats memory for the removed flow */
360 		dladm_flow_stat_free(tofree->fc_stat);
361 		free(tofree);
362 	}
363 }
364 
365 static boolean_t
366 print_flow_stats_cb(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
367 {
368 	flow_args_t	*fargs = of_arg->ofmt_cbarg;
369 	flow_stat_t	*diff_stats = fargs->flow_s_stat;
370 	char		unit = fargs->flow_s_unit;
371 	boolean_t	parsable = fargs->flow_s_parsable;
372 
373 	switch (of_arg->ofmt_id) {
374 	case FLOW_S_FLOW:
375 		(void) snprintf(buf, bufsize, "%s", fargs->flow_s_flow);
376 		break;
377 	case FLOW_S_IPKTS:
378 		map_to_units(buf, bufsize, diff_stats->fl_ipackets, unit,
379 		    parsable);
380 		break;
381 	case FLOW_S_RBYTES:
382 		map_to_units(buf, bufsize, diff_stats->fl_rbytes, unit,
383 		    parsable);
384 		break;
385 	case FLOW_S_IERRORS:
386 		map_to_units(buf, bufsize, diff_stats->fl_ierrors, unit,
387 		    parsable);
388 		break;
389 	case FLOW_S_OPKTS:
390 		map_to_units(buf, bufsize, diff_stats->fl_opackets, unit,
391 		    parsable);
392 		break;
393 	case FLOW_S_OBYTES:
394 		map_to_units(buf, bufsize, diff_stats->fl_obytes, unit,
395 		    parsable);
396 		break;
397 	case FLOW_S_OERRORS:
398 		map_to_units(buf, bufsize, diff_stats->fl_oerrors, unit,
399 		    parsable);
400 		break;
401 	default:
402 		die("invalid input");
403 		break;
404 	}
405 	return (B_TRUE);
406 }
407 
408 /* ARGSUSED */
409 static int
410 query_flow_stats(dladm_handle_t handle, dladm_flow_attr_t *attr, void *arg)
411 {
412 	show_flow_state_t	*state = arg;
413 	flow_chain_t		*flow_node;
414 	flow_stat_t		*curr_stat;
415 	flow_stat_t		*prev_stat;
416 	flow_stat_t		*diff_stat;
417 	char			*flowname = attr->fa_flowname;
418 	flow_args_t		fargs;
419 
420 	/* Get previous stats for the flow */
421 	flow_node = get_flow_prev_stat(flowname, arg);
422 	if (flow_node == NULL)
423 		goto done;
424 
425 	flow_node->fc_visited = B_TRUE;
426 	prev_stat = flow_node->fc_stat;
427 
428 	/* Query library for current stats */
429 	curr_stat = dladm_flow_stat_query(flowname);
430 	if (curr_stat == NULL)
431 		goto done;
432 
433 	/* current stats - prev iteration stats */
434 	diff_stat = dladm_flow_stat_diff(curr_stat, prev_stat);
435 
436 	/* Free prev stats */
437 	dladm_flow_stat_free(prev_stat);
438 
439 	/* Prev <- curr stats */
440 	flow_node->fc_stat = curr_stat;
441 
442 	if (diff_stat == NULL)
443 		goto done;
444 
445 	/* Print stats */
446 	fargs.flow_s_flow = flowname;
447 	fargs.flow_s_stat = diff_stat;
448 	fargs.flow_s_unit = state->fs_unit;
449 	fargs.flow_s_parsable = state->fs_parsable;
450 	ofmt_print(state->fs_ofmt, &fargs);
451 
452 	/* Free diff stats */
453 	dladm_flow_stat_free(diff_stat);
454 done:
455 	return (DLADM_WALK_CONTINUE);
456 }
457 
458 /*
459  * Wrapper of dladm_walk_flow(query_flow_stats,...) to make it usable for
460  * dladm_walk_datalink_id(). Used for showing flow stats for
461  * all flows on all links.
462  */
463 static int
464 query_link_flow_stats(dladm_handle_t dh, datalink_id_t linkid, void * arg)
465 {
466 	if (dladm_walk_flow(query_flow_stats, dh, linkid, arg, B_FALSE)
467 	    == DLADM_STATUS_OK)
468 		return (DLADM_WALK_CONTINUE);
469 	else
470 		return (DLADM_WALK_TERMINATE);
471 }
472 
473 void
474 print_all_stats(name_value_stat_entry_t *stat_entry)
475 {
476 	name_value_stat_t	*curr_stat;
477 
478 	printf("%s\n", stat_entry->nve_header);
479 
480 	for (curr_stat = stat_entry->nve_stats; curr_stat != NULL;
481 	    curr_stat = curr_stat->nv_nextstat) {
482 		printf("\t%15s", curr_stat->nv_statname);
483 		printf("\t%15llu\n", curr_stat->nv_statval);
484 	}
485 }
486 
487 /* ARGSUSED */
488 static int
489 dump_one_flow_stats(dladm_handle_t handle, dladm_flow_attr_t *attr, void *arg)
490 {
491 	char	*flowname = attr->fa_flowname;
492 	void	*stat;
493 
494 	stat = dladm_flow_stat_query_all(flowname);
495 	if (stat == NULL)
496 		goto done;
497 	print_all_stats(stat);
498 	dladm_flow_stat_query_all_free(stat);
499 
500 done:
501 	return (DLADM_WALK_CONTINUE);
502 }
503 
504 /*
505  * Wrapper of dladm_walk_flow(query_flow_stats,...) to make it usable for
506  * dladm_walk_datalink_id(). Used for showing flow stats for
507  * all flows on all links.
508  */
509 static int
510 dump_link_flow_stats(dladm_handle_t dh, datalink_id_t linkid, void * arg)
511 {
512 	if (dladm_walk_flow(dump_one_flow_stats, dh, linkid, arg, B_FALSE)
513 	    == DLADM_STATUS_OK)
514 		return (DLADM_WALK_CONTINUE);
515 	else
516 		return (DLADM_WALK_TERMINATE);
517 }
518 
519 static void
520 dump_all_flow_stats(dladm_flow_attr_t *attrp, void *arg, datalink_id_t linkid,
521     boolean_t flow_arg)
522 {
523 	/* Show stats for named flow */
524 	if (flow_arg)  {
525 		(void) dump_one_flow_stats(handle, attrp, arg);
526 
527 	/* Show stats for flows on one link */
528 	} else if (linkid != DATALINK_INVALID_LINKID) {
529 		(void) dladm_walk_flow(dump_one_flow_stats, handle, linkid,
530 		    arg, B_FALSE);
531 
532 	/* Show stats for all flows on all links */
533 	} else {
534 		(void) dladm_walk_datalink_id(dump_link_flow_stats,
535 		    handle, arg, DATALINK_CLASS_ALL,
536 		    DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
537 	}
538 }
539 
540 int
541 main(int argc, char *argv[])
542 {
543 	dladm_status_t 		status;
544 	int			option;
545 	boolean_t		r_arg = B_FALSE;
546 	boolean_t		t_arg = B_FALSE;
547 	boolean_t		p_arg = B_FALSE;
548 	boolean_t		i_arg = B_FALSE;
549 	boolean_t		o_arg = B_FALSE;
550 	boolean_t		u_arg = B_FALSE;
551 	boolean_t		A_arg = B_FALSE;
552 	boolean_t		flow_arg = B_FALSE;
553 	datalink_id_t		linkid = DATALINK_ALL_LINKID;
554 	char			linkname[MAXLINKNAMELEN];
555 	char			flowname[MAXFLOWNAMELEN];
556 	uint32_t		interval = 0;
557 	char			unit = '\0';
558 	show_flow_state_t	state;
559 	char			*fields_str = NULL;
560 	char			*o_fields_str = NULL;
561 
562 	char			*total_stat_fields =
563 	    "flow,ipkts,rbytes,ierrs,opkts,obytes,oerrs";
564 	char			*rx_stat_fields =
565 	    "flow,ipkts,rbytes,ierrs";
566 	char			*tx_stat_fields =
567 	    "flow,opkts,obytes,oerrs";
568 
569 	ofmt_handle_t		ofmt;
570 	ofmt_status_t		oferr;
571 	uint_t			ofmtflags = OFMT_RIGHTJUST;
572 
573 	dladm_flow_attr_t	attr;
574 
575 	(void) setlocale(LC_ALL, "");
576 #if !defined(TEXT_DOMAIN)
577 #define	TEXT_DOMAIN "SYS_TEST"
578 #endif
579 	(void) textdomain(TEXT_DOMAIN);
580 
581 	progname = argv[0];
582 
583 	/* Open the libdladm handle */
584 	if ((status = dladm_open(&handle)) != DLADM_STATUS_OK)
585 		die_dlerr(status, "could not open /dev/dld");
586 
587 	bzero(&state, sizeof (state));
588 
589 	opterr = 0;
590 	while ((option = getopt_long(argc, argv, ":rtApi:o:u:l:h",
591 	    NULL, NULL)) != -1) {
592 		switch (option) {
593 		case 'r':
594 			if (r_arg)
595 				die_optdup(option);
596 
597 			r_arg = B_TRUE;
598 			break;
599 		case 't':
600 			if (t_arg)
601 				die_optdup(option);
602 
603 			t_arg = B_TRUE;
604 			break;
605 		case 'A':
606 			if (A_arg)
607 				die_optdup(option);
608 
609 			A_arg = B_TRUE;
610 			break;
611 		case 'p':
612 			if (p_arg)
613 				die_optdup(option);
614 
615 			p_arg = B_TRUE;
616 			break;
617 		case 'i':
618 			if (i_arg)
619 				die_optdup(option);
620 
621 			i_arg = B_TRUE;
622 			if (!dladm_str2interval(optarg, &interval))
623 				die("invalid interval value '%s'", optarg);
624 			break;
625 		case 'o':
626 			o_arg = B_TRUE;
627 			o_fields_str = optarg;
628 			break;
629 		case 'u':
630 			if (u_arg)
631 				die_optdup(option);
632 
633 			u_arg = B_TRUE;
634 			if (!flowstat_unit(optarg, &unit))
635 				die("invalid unit value '%s',"
636 				    "unit must be R|K|M|G|T|P", optarg);
637 			break;
638 		case 'l':
639 			if (strlcpy(linkname, optarg, MAXLINKNAMELEN)
640 			    >= MAXLINKNAMELEN)
641 				die("link name too long\n");
642 			if (dladm_name2info(handle, linkname, &linkid, NULL,
643 			    NULL, NULL) != DLADM_STATUS_OK)
644 				die("invalid link '%s'", linkname);
645 			break;
646 		case 'h':
647 			if (r_arg || t_arg || p_arg || o_arg || u_arg ||
648 			    i_arg || A_arg) {
649 				die("the option -h is not compatible with "
650 				    "-r, -t, -p, -o, -u, -i, -A");
651 			}
652 			do_show_history(argc, argv);
653 			return (0);
654 			break;
655 		default:
656 			die_opterr(optopt, option, usage_ermsg);
657 			break;
658 		}
659 	}
660 
661 	if (r_arg && t_arg)
662 		die("the option -t and -r are not compatible");
663 
664 	if (u_arg && p_arg)
665 		die("the option -u and -p are not compatible");
666 
667 	if (p_arg && !o_arg)
668 		die("-p requires -o");
669 
670 	if (p_arg && strcasecmp(o_fields_str, "all") == 0)
671 		die("\"-o all\" is invalid with -p");
672 
673 	if (A_arg &&
674 	    (r_arg || t_arg || p_arg || o_arg || u_arg || i_arg))
675 		die("the option -A is not compatible with "
676 		    "-r, -t, -p, -o, -u, -i");
677 
678 	/* get flow name (optional last argument) */
679 	if (optind == (argc-1)) {
680 		if (strlcpy(flowname, argv[optind], MAXFLOWNAMELEN)
681 		    >= MAXFLOWNAMELEN)
682 			die("flow name too long");
683 		flow_arg = B_TRUE;
684 	} else if (optind != argc) {
685 		usage();
686 	}
687 
688 	if (flow_arg &&
689 	    dladm_flow_info(handle, flowname, &attr) != DLADM_STATUS_OK)
690 		die("invalid flow %s", flowname);
691 
692 	if (A_arg) {
693 		dump_all_flow_stats(&attr, &state, linkid, flow_arg);
694 		return (0);
695 	}
696 
697 	state.fs_unit = unit;
698 	state.fs_parsable = p_arg;
699 
700 	if (state.fs_parsable)
701 		ofmtflags |= OFMT_PARSABLE;
702 
703 	if (r_arg)
704 		fields_str = rx_stat_fields;
705 	else if (t_arg)
706 		fields_str = tx_stat_fields;
707 	else
708 		fields_str = total_stat_fields;
709 
710 	if (o_arg) {
711 		fields_str = (strcasecmp(o_fields_str, "all") == 0) ?
712 		    fields_str : o_fields_str;
713 	}
714 
715 	oferr = ofmt_open(fields_str, flow_s_fields, ofmtflags, 0, &ofmt);
716 	ofmt_check(oferr, state.fs_parsable, ofmt, die, warn);
717 	state.fs_ofmt = ofmt;
718 
719 	for (;;) {
720 		/* Show stats for named flow */
721 		if (flow_arg)  {
722 			(void) query_flow_stats(handle, &attr, &state);
723 
724 		/* Show stats for flows on one link */
725 		} else if (linkid != DATALINK_INVALID_LINKID) {
726 			(void) dladm_walk_flow(query_flow_stats, handle, linkid,
727 			    &state, B_FALSE);
728 
729 		/* Show stats for all flows on all links */
730 		} else {
731 			(void) dladm_walk_datalink_id(query_link_flow_stats,
732 			    handle, &state, DATALINK_CLASS_ALL,
733 			    DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
734 		}
735 
736 		if (interval == 0)
737 			break;
738 
739 		(void) fflush(stdout);
740 		cleanup_removed_flows(&state);
741 		(void) sleep(interval);
742 	}
743 	ofmt_close(ofmt);
744 
745 	dladm_close(handle);
746 	return (0);
747 }
748 
749 /* ARGSUSED */
750 static int
751 show_history_date(dladm_usage_t *history, void *arg)
752 {
753 	show_history_state_t	*state = (show_history_state_t *)arg;
754 	time_t			stime;
755 	char			timebuf[20];
756 	dladm_flow_attr_t	attr;
757 	dladm_status_t		status;
758 
759 	/*
760 	 * Only show historical information for existing flows unless '-a'
761 	 * is specified.
762 	 */
763 	if (!state->us_showall && ((status = dladm_flow_info(handle,
764 	    history->du_name, &attr)) != DLADM_STATUS_OK)) {
765 		return (status);
766 	}
767 
768 	stime = history->du_stime;
769 	(void) strftime(timebuf, sizeof (timebuf), "%m/%d/%Y",
770 	    localtime(&stime));
771 	(void) printf("%s\n", timebuf);
772 
773 	return (DLADM_STATUS_OK);
774 }
775 
776 static int
777 show_history_time(dladm_usage_t *history, void *arg)
778 {
779 	show_history_state_t	*state = (show_history_state_t *)arg;
780 	char			buf[DLADM_STRSIZE];
781 	history_l_fields_buf_t 	ubuf;
782 	time_t			time;
783 	double			bw;
784 	dladm_flow_attr_t	attr;
785 	dladm_status_t		status;
786 
787 	/*
788 	 * Only show historical information for existing flows unless '-a'
789 	 * is specified.
790 	 */
791 	if (!state->us_showall && ((status = dladm_flow_info(handle,
792 	    history->du_name, &attr)) != DLADM_STATUS_OK)) {
793 		return (status);
794 	}
795 
796 	if (state->us_plot) {
797 		if (!state->us_printheader) {
798 			if (state->us_first) {
799 				(void) printf("# Time");
800 				state->us_first = B_FALSE;
801 			}
802 			(void) printf(" %s", history->du_name);
803 			if (history->du_last) {
804 				(void) printf("\n");
805 				state->us_first = B_TRUE;
806 				state->us_printheader = B_TRUE;
807 			}
808 		} else {
809 			if (state->us_first) {
810 				time = history->du_etime;
811 				(void) strftime(buf, sizeof (buf), "%T",
812 				    localtime(&time));
813 				state->us_first = B_FALSE;
814 				(void) printf("%s", buf);
815 			}
816 			bw = (double)history->du_bandwidth/1000;
817 			(void) printf(" %.2f", bw);
818 			if (history->du_last) {
819 				(void) printf("\n");
820 				state->us_first = B_TRUE;
821 			}
822 		}
823 		return (DLADM_STATUS_OK);
824 	}
825 
826 	bzero(&ubuf, sizeof (ubuf));
827 
828 	(void) snprintf(ubuf.history_l_flow, sizeof (ubuf.history_l_flow), "%s",
829 	    history->du_name);
830 	time = history->du_stime;
831 	(void) strftime(buf, sizeof (buf), "%T", localtime(&time));
832 	(void) snprintf(ubuf.history_l_stime, sizeof (ubuf.history_l_stime),
833 	    "%s", buf);
834 	time = history->du_etime;
835 	(void) strftime(buf, sizeof (buf), "%T", localtime(&time));
836 	(void) snprintf(ubuf.history_l_etime, sizeof (ubuf.history_l_etime),
837 	    "%s", buf);
838 	(void) snprintf(ubuf.history_l_rbytes, sizeof (ubuf.history_l_rbytes),
839 	    "%llu", history->du_rbytes);
840 	(void) snprintf(ubuf.history_l_obytes, sizeof (ubuf.history_l_obytes),
841 	    "%llu", history->du_obytes);
842 	(void) snprintf(ubuf.history_l_bandwidth,
843 	    sizeof (ubuf.history_l_bandwidth), "%s Mbps",
844 	    dladm_bw2str(history->du_bandwidth, buf));
845 
846 	ofmt_print(state->us_ofmt, (void *)&ubuf);
847 	return (DLADM_STATUS_OK);
848 }
849 
850 static int
851 show_history_res(dladm_usage_t *history, void *arg)
852 {
853 	show_history_state_t	*state = (show_history_state_t *)arg;
854 	char			buf[DLADM_STRSIZE];
855 	history_fields_buf_t	ubuf;
856 	dladm_flow_attr_t	attr;
857 	dladm_status_t		status;
858 
859 	/*
860 	 * Only show historical information for existing flows unless '-a'
861 	 * is specified.
862 	 */
863 	if (!state->us_showall && ((status = dladm_flow_info(handle,
864 	    history->du_name, &attr)) != DLADM_STATUS_OK)) {
865 		return (status);
866 	}
867 
868 	bzero(&ubuf, sizeof (ubuf));
869 
870 	(void) snprintf(ubuf.history_flow, sizeof (ubuf.history_flow), "%s",
871 	    history->du_name);
872 	(void) snprintf(ubuf.history_duration, sizeof (ubuf.history_duration),
873 	    "%llu", history->du_duration);
874 	(void) snprintf(ubuf.history_ipackets, sizeof (ubuf.history_ipackets),
875 	    "%llu", history->du_ipackets);
876 	(void) snprintf(ubuf.history_rbytes, sizeof (ubuf.history_rbytes),
877 	    "%llu", history->du_rbytes);
878 	(void) snprintf(ubuf.history_opackets, sizeof (ubuf.history_opackets),
879 	    "%llu", history->du_opackets);
880 	(void) snprintf(ubuf.history_obytes, sizeof (ubuf.history_obytes),
881 	    "%llu", history->du_obytes);
882 	(void) snprintf(ubuf.history_bandwidth, sizeof (ubuf.history_bandwidth),
883 	    "%s Mbps", dladm_bw2str(history->du_bandwidth, buf));
884 
885 	ofmt_print(state->us_ofmt, (void *)&ubuf);
886 
887 	return (DLADM_STATUS_OK);
888 }
889 
890 static boolean_t
891 valid_formatspec(char *formatspec_str)
892 {
893 	return (strcmp(formatspec_str, "gnuplot") == 0);
894 }
895 
896 /* ARGSUSED */
897 static void
898 do_show_history(int argc, char *argv[])
899 {
900 	char			*file = NULL;
901 	int			opt;
902 	dladm_status_t		status;
903 	boolean_t		d_arg = B_FALSE;
904 	char			*stime = NULL;
905 	char			*etime = NULL;
906 	char			*resource = NULL;
907 	show_history_state_t	state;
908 	boolean_t		o_arg = B_FALSE;
909 	boolean_t		F_arg = B_FALSE;
910 	char			*fields_str = NULL;
911 	char			*formatspec_str = NULL;
912 	char			*all_fields =
913 	    "flow,duration,ipackets,rbytes,opackets,obytes,bandwidth";
914 	char			*all_l_fields =
915 	    "flow,start,end,rbytes,obytes,bandwidth";
916 	ofmt_handle_t		ofmt;
917 	ofmt_status_t		oferr;
918 	uint_t			ofmtflags = 0;
919 
920 	bzero(&state, sizeof (show_history_state_t));
921 	state.us_parsable = B_FALSE;
922 	state.us_printheader = B_FALSE;
923 	state.us_plot = B_FALSE;
924 	state.us_first = B_TRUE;
925 
926 	while ((opt = getopt(argc, argv, "das:e:o:f:F:")) != -1) {
927 		switch (opt) {
928 		case 'd':
929 			d_arg = B_TRUE;
930 			break;
931 		case 'a':
932 			state.us_showall = B_TRUE;
933 			break;
934 		case 'f':
935 			file = optarg;
936 			break;
937 		case 's':
938 			stime = optarg;
939 			break;
940 		case 'e':
941 			etime = optarg;
942 			break;
943 		case 'o':
944 			o_arg = B_TRUE;
945 			fields_str = optarg;
946 			break;
947 		case 'F':
948 			state.us_plot = F_arg = B_TRUE;
949 			formatspec_str = optarg;
950 			break;
951 		default:
952 			die_opterr(optopt, opt, usage_ermsg);
953 		}
954 	}
955 
956 	if (file == NULL)
957 		die("-h requires a file");
958 
959 	if (optind == (argc-1)) {
960 		dladm_flow_attr_t	attr;
961 
962 		resource = argv[optind];
963 		if (!state.us_showall &&
964 		    dladm_flow_info(handle, resource, &attr) !=
965 		    DLADM_STATUS_OK) {
966 			die("invalid flow: '%s'", resource);
967 		}
968 	}
969 
970 	if (state.us_parsable)
971 		ofmtflags |= OFMT_PARSABLE;
972 	if (resource == NULL && stime == NULL && etime == NULL) {
973 		if (!o_arg || (o_arg && strcasecmp(fields_str, "all") == 0))
974 			fields_str = all_fields;
975 		oferr = ofmt_open(fields_str, history_fields, ofmtflags,
976 		    0, &ofmt);
977 	} else {
978 		if (!o_arg || (o_arg && strcasecmp(fields_str, "all") == 0))
979 			fields_str = all_l_fields;
980 		oferr = ofmt_open(fields_str, history_l_fields, ofmtflags,
981 		    0, &ofmt);
982 	}
983 
984 	ofmt_check(oferr, state.us_parsable, ofmt, die, warn);
985 	state.us_ofmt = ofmt;
986 
987 	if (F_arg && d_arg)
988 		die("incompatible -d and -F options");
989 
990 	if (F_arg && !valid_formatspec(formatspec_str))
991 		die("Format specifier %s not supported", formatspec_str);
992 
993 	if (d_arg) {
994 		/* Print log dates */
995 		status = dladm_usage_dates(show_history_date,
996 		    DLADM_LOGTYPE_FLOW, file, resource, &state);
997 	} else if (resource == NULL && stime == NULL && etime == NULL &&
998 	    !F_arg) {
999 		/* Print summary */
1000 		status = dladm_usage_summary(show_history_res,
1001 		    DLADM_LOGTYPE_FLOW, file, &state);
1002 	} else if (resource != NULL) {
1003 		/* Print log entries for named resource */
1004 		status = dladm_walk_usage_res(show_history_time,
1005 		    DLADM_LOGTYPE_FLOW, file, resource, stime, etime, &state);
1006 	} else {
1007 		/* Print time and information for each flow */
1008 		status = dladm_walk_usage_time(show_history_time,
1009 		    DLADM_LOGTYPE_FLOW, file, stime, etime, &state);
1010 	}
1011 
1012 	ofmt_close(ofmt);
1013 	if (status != DLADM_STATUS_OK)
1014 		die_dlerr(status, "-h");
1015 	dladm_close(handle);
1016 }
1017 
1018 static void
1019 warn(const char *format, ...)
1020 {
1021 	va_list alist;
1022 
1023 	format = gettext(format);
1024 	(void) fprintf(stderr, "%s: warning: ", progname);
1025 
1026 	va_start(alist, format);
1027 	(void) vfprintf(stderr, format, alist);
1028 	va_end(alist);
1029 
1030 	(void) putc('\n', stderr);
1031 }
1032 
1033 /* PRINTFLIKE1 */
1034 static void
1035 die(const char *format, ...)
1036 {
1037 	va_list alist;
1038 
1039 	format = gettext(format);
1040 	(void) fprintf(stderr, "%s: ", progname);
1041 
1042 	va_start(alist, format);
1043 	(void) vfprintf(stderr, format, alist);
1044 	va_end(alist);
1045 
1046 	(void) putc('\n', stderr);
1047 
1048 	/* close dladm handle if it was opened */
1049 	if (handle != NULL)
1050 		dladm_close(handle);
1051 
1052 	exit(EXIT_FAILURE);
1053 }
1054 
1055 static void
1056 die_optdup(int opt)
1057 {
1058 	die("the option -%c cannot be specified more than once", opt);
1059 }
1060 
1061 static void
1062 die_opterr(int opt, int opterr, const char *usage)
1063 {
1064 	switch (opterr) {
1065 	case ':':
1066 		die("option '-%c' requires a value\nusage: %s", opt,
1067 		    gettext(usage));
1068 		break;
1069 	case '?':
1070 	default:
1071 		die("unrecognized option '-%c'\nusage: %s", opt,
1072 		    gettext(usage));
1073 		break;
1074 	}
1075 }
1076 
1077 /* PRINTFLIKE2 */
1078 static void
1079 die_dlerr(dladm_status_t err, const char *format, ...)
1080 {
1081 	va_list alist;
1082 	char	errmsg[DLADM_STRSIZE];
1083 
1084 	format = gettext(format);
1085 	(void) fprintf(stderr, "%s: ", progname);
1086 
1087 	va_start(alist, format);
1088 	(void) vfprintf(stderr, format, alist);
1089 	va_end(alist);
1090 	(void) fprintf(stderr, ": %s\n", dladm_status2str(err, errmsg));
1091 
1092 	/* close dladm handle if it was opened */
1093 	if (handle != NULL)
1094 		dladm_close(handle);
1095 
1096 	exit(EXIT_FAILURE);
1097 }
1098 
1099 
1100 /*
1101  * default output callback function that, when invoked from dladm_print_output,
1102  * prints string which is offset by of_arg->ofmt_id within buf.
1103  */
1104 static boolean_t
1105 print_default_cb(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
1106 {
1107 	char *value;
1108 
1109 	value = (char *)of_arg->ofmt_cbarg + of_arg->ofmt_id;
1110 	(void) strlcpy(buf, value, bufsize);
1111 	return (B_TRUE);
1112 }
1113