/*
* 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 <fcntl.h>
#include <stdlib.h>
#include <strings.h>
#include <exacct.h>
#include <net/if.h>
#include <sys/ethernet.h>
#include <libdladm.h>
#define TIMEBUFLEN 20
#define GBIT 1000000000
#define MBIT 1000000
#define KBIT 1000
#define NET_RESET_TOT(tbytes, ttime, tibytes, tobytes, step) { \
(step) = 1; \
(tbytes) = 0; \
(ttime) = 0; \
(tibytes) = 0; \
(tobytes) = 0; \
}
/* Flow/Link Descriptor */
typedef struct net_desc_s {
char net_desc_name[LIFNAMSIZ];
char net_desc_devname[LIFNAMSIZ];
uchar_t net_desc_ehost[ETHERADDRL];
uchar_t net_desc_edest[ETHERADDRL];
ushort_t net_desc_vlan_tpid;
ushort_t net_desc_vlan_tci;
ushort_t net_desc_sap;
ushort_t net_desc_cpuid;
ushort_t net_desc_priority;
uint64_t net_desc_bw_limit;
in6_addr_t net_desc_saddr;
in6_addr_t net_desc_daddr;
boolean_t net_desc_isv4;
in_port_t net_desc_sport;
in_port_t net_desc_dport;
uint8_t net_desc_protocol;
uint8_t net_desc_dsfield;
boolean_t net_desc_newrec;
} net_desc_t;
/* Time structure: Year, Month, Day, Hour, Min, Sec */
typedef struct net_time_s {
int net_time_yr;
int net_time_mon;
int net_time_day;
int net_time_hr;
int net_time_min;
int net_time_sec;
} net_time_t;
/* Flow/Link Stats */
typedef struct net_stat_s {
char net_stat_name[LIFNAMSIZ];
uint64_t net_stat_ibytes;
uint64_t net_stat_obytes;
uint64_t net_stat_ipackets;
uint64_t net_stat_opackets;
uint64_t net_stat_ierrors;
uint64_t net_stat_oerrors;
uint64_t net_stat_tibytes;
uint64_t net_stat_tobytes;
uint64_t net_stat_tipackets;
uint64_t net_stat_topackets;
uint64_t net_stat_tierrors;
uint64_t net_stat_toerrors;
uint64_t net_stat_ctime;
uint64_t net_stat_tdiff;
net_time_t net_stat_time;
struct net_stat_s *net_stat_next;
net_desc_t *net_stat_desc;
boolean_t net_stat_isref;
} net_stat_t;
/* Used to create the [gnu]plot file */
typedef struct net_plot_entry_s {
char *net_pe_name;
uint64_t net_pe_tottime;
uint64_t net_pe_totbytes;
uint64_t net_pe_totibytes;
uint64_t net_pe_totobytes;
uint64_t net_pe_lasttime;
} net_plot_entry_t;
/* Stats entry */
typedef struct net_entry_s {
net_desc_t *net_entry_desc;
net_stat_t *net_entry_shead;
net_stat_t *net_entry_stail;
int net_entry_scount;
net_stat_t *net_entry_sref;
net_stat_t *net_entry_tstats;
uint64_t net_entry_ttime;
struct net_entry_s *net_entry_next;
} net_entry_t;
/* Time sorted list */
typedef struct net_time_entry_s {
net_stat_t *my_time_stat;
struct net_time_entry_s *net_time_entry_next;
struct net_time_entry_s *net_time_entry_prev;
} net_time_entry_t;
/* The parsed table */
typedef struct net_table_s {
/* List of stats */
net_entry_t *net_table_head;
net_entry_t *net_table_tail;
int net_entries;
/*
* Optimization I : List sorted by time, i.e:
* Time Resource ..
* -------------------------------
* 11.15.10 bge0
* 11.15.10 ce0
* 11.15.10 vnic1
* 11.15.15 bge0
* 11.15.15 ce0
* 11.15.15 vnic1
*/
net_time_entry_t *net_time_head;
net_time_entry_t *net_time_tail;
/*
* Optimization II : List sorted by resources
* Time Resource ..
* -------------------------------
* 11.15.10 bge0
* 11.15.15 bge0
* 11.15.10 ce0
* 11.15.15 ce0
* 11.15.10 vnic1
* 11.15.15 vnic1
*/
net_time_entry_t *net_ctime_head;
net_time_entry_t *net_ctime_tail;
/* Common to both the above (sorted) lists. */
int net_time_entries;
} net_table_t;
#define NET_DATE_GREATER 0
#define NET_DATE_LESSER 1
#define NET_DATE_EQUAL 2
#define NET_TIME_GREATER 0
#define NET_TIME_LESSER 1
#define NET_TIME_EQUAL 2
#ifndef _LP64
#define FMT_UINT64 "%-15llu"
#else
#define FMT_UINT64 "%-15lu"
#endif
/*
* Given a timebuf of the form M/D/Y,H:M:S break it into individual elements.
*/
static void
dissect_time(char *tbuf, net_time_t *nt)
{
char *d;
char *t;
char *dd;
char *h;
char *endp;
if (tbuf == NULL || nt == NULL)
return;
d = strtok(tbuf, ","); /* Date */
t = strtok(NULL, ","); /* Time */
/* Month */
dd = strtok(d, "/");
if (dd == NULL)
return;
nt->net_time_mon = strtol(dd, &endp, 10);
/* Day */
dd = strtok(NULL, "/");
if (dd == NULL)
return;
nt->net_time_day = strtol(dd, &endp, 10);
/* Year */
dd = strtok(NULL, "/");
if (dd == NULL)
return;
nt->net_time_yr = strtol(dd, &endp, 10);
if (strlen(dd) <= 2)
nt->net_time_yr += 2000;
if (t == NULL)
return;
/* Hour */
h = strtok(t, ":");
if (h == NULL)
return;
nt->net_time_hr = strtol(h, &endp, 10);
/* Min */
h = strtok(NULL, ":");
if (h == NULL)
return;
nt->net_time_min = strtol(h, &endp, 10);
/* Sec */
h = strtok(NULL, ":");
if (h == NULL)
return;
nt->net_time_sec = strtol(h, &endp, 10);
}
/* Get a stat item from an object in the exacct file */
static void
add_stat_item(ea_object_t *o, net_stat_t *ns)
{
switch (o->eo_catalog & EXT_TYPE_MASK) {
case EXT_STRING:
if ((o->eo_catalog & EXD_DATA_MASK) == EXD_NET_STATS_NAME) {
(void) strncpy(ns->net_stat_name, o->eo_item.ei_string,
strlen(o->eo_item.ei_string));
}
break;
case EXT_UINT64:
if ((o->eo_catalog & EXD_DATA_MASK) == EXD_NET_STATS_CURTIME) {
time_t _time;
char timebuf[TIMEBUFLEN];
ns->net_stat_ctime = o->eo_item.ei_uint64;
_time = ns->net_stat_ctime;
(void) strftime(timebuf, sizeof (timebuf),
"%m/%d/%Y,%T\n", localtime(&_time));
dissect_time(timebuf, &ns->net_stat_time);
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_STATS_IBYTES) {
ns->net_stat_ibytes = o->eo_item.ei_uint64;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_STATS_OBYTES) {
ns->net_stat_obytes = o->eo_item.ei_uint64;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_STATS_IPKTS) {
ns->net_stat_ipackets = o->eo_item.ei_uint64;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_STATS_OPKTS) {
ns->net_stat_opackets = o->eo_item.ei_uint64;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_STATS_IERRPKTS) {
ns->net_stat_ierrors = o->eo_item.ei_uint64;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_STATS_OERRPKTS) {
ns->net_stat_oerrors = o->eo_item.ei_uint64;
}
break;
default:
break;
}
}
/* Get a description item from an object in the exacct file */
static void
add_desc_item(ea_object_t *o, net_desc_t *nd)
{
switch (o->eo_catalog & EXT_TYPE_MASK) {
case EXT_STRING:
if ((o->eo_catalog & EXD_DATA_MASK) == EXD_NET_DESC_NAME) {
(void) strncpy(nd->net_desc_name, o->eo_item.ei_string,
strlen(o->eo_item.ei_string));
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_DEVNAME) {
(void) strncpy(nd->net_desc_devname,
o->eo_item.ei_string, strlen(o->eo_item.ei_string));
}
break;
case EXT_UINT8:
if ((o->eo_catalog & EXD_DATA_MASK) == EXD_NET_DESC_PROTOCOL) {
nd->net_desc_protocol = o->eo_item.ei_uint8;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_DSFIELD) {
nd->net_desc_dsfield = o->eo_item.ei_uint8;
}
break;
case EXT_UINT16:
if ((o->eo_catalog & EXD_DATA_MASK) == EXD_NET_DESC_SPORT) {
nd->net_desc_sport = o->eo_item.ei_uint16;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_DPORT) {
nd->net_desc_dport = o->eo_item.ei_uint16;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_SAP) {
nd->net_desc_sap = o->eo_item.ei_uint16;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_VLAN_TPID) {
nd->net_desc_vlan_tpid = o->eo_item.ei_uint16;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_VLAN_TCI) {
nd->net_desc_vlan_tci = o->eo_item.ei_uint16;
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_PRIORITY) {
nd->net_desc_priority = o->eo_item.ei_uint16;
}
break;
case EXT_UINT32:
if ((o->eo_catalog & EXD_DATA_MASK) == EXD_NET_DESC_V4SADDR ||
(o->eo_catalog & EXD_DATA_MASK) == EXD_NET_DESC_V4DADDR) {
struct in_addr addr;
addr.s_addr = htonl(o->eo_item.ei_uint32);
if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_V4SADDR) {
IN6_INADDR_TO_V4MAPPED(&addr,
&nd->net_desc_saddr);
} else {
IN6_INADDR_TO_V4MAPPED(&addr,
&nd->net_desc_daddr);
}
}
break;
case EXT_UINT64:
if ((o->eo_catalog & EXD_DATA_MASK) == EXD_NET_DESC_BWLIMIT)
nd->net_desc_bw_limit = o->eo_item.ei_uint64;
break;
case EXT_RAW:
if ((o->eo_catalog & EXD_DATA_MASK) == EXD_NET_DESC_V6SADDR ||
(o->eo_catalog & EXD_DATA_MASK) == EXD_NET_DESC_V6DADDR) {
in6_addr_t addr;
addr = *(in6_addr_t *)o->eo_item.ei_raw;
if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_V6SADDR) {
nd->net_desc_saddr = addr;
} else {
nd->net_desc_daddr = addr;
}
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_EHOST) {
bcopy((uchar_t *)o->eo_item.ei_raw, nd->net_desc_ehost,
ETHERADDRL);
} else if ((o->eo_catalog & EXD_DATA_MASK) ==
EXD_NET_DESC_EDEST) {
bcopy((uchar_t *)o->eo_item.ei_raw, nd->net_desc_edest,
ETHERADDRL);
}
break;
default:
break;
}
}
/* Add a description item to the table */
static dladm_status_t
add_desc_to_tbl(net_table_t *net_table, net_desc_t *nd)
{
net_entry_t *ne;
if ((ne = calloc(1, sizeof (net_entry_t))) == NULL)
return (DLADM_STATUS_NOMEM);
if ((ne->net_entry_tstats = calloc(1, sizeof (net_stat_t))) == NULL) {
free(ne);
return (DLADM_STATUS_NOMEM);
}
ne->net_entry_desc = nd;
ne->net_entry_shead = NULL;
ne->net_entry_stail = NULL;
ne->net_entry_scount = 0;
if (net_table->net_table_head == NULL) {
net_table->net_table_head = ne;
net_table->net_table_tail = ne;
} else {
net_table->net_table_tail->net_entry_next = ne;
net_table->net_table_tail = ne;
}
net_table->net_entries++;
return (DLADM_STATUS_OK);
}
/* Compare dates and return if t1 is equal, greater or lesser than t2 */
static int
compare_date(net_time_t *t1, net_time_t *t2)
{
if (t1->net_time_yr == t2->net_time_yr &&
t1->net_time_mon == t2->net_time_mon &&
t1->net_time_day == t2->net_time_day) {
return (NET_DATE_EQUAL);
}
if (t1->net_time_yr > t2->net_time_yr ||
(t1->net_time_yr == t2->net_time_yr &&
t1->net_time_mon > t2->net_time_mon) ||
(t1->net_time_yr == t2->net_time_yr &&
t1->net_time_mon == t2->net_time_mon &&
t1->net_time_day > t2->net_time_day)) {
return (NET_DATE_GREATER);
}
return (NET_DATE_LESSER);
}
/* Compare times and return if t1 is equal, greater or lesser than t2 */
static int
compare_time(net_time_t *t1, net_time_t *t2)
{
int cd;
cd = compare_date(t1, t2);
if (cd == NET_DATE_GREATER) {
return (NET_TIME_GREATER);
} else if (cd == NET_DATE_LESSER) {
return (NET_TIME_LESSER);
} else {
if (t1->net_time_hr == t2->net_time_hr &&
t1->net_time_min == t2->net_time_min &&
t1->net_time_sec == t2->net_time_sec) {
return (NET_TIME_EQUAL);
}
if (t1->net_time_hr > t2->net_time_hr ||
(t1->net_time_hr == t2->net_time_hr &&
t1->net_time_min > t2->net_time_min) ||
(t1->net_time_hr == t2->net_time_hr &&
t1->net_time_min == t2->net_time_min &&
t1->net_time_sec > t2->net_time_sec)) {
return (NET_TIME_GREATER);
}
}
return (NET_TIME_LESSER);
}
/*
* Given a start and end time and start and end entries check if the
* times are within the range, and adjust, if needed.
*/
static dladm_status_t
chk_time_bound(net_time_t *s, net_time_t *e, net_time_t *sns,
net_time_t *ens)
{
if (s != NULL && e != NULL) {
if (compare_time(s, e) == NET_TIME_GREATER)
return (DLADM_STATUS_BADTIMEVAL);
}
if (s != NULL) {
if (compare_time(s, sns) == NET_TIME_LESSER) {
s->net_time_yr = sns->net_time_yr;
s->net_time_mon = sns->net_time_mon;
s->net_time_day = sns->net_time_day;
s->net_time_hr = sns->net_time_hr;
s->net_time_min = sns->net_time_min;
s->net_time_sec = sns->net_time_sec;
}
}
if (e != NULL) {
if (compare_time(e, ens) == NET_TIME_GREATER) {
e->net_time_yr = ens->net_time_yr;
e->net_time_mon = ens->net_time_mon;
e->net_time_day = ens->net_time_day;
e->net_time_hr = ens->net_time_hr;
e->net_time_min = ens->net_time_min;
e->net_time_sec = ens->net_time_sec;
}
}
return (DLADM_STATUS_OK);
}
/*
* Given a start and end time (strings), convert them into net_time_t
* and also check for the range given the head and tail of the list.
* If stime is lower then head or etime is greated than tail, adjust.
*/
static dladm_status_t
get_time_range(net_time_entry_t *head, net_time_entry_t *tail,
net_time_t *st, net_time_t *et, char *stime, char *etime)
{
bzero(st, sizeof (net_time_t));
bzero(et, sizeof (net_time_t));
if (stime == NULL && etime == NULL)
return (0);
if (stime != NULL)
dissect_time(stime, st);
if (etime != NULL)
dissect_time(etime, et);
if (stime != NULL || etime != NULL) {
return (chk_time_bound(stime == NULL ? NULL : st,
etime == NULL ? NULL : et,
&head->my_time_stat->net_stat_time,
&tail->my_time_stat->net_stat_time));
}
return (0);
}
/*
* Walk the list from a given starting point and return when we find
* an entry that is greater or equal to st. lasttime will point to the
* previous time entry.
*/
static void
get_starting_point(net_time_entry_t *head, net_time_entry_t **start,
net_time_t *st, char *stime, uint64_t *lasttime)
{
net_time_entry_t *next = head;
if (head == NULL) {
*start = NULL;
return;
}
if (stime == NULL) {
*start = head;
*lasttime = head->my_time_stat->net_stat_ctime;
return;
}
*start = NULL;
while (next != NULL) {
if (compare_time(st,
&next->my_time_stat->net_stat_time) != NET_TIME_LESSER) {
*lasttime = next->my_time_stat->net_stat_ctime;
next = next->net_time_entry_next;
continue;
}
*start = next;
break;
}
}
/*
* Point entry (pe) functions
*/
/* Clear all the counters. Done after the contents are written to the file */
static void
clear_pe(net_plot_entry_t *pe, int entries, int *pentries)
{
int count;
for (count = 0; count < entries; count++) {
pe[count].net_pe_totbytes = 0;
pe[count].net_pe_totibytes = 0;
pe[count].net_pe_totobytes = 0;
pe[count].net_pe_tottime = 0;
}
*pentries = 0;
}
/* Update an entry in the point entry table */
static void
update_pe(net_plot_entry_t *pe, net_stat_t *nns, int nentries,
int *pentries, uint64_t lasttime)
{
int count;
for (count = 0; count < nentries; count++) {
if (strcmp(pe[count].net_pe_name, nns->net_stat_name) == 0)
break;
}
if (count == nentries)
return;
if (pe[count].net_pe_totbytes == 0)
pe[count].net_pe_lasttime = lasttime;
pe[count].net_pe_totbytes += nns->net_stat_ibytes +
nns->net_stat_obytes;
pe[count].net_pe_tottime += nns->net_stat_tdiff;
pe[count].net_pe_totibytes += nns->net_stat_ibytes;
pe[count].net_pe_totobytes += nns->net_stat_obytes;
(*pentries)++;
}
/* Flush the contents of the point entry table to the file. */
static void
add_pe_to_file(int (*fn)(dladm_usage_t *, void *), net_plot_entry_t *pe,
net_stat_t *ns, int entries, void *arg)
{
int count;
dladm_usage_t usage;
uint64_t tottime;
bcopy(&ns->net_stat_ctime, &usage.du_etime, sizeof (usage.du_etime));
for (count = 0; count < entries; count++) {
bcopy(pe[count].net_pe_name, &usage.du_name,
sizeof (usage.du_name));
bcopy(&pe[count].net_pe_lasttime, &usage.du_stime,
sizeof (usage.du_stime));
usage.du_rbytes = pe[count].net_pe_totibytes;
usage.du_obytes = pe[count].net_pe_totobytes;
tottime = pe[count].net_pe_tottime;
usage.du_bandwidth = (tottime > 0) ?
((pe[count].net_pe_totbytes * 8) / tottime) : 0;
usage.du_last = (count == entries-1);
fn(&usage, arg);
}
}
/*
* Net entry functions
*/
static net_entry_t *
get_ne_from_table(net_table_t *net_table, char *name)
{
int count;
net_desc_t *nd;
net_entry_t *ne = net_table->net_table_head;
for (count = 0; count < net_table->net_entries; count++) {
nd = ne->net_entry_desc;
if (strcmp(name, nd->net_desc_name) == 0)
return (ne);
ne = ne->net_entry_next;
}
return (NULL);
}
/* Get the entry for the descriptor, if it exists */
static net_desc_t *
get_ndesc(net_table_t *net_table, net_desc_t *nd)
{
int count;
net_desc_t *nd1;
net_entry_t *ne = net_table->net_table_head;
for (count = 0; count < net_table->net_entries; count++) {
nd1 = ne->net_entry_desc;
if (strcmp(nd1->net_desc_name, nd->net_desc_name) == 0 &&
strcmp(nd1->net_desc_devname, nd->net_desc_devname) == 0 &&
bcmp(nd1->net_desc_ehost, nd->net_desc_ehost,
ETHERADDRL) == 0 &&
bcmp(nd1->net_desc_edest, nd->net_desc_edest,
ETHERADDRL) == 0 &&
nd1->net_desc_vlan_tpid == nd->net_desc_vlan_tpid &&
nd1->net_desc_vlan_tci == nd->net_desc_vlan_tci &&
nd1->net_desc_sap == nd->net_desc_sap &&
nd1->net_desc_cpuid == nd->net_desc_cpuid &&
nd1->net_desc_priority == nd->net_desc_priority &&
nd1->net_desc_bw_limit == nd->net_desc_bw_limit &&
nd1->net_desc_sport == nd->net_desc_sport &&
nd1->net_desc_dport == nd->net_desc_dport &&
nd1->net_desc_protocol == nd->net_desc_protocol &&
nd1->net_desc_dsfield == nd->net_desc_dsfield &&
IN6_ARE_ADDR_EQUAL(&nd1->net_desc_saddr,
&nd->net_desc_saddr) &&
IN6_ARE_ADDR_EQUAL(&nd1->net_desc_daddr,
&nd->net_desc_daddr)) {
return (nd1);
}
ne = ne->net_entry_next;
}
return (NULL);
}
/*
* Update the stat entries. The stats in the file are cumulative, so in order
* to have increments, we maintain a reference stat entry, which contains
* the stats when the record was first written and a total stat entry, which
* maintains the running count. When we want to add a stat entry, if it
* the reference stat entry, we don't come here. For subsequent entries,
* we get the increment by subtracting the current value from the reference
* stat and the total stat.
*/
static void
update_stats(net_stat_t *ns1, net_entry_t *ne, net_stat_t *ref)
{
/* get the increment */
ns1->net_stat_ibytes -= (ref->net_stat_ibytes + ref->net_stat_tibytes);
ns1->net_stat_obytes -= (ref->net_stat_obytes + ref->net_stat_tobytes);
ns1->net_stat_ipackets -= (ref->net_stat_ipackets +
ref->net_stat_tipackets);
ns1->net_stat_opackets -= (ref->net_stat_opackets +
ref->net_stat_topackets);
ns1->net_stat_ierrors -= (ref->net_stat_ierrors +
ref->net_stat_tierrors);
ns1->net_stat_oerrors -= (ref->net_stat_oerrors +
ref->net_stat_toerrors);
/* update total bytes */
ref->net_stat_tibytes += ns1->net_stat_ibytes;
ref->net_stat_tobytes += ns1->net_stat_obytes;
ref->net_stat_tipackets += ns1->net_stat_ipackets;
ref->net_stat_topackets += ns1->net_stat_opackets;
ref->net_stat_tierrors += ns1->net_stat_ierrors;
ref->net_stat_toerrors += ns1->net_stat_oerrors;
ne->net_entry_tstats->net_stat_ibytes += ns1->net_stat_ibytes;
ne->net_entry_tstats->net_stat_obytes += ns1->net_stat_obytes;
ne->net_entry_tstats->net_stat_ipackets += ns1->net_stat_ipackets;
ne->net_entry_tstats->net_stat_opackets += ns1->net_stat_opackets;
ne->net_entry_tstats->net_stat_ierrors += ns1->net_stat_ierrors;
ne->net_entry_tstats->net_stat_oerrors += ns1->net_stat_oerrors;
}
/* Add the stat entry into the table */
static dladm_status_t
add_stat_to_tbl(net_table_t *net_table, net_stat_t *ns)
{
net_entry_t *ne;
ne = get_ne_from_table(net_table, ns->net_stat_name);
if (ne == NULL)
return (DLADM_STATUS_NOMEM);
/* Ptr to flow desc */
ns->net_stat_desc = ne->net_entry_desc;
if (ns->net_stat_desc->net_desc_newrec) {
ns->net_stat_desc->net_desc_newrec = B_FALSE;
ns->net_stat_isref = B_TRUE;
ne->net_entry_sref = ns;
} else if (ns->net_stat_ibytes < ne->net_entry_sref->net_stat_tibytes ||
(ns->net_stat_obytes < ne->net_entry_sref->net_stat_tobytes)) {
ns->net_stat_isref = B_TRUE;
ne->net_entry_sref = ns;
} else {
ns->net_stat_isref = B_FALSE;
update_stats(ns, ne, ne->net_entry_sref);
}
if (ne->net_entry_shead == NULL) {
ne->net_entry_shead = ns;
ne->net_entry_stail = ns;
} else {
if (!ns->net_stat_isref) {
ne->net_entry_ttime += (ns->net_stat_ctime -
ne->net_entry_stail->net_stat_ctime);
ns->net_stat_tdiff = ns->net_stat_ctime -
ne->net_entry_stail->net_stat_ctime;
}
ne->net_entry_stail->net_stat_next = ns;
ne->net_entry_stail = ns;
}
ne->net_entry_scount++;
return (DLADM_STATUS_OK);
}
/* Add a flow/link descriptor record to the table */
static dladm_status_t
add_desc(net_table_t *net_table, ea_file_t *ef, int nobjs)
{
net_desc_t *nd;
net_desc_t *dnd;
int count;
ea_object_t scratch;
if ((nd = calloc(1, sizeof (net_desc_t))) == NULL)
return (DLADM_STATUS_NOMEM);
nd->net_desc_newrec = B_TRUE;
for (count = 0; count < nobjs; count++) {
if (ea_get_object(ef, &scratch) == -1) {
free(nd);
return (DLADM_STATUS_NOMEM);
}
add_desc_item(&scratch, nd);
}
if ((dnd = get_ndesc(net_table, nd)) != NULL) {
dnd->net_desc_newrec = B_TRUE;
free(nd);
return (DLADM_STATUS_OK);
}
if (add_desc_to_tbl(net_table, nd) != 0) {
free(nd);
return (DLADM_STATUS_NOMEM);
}
return (DLADM_STATUS_OK);
}
/* Make an entry into the time sorted list */
static void
addto_time_list(net_table_t *net_table, net_time_entry_t *nt,
net_time_entry_t *ntc)
{
net_stat_t *ns = nt->my_time_stat;
net_stat_t *ns1;
net_time_entry_t *end;
net_time_t *t1;
int count;
t1 = &ns->net_stat_time;
net_table->net_time_entries++;
if (net_table->net_time_head == NULL) {
net_table->net_time_head = nt;
net_table->net_time_tail = nt;
} else {
net_table->net_time_tail->net_time_entry_next = nt;
nt->net_time_entry_prev = net_table->net_time_tail;
net_table->net_time_tail = nt;
}
if (net_table->net_ctime_head == NULL) {
net_table->net_ctime_head = ntc;
net_table->net_ctime_tail = ntc;
} else {
end = net_table->net_ctime_tail;
count = 0;
while (count < net_table->net_time_entries - 1) {
ns1 = end->my_time_stat;
/* Just add it to the tail */
if (compare_date(t1, &ns1->net_stat_time) ==
NET_DATE_GREATER) {
break;
}
if (strcmp(ns1->net_stat_name, ns->net_stat_name) ==
0) {
ntc->net_time_entry_next =
end->net_time_entry_next;
if (end->net_time_entry_next != NULL) {
end->net_time_entry_next->
net_time_entry_prev = ntc;
} else {
net_table->net_ctime_tail = ntc;
}
end->net_time_entry_next = ntc;
ntc->net_time_entry_prev = end;
return;
}
count++;
end = end->net_time_entry_prev;
}
net_table->net_ctime_tail->net_time_entry_next = ntc;
ntc->net_time_entry_prev = net_table->net_ctime_tail;
net_table->net_ctime_tail = ntc;
}
}
/* Add stat entry into the lists */
static dladm_status_t
add_stats(net_table_t *net_table, ea_file_t *ef, int nobjs)
{
net_stat_t *ns;
int count;
ea_object_t scratch;
net_time_entry_t *nt;
net_time_entry_t *ntc;
if ((ns = calloc(1, sizeof (net_stat_t))) == NULL)
return (DLADM_STATUS_NOMEM);
if ((nt = calloc(1, sizeof (net_time_entry_t))) == NULL) {
free(ns);
return (DLADM_STATUS_NOMEM);
}
if ((ntc = calloc(1, sizeof (net_time_entry_t))) == NULL) {
free(ns);
free(nt);
return (DLADM_STATUS_NOMEM);
}
nt->my_time_stat = ns;
ntc->my_time_stat = ns;
for (count = 0; count < nobjs; count++) {
if (ea_get_object(ef, &scratch) == -1) {
free(ns);
free(nt);
free(ntc);
return (DLADM_STATUS_NOMEM);
}
add_stat_item(&scratch, ns);
}
if (add_stat_to_tbl(net_table, ns) != 0) {
free(ns);
free(nt);
free(ntc);
return (DLADM_STATUS_NOMEM);
}
addto_time_list(net_table, nt, ntc);
return (DLADM_STATUS_OK);
}
/* Free the entire table */
static void
free_logtable(net_table_t *net_table)
{
net_entry_t *head;
net_entry_t *next;
net_stat_t *ns;
net_stat_t *ns1;
net_time_entry_t *thead;
net_time_entry_t *tnext;
thead = net_table->net_time_head;
while (thead != NULL) {
thead->my_time_stat = NULL;
tnext = thead->net_time_entry_next;
thead->net_time_entry_next = NULL;
thead->net_time_entry_prev = NULL;
free(thead);
thead = tnext;
}
net_table->net_time_head = NULL;
net_table->net_time_tail = NULL;
thead = net_table->net_ctime_head;
while (thead != NULL) {
thead->my_time_stat = NULL;
tnext = thead->net_time_entry_next;
thead->net_time_entry_next = NULL;
thead->net_time_entry_prev = NULL;
free(thead);
thead = tnext;
}
net_table->net_ctime_head = NULL;
net_table->net_ctime_tail = NULL;
net_table->net_time_entries = 0;
head = net_table->net_table_head;
while (head != NULL) {
next = head->net_entry_next;
head->net_entry_next = NULL;
ns = head->net_entry_shead;
while (ns != NULL) {
ns1 = ns->net_stat_next;
free(ns);
ns = ns1;
}
head->net_entry_scount = 0;
head->net_entry_sref = NULL;
free(head->net_entry_desc);
free(head->net_entry_tstats);
free(head);
head = next;
}
net_table->net_table_head = NULL;
net_table->net_table_tail = NULL;
net_table->net_time_entries = 0;
free(net_table);
}
/* Parse the exacct file, and return the parsed table. */
static void *
parse_logfile(char *file, int logtype, dladm_status_t *status)
{
ea_file_t ef;
ea_object_t scratch;
net_table_t *net_table;
*status = DLADM_STATUS_OK;
if ((net_table = calloc(1, sizeof (net_table_t))) == NULL) {
*status = DLADM_STATUS_NOMEM;
return (NULL);
}
if (ea_open(&ef, file, NULL, 0, O_RDONLY, 0) == -1) {
*status = DLADM_STATUS_BADARG;
free(net_table);
return (NULL);
}
bzero(&scratch, sizeof (ea_object_t));
while (ea_get_object(&ef, &scratch) != -1) {
if (scratch.eo_type != EO_GROUP) {
(void) ea_free_item(&scratch, EUP_ALLOC);
(void) bzero(&scratch, sizeof (ea_object_t));
continue;
}
/* Read Link Desc/Stat records */
if (logtype == DLADM_LOGTYPE_FLOW) {
/* Flow Descriptor */
if ((scratch.eo_catalog &
EXD_DATA_MASK) == EXD_GROUP_NET_FLOW_DESC) {
(void) add_desc(net_table, &ef,
scratch.eo_group.eg_nobjs - 1);
/* Flow Stats */
} else if ((scratch.eo_catalog &
EXD_DATA_MASK) == EXD_GROUP_NET_FLOW_STATS) {
(void) add_stats(net_table, &ef,
scratch.eo_group.eg_nobjs - 1);
}
} else if (logtype == DLADM_LOGTYPE_LINK) {
/* Link Descriptor */
if ((scratch.eo_catalog &
EXD_DATA_MASK) == EXD_GROUP_NET_LINK_DESC) {
(void) add_desc(net_table, &ef,
scratch.eo_group.eg_nobjs - 1);
/* Link Stats */
} else if ((scratch.eo_catalog &
EXD_DATA_MASK) == EXD_GROUP_NET_LINK_STATS) {
(void) add_stats(net_table, &ef,
scratch.eo_group.eg_nobjs - 1);
}
} else {
if (((scratch.eo_catalog & EXD_DATA_MASK) ==
EXD_GROUP_NET_LINK_DESC) || ((scratch.eo_catalog &
EXD_DATA_MASK) == EXD_GROUP_NET_FLOW_DESC)) {
(void) add_desc(net_table, &ef,
scratch.eo_group.eg_nobjs - 1);
} else if (((scratch.eo_catalog & EXD_DATA_MASK) ==
EXD_GROUP_NET_LINK_STATS) || ((scratch.eo_catalog &
EXD_DATA_MASK) == EXD_GROUP_NET_FLOW_STATS)) {
(void) add_stats(net_table, &ef,
scratch.eo_group.eg_nobjs - 1);
}
}
(void) ea_free_item(&scratch, EUP_ALLOC);
(void) bzero(&scratch, sizeof (ea_object_t));
}
(void) ea_close(&ef);
return ((void *)net_table);
}
/*
* Walk the ctime list. This is used when looking for usage records
* based on a "resource" name.
*/
dladm_status_t
dladm_walk_usage_res(int (*fn)(dladm_usage_t *, void *), int logtype,
char *logfile, char *resource, char *stime, char *etime, void *arg)
{
net_table_t *net_table;
net_time_t st, et;
net_time_entry_t *start;
net_stat_t *ns = NULL;
net_stat_t *nns;
uint64_t tot_time = 0;
uint64_t last_time;
uint64_t tot_bytes = 0;
uint64_t tot_ibytes = 0;
uint64_t tot_obytes = 0;
boolean_t gotstart = B_FALSE;
dladm_status_t status;
dladm_usage_t usage;
int step = 1;
/* Parse the log file */
net_table = parse_logfile(logfile, logtype, &status);
if (net_table == NULL)
return (status);
if (net_table->net_entries == 0)
return (DLADM_STATUS_OK);
start = net_table->net_ctime_head;
/* Time range */
status = get_time_range(net_table->net_ctime_head,
net_table->net_ctime_tail, &st, &et, stime, etime);
if (status != DLADM_STATUS_OK)
return (status);
while (start != NULL) {
nns = start->my_time_stat;
/* Get to the resource we are interested in */
if (strcmp(resource, nns->net_stat_name) != 0) {
start = start->net_time_entry_next;
continue;
}
/* Find the first record */
if (!gotstart) {
get_starting_point(start, &start, &st, stime,
&last_time);
if (start == NULL)
break;
nns = start->my_time_stat;
gotstart = B_TRUE;
}
/* Write one entry and return if we are out of the range */
if (etime != NULL && compare_time(&nns->net_stat_time, &et)
== NET_TIME_GREATER) {
if (tot_bytes != 0) {
bcopy(ns->net_stat_name, &usage.du_name,
sizeof (usage.du_name));
bcopy(&last_time, &usage.du_stime,
sizeof (usage.du_stime));
bcopy(&ns->net_stat_ctime, &usage.du_etime,
sizeof (usage.du_etime));
usage.du_rbytes = tot_ibytes;
usage.du_obytes = tot_obytes;
usage.du_bandwidth = tot_bytes*8/tot_time;
usage.du_last = B_TRUE;
fn(&usage, arg);
}
return (DLADM_STATUS_OK);
}
/*
* If this is a reference entry, just print what we have
* and proceed.
*/
if (nns->net_stat_isref) {
if (tot_bytes != 0) {
bcopy(&nns->net_stat_name, &usage.du_name,
sizeof (usage.du_name));
bcopy(&nns->net_stat_ctime, &usage.du_stime,
sizeof (usage.du_stime));
usage.du_rbytes = tot_ibytes;
usage.du_obytes = tot_obytes;
usage.du_bandwidth = tot_bytes*8/tot_time;
usage.du_last = B_TRUE;
fn(&usage, arg);
NET_RESET_TOT(tot_bytes, tot_time, tot_ibytes,
tot_obytes, step);
}
last_time = nns->net_stat_ctime;
start = start->net_time_entry_next;
continue;
}
ns = nns;
if (--step == 0) {
tot_bytes += ns->net_stat_ibytes + ns->net_stat_obytes;
tot_ibytes += ns->net_stat_ibytes;
tot_obytes += ns->net_stat_obytes;
tot_time += ns->net_stat_tdiff;
bcopy(&ns->net_stat_name, &usage.du_name,
sizeof (usage.du_name));
bcopy(&last_time, &usage.du_stime,
sizeof (usage.du_stime));
bcopy(&ns->net_stat_ctime, &usage.du_etime,
sizeof (usage.du_etime));
usage.du_rbytes = tot_ibytes;
usage.du_obytes = tot_obytes;
usage.du_bandwidth = tot_bytes*8/tot_time;
usage.du_last = B_TRUE;
fn(&usage, arg);
NET_RESET_TOT(tot_bytes, tot_time, tot_ibytes,
tot_obytes, step);
last_time = ns->net_stat_ctime;
} else {
tot_bytes += ns->net_stat_ibytes + ns->net_stat_obytes;
tot_ibytes += ns->net_stat_ibytes;
tot_obytes += ns->net_stat_obytes;
tot_time += ns->net_stat_tdiff;
}
start = start->net_time_entry_next;
}
if (tot_bytes != 0) {
bcopy(&ns->net_stat_name, &usage.du_name,
sizeof (usage.du_name));
bcopy(&last_time, &usage.du_stime,
sizeof (usage.du_stime));
bcopy(&ns->net_stat_ctime, &usage.du_etime,
sizeof (usage.du_etime));
usage.du_rbytes = tot_ibytes;
usage.du_obytes = tot_obytes;
usage.du_bandwidth = tot_bytes*8/tot_time;
usage.du_last = B_TRUE;
fn(&usage, arg);
}
free_logtable(net_table);
return (status);
}
/*
* Walk the time sorted list if a resource is not specified.
*/
dladm_status_t
dladm_walk_usage_time(int (*fn)(dladm_usage_t *, void *), int logtype,
char *logfile, char *stime, char *etime, void *arg)
{
net_table_t *net_table;
net_time_entry_t *start;
net_stat_t *ns = NULL, *nns;
net_time_t st, et, *t1;
net_desc_t *nd;
net_entry_t *ne;
net_plot_entry_t *pe;
int count;
int step = 1;
int nentries = 0, pentries = 0;
uint64_t last_time;
dladm_status_t status;
/* Parse the log file */
net_table = parse_logfile(logfile, logtype, &status);
if (net_table == NULL)
return (status);
if (net_table->net_entries == 0)
return (DLADM_STATUS_OK);
start = net_table->net_time_head;
/* Find the first and last records and starting point */
status = get_time_range(net_table->net_time_head,
net_table->net_time_tail, &st, &et, stime, etime);
if (status != DLADM_STATUS_OK)
return (status);
get_starting_point(start, &start, &st, stime, &last_time);
/*
* Could assert to be non-null, since get_time_range()
* would have adjusted.
*/
if (start == NULL)
return (DLADM_STATUS_BADTIMEVAL);
/*
* Collect entries for all resources in a time slot before
* writing to the file.
*/
nentries = net_table->net_entries;
pe = malloc(sizeof (net_plot_entry_t) * net_table->net_entries + 1);
if (pe == NULL)
return (DLADM_STATUS_NOMEM);
ne = net_table->net_table_head;
for (count = 0; count < nentries; count++) {
nd = ne->net_entry_desc;
pe[count].net_pe_name = nd->net_desc_name;
ne = ne->net_entry_next;
}
clear_pe(pe, nentries, &pentries);
/* Write header to file */
/* add_pe_to_file(fn, pe, ns, nentries, arg); */
t1 = &start->my_time_stat->net_stat_time;
while (start != NULL) {
nns = start->my_time_stat;
/*
* We have crossed the time boundary, check if we need to
* print out now.
*/
if (compare_time(&nns->net_stat_time, t1) ==
NET_TIME_GREATER) {
/* return if we are out of the range */
if (etime != NULL &&
compare_time(&nns->net_stat_time, &et) ==
NET_TIME_GREATER) {
if (pentries > 0) {
add_pe_to_file(fn, pe, ns, nentries,
arg);
clear_pe(pe, nentries, &pentries);
}
free(pe);
return (DLADM_STATUS_OK);
}
/* update the stats from the ns. */
t1 = &nns->net_stat_time;
last_time = ns->net_stat_ctime;
if (--step == 0) {
if (pentries > 0) {
add_pe_to_file(fn, pe, ns, nentries,
arg);
clear_pe(pe, nentries, &pentries);
}
step = 1;
}
}
/*
* if this is a reference entry, just print what we have
* for this resource and proceed. We will end up writing
* the stats for all the entries when we hit a ref element,
* which means 'steps' for some might not be accurate, but
* that is fine, the alternative is to write only the
* resource for which we hit a reference entry.
*/
if (nns->net_stat_isref) {
if (pentries > 0) {
add_pe_to_file(fn, pe, ns, nentries, arg);
clear_pe(pe, nentries, &pentries);
}
step = 1;
} else {
update_pe(pe, nns, nentries, &pentries, last_time);
}
ns = nns;
start = start->net_time_entry_next;
}
if (pentries > 0)
add_pe_to_file(fn, pe, ns, nentries, arg);
free(pe);
free_logtable(net_table);
return (DLADM_STATUS_OK);
}
dladm_status_t
dladm_usage_summary(int (*fn)(dladm_usage_t *, void *), int logtype,
char *logfile, void *arg)
{
net_table_t *net_table;
net_entry_t *ne;
net_desc_t *nd;
net_stat_t *ns;
int count;
dladm_usage_t usage;
dladm_status_t status;
/* Parse the log file */
net_table = parse_logfile(logfile, logtype, &status);
if (net_table == NULL)
return (status);
if (net_table->net_entries == 0)
return (DLADM_STATUS_OK);
ne = net_table->net_table_head;
for (count = 0; count < net_table->net_entries; count++) {
ns = ne->net_entry_tstats;
nd = ne->net_entry_desc;
if (ns->net_stat_ibytes + ns->net_stat_obytes == 0) {
ne = ne->net_entry_next;
continue;
}
bcopy(&nd->net_desc_name, &usage.du_name,
sizeof (usage.du_name));
usage.du_duration = ne->net_entry_ttime;
usage.du_ipackets = ns->net_stat_ipackets;
usage.du_rbytes = ns->net_stat_ibytes;
usage.du_opackets = ns->net_stat_opackets;
usage.du_obytes = ns->net_stat_obytes;
usage.du_bandwidth =
(ns->net_stat_ibytes + ns->net_stat_obytes) * 8 /
usage.du_duration;
usage.du_last = (count == net_table->net_entries-1);
fn(&usage, arg);
ne = ne->net_entry_next;
}
free_logtable(net_table);
return (DLADM_STATUS_OK);
}
/*
* Walk the ctime list and display the dates of the records.
*/
dladm_status_t
dladm_usage_dates(int (*fn)(dladm_usage_t *, void *), int logtype,
char *logfile, char *resource, void *arg)
{
net_table_t *net_table;
net_time_entry_t *start;
net_stat_t *nns;
net_time_t st;
net_time_t *lasttime = NULL;
uint64_t last_time;
boolean_t gotstart = B_FALSE;
dladm_status_t status;
dladm_usage_t usage;
/* Parse the log file */
net_table = parse_logfile(logfile, logtype, &status);
if (net_table == NULL)
return (status);
if (net_table->net_entries == 0)
return (DLADM_STATUS_OK);
start = net_table->net_ctime_head;
while (start != NULL) {
nns = start->my_time_stat;
/* get to the resource we are interested in */
if (resource != NULL) {
if (strcmp(resource, nns->net_stat_name) != 0) {
start = start->net_time_entry_next;
continue;
}
}
/* get the starting point in the logfile */
if (!gotstart) {
get_starting_point(start, &start, &st, NULL,
&last_time);
if (start == NULL)
break;
nns = start->my_time_stat;
gotstart = B_TRUE;
}
if (lasttime == NULL ||
compare_date(&nns->net_stat_time, lasttime) ==
NET_DATE_GREATER) {
bzero(&usage, sizeof (dladm_usage_t));
(void) strlcpy(usage.du_name, nns->net_stat_name,
sizeof (usage.du_name));
bcopy(&nns->net_stat_ctime, &usage.du_stime,
sizeof (usage.du_stime));
fn(&usage, arg);
lasttime = &nns->net_stat_time;
}
start = start->net_time_entry_next;
continue;
}
free_logtable(net_table);
return (status);
}