ethtool: add interface to read RMON stats
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>
This commit is contained in:
parent
bfad2b979d
commit
a8b06e9d40
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user