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