summaryrefslogtreecommitdiff
path: root/usr/src/cmd/flowstat/flowstat.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/flowstat/flowstat.c')
-rw-r--r--usr/src/cmd/flowstat/flowstat.c1149
1 files changed, 1149 insertions, 0 deletions
diff --git a/usr/src/cmd/flowstat/flowstat.c b/usr/src/cmd/flowstat/flowstat.c
new file mode 100644
index 0000000000..3ddff9e34f
--- /dev/null
+++ b/usr/src/cmd/flowstat/flowstat.c
@@ -0,0 +1,1149 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <stdio.h>
+#include <locale.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stropts.h>
+#include <errno.h>
+#include <strings.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <priv.h>
+#include <netdb.h>
+#include <libintl.h>
+#include <libdlflow.h>
+#include <libdllink.h>
+#include <libdlstat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/ethernet.h>
+#include <inet/ip.h>
+#include <inet/ip6.h>
+#include <stddef.h>
+#include <ofmt.h>
+
+typedef struct flow_chain_s {
+ char fc_flowname[MAXFLOWNAMELEN];
+ boolean_t fc_visited;
+ flow_stat_t *fc_stat;
+ struct flow_chain_s *fc_next;
+} flow_chain_t;
+
+typedef struct show_flow_state {
+ flow_chain_t *fs_flowchain;
+ ofmt_handle_t fs_ofmt;
+ char fs_unit;
+ boolean_t fs_parsable;
+} show_flow_state_t;
+
+typedef struct show_history_state_s {
+ boolean_t us_plot;
+ boolean_t us_parsable;
+ boolean_t us_printheader;
+ boolean_t us_first;
+ boolean_t us_showall;
+ ofmt_handle_t us_ofmt;
+} show_history_state_t;
+
+static void do_show_history(int, char **);
+
+static int query_flow_stats(dladm_handle_t, dladm_flow_attr_t *, void *);
+static int query_link_flow_stats(dladm_handle_t, datalink_id_t, void *);
+
+static void die(const char *, ...);
+static void die_optdup(int);
+static void die_opterr(int, int, const char *);
+static void die_dlerr(dladm_status_t, const char *, ...);
+static void warn(const char *, ...);
+
+/* callback functions for printing output */
+static ofmt_cb_t print_default_cb, print_flow_stats_cb;
+static void flowstat_ofmt_check(ofmt_status_t, boolean_t, ofmt_handle_t);
+
+#define NULL_OFMT {NULL, 0, 0, NULL}
+
+/*
+ * structures for flowstat (printing live statistics)
+ */
+typedef enum {
+ FLOW_S_FLOW,
+ FLOW_S_IPKTS,
+ FLOW_S_RBYTES,
+ FLOW_S_IERRORS,
+ FLOW_S_OPKTS,
+ FLOW_S_OBYTES,
+ FLOW_S_OERRORS
+} flow_s_field_index_t;
+
+static ofmt_field_t flow_s_fields[] = {
+/* name, field width, index, callback */
+{ "FLOW", 15, FLOW_S_FLOW, print_flow_stats_cb},
+{ "IPKTS", 8, FLOW_S_IPKTS, print_flow_stats_cb},
+{ "RBYTES", 8, FLOW_S_RBYTES, print_flow_stats_cb},
+{ "IERRS", 8, FLOW_S_IERRORS, print_flow_stats_cb},
+{ "OPKTS", 8, FLOW_S_OPKTS, print_flow_stats_cb},
+{ "OBYTES", 8, FLOW_S_OBYTES, print_flow_stats_cb},
+{ "OERRS", 8, FLOW_S_OERRORS, print_flow_stats_cb},
+NULL_OFMT}
+;
+
+typedef struct flow_args_s {
+ char *flow_s_flow;
+ flow_stat_t *flow_s_stat;
+ char flow_s_unit;
+ boolean_t flow_s_parsable;
+} flow_args_t;
+
+/*
+ * structures for 'flowstat -h'
+ */
+typedef struct history_fields_buf_s {
+ char history_flow[12];
+ char history_duration[10];
+ char history_ipackets[9];
+ char history_rbytes[10];
+ char history_opackets[9];
+ char history_obytes[10];
+ char history_bandwidth[14];
+} history_fields_buf_t;
+
+static ofmt_field_t history_fields[] = {
+/* name, field width, offset */
+{ "FLOW", 13,
+ offsetof(history_fields_buf_t, history_flow), print_default_cb},
+{ "DURATION", 11,
+ offsetof(history_fields_buf_t, history_duration), print_default_cb},
+{ "IPACKETS", 10,
+ offsetof(history_fields_buf_t, history_ipackets), print_default_cb},
+{ "RBYTES", 11,
+ offsetof(history_fields_buf_t, history_rbytes), print_default_cb},
+{ "OPACKETS", 10,
+ offsetof(history_fields_buf_t, history_opackets), print_default_cb},
+{ "OBYTES", 11,
+ offsetof(history_fields_buf_t, history_obytes), print_default_cb},
+{ "BANDWIDTH", 15,
+ offsetof(history_fields_buf_t, history_bandwidth), print_default_cb},
+NULL_OFMT}
+;
+
+typedef struct history_l_fields_buf_s {
+ char history_l_flow[12];
+ char history_l_stime[13];
+ char history_l_etime[13];
+ char history_l_rbytes[8];
+ char history_l_obytes[8];
+ char history_l_bandwidth[14];
+} history_l_fields_buf_t;
+
+static ofmt_field_t history_l_fields[] = {
+/* name, field width, offset */
+{ "FLOW", 13,
+ offsetof(history_l_fields_buf_t, history_l_flow), print_default_cb},
+{ "START", 14,
+ offsetof(history_l_fields_buf_t, history_l_stime), print_default_cb},
+{ "END", 14,
+ offsetof(history_l_fields_buf_t, history_l_etime), print_default_cb},
+{ "RBYTES", 9,
+ offsetof(history_l_fields_buf_t, history_l_rbytes), print_default_cb},
+{ "OBYTES", 9,
+ offsetof(history_l_fields_buf_t, history_l_obytes), print_default_cb},
+{ "BANDWIDTH", 15,
+ offsetof(history_l_fields_buf_t, history_l_bandwidth),
+ print_default_cb},
+NULL_OFMT}
+;
+
+static char *progname;
+
+/*
+ * Handle to libdladm. Opened in main() before the sub-command
+ * specific function is called.
+ */
+static dladm_handle_t handle = NULL;
+
+const char *usage_ermsg = "flowstat [-r | -t] [-i interval] "
+ "[-l link] [flow]\n"
+ " flowstat [-S] [-A] [-i interval] [-p] [ -o field[,...]]\n"
+ " [-u R|K|M|G|T|P] [-l link] [flow]\n"
+ " flowstat -h [-a] [-d] [-F format]"
+ " [-s <DD/MM/YYYY,HH:MM:SS>]\n"
+ " [-e <DD/MM/YYYY,HH:MM:SS>] -f <logfile> "
+ "[<flow>]";
+
+static void
+usage(void)
+{
+ (void) fprintf(stderr, "%s\n", gettext(usage_ermsg));
+
+ /* close dladm handle if it was opened */
+ if (handle != NULL)
+ dladm_close(handle);
+
+ exit(1);
+}
+
+boolean_t
+flowstat_unit(char *oarg, char *unit)
+{
+ if ((strcmp(oarg, "R") == 0) || (strcmp(oarg, "K") == 0) ||
+ (strcmp(oarg, "M") == 0) || (strcmp(oarg, "G") == 0) ||
+ (strcmp(oarg, "T") == 0) || (strcmp(oarg, "P") == 0)) {
+ *unit = oarg[0];
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+void
+map_to_units(char *buf, uint_t bufsize, double num, char unit,
+ boolean_t parsable)
+{
+ if (parsable) {
+ (void) snprintf(buf, bufsize, "%.0lf", num);
+ return;
+ }
+
+ if (unit == '\0') {
+ int index;
+
+ for (index = 0; (int)(num/1000) != 0; index++, num /= 1000)
+ ;
+
+ switch (index) {
+ case 0:
+ unit = '\0';
+ break;
+ case 1:
+ unit = 'K';
+ break;
+ case 2:
+ unit = 'M';
+ break;
+ case 3:
+ unit = 'G';
+ break;
+ case 4:
+ unit = 'T';
+ break;
+ case 5:
+ /* Largest unit supported */
+ default:
+ unit = 'P';
+ break;
+ }
+ } else {
+ switch (unit) {
+ case 'R':
+ /* Already raw numbers */
+ unit = '\0';
+ break;
+ case 'K':
+ num /= 1000;
+ break;
+ case 'M':
+ num /= (1000*1000);
+ break;
+ case 'G':
+ num /= (1000*1000*1000);
+ break;
+ case 'T':
+ num /= (1000.0*1000.0*1000.0*1000.0);
+ break;
+ case 'P':
+ /* Largest unit supported */
+ default:
+ num /= (1000.0*1000.0*1000.0*1000.0*1000.0);
+ break;
+ }
+ }
+
+ if (unit == '\0')
+ (void) snprintf(buf, bufsize, " %7.0lf%c", num, unit);
+ else
+ (void) snprintf(buf, bufsize, " %6.2lf%c", num, unit);
+}
+
+flow_chain_t *
+get_flow_prev_stat(const char *flowname, void *arg)
+{
+ show_flow_state_t *state = arg;
+ flow_chain_t *flow_curr = NULL;
+
+ /* Scan prev flowname list and look for entry matching this entry */
+ for (flow_curr = state->fs_flowchain; flow_curr;
+ flow_curr = flow_curr->fc_next) {
+ if (strcmp(flow_curr->fc_flowname, flowname) == 0)
+ break;
+ }
+
+ /* New flow, add it */
+ if (flow_curr == NULL) {
+ flow_curr = (flow_chain_t *)malloc(sizeof (flow_chain_t));
+ if (flow_curr == NULL)
+ goto done;
+ (void) strncpy(flow_curr->fc_flowname, flowname,
+ MAXFLOWNAMELEN);
+ flow_curr->fc_stat = NULL;
+ flow_curr->fc_next = state->fs_flowchain;
+ state->fs_flowchain = flow_curr;
+ }
+done:
+ return (flow_curr);
+}
+
+/*
+ * Number of flows may change while flowstat -i is executing.
+ * Free memory allocated for flows that are no longer there.
+ * Prepare for next iteration by marking visited = false for
+ * existing stat entries.
+ */
+static void
+cleanup_removed_flows(show_flow_state_t *state)
+{
+ flow_chain_t *fcurr;
+ flow_chain_t *fprev;
+ flow_chain_t *tofree;
+
+ /* Delete all nodes from the list that have fc_visited marked false */
+ fcurr = state->fs_flowchain;
+ while (fcurr != NULL) {
+ if (fcurr->fc_visited) {
+ fcurr->fc_visited = B_FALSE;
+ fprev = fcurr;
+ fcurr = fcurr->fc_next;
+ continue;
+ }
+
+ /* Is it head of the list? */
+ if (fcurr == state->fs_flowchain)
+ state->fs_flowchain = fcurr->fc_next;
+ else
+ fprev->fc_next = fcurr->fc_next;
+
+ /* fprev remains the same */
+ tofree = fcurr;
+ fcurr = fcurr->fc_next;
+
+ /* Free stats memory for the removed flow */
+ dladm_flow_stat_free(tofree->fc_stat);
+ free(tofree);
+ }
+}
+
+static boolean_t
+print_flow_stats_cb(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
+{
+ flow_args_t *fargs = of_arg->ofmt_cbarg;
+ flow_stat_t *diff_stats = fargs->flow_s_stat;
+ char unit = fargs->flow_s_unit;
+ boolean_t parsable = fargs->flow_s_parsable;
+
+ switch (of_arg->ofmt_id) {
+ case FLOW_S_FLOW:
+ (void) snprintf(buf, bufsize, "%s", fargs->flow_s_flow);
+ break;
+ case FLOW_S_IPKTS:
+ map_to_units(buf, bufsize, diff_stats->fl_ipackets, unit,
+ parsable);
+ break;
+ case FLOW_S_RBYTES:
+ map_to_units(buf, bufsize, diff_stats->fl_rbytes, unit,
+ parsable);
+ break;
+ case FLOW_S_IERRORS:
+ map_to_units(buf, bufsize, diff_stats->fl_ierrors, unit,
+ parsable);
+ break;
+ case FLOW_S_OPKTS:
+ map_to_units(buf, bufsize, diff_stats->fl_opackets, unit,
+ parsable);
+ break;
+ case FLOW_S_OBYTES:
+ map_to_units(buf, bufsize, diff_stats->fl_obytes, unit,
+ parsable);
+ break;
+ case FLOW_S_OERRORS:
+ map_to_units(buf, bufsize, diff_stats->fl_oerrors, unit,
+ parsable);
+ break;
+ default:
+ die("invalid input");
+ break;
+ }
+ return (B_TRUE);
+}
+
+/* ARGSUSED */
+static int
+query_flow_stats(dladm_handle_t handle, dladm_flow_attr_t *attr, void *arg)
+{
+ show_flow_state_t *state = arg;
+ flow_chain_t *flow_node;
+ flow_stat_t *curr_stat;
+ flow_stat_t *prev_stat;
+ flow_stat_t *diff_stat;
+ char *flowname = attr->fa_flowname;
+ flow_args_t fargs;
+
+ /* Get previous stats for the flow */
+ flow_node = get_flow_prev_stat(flowname, arg);
+ if (flow_node == NULL)
+ goto done;
+
+ flow_node->fc_visited = B_TRUE;
+ prev_stat = flow_node->fc_stat;
+
+ /* Query library for current stats */
+ curr_stat = dladm_flow_stat_query(flowname);
+ if (curr_stat == NULL)
+ goto done;
+
+ /* current stats - prev iteration stats */
+ diff_stat = dladm_flow_stat_diff(curr_stat, prev_stat);
+
+ /* Free prev stats */
+ dladm_flow_stat_free(prev_stat);
+
+ /* Prev <- curr stats */
+ flow_node->fc_stat = curr_stat;
+
+ if (diff_stat == NULL)
+ goto done;
+
+ /* Print stats */
+ fargs.flow_s_flow = flowname;
+ fargs.flow_s_stat = diff_stat;
+ fargs.flow_s_unit = state->fs_unit;
+ fargs.flow_s_parsable = state->fs_parsable;
+ ofmt_print(state->fs_ofmt, &fargs);
+
+ /* Free diff stats */
+ dladm_flow_stat_free(diff_stat);
+done:
+ return (DLADM_WALK_CONTINUE);
+}
+
+/*
+ * Wrapper of dladm_walk_flow(query_flow_stats,...) to make it usable for
+ * dladm_walk_datalink_id(). Used for showing flow stats for
+ * all flows on all links.
+ */
+static int
+query_link_flow_stats(dladm_handle_t dh, datalink_id_t linkid, void * arg)
+{
+ if (dladm_walk_flow(query_flow_stats, dh, linkid, arg, B_FALSE)
+ == DLADM_STATUS_OK)
+ return (DLADM_WALK_CONTINUE);
+ else
+ return (DLADM_WALK_TERMINATE);
+}
+
+void
+print_all_stats(name_value_stat_entry_t *stat_entry)
+{
+ name_value_stat_t *curr_stat;
+
+ printf("%s\n", stat_entry->nve_header);
+
+ for (curr_stat = stat_entry->nve_stats; curr_stat != NULL;
+ curr_stat = curr_stat->nv_nextstat) {
+ printf("\t%15s", curr_stat->nv_statname);
+ printf("\t%15llu\n", curr_stat->nv_statval);
+ }
+}
+
+/* ARGSUSED */
+static int
+dump_one_flow_stats(dladm_handle_t handle, dladm_flow_attr_t *attr, void *arg)
+{
+ char *flowname = attr->fa_flowname;
+ void *stat;
+
+ stat = dladm_flow_stat_query_all(flowname);
+ if (stat == NULL)
+ goto done;
+ print_all_stats(stat);
+ dladm_flow_stat_query_all_free(stat);
+
+done:
+ return (DLADM_WALK_CONTINUE);
+}
+
+/*
+ * Wrapper of dladm_walk_flow(query_flow_stats,...) to make it usable for
+ * dladm_walk_datalink_id(). Used for showing flow stats for
+ * all flows on all links.
+ */
+static int
+dump_link_flow_stats(dladm_handle_t dh, datalink_id_t linkid, void * arg)
+{
+ if (dladm_walk_flow(dump_one_flow_stats, dh, linkid, arg, B_FALSE)
+ == DLADM_STATUS_OK)
+ return (DLADM_WALK_CONTINUE);
+ else
+ return (DLADM_WALK_TERMINATE);
+}
+
+static void
+dump_all_flow_stats(dladm_flow_attr_t *attrp, void *arg, datalink_id_t linkid,
+ boolean_t flow_arg)
+{
+ /* Show stats for named flow */
+ if (flow_arg) {
+ (void) dump_one_flow_stats(handle, attrp, arg);
+
+ /* Show stats for flows on one link */
+ } else if (linkid != DATALINK_INVALID_LINKID) {
+ (void) dladm_walk_flow(dump_one_flow_stats, handle, linkid,
+ arg, B_FALSE);
+
+ /* Show stats for all flows on all links */
+ } else {
+ (void) dladm_walk_datalink_id(dump_link_flow_stats,
+ handle, arg, DATALINK_CLASS_ALL,
+ DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ dladm_status_t status;
+ int option;
+ boolean_t r_arg = B_FALSE;
+ boolean_t t_arg = B_FALSE;
+ boolean_t p_arg = B_FALSE;
+ boolean_t i_arg = B_FALSE;
+ boolean_t o_arg = B_FALSE;
+ boolean_t u_arg = B_FALSE;
+ boolean_t A_arg = B_FALSE;
+ boolean_t S_arg = B_FALSE;
+ boolean_t flow_arg = B_FALSE;
+ datalink_id_t linkid = DATALINK_ALL_LINKID;
+ char linkname[MAXLINKNAMELEN];
+ char flowname[MAXFLOWNAMELEN];
+ uint32_t interval = 0;
+ char unit = '\0';
+ show_flow_state_t state;
+ char *fields_str = NULL;
+ char *o_fields_str = NULL;
+
+ char *total_stat_fields =
+ "flow,ipkts,rbytes,ierrs,opkts,obytes,oerrs";
+ char *rx_stat_fields =
+ "flow,ipkts,rbytes,ierrs";
+ char *tx_stat_fields =
+ "flow,opkts,obytes,oerrs";
+
+ ofmt_handle_t ofmt;
+ ofmt_status_t oferr;
+ uint_t ofmtflags = OFMT_RIGHTJUST;
+
+ dladm_flow_attr_t attr;
+
+ (void) setlocale(LC_ALL, "");
+#if !defined(TEXT_DOMAIN)
+#define TEXT_DOMAIN "SYS_TEST"
+#endif
+ (void) textdomain(TEXT_DOMAIN);
+
+ progname = argv[0];
+
+ /* Open the libdladm handle */
+ if ((status = dladm_open(&handle)) != DLADM_STATUS_OK)
+ die_dlerr(status, "could not open /dev/dld");
+
+ bzero(&state, sizeof (state));
+
+ opterr = 0;
+ while ((option = getopt_long(argc, argv, ":rtApSi:o:u:l:h",
+ NULL, NULL)) != -1) {
+ switch (option) {
+ case 'r':
+ if (r_arg)
+ die_optdup(option);
+
+ r_arg = B_TRUE;
+ break;
+ case 't':
+ if (t_arg)
+ die_optdup(option);
+
+ t_arg = B_TRUE;
+ break;
+ case 'A':
+ if (A_arg)
+ die_optdup(option);
+
+ A_arg = B_TRUE;
+ break;
+ case 'p':
+ if (p_arg)
+ die_optdup(option);
+
+ p_arg = B_TRUE;
+ break;
+ case 'S':
+ if (S_arg)
+ die_optdup(option);
+ S_arg = B_TRUE;
+ break;
+ case 'i':
+ if (i_arg)
+ die_optdup(option);
+
+ i_arg = B_TRUE;
+ if (!dladm_str2interval(optarg, &interval))
+ die("invalid interval value '%s'", optarg);
+ break;
+ case 'o':
+ o_arg = B_TRUE;
+ o_fields_str = optarg;
+ break;
+ case 'u':
+ if (u_arg)
+ die_optdup(option);
+
+ u_arg = B_TRUE;
+ if (!flowstat_unit(optarg, &unit))
+ die("invalid unit value '%s',"
+ "unit must be R|K|M|G|T|P", optarg);
+ break;
+ case 'l':
+ if (strlcpy(linkname, optarg, MAXLINKNAMELEN)
+ >= MAXLINKNAMELEN)
+ die("link name too long\n");
+ if (dladm_name2info(handle, linkname, &linkid, NULL,
+ NULL, NULL) != DLADM_STATUS_OK)
+ die("invalid link '%s'", linkname);
+ break;
+ case 'h':
+ if (r_arg || t_arg || p_arg || o_arg || u_arg ||
+ i_arg || S_arg || A_arg) {
+ die("the option -h is not compatible with "
+ "-r, -t, -p, -o, -u, -i, -S, -A");
+ }
+ do_show_history(argc, argv);
+ return (0);
+ break;
+ default:
+ die_opterr(optopt, option, usage_ermsg);
+ break;
+ }
+ }
+
+ if (r_arg && t_arg)
+ die("the option -t and -r are not compatible");
+
+ if (u_arg && p_arg)
+ die("the option -u and -p are not compatible");
+
+ if (p_arg && !o_arg)
+ die("-p requires -o");
+
+ if (p_arg && strcasecmp(o_fields_str, "all") == 0)
+ die("\"-o all\" is invalid with -p");
+
+ if (S_arg &&
+ (r_arg || t_arg || p_arg || o_arg || u_arg))
+ die("the option -S is not compatible with "
+ "-r, -t, -p, -o, -u");
+
+ if (A_arg &&
+ (r_arg || t_arg || p_arg || o_arg || u_arg || i_arg))
+ die("the option -A is not compatible with "
+ "-r, -t, -p, -o, -u, -i");
+
+ /* get flow name (optional last argument) */
+ if (optind == (argc-1)) {
+ if (strlcpy(flowname, argv[optind], MAXFLOWNAMELEN)
+ >= MAXFLOWNAMELEN)
+ die("flow name too long");
+ flow_arg = B_TRUE;
+ } else if (optind != argc) {
+ usage();
+ }
+
+ if (S_arg) {
+ dladm_continuous(handle, linkid, (flow_arg ? flowname : NULL),
+ interval, FLOW_REPORT);
+ return (0);
+ }
+
+ if (flow_arg &&
+ dladm_flow_info(handle, flowname, &attr) != DLADM_STATUS_OK)
+ die("invalid flow %s", flowname);
+
+ if (A_arg) {
+ dump_all_flow_stats(&attr, &state, linkid, flow_arg);
+ return (0);
+ }
+
+ state.fs_unit = unit;
+ state.fs_parsable = p_arg;
+
+ if (state.fs_parsable)
+ ofmtflags |= OFMT_PARSABLE;
+
+ if (r_arg)
+ fields_str = rx_stat_fields;
+ else if (t_arg)
+ fields_str = tx_stat_fields;
+ else
+ fields_str = total_stat_fields;
+
+ if (o_arg) {
+ fields_str = (strcasecmp(o_fields_str, "all") == 0) ?
+ fields_str : o_fields_str;
+ }
+
+ oferr = ofmt_open(fields_str, flow_s_fields, ofmtflags, 0, &ofmt);
+ flowstat_ofmt_check(oferr, state.fs_parsable, ofmt);
+ state.fs_ofmt = ofmt;
+
+ for (;;) {
+ /* Show stats for named flow */
+ if (flow_arg) {
+ (void) query_flow_stats(handle, &attr, &state);
+
+ /* Show stats for flows on one link */
+ } else if (linkid != DATALINK_INVALID_LINKID) {
+ (void) dladm_walk_flow(query_flow_stats, handle, linkid,
+ &state, B_FALSE);
+
+ /* Show stats for all flows on all links */
+ } else {
+ (void) dladm_walk_datalink_id(query_link_flow_stats,
+ handle, &state, DATALINK_CLASS_ALL,
+ DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
+ }
+
+ if (interval == 0)
+ break;
+
+ (void) fflush(stdout);
+ cleanup_removed_flows(&state);
+ (void) sleep(interval);
+ }
+ ofmt_close(ofmt);
+
+ dladm_close(handle);
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+show_history_date(dladm_usage_t *history, void *arg)
+{
+ show_history_state_t *state = (show_history_state_t *)arg;
+ time_t stime;
+ char timebuf[20];
+ dladm_flow_attr_t attr;
+ dladm_status_t status;
+
+ /*
+ * Only show historical information for existing flows unless '-a'
+ * is specified.
+ */
+ if (!state->us_showall && ((status = dladm_flow_info(handle,
+ history->du_name, &attr)) != DLADM_STATUS_OK)) {
+ return (status);
+ }
+
+ stime = history->du_stime;
+ (void) strftime(timebuf, sizeof (timebuf), "%m/%d/%Y",
+ localtime(&stime));
+ (void) printf("%s\n", timebuf);
+
+ return (DLADM_STATUS_OK);
+}
+
+static int
+show_history_time(dladm_usage_t *history, void *arg)
+{
+ show_history_state_t *state = (show_history_state_t *)arg;
+ char buf[DLADM_STRSIZE];
+ history_l_fields_buf_t ubuf;
+ time_t time;
+ double bw;
+ dladm_flow_attr_t attr;
+ dladm_status_t status;
+
+ /*
+ * Only show historical information for existing flows unless '-a'
+ * is specified.
+ */
+ if (!state->us_showall && ((status = dladm_flow_info(handle,
+ history->du_name, &attr)) != DLADM_STATUS_OK)) {
+ return (status);
+ }
+
+ if (state->us_plot) {
+ if (!state->us_printheader) {
+ if (state->us_first) {
+ (void) printf("# Time");
+ state->us_first = B_FALSE;
+ }
+ (void) printf(" %s", history->du_name);
+ if (history->du_last) {
+ (void) printf("\n");
+ state->us_first = B_TRUE;
+ state->us_printheader = B_TRUE;
+ }
+ } else {
+ if (state->us_first) {
+ time = history->du_etime;
+ (void) strftime(buf, sizeof (buf), "%T",
+ localtime(&time));
+ state->us_first = B_FALSE;
+ (void) printf("%s", buf);
+ }
+ bw = (double)history->du_bandwidth/1000;
+ (void) printf(" %.2f", bw);
+ if (history->du_last) {
+ (void) printf("\n");
+ state->us_first = B_TRUE;
+ }
+ }
+ return (DLADM_STATUS_OK);
+ }
+
+ bzero(&ubuf, sizeof (ubuf));
+
+ (void) snprintf(ubuf.history_l_flow, sizeof (ubuf.history_l_flow), "%s",
+ history->du_name);
+ time = history->du_stime;
+ (void) strftime(buf, sizeof (buf), "%T", localtime(&time));
+ (void) snprintf(ubuf.history_l_stime, sizeof (ubuf.history_l_stime),
+ "%s", buf);
+ time = history->du_etime;
+ (void) strftime(buf, sizeof (buf), "%T", localtime(&time));
+ (void) snprintf(ubuf.history_l_etime, sizeof (ubuf.history_l_etime),
+ "%s", buf);
+ (void) snprintf(ubuf.history_l_rbytes, sizeof (ubuf.history_l_rbytes),
+ "%llu", history->du_rbytes);
+ (void) snprintf(ubuf.history_l_obytes, sizeof (ubuf.history_l_obytes),
+ "%llu", history->du_obytes);
+ (void) snprintf(ubuf.history_l_bandwidth,
+ sizeof (ubuf.history_l_bandwidth), "%s Mbps",
+ dladm_bw2str(history->du_bandwidth, buf));
+
+ ofmt_print(state->us_ofmt, (void *)&ubuf);
+ return (DLADM_STATUS_OK);
+}
+
+static int
+show_history_res(dladm_usage_t *history, void *arg)
+{
+ show_history_state_t *state = (show_history_state_t *)arg;
+ char buf[DLADM_STRSIZE];
+ history_fields_buf_t ubuf;
+ dladm_flow_attr_t attr;
+ dladm_status_t status;
+
+ /*
+ * Only show historical information for existing flows unless '-a'
+ * is specified.
+ */
+ if (!state->us_showall && ((status = dladm_flow_info(handle,
+ history->du_name, &attr)) != DLADM_STATUS_OK)) {
+ return (status);
+ }
+
+ bzero(&ubuf, sizeof (ubuf));
+
+ (void) snprintf(ubuf.history_flow, sizeof (ubuf.history_flow), "%s",
+ history->du_name);
+ (void) snprintf(ubuf.history_duration, sizeof (ubuf.history_duration),
+ "%llu", history->du_duration);
+ (void) snprintf(ubuf.history_ipackets, sizeof (ubuf.history_ipackets),
+ "%llu", history->du_ipackets);
+ (void) snprintf(ubuf.history_rbytes, sizeof (ubuf.history_rbytes),
+ "%llu", history->du_rbytes);
+ (void) snprintf(ubuf.history_opackets, sizeof (ubuf.history_opackets),
+ "%llu", history->du_opackets);
+ (void) snprintf(ubuf.history_obytes, sizeof (ubuf.history_obytes),
+ "%llu", history->du_obytes);
+ (void) snprintf(ubuf.history_bandwidth, sizeof (ubuf.history_bandwidth),
+ "%s Mbps", dladm_bw2str(history->du_bandwidth, buf));
+
+ ofmt_print(state->us_ofmt, (void *)&ubuf);
+
+ return (DLADM_STATUS_OK);
+}
+
+static boolean_t
+valid_formatspec(char *formatspec_str)
+{
+ return (strcmp(formatspec_str, "gnuplot") == 0);
+}
+
+/* ARGSUSED */
+static void
+do_show_history(int argc, char *argv[])
+{
+ char *file = NULL;
+ int opt;
+ dladm_status_t status;
+ boolean_t d_arg = B_FALSE;
+ char *stime = NULL;
+ char *etime = NULL;
+ char *resource = NULL;
+ show_history_state_t state;
+ boolean_t o_arg = B_FALSE;
+ boolean_t F_arg = B_FALSE;
+ char *fields_str = NULL;
+ char *formatspec_str = NULL;
+ char *all_fields =
+ "flow,duration,ipackets,rbytes,opackets,obytes,bandwidth";
+ char *all_l_fields =
+ "flow,start,end,rbytes,obytes,bandwidth";
+ ofmt_handle_t ofmt;
+ ofmt_status_t oferr;
+ uint_t ofmtflags = 0;
+
+ bzero(&state, sizeof (show_history_state_t));
+ state.us_parsable = B_FALSE;
+ state.us_printheader = B_FALSE;
+ state.us_plot = B_FALSE;
+ state.us_first = B_TRUE;
+
+ while ((opt = getopt(argc, argv, "das:e:o:f:F:")) != -1) {
+ switch (opt) {
+ case 'd':
+ d_arg = B_TRUE;
+ break;
+ case 'a':
+ state.us_showall = B_TRUE;
+ break;
+ case 'f':
+ file = optarg;
+ break;
+ case 's':
+ stime = optarg;
+ break;
+ case 'e':
+ etime = optarg;
+ break;
+ case 'o':
+ o_arg = B_TRUE;
+ fields_str = optarg;
+ break;
+ case 'F':
+ state.us_plot = F_arg = B_TRUE;
+ formatspec_str = optarg;
+ break;
+ default:
+ die_opterr(optopt, opt, usage_ermsg);
+ }
+ }
+
+ if (file == NULL)
+ die("-h requires a file");
+
+ if (optind == (argc-1)) {
+ dladm_flow_attr_t attr;
+
+ resource = argv[optind];
+ if (!state.us_showall &&
+ dladm_flow_info(handle, resource, &attr) !=
+ DLADM_STATUS_OK) {
+ die("invalid flow: '%s'", resource);
+ }
+ }
+
+ if (state.us_parsable)
+ ofmtflags |= OFMT_PARSABLE;
+ if (resource == NULL && stime == NULL && etime == NULL) {
+ if (!o_arg || (o_arg && strcasecmp(fields_str, "all") == 0))
+ fields_str = all_fields;
+ oferr = ofmt_open(fields_str, history_fields, ofmtflags,
+ 0, &ofmt);
+ } else {
+ if (!o_arg || (o_arg && strcasecmp(fields_str, "all") == 0))
+ fields_str = all_l_fields;
+ oferr = ofmt_open(fields_str, history_l_fields, ofmtflags,
+ 0, &ofmt);
+ }
+
+ flowstat_ofmt_check(oferr, state.us_parsable, ofmt);
+ state.us_ofmt = ofmt;
+
+ if (F_arg && d_arg)
+ die("incompatible -d and -F options");
+
+ if (F_arg && !valid_formatspec(formatspec_str))
+ die("Format specifier %s not supported", formatspec_str);
+
+ if (d_arg) {
+ /* Print log dates */
+ status = dladm_usage_dates(show_history_date,
+ DLADM_LOGTYPE_FLOW, file, resource, &state);
+ } else if (resource == NULL && stime == NULL && etime == NULL &&
+ !F_arg) {
+ /* Print summary */
+ status = dladm_usage_summary(show_history_res,
+ DLADM_LOGTYPE_FLOW, file, &state);
+ } else if (resource != NULL) {
+ /* Print log entries for named resource */
+ status = dladm_walk_usage_res(show_history_time,
+ DLADM_LOGTYPE_FLOW, file, resource, stime, etime, &state);
+ } else {
+ /* Print time and information for each flow */
+ status = dladm_walk_usage_time(show_history_time,
+ DLADM_LOGTYPE_FLOW, file, stime, etime, &state);
+ }
+
+ ofmt_close(ofmt);
+ if (status != DLADM_STATUS_OK)
+ die_dlerr(status, "-h");
+ dladm_close(handle);
+}
+
+static void
+warn(const char *format, ...)
+{
+ va_list alist;
+
+ format = gettext(format);
+ (void) fprintf(stderr, "%s: warning: ", progname);
+
+ va_start(alist, format);
+ (void) vfprintf(stderr, format, alist);
+ va_end(alist);
+
+ (void) putc('\n', stderr);
+}
+
+/* PRINTFLIKE1 */
+static void
+die(const char *format, ...)
+{
+ va_list alist;
+
+ format = gettext(format);
+ (void) fprintf(stderr, "%s: ", progname);
+
+ va_start(alist, format);
+ (void) vfprintf(stderr, format, alist);
+ va_end(alist);
+
+ (void) putc('\n', stderr);
+
+ /* close dladm handle if it was opened */
+ if (handle != NULL)
+ dladm_close(handle);
+
+ exit(EXIT_FAILURE);
+}
+
+static void
+die_optdup(int opt)
+{
+ die("the option -%c cannot be specified more than once", opt);
+}
+
+static void
+die_opterr(int opt, int opterr, const char *usage)
+{
+ switch (opterr) {
+ case ':':
+ die("option '-%c' requires a value\nusage: %s", opt,
+ gettext(usage));
+ break;
+ case '?':
+ default:
+ die("unrecognized option '-%c'\nusage: %s", opt,
+ gettext(usage));
+ break;
+ }
+}
+
+/* PRINTFLIKE2 */
+static void
+die_dlerr(dladm_status_t err, const char *format, ...)
+{
+ va_list alist;
+ char errmsg[DLADM_STRSIZE];
+
+ format = gettext(format);
+ (void) fprintf(stderr, "%s: ", progname);
+
+ va_start(alist, format);
+ (void) vfprintf(stderr, format, alist);
+ va_end(alist);
+ (void) fprintf(stderr, ": %s\n", dladm_status2str(err, errmsg));
+
+ /* close dladm handle if it was opened */
+ if (handle != NULL)
+ dladm_close(handle);
+
+ exit(EXIT_FAILURE);
+}
+
+
+/*
+ * default output callback function that, when invoked from dladm_print_output,
+ * prints string which is offset by of_arg->ofmt_id within buf.
+ */
+static boolean_t
+print_default_cb(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
+{
+ char *value;
+
+ value = (char *)of_arg->ofmt_cbarg + of_arg->ofmt_id;
+ (void) strlcpy(buf, value, bufsize);
+ return (B_TRUE);
+}
+
+static void
+flowstat_ofmt_check(ofmt_status_t oferr, boolean_t parsable,
+ ofmt_handle_t ofmt)
+{
+ char buf[OFMT_BUFSIZE];
+
+ if (oferr == OFMT_SUCCESS)
+ return;
+ (void) ofmt_strerror(ofmt, oferr, buf, sizeof (buf));
+ /*
+ * All errors are considered fatal in parsable mode.
+ * NOMEM errors are always fatal, regardless of mode.
+ * For other errors, we print diagnostics in human-readable
+ * mode and processs what we can.
+ */
+ if (parsable || oferr == OFMT_ENOFIELDS) {
+ ofmt_close(ofmt);
+ die(buf);
+ } else {
+ warn(buf);
+ }
+}