netdev: add per-queue statistics
[linux-2.6-microblaze.git] / net / core / netdev-genl.c
index 918b109..7fa75e1 100644 (file)
@@ -8,6 +8,7 @@
 #include <net/xdp.h>
 #include <net/xdp_sock.h>
 #include <net/netdev_rx_queue.h>
+#include <net/netdev_queues.h>
 #include <net/busy_poll.h>
 
 #include "netdev-genl-gen.h"
@@ -460,6 +461,218 @@ int netdev_nl_queue_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
        return err;
 }
 
+#define NETDEV_STAT_NOT_SET            (~0ULL)
+
+static void netdev_nl_stats_add(void *_sum, const void *_add, size_t size)
+{
+       const u64 *add = _add;
+       u64 *sum = _sum;
+
+       while (size) {
+               if (*add != NETDEV_STAT_NOT_SET && *sum != NETDEV_STAT_NOT_SET)
+                       *sum += *add;
+               sum++;
+               add++;
+               size -= 8;
+       }
+}
+
+static int netdev_stat_put(struct sk_buff *rsp, unsigned int attr_id, u64 value)
+{
+       if (value == NETDEV_STAT_NOT_SET)
+               return 0;
+       return nla_put_uint(rsp, attr_id, value);
+}
+
+static int
+netdev_nl_stats_write_rx(struct sk_buff *rsp, struct netdev_queue_stats_rx *rx)
+{
+       if (netdev_stat_put(rsp, NETDEV_A_QSTATS_RX_PACKETS, rx->packets) ||
+           netdev_stat_put(rsp, NETDEV_A_QSTATS_RX_BYTES, rx->bytes))
+               return -EMSGSIZE;
+       return 0;
+}
+
+static int
+netdev_nl_stats_write_tx(struct sk_buff *rsp, struct netdev_queue_stats_tx *tx)
+{
+       if (netdev_stat_put(rsp, NETDEV_A_QSTATS_TX_PACKETS, tx->packets) ||
+           netdev_stat_put(rsp, NETDEV_A_QSTATS_TX_BYTES, tx->bytes))
+               return -EMSGSIZE;
+       return 0;
+}
+
+static int
+netdev_nl_stats_queue(struct net_device *netdev, struct sk_buff *rsp,
+                     u32 q_type, int i, const struct genl_info *info)
+{
+       const struct netdev_stat_ops *ops = netdev->stat_ops;
+       struct netdev_queue_stats_rx rx;
+       struct netdev_queue_stats_tx tx;
+       void *hdr;
+
+       hdr = genlmsg_iput(rsp, info);
+       if (!hdr)
+               return -EMSGSIZE;
+       if (nla_put_u32(rsp, NETDEV_A_QSTATS_IFINDEX, netdev->ifindex) ||
+           nla_put_u32(rsp, NETDEV_A_QSTATS_QUEUE_TYPE, q_type) ||
+           nla_put_u32(rsp, NETDEV_A_QSTATS_QUEUE_ID, i))
+               goto nla_put_failure;
+
+       switch (q_type) {
+       case NETDEV_QUEUE_TYPE_RX:
+               memset(&rx, 0xff, sizeof(rx));
+               ops->get_queue_stats_rx(netdev, i, &rx);
+               if (!memchr_inv(&rx, 0xff, sizeof(rx)))
+                       goto nla_cancel;
+               if (netdev_nl_stats_write_rx(rsp, &rx))
+                       goto nla_put_failure;
+               break;
+       case NETDEV_QUEUE_TYPE_TX:
+               memset(&tx, 0xff, sizeof(tx));
+               ops->get_queue_stats_tx(netdev, i, &tx);
+               if (!memchr_inv(&tx, 0xff, sizeof(tx)))
+                       goto nla_cancel;
+               if (netdev_nl_stats_write_tx(rsp, &tx))
+                       goto nla_put_failure;
+               break;
+       }
+
+       genlmsg_end(rsp, hdr);
+       return 0;
+
+nla_cancel:
+       genlmsg_cancel(rsp, hdr);
+       return 0;
+nla_put_failure:
+       genlmsg_cancel(rsp, hdr);
+       return -EMSGSIZE;
+}
+
+static int
+netdev_nl_stats_by_queue(struct net_device *netdev, struct sk_buff *rsp,
+                        const struct genl_info *info,
+                        struct netdev_nl_dump_ctx *ctx)
+{
+       const struct netdev_stat_ops *ops = netdev->stat_ops;
+       int i, err;
+
+       if (!(netdev->flags & IFF_UP))
+               return 0;
+
+       i = ctx->rxq_idx;
+       while (ops->get_queue_stats_rx && i < netdev->real_num_rx_queues) {
+               err = netdev_nl_stats_queue(netdev, rsp, NETDEV_QUEUE_TYPE_RX,
+                                           i, info);
+               if (err)
+                       return err;
+               ctx->rxq_idx = i++;
+       }
+       i = ctx->txq_idx;
+       while (ops->get_queue_stats_tx && i < netdev->real_num_tx_queues) {
+               err = netdev_nl_stats_queue(netdev, rsp, NETDEV_QUEUE_TYPE_TX,
+                                           i, info);
+               if (err)
+                       return err;
+               ctx->txq_idx = i++;
+       }
+
+       ctx->rxq_idx = 0;
+       ctx->txq_idx = 0;
+       return 0;
+}
+
+static int
+netdev_nl_stats_by_netdev(struct net_device *netdev, struct sk_buff *rsp,
+                         const struct genl_info *info)
+{
+       struct netdev_queue_stats_rx rx_sum, rx;
+       struct netdev_queue_stats_tx tx_sum, tx;
+       const struct netdev_stat_ops *ops;
+       void *hdr;
+       int i;
+
+       ops = netdev->stat_ops;
+       /* Netdev can't guarantee any complete counters */
+       if (!ops->get_base_stats)
+               return 0;
+
+       memset(&rx_sum, 0xff, sizeof(rx_sum));
+       memset(&tx_sum, 0xff, sizeof(tx_sum));
+
+       ops->get_base_stats(netdev, &rx_sum, &tx_sum);
+
+       /* The op was there, but nothing reported, don't bother */
+       if (!memchr_inv(&rx_sum, 0xff, sizeof(rx_sum)) &&
+           !memchr_inv(&tx_sum, 0xff, sizeof(tx_sum)))
+               return 0;
+
+       hdr = genlmsg_iput(rsp, info);
+       if (!hdr)
+               return -EMSGSIZE;
+       if (nla_put_u32(rsp, NETDEV_A_QSTATS_IFINDEX, netdev->ifindex))
+               goto nla_put_failure;
+
+       for (i = 0; i < netdev->real_num_rx_queues; i++) {
+               memset(&rx, 0xff, sizeof(rx));
+               if (ops->get_queue_stats_rx)
+                       ops->get_queue_stats_rx(netdev, i, &rx);
+               netdev_nl_stats_add(&rx_sum, &rx, sizeof(rx));
+       }
+       for (i = 0; i < netdev->real_num_tx_queues; i++) {
+               memset(&tx, 0xff, sizeof(tx));
+               if (ops->get_queue_stats_tx)
+                       ops->get_queue_stats_tx(netdev, i, &tx);
+               netdev_nl_stats_add(&tx_sum, &tx, sizeof(tx));
+       }
+
+       if (netdev_nl_stats_write_rx(rsp, &rx_sum) ||
+           netdev_nl_stats_write_tx(rsp, &tx_sum))
+               goto nla_put_failure;
+
+       genlmsg_end(rsp, hdr);
+       return 0;
+
+nla_put_failure:
+       genlmsg_cancel(rsp, hdr);
+       return -EMSGSIZE;
+}
+
+int netdev_nl_qstats_get_dumpit(struct sk_buff *skb,
+                               struct netlink_callback *cb)
+{
+       struct netdev_nl_dump_ctx *ctx = netdev_dump_ctx(cb);
+       const struct genl_info *info = genl_info_dump(cb);
+       struct net *net = sock_net(skb->sk);
+       struct net_device *netdev;
+       unsigned int scope;
+       int err = 0;
+
+       scope = 0;
+       if (info->attrs[NETDEV_A_QSTATS_SCOPE])
+               scope = nla_get_uint(info->attrs[NETDEV_A_QSTATS_SCOPE]);
+
+       rtnl_lock();
+       for_each_netdev_dump(net, netdev, ctx->ifindex) {
+               if (!netdev->stat_ops)
+                       continue;
+
+               switch (scope) {
+               case 0:
+                       err = netdev_nl_stats_by_netdev(netdev, skb, info);
+                       break;
+               case NETDEV_QSTATS_SCOPE_QUEUE:
+                       err = netdev_nl_stats_by_queue(netdev, skb, info, ctx);
+                       break;
+               }
+               if (err < 0)
+                       break;
+       }
+       rtnl_unlock();
+
+       return err;
+}
+
 static int netdev_genl_netdevice_event(struct notifier_block *nb,
                                       unsigned long event, void *ptr)
 {