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