blob: dec2c20106cb3021af9ae1830e2d6269bd008885 [file] [log] [blame]
/*
* Copyright 2015 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <linux/netdevice.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
// number of samples to print on each log line.
// if you change this there is a printf below which must be adjusted.
#define SAMPLES 8
#ifndef UNIT_TESTS
#define CLOCK_GETTIME clock_gettime
#endif /* UNIT_TESTS */
uint64_t mono_usecs(void)
{
struct timespec ts;
uint64_t usec;
if (CLOCK_GETTIME(CLOCK_MONOTONIC, &ts) < 0) {
perror("clock_gettime(CLOCK_MONOTONIC)");
exit(1);
}
usec = ts.tv_sec * 1000000ULL;
usec += ts.tv_nsec / 1000ULL;
return usec;
}
int netlink_socket()
{
int s;
struct sockaddr_nl snl;
if ((s = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket AF_NETLINK failed");
exit(1);
}
memset(&snl, 0, sizeof(snl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid();
if (bind(s, (struct sockaddr *) &snl, sizeof(snl))) {
perror("bind AF_NETLINK failed");
exit(1);
}
return s;
}
#ifndef UNIT_TESTS
void sendreq(int s, const char *ifname)
{
struct {
struct nlmsghdr nh;
struct ifinfomsg ifi;
char attr[RTA_SPACE(IFNAMSIZ)];
} req;
struct sockaddr_nl snl;
struct rtattr *rta;
if (strlen(ifname) > IFNAMSIZ) {
fprintf(stderr, "interface name is too long.\n");
exit(1);
}
memset(&req, 0, sizeof(req));
req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifi));
req.nh.nlmsg_type = RTM_GETLINK;
req.nh.nlmsg_flags = NLM_F_REQUEST;
req.nh.nlmsg_seq = 1;
req.ifi.ifi_family = AF_PACKET;
rta = (struct rtattr *)&req.attr;
rta->rta_type = IFLA_IFNAME;
rta->rta_len = RTA_LENGTH(strlen(ifname));
snprintf(RTA_DATA(rta), IFNAMSIZ, "%s", ifname);
req.nh.nlmsg_len = NLMSG_ALIGN(req.nh.nlmsg_len) + RTA_ALIGN(rta->rta_len);
memset(&snl, 0, sizeof(snl));
snl.nl_family = AF_NETLINK;
if (sendto(s, &req, req.nh.nlmsg_len, 0,
(struct sockaddr *)&snl, sizeof(snl)) < 0) {
perror("sendto AF_NETLINK failed");
exit(1);
}
}
#endif /* UNIT_TESTS */
#ifndef UNIT_TESTS
void recvresp(int s, uint32_t *tx_bytes, uint32_t *rx_bytes,
uint32_t *tx_pkts, uint32_t *rx_pkts, uint32_t *rx_multipkts)
{
ssize_t len;
unsigned char buf[4096];
struct nlmsghdr *nh = (struct nlmsghdr *)buf;
struct ifinfomsg *ifmsg;
struct rtattr *attr;
int attrlen;
len = recv(s, buf, sizeof(buf), 0);
while (NLMSG_OK(nh, len)) {
struct rtnl_link_stats *stats;
if (nh->nlmsg_type == NLMSG_ERROR) {
fprintf(stderr, "NLMSG_ERROR\n");
exit(1);
}
ifmsg = NLMSG_DATA(nh);
attr = IFLA_RTA(ifmsg);
attrlen = NLMSG_PAYLOAD(nh, sizeof(struct ifinfomsg));
while (RTA_OK(attr, attrlen)) {
if (attr->rta_type == IFLA_STATS) {
stats = (struct rtnl_link_stats *)RTA_DATA(attr);
*rx_bytes = stats->rx_bytes;
*tx_bytes = stats->tx_bytes;
*tx_pkts = stats->tx_packets;
*rx_pkts = stats->rx_packets;
*rx_multipkts = stats->multicast;
}
attr = RTA_NEXT(attr, attrlen);
}
nh = NLMSG_NEXT(nh, len);
}
}
#endif /* UNIT_TESTS */
struct saved_counters {
uint32_t tx_bytes;
uint32_t rx_bytes;
uint32_t tx_pkts;
uint32_t rx_unipkts;
uint32_t rx_multipkts;
};
void accumulate_stats(int s, double delta, const char *interface,
double *tx_kbps, double *rx_kbps, double *tx_pps,
double *rx_uni_pps, double *rx_multi_pps,
struct saved_counters *old)
{
uint32_t tx_bytes, rx_bytes, tx_pkts, rx_pkts, rx_multipkts;
uint32_t tx_bytes2, rx_bytes2, tx_pkts2, rx_pkts2, rx_multipkts2;
uint32_t rx_unipkts;
static int max_underflow_log = 10;
sendreq(s, interface);
recvresp(s, &tx_bytes, &rx_bytes, &tx_pkts, &rx_pkts, &rx_multipkts);
/*
* Most hardware platforms do not have an RX unicast packet counter, they
* have an overall RX counter and they have a multicast counter. We cannot
* read the two counters atomically, the hardware does not provide a way
* to do so.
*
* Therefore there is a race condition. Assume no packets have arrived, so
* we read an rx_packets count of zero. At that instant a multicast packet
* arrives, so rx_packets and rx_multipackets both increment to 1. We've
* already read the rx_packets counter so its too late for that, but we
* read the rx_multipkts counter of 1.
*
* rx_unipkts = (rx_packets - rx_multipkts) is 4,294,967,295, which
* is very wrong.
*
* To resolve this, we read the counters twice. We use the rx_multipkts
* count from the first read, and the rx_packets count from the second,
* to ensure that rx_packets has a chance to update.
*/
sendreq(s, interface);
recvresp(s, &tx_bytes2, &rx_bytes2, &tx_pkts2, &rx_pkts2, &rx_multipkts2);
*tx_kbps = (8.0 * (tx_bytes - old->tx_bytes) / 1000.0) / delta;
*rx_kbps = (8.0 * (rx_bytes - old->rx_bytes) / 1000.0) / delta;
*tx_pps = (tx_pkts - old->tx_pkts) / delta;
rx_unipkts = rx_pkts2 - rx_multipkts;
*rx_uni_pps = (rx_unipkts - old->rx_unipkts) / delta;
*rx_multi_pps = (rx_multipkts - old->rx_multipkts) / delta;
if (*rx_uni_pps > (double)0x80000000) {
*rx_uni_pps = 0;
if (max_underflow_log > 0) {
printf("rx_unipkts underflow: pkts2 %u multipkts %u old_unipkts %u\n",
rx_pkts2, rx_multipkts, old->rx_unipkts);
max_underflow_log--;
}
}
old->tx_bytes = tx_bytes;
old->rx_bytes = rx_bytes;
old->tx_pkts = tx_pkts;
old->rx_unipkts = rx_unipkts;
old->rx_multipkts = rx_multipkts;
}
#ifndef UNIT_TESTS
void usage(const char *progname)
{
fprintf(stderr, "usage: %s -i foo0\n", progname);
fprintf(stderr, "\t-i foo0: network interface to monitor.\n");
exit(1);
}
int main(int argc, char **argv)
{
int c;
const char *interface = NULL;
int s = netlink_socket();
uint64_t start;
double junk;
struct saved_counters old;
int i = 0;
while ((c = getopt(argc, argv, "i:")) >= 0) {
switch (c) {
case 'i':
interface = optarg;
break;
default:
case '?':
usage(argv[0]);
break;
}
}
if (!interface) {
usage(argv[0]);
}
setlinebuf(stdout);
start = mono_usecs();
accumulate_stats(s, 1.0, interface, &junk, &junk, &junk, &junk, &junk, &old);
while (1) {
uint64_t timestamp;
double delta;
double tx_kbps[SAMPLES];
double rx_kbps[SAMPLES];
double tx_pps[SAMPLES];
double rx_uni_pps[SAMPLES];
double rx_multi_pps[SAMPLES];
sleep(1);
timestamp = mono_usecs();
delta = (timestamp - start) / 1000000.0;
accumulate_stats(s, delta, interface, &tx_kbps[i], &rx_kbps[i], &tx_pps[i],
&rx_uni_pps[i], &rx_multi_pps[i], &old);
i++;
if (i == SAMPLES) {
printf("%s TX Kbps %.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f\n",
interface,
tx_kbps[0], tx_kbps[1], tx_kbps[2], tx_kbps[3],
tx_kbps[4], tx_kbps[5], tx_kbps[6], tx_kbps[7]);
printf("%s RX Kbps %.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f\n",
interface,
rx_kbps[0], rx_kbps[1], rx_kbps[2], rx_kbps[3],
rx_kbps[4], rx_kbps[5], rx_kbps[6], rx_kbps[7]);
printf("%s TX pps %.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f\n",
interface,
tx_pps[0], tx_pps[1], tx_pps[2], tx_pps[3],
tx_pps[4], tx_pps[5], tx_pps[6], tx_pps[7]);
printf("%s RX unipps %.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f\n",
interface,
rx_uni_pps[0], rx_uni_pps[1], rx_uni_pps[2], rx_uni_pps[3],
rx_uni_pps[4], rx_uni_pps[5], rx_uni_pps[6], rx_uni_pps[7]);
printf("%s RX multipps %.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f,%.0f\n",
interface,
rx_multi_pps[0], rx_multi_pps[1], rx_multi_pps[2], rx_multi_pps[3],
rx_multi_pps[4], rx_multi_pps[5], rx_multi_pps[6], rx_multi_pps[7]);
i = 0;
}
start = timestamp;
}
}
#endif /* UNIT_TESTS */