diff options
Diffstat (limited to 'usr/src/cmd/flowstat/flowstat.c')
-rw-r--r-- | usr/src/cmd/flowstat/flowstat.c | 1149 |
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); + } +} |