ethtool: add interface to read RMON stats
authorJakub Kicinski <kuba@kernel.org>
Fri, 16 Apr 2021 19:27:42 +0000 (12:27 -0700)
committerDavid S. Miller <davem@davemloft.net>
Fri, 16 Apr 2021 23:59:20 +0000 (16:59 -0700)
Most devices maintain RMON (RFC 2819) stats - particularly
the "histogram" of packets received by size. Unlike other
RFCs which duplicate IEEE stats, the short/oversized frame
counters in RMON don't seem to match IEEE stats 1-to-1 either,
so expose those, too. Do not expose basic packet, CRC errors
etc - those are already otherwise covered.

Because standard defines packet ranges only up to 1518, and
everything above that should theoretically be "oversized"
- devices often create their own ranges.

Going beyond what the RFC defines - expose the "histogram"
in the Tx direction (assume for now that the ranges will
be the same).

Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/ethtool.h
include/uapi/linux/ethtool.h
include/uapi/linux/ethtool_netlink.h
net/ethtool/netlink.h
net/ethtool/stats.c
net/ethtool/strset.c

index 1ca6b83..e030f75 100644 (file)
@@ -346,6 +346,44 @@ struct ethtool_fec_stats {
        } corrected_blocks, uncorrectable_blocks, corrected_bits;
 };
 
+/**
+ * struct ethtool_rmon_hist_range - byte range for histogram statistics
+ * @low: low bound of the bucket (inclusive)
+ * @high: high bound of the bucket (inclusive)
+ */
+struct ethtool_rmon_hist_range {
+       u16 low;
+       u16 high;
+};
+
+#define ETHTOOL_RMON_HIST_MAX  10
+
+/**
+ * struct ethtool_rmon_stats - selected RMON (RFC 2819) statistics
+ * @undersize_pkts: Equivalent to `etherStatsUndersizePkts` from the RFC.
+ * @oversize_pkts: Equivalent to `etherStatsOversizePkts` from the RFC.
+ * @fragments: Equivalent to `etherStatsFragments` from the RFC.
+ * @jabbers: Equivalent to `etherStatsJabbers` from the RFC.
+ * @hist: Packet counter for packet length buckets (e.g.
+ *     `etherStatsPkts128to255Octets` from the RFC).
+ * @hist_tx: Tx counters in similar form to @hist, not defined in the RFC.
+ *
+ * Selection of RMON (RFC 2819) statistics which are not exposed via different
+ * APIs, primarily the packet-length-based counters.
+ * Unfortunately different designs choose different buckets beyond
+ * the 1024B mark (jumbo frame teritory), so the definition of the bucket
+ * ranges is left to the driver.
+ */
+struct ethtool_rmon_stats {
+       u64 undersize_pkts;
+       u64 oversize_pkts;
+       u64 fragments;
+       u64 jabbers;
+
+       u64 hist[ETHTOOL_RMON_HIST_MAX];
+       u64 hist_tx[ETHTOOL_RMON_HIST_MAX];
+};
+
 #define ETH_MODULE_EEPROM_PAGE_LEN     128
 #define ETH_MODULE_MAX_I2C_ADDRESS     0x7f
 
@@ -534,6 +572,8 @@ struct ethtool_module_eeprom {
  * @get_eth_phy_stats: Query some of the IEEE 802.3 PHY statistics.
  * @get_eth_mac_stats: Query some of the IEEE 802.3 MAC statistics.
  * @get_eth_ctrl_stats: Query some of the IEEE 802.3 MAC Ctrl statistics.
+ * @get_rmon_stats: Query some of the RMON (RFC 2819) statistics.
+ *     Set %ranges to a pointer to zero-terminated array of byte ranges.
  *
  * All operations are optional (i.e. the function pointer may be set
  * to %NULL) and callers must take this into account.  Callers must
@@ -650,6 +690,9 @@ struct ethtool_ops {
                                     struct ethtool_eth_mac_stats *mac_stats);
        void    (*get_eth_ctrl_stats)(struct net_device *dev,
                                      struct ethtool_eth_ctrl_stats *ctrl_stats);
+       void    (*get_rmon_stats)(struct net_device *dev,
+                                 struct ethtool_rmon_stats *rmon_stats,
+                                 const struct ethtool_rmon_hist_range **ranges);
 };
 
 int ethtool_check_ops(const struct ethtool_ops *ops);
