diff --git a/misc/ifstat.c b/misc/ifstat.c index 5bcbcc8e..94671191 100644 --- a/misc/ifstat.c +++ b/misc/ifstat.c @@ -34,6 +34,7 @@ #include "libnetlink.h" #include "json_writer.h" #include "SNAPSHOT.h" +#include "utils.h" int dump_zeros; int reset_history; @@ -48,17 +49,21 @@ int pretty; double W; char **patterns; int npatterns; +bool is_extended; +int filter_type; +int sub_type; char info_source[128]; int source_mismatch; #define MAXS (sizeof(struct rtnl_link_stats)/sizeof(__u32)) +#define NO_SUB_TYPE 0xffff struct ifstat_ent { struct ifstat_ent *next; char *name; int ifindex; - unsigned long long val[MAXS]; + __u64 val[MAXS]; double rate[MAXS]; __u32 ival[MAXS]; }; @@ -106,6 +111,48 @@ static int match(const char *id) return 0; } +static int get_nlmsg_extended(const struct sockaddr_nl *who, + struct nlmsghdr *m, void *arg) +{ + struct if_stats_msg *ifsm = NLMSG_DATA(m); + struct rtattr *tb[IFLA_STATS_MAX+1]; + int len = m->nlmsg_len; + struct ifstat_ent *n; + + if (m->nlmsg_type != RTM_NEWSTATS) + return 0; + + len -= NLMSG_LENGTH(sizeof(*ifsm)); + if (len < 0) + return -1; + + parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len); + if (tb[filter_type] == NULL) + return 0; + + n = malloc(sizeof(*n)); + if (!n) + abort(); + + n->ifindex = ifsm->ifindex; + n->name = strdup(ll_index_to_name(ifsm->ifindex)); + + if (sub_type == NO_SUB_TYPE) { + memcpy(&n->val, RTA_DATA(tb[filter_type]), sizeof(n->val)); + } else { + struct rtattr *attr; + + attr = parse_rtattr_one_nested(sub_type, tb[filter_type]); + if (attr == NULL) + return 0; + memcpy(&n->val, RTA_DATA(attr), sizeof(n->val)); + } + memset(&n->rate, 0, sizeof(n->rate)); + n->next = kern_db; + kern_db = n; + return 0; +} + static int get_nlmsg(const struct sockaddr_nl *who, struct nlmsghdr *m, void *arg) { @@ -147,18 +194,34 @@ static void load_info(void) { struct ifstat_ent *db, *n; struct rtnl_handle rth; + __u32 filter_mask; if (rtnl_open(&rth, 0) < 0) exit(1); - if (rtnl_wilddump_request(&rth, AF_INET, RTM_GETLINK) < 0) { - perror("Cannot send dump request"); - exit(1); - } + if (is_extended) { + ll_init_map(&rth); + filter_mask = IFLA_STATS_FILTER_BIT(filter_type); + if (rtnl_wilddump_stats_req_filter(&rth, AF_UNSPEC, RTM_GETSTATS, + filter_mask) < 0) { + perror("Cannot send dump request"); + exit(1); + } - if (rtnl_dump_filter(&rth, get_nlmsg, NULL) < 0) { - fprintf(stderr, "Dump terminated\n"); - exit(1); + if (rtnl_dump_filter(&rth, get_nlmsg_extended, NULL) < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + } else { + if (rtnl_wilddump_request(&rth, AF_INET, RTM_GETLINK) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (rtnl_dump_filter(&rth, get_nlmsg, NULL) < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } } rtnl_close(&rth); @@ -553,10 +616,17 @@ static void update_db(int interval) } for (i = 0; i < MAXS; i++) { double sample; - unsigned long incr = h1->ival[i] - n->ival[i]; + __u64 incr; + + if (is_extended) { + incr = h1->val[i] - n->val[i]; + n->val[i] = h1->val[i]; + } else { + incr = (__u32) (h1->ival[i] - n->ival[i]); + n->val[i] += incr; + n->ival[i] = h1->ival[i]; + } - n->val[i] += incr; - n->ival[i] = h1->ival[i]; sample = (double)(incr*1000)/interval; if (interval >= scan_interval) { n->rate[i] += W*(sample-n->rate[i]); @@ -656,6 +726,47 @@ static int verify_forging(int fd) return -1; } +static void xstat_usage(void) +{ + fprintf(stderr, +"Usage: ifstat supported xstats:\n"); +} + +struct extended_stats_options_t { + char *name; + int id; + int sub_type; +}; + +/* Note: if one xstat name is subset of another, it should be before it in this + * list. + * Name length must be under 64 chars. + */ +static const struct extended_stats_options_t extended_stats_options[] = { +}; + +static const char *get_filter_type(const char *name) +{ + int name_len; + int i; + + name_len = strlen(name); + for (i = 0; i < ARRAY_SIZE(extended_stats_options); i++) { + const struct extended_stats_options_t *xstat; + + xstat = &extended_stats_options[i]; + if (strncmp(name, xstat->name, name_len) == 0) { + filter_type = xstat->id; + sub_type = xstat->sub_type; + return xstat->name; + } + } + + fprintf(stderr, "invalid ifstat extension %s\n", name); + xstat_usage(); + return NULL; +} + static void usage(void) __attribute__((noreturn)); static void usage(void) @@ -673,7 +784,8 @@ static void usage(void) " -s, --noupdate don't update history\n" " -t, --interval=SECS report average over the last SECS\n" " -V, --version output version information\n" -" -z, --zeros show entries with zero activity\n"); +" -z, --zeros show entries with zero activity\n" +" -x, --extended=TYPE show extended stats of TYPE\n"); exit(-1); } @@ -691,6 +803,7 @@ static const struct option longopts[] = { { "interval", 1, 0, 't' }, { "version", 0, 0, 'V' }, { "zeros", 0, 0, 'z' }, + { "extended", 1, 0, 'x'}, { 0 } }; @@ -699,10 +812,12 @@ int main(int argc, char *argv[]) char hist_name[128]; struct sockaddr_un sun; FILE *hist_fp = NULL; + const char *stats_type = NULL; int ch; int fd; - while ((ch = getopt_long(argc, argv, "hjpvVzrnasd:t:e", + is_extended = false; + while ((ch = getopt_long(argc, argv, "hjpvVzrnasd:t:ex:", longopts, NULL)) != EOF) { switch (ch) { case 'z': @@ -743,6 +858,10 @@ int main(int argc, char *argv[]) exit(-1); } break; + case 'x': + stats_type = optarg; + is_extended = true; + break; case 'v': case 'V': printf("ifstat utility, iproute2-ss%s\n", SNAPSHOT); @@ -757,6 +876,12 @@ int main(int argc, char *argv[]) argc -= optind; argv += optind; + if (stats_type) { + stats_type = get_filter_type(stats_type); + if (!stats_type) + exit(-1); + } + sun.sun_family = AF_UNIX; sun.sun_path[0] = 0; sprintf(sun.sun_path+1, "ifstat%d", getuid()); @@ -795,8 +920,13 @@ int main(int argc, char *argv[]) snprintf(hist_name, sizeof(hist_name), "%s", getenv("IFSTAT_HISTORY")); else - snprintf(hist_name, sizeof(hist_name), - "%s/.ifstat.u%d", P_tmpdir, getuid()); + if (!stats_type) + snprintf(hist_name, sizeof(hist_name), + "%s/.ifstat.u%d", P_tmpdir, getuid()); + else + snprintf(hist_name, sizeof(hist_name), + "%s/.%s_ifstat.u%d", P_tmpdir, stats_type, + getuid()); if (reset_history) unlink(hist_name);