index 9cb8df8..cfef6b0 100644 (file)
@@ -673,6 +673,7 @@ enum ethtool_link_ext_substate_cable_issue {
  * @ETH_SS_STATS_ETH_PHY: names of IEEE 802.3 PHY statistics
  * @ETH_SS_STATS_ETH_MAC: names of IEEE 802.3 MAC statistics
  * @ETH_SS_STATS_ETH_CTRL: names of IEEE 802.3 MAC Control statistics
+ * @ETH_SS_STATS_RMON: names of RMON statistics
  *
  * @ETH_SS_COUNT: number of defined string sets
  */
@@ -697,6 +698,7 @@ enum ethtool_stringset {
        ETH_SS_STATS_ETH_PHY,
        ETH_SS_STATS_ETH_MAC,
        ETH_SS_STATS_ETH_CTRL,
+       ETH_SS_STATS_RMON,
 
        /* add new constants above here */
        ETH_SS_COUNT
index 2ea5f04..825cfda 100644 (file)
@@ -700,6 +700,7 @@ enum {
        ETHTOOL_STATS_ETH_PHY,
        ETHTOOL_STATS_ETH_MAC,
        ETHTOOL_STATS_ETH_CTRL,
+       ETHTOOL_STATS_RMON,
 
        /* add new constants above here */
        __ETHTOOL_STATS_CNT
@@ -714,6 +715,13 @@ enum {
 
        ETHTOOL_A_STATS_GRP_STAT,               /* nest */
 
+       ETHTOOL_A_STATS_GRP_HIST_RX,            /* nest */
+       ETHTOOL_A_STATS_GRP_HIST_TX,            /* nest */
+
+       ETHTOOL_A_STATS_GRP_HIST_BKT_LOW,       /* u32 */
+       ETHTOOL_A_STATS_GRP_HIST_BKT_HI,        /* u32 */
+       ETHTOOL_A_STATS_GRP_HIST_VAL,           /* u64 */
+
        /* add new constants above here */
        __ETHTOOL_A_STATS_GRP_CNT,
        ETHTOOL_A_STATS_GRP_MAX = (__ETHTOOL_A_STATS_CNT - 1)
@@ -793,6 +801,21 @@ enum {
        ETHTOOL_A_STATS_ETH_CTRL_MAX = (__ETHTOOL_A_STATS_ETH_CTRL_CNT - 1)
 };
 
+enum {
+       /* etherStatsUndersizePkts */
+       ETHTOOL_A_STATS_RMON_UNDERSIZE,
+       /* etherStatsOversizePkts */
+       ETHTOOL_A_STATS_RMON_OVERSIZE,
+       /* etherStatsFragments */
+       ETHTOOL_A_STATS_RMON_FRAG,
+       /* etherStatsJabbers */
+       ETHTOOL_A_STATS_RMON_JABBER,
+
+       /* add new constants above here */
+       __ETHTOOL_A_STATS_RMON_CNT,
+       ETHTOOL_A_STATS_RMON_MAX = (__ETHTOOL_A_STATS_RMON_CNT - 1)
+};
+
 /* generic netlink info */
 #define ETHTOOL_GENL_NAME "ethtool"
 #define ETHTOOL_GENL_VERSION 1
index febfa61..bed3afd 100644 (file)
@@ -405,5 +405,6 @@ extern const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN];
 extern const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN];
 extern const char stats_eth_mac_names[__ETHTOOL_A_STATS_ETH_MAC_CNT][ETH_GSTRING_LEN];
 extern const char stats_eth_ctrl_names[__ETHTOOL_A_STATS_ETH_CTRL_CNT][ETH_GSTRING_LEN];
+extern const char stats_rmon_names[__ETHTOOL_A_STATS_RMON_CNT][ETH_GSTRING_LEN];
 
 #endif /* _NET_ETHTOOL_NETLINK_H */
index f4fded6..acb2b08 100644 (file)
@@ -17,6 +17,8 @@ struct stats_reply_data {
        struct ethtool_eth_phy_stats    phy_stats;
        struct ethtool_eth_mac_stats    mac_stats;
        struct ethtool_eth_ctrl_stats   ctrl_stats;
+       struct ethtool_rmon_stats       rmon_stats;
+       const struct ethtool_rmon_hist_range    *rmon_ranges;
 };
 
 #define STATS_REPDATA(__reply_base) \
@@ -26,6 +28,7 @@ const char stats_std_names[__ETHTOOL_STATS_CNT][ETH_GSTRING_LEN] = {
        [ETHTOOL_STATS_ETH_PHY]                 = "eth-phy",
        [ETHTOOL_STATS_ETH_MAC]                 = "eth-mac",
        [ETHTOOL_STATS_ETH_CTRL]                = "eth-ctrl",
+       [ETHTOOL_STATS_RMON]                    = "rmon",
 };
 
 const char stats_eth_phy_names[__ETHTOOL_A_STATS_ETH_PHY_CNT][ETH_GSTRING_LEN] = {
@@ -63,6 +66,13 @@ const char stats_eth_ctrl_names[__ETHTOOL_A_STATS_ETH_CTRL_CNT][ETH_GSTRING_LEN]
        [ETHTOOL_A_STATS_ETH_CTRL_5_RX_UNSUP]   = "UnsupportedOpcodesReceived",
 };
 
+const char stats_rmon_names[__ETHTOOL_A_STATS_RMON_CNT][ETH_GSTRING_LEN] = {
+       [ETHTOOL_A_STATS_RMON_UNDERSIZE]        = "etherStatsUndersizePkts",
+       [ETHTOOL_A_STATS_RMON_OVERSIZE]         = "etherStatsOversizePkts",
+       [ETHTOOL_A_STATS_RMON_FRAG]             = "etherStatsFragments",
+       [ETHTOOL_A_STATS_RMON_JABBER]           = "etherStatsJabbers",
+};
+
 const struct nla_policy ethnl_stats_get_policy[ETHTOOL_A_STATS_GROUPS + 1] = {
        [ETHTOOL_A_STATS_HEADER]        =
                NLA_POLICY_NESTED(ethnl_header_policy),
@@ -107,6 +117,7 @@ static int stats_prepare_data(const struct ethnl_req_info *req_base,
        memset(&data->phy_stats, 0xff, sizeof(data->phy_stats));
        memset(&data->mac_stats, 0xff, sizeof(data->mac_stats));
        memset(&data->ctrl_stats, 0xff, sizeof(data->mac_stats));
+       memset(&data->rmon_stats, 0xff, sizeof(data->rmon_stats));
 
        if (test_bit(ETHTOOL_STATS_ETH_PHY, req_info->stat_mask) &&
            dev->ethtool_ops->get_eth_phy_stats)
@@ -117,6 +128,10 @@ static int stats_prepare_data(const struct ethnl_req_info *req_base,
        if (test_bit(ETHTOOL_STATS_ETH_CTRL, req_info->stat_mask) &&
            dev->ethtool_ops->get_eth_ctrl_stats)
                dev->ethtool_ops->get_eth_ctrl_stats(dev, &data->ctrl_stats);
+       if (test_bit(ETHTOOL_STATS_RMON, req_info->stat_mask) &&
+           dev->ethtool_ops->get_rmon_stats)
+               dev->ethtool_ops->get_rmon_stats(dev, &data->rmon_stats,
+                                                &data->rmon_ranges);
 
        ethnl_ops_complete(dev);
        return 0;
@@ -141,6 +156,16 @@ static int stats_reply_size(const struct ethnl_req_info *req_base,
                n_stats += sizeof(struct ethtool_eth_ctrl_stats) / sizeof(u64);
                n_grps++;
        }
+       if (test_bit(ETHTOOL_STATS_RMON, req_info->stat_mask)) {
+               n_stats += sizeof(struct ethtool_rmon_stats) / sizeof(u64);
+               n_grps++;
+               /* Above includes the space for _A_STATS_GRP_HIST_VALs */
+
+               len += (nla_total_size(0) +     /* _A_STATS_GRP_HIST */
+                       nla_total_size(4) +     /* _A_STATS_GRP_HIST_BKT_LOW */
+                       nla_total_size(4)) *    /* _A_STATS_GRP_HIST_BKT_HI */
+                       ETHTOOL_RMON_HIST_MAX * 2;
+       }
 
        len += n_grps * (nla_total_size(0) + /* _A_STATS_GRP */
                         nla_total_size(4) + /* _A_STATS_GRP_ID */
@@ -258,6 +283,65 @@ static int stats_put_ctrl_stats(struct sk_buff *skb,
        return 0;
 }
 
+static int stats_put_rmon_hist(struct sk_buff *skb, u32 attr, const u64 *hist,
+                              const struct ethtool_rmon_hist_range *ranges)
+{
+       struct nlattr *nest;
+       int i;
+
+       if (!ranges)
+               return 0;
+
+       for (i = 0; i < ETHTOOL_RMON_HIST_MAX; i++) {
+               if (!ranges[i].low && !ranges[i].high)
+                       break;
+               if (hist[i] == ETHTOOL_STAT_NOT_SET)
+                       continue;
+
+               nest = nla_nest_start(skb, attr);
+               if (!nest)
+                       return -EMSGSIZE;
+
+               if (nla_put_u32(skb, ETHTOOL_A_STATS_GRP_HIST_BKT_LOW,
+                               ranges[i].low) ||
+                   nla_put_u32(skb, ETHTOOL_A_STATS_GRP_HIST_BKT_HI,
+                               ranges[i].high) ||
+                   nla_put_u64_64bit(skb, ETHTOOL_A_STATS_GRP_HIST_VAL,
+                                     hist[i], ETHTOOL_A_STATS_GRP_PAD))
+                       goto err_cancel_hist;
+
+               nla_nest_end(skb, nest);
+       }
+
+       return 0;
+
+err_cancel_hist:
+       nla_nest_cancel(skb, nest);
+       return -EMSGSIZE;
+}
+
+static int stats_put_rmon_stats(struct sk_buff *skb,
+                               const struct stats_reply_data *data)
+{
+       if (stats_put_rmon_hist(skb, ETHTOOL_A_STATS_GRP_HIST_RX,
+                               data->rmon_stats.hist, data->rmon_ranges) ||
+           stats_put_rmon_hist(skb, ETHTOOL_A_STATS_GRP_HIST_TX,
+                               data->rmon_stats.hist_tx, data->rmon_ranges))
+               return -EMSGSIZE;
+
+       if (stat_put(skb, ETHTOOL_A_STATS_RMON_UNDERSIZE,
+                    data->rmon_stats.undersize_pkts) ||
+           stat_put(skb, ETHTOOL_A_STATS_RMON_OVERSIZE,
+                    data->rmon_stats.oversize_pkts) ||
+           stat_put(skb, ETHTOOL_A_STATS_RMON_FRAG,
+                    data->rmon_stats.fragments) ||
+           stat_put(skb, ETHTOOL_A_STATS_RMON_JABBER,
+                    data->rmon_stats.jabbers))
+               return -EMSGSIZE;
+
+       return 0;
+}
+
 static int stats_put_stats(struct sk_buff *skb,
                           const struct stats_reply_data *data,
                           u32 id, u32 ss_id,
@@ -305,6 +389,9 @@ static int stats_fill_reply(struct sk_buff *skb,
                ret = stats_put_stats(skb, data, ETHTOOL_STATS_ETH_CTRL,
                                      ETH_SS_STATS_ETH_CTRL,
                                      stats_put_ctrl_stats);
+       if (!ret && test_bit(ETHTOOL_STATS_RMON, req_info->stat_mask))
+               ret = stats_put_stats(skb, data, ETHTOOL_STATS_RMON,
+                                     ETH_SS_STATS_RMON, stats_put_rmon_stats);
 
        return ret;
 }
index a33c603..b3029ff 100644 (file)
@@ -100,6 +100,11 @@ static const struct strset_info info_template[] = {
                .count          = __ETHTOOL_A_STATS_ETH_CTRL_CNT,
                .strings        = stats_eth_ctrl_names,
        },
+       [ETH_SS_STATS_RMON] = {
+               .per_dev        = false,
+               .count          = __ETHTOOL_A_STATS_RMON_CNT,
+               .strings        = stats_rmon_names,
+       },
 };
 
 struct strset_req_info {