Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: cdc_ncm/cdc_mbim: adding NCM protocol statistics

To have an idea of the effects of the protocol coalescing
it's useful to have some counters showing the different
aspects.

Due to the asymmetrical usbnet interface the netdev
rx_bytes counter has been counting real received payload,
while the tx_bytes counter has included the NCM/MBIM
framing overhead. This overhead can be many times the
payload because of the aggressive padding strategy of
this driver, and will vary a lot depending on device
and traffic.

With very few exceptions, users are only interested in
the payload size. Having an somewhat accurate payload
byte counter is particularly important for mobile
broadband devices, which many NCM devices and of course
all MBIM devices are. Users and userspace applications
will use this counter to monitor account quotas.

Having protocol specific counters for the overhead, we are
now able to correct the tx_bytes netdev counter so that
it shows the real payload

Signed-off-by: Bjørn Mork <bjorn@mork.no>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Bjørn Mork and committed by
David S. Miller
beeecd42 43e4c6df

+108
+6
drivers/net/usb/cdc_mbim.c
··· 420 420 struct usb_cdc_ncm_dpe16 *dpe16; 421 421 int ndpoffset; 422 422 int loopcount = 50; /* arbitrary max preventing infinite loop */ 423 + u32 payload = 0; 423 424 u8 *c; 424 425 u16 tci; 425 426 ··· 483 482 if (!skb) 484 483 goto error; 485 484 usbnet_skb_return(dev, skb); 485 + payload += len; /* count payload bytes in this NTB */ 486 486 } 487 487 } 488 488 err_ndp: ··· 491 489 ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); 492 490 if (ndpoffset && loopcount--) 493 491 goto next_ndp; 492 + 493 + /* update stats */ 494 + ctx->rx_overhead += skb_in->len - payload; 495 + ctx->rx_ntbs++; 494 496 495 497 return 1; 496 498 error:
+91
drivers/net/usb/cdc_ncm.c
··· 65 65 static enum hrtimer_restart cdc_ncm_tx_timer_cb(struct hrtimer *hr_timer); 66 66 static struct usb_driver cdc_ncm_driver; 67 67 68 + struct cdc_ncm_stats { 69 + char stat_string[ETH_GSTRING_LEN]; 70 + int sizeof_stat; 71 + int stat_offset; 72 + }; 73 + 74 + #define CDC_NCM_STAT(str, m) { \ 75 + .stat_string = str, \ 76 + .sizeof_stat = sizeof(((struct cdc_ncm_ctx *)0)->m), \ 77 + .stat_offset = offsetof(struct cdc_ncm_ctx, m) } 78 + #define CDC_NCM_SIMPLE_STAT(m) CDC_NCM_STAT(__stringify(m), m) 79 + 80 + static const struct cdc_ncm_stats cdc_ncm_gstrings_stats[] = { 81 + CDC_NCM_SIMPLE_STAT(tx_reason_ntb_full), 82 + CDC_NCM_SIMPLE_STAT(tx_reason_ndp_full), 83 + CDC_NCM_SIMPLE_STAT(tx_reason_timeout), 84 + CDC_NCM_SIMPLE_STAT(tx_reason_max_datagram), 85 + CDC_NCM_SIMPLE_STAT(tx_overhead), 86 + CDC_NCM_SIMPLE_STAT(tx_ntbs), 87 + CDC_NCM_SIMPLE_STAT(rx_overhead), 88 + CDC_NCM_SIMPLE_STAT(rx_ntbs), 89 + }; 90 + 91 + static int cdc_ncm_get_sset_count(struct net_device __always_unused *netdev, int sset) 92 + { 93 + switch (sset) { 94 + case ETH_SS_STATS: 95 + return ARRAY_SIZE(cdc_ncm_gstrings_stats); 96 + default: 97 + return -EOPNOTSUPP; 98 + } 99 + } 100 + 101 + static void cdc_ncm_get_ethtool_stats(struct net_device *netdev, 102 + struct ethtool_stats __always_unused *stats, 103 + u64 *data) 104 + { 105 + struct usbnet *dev = netdev_priv(netdev); 106 + struct cdc_ncm_ctx *ctx = (struct cdc_ncm_ctx *)dev->data[0]; 107 + int i; 108 + char *p = NULL; 109 + 110 + for (i = 0; i < ARRAY_SIZE(cdc_ncm_gstrings_stats); i++) { 111 + p = (char *)ctx + cdc_ncm_gstrings_stats[i].stat_offset; 112 + data[i] = (cdc_ncm_gstrings_stats[i].sizeof_stat == sizeof(u64)) ? *(u64 *)p : *(u32 *)p; 113 + } 114 + } 115 + 116 + static void cdc_ncm_get_strings(struct net_device __always_unused *netdev, u32 stringset, u8 *data) 117 + { 118 + u8 *p = data; 119 + int i; 120 + 121 + switch (stringset) { 122 + case ETH_SS_STATS: 123 + for (i = 0; i < ARRAY_SIZE(cdc_ncm_gstrings_stats); i++) { 124 + memcpy(p, cdc_ncm_gstrings_stats[i].stat_string, ETH_GSTRING_LEN); 125 + p += ETH_GSTRING_LEN; 126 + } 127 + } 128 + } 129 + 68 130 static int cdc_ncm_get_coalesce(struct net_device *netdev, 69 131 struct ethtool_coalesce *ec) 70 132 { ··· 184 122 .get_msglevel = usbnet_get_msglevel, 185 123 .set_msglevel = usbnet_set_msglevel, 186 124 .get_ts_info = ethtool_op_get_ts_info, 125 + .get_sset_count = cdc_ncm_get_sset_count, 126 + .get_strings = cdc_ncm_get_strings, 127 + .get_ethtool_stats = cdc_ncm_get_ethtool_stats, 187 128 .get_coalesce = cdc_ncm_get_coalesce, 188 129 .set_coalesce = cdc_ncm_set_coalesce, 189 130 }; ··· 927 862 928 863 /* count total number of frames in this NTB */ 929 864 ctx->tx_curr_frame_num = 0; 865 + 866 + /* recent payload counter for this skb_out */ 867 + ctx->tx_curr_frame_payload = 0; 930 868 } 931 869 932 870 for (n = ctx->tx_curr_frame_num; n < ctx->tx_max_datagrams; n++) { ··· 967 899 ctx->tx_rem_sign = sign; 968 900 skb = NULL; 969 901 ready2send = 1; 902 + ctx->tx_reason_ntb_full++; /* count reason for transmitting */ 970 903 } 971 904 break; 972 905 } ··· 981 912 ndp16->dpe16[index].wDatagramIndex = cpu_to_le16(skb_out->len); 982 913 ndp16->wLength = cpu_to_le16(ndplen + sizeof(struct usb_cdc_ncm_dpe16)); 983 914 memcpy(skb_put(skb_out, skb->len), skb->data, skb->len); 915 + ctx->tx_curr_frame_payload += skb->len; /* count real tx payload data */ 984 916 dev_kfree_skb_any(skb); 985 917 skb = NULL; 986 918 987 919 /* send now if this NDP is full */ 988 920 if (index >= CDC_NCM_DPT_DATAGRAMS_MAX) { 989 921 ready2send = 1; 922 + ctx->tx_reason_ndp_full++; /* count reason for transmitting */ 990 923 break; 991 924 } 992 925 } ··· 1018 947 goto exit_no_skb; 1019 948 1020 949 } else { 950 + if (n == ctx->tx_max_datagrams) 951 + ctx->tx_reason_max_datagram++; /* count reason for transmitting */ 1021 952 /* frame goes out */ 1022 953 /* variables will be reset at next call */ 1023 954 } ··· 1047 974 /* return skb */ 1048 975 ctx->tx_curr_skb = NULL; 1049 976 dev->net->stats.tx_packets += ctx->tx_curr_frame_num; 977 + 978 + /* keep private stats: framing overhead and number of NTBs */ 979 + ctx->tx_overhead += skb_out->len - ctx->tx_curr_frame_payload; 980 + ctx->tx_ntbs++; 981 + 982 + /* usbnet has already counted all the framing overhead. 983 + * Adjust the stats so that the tx_bytes counter show real 984 + * payload data instead. 985 + */ 986 + dev->net->stats.tx_bytes -= skb_out->len - ctx->tx_curr_frame_payload; 987 + 1050 988 return skb_out; 1051 989 1052 990 exit_no_skb: ··· 1098 1014 cdc_ncm_tx_timeout_start(ctx); 1099 1015 spin_unlock_bh(&ctx->mtx); 1100 1016 } else if (dev->net != NULL) { 1017 + ctx->tx_reason_timeout++; /* count reason for transmitting */ 1101 1018 spin_unlock_bh(&ctx->mtx); 1102 1019 netif_tx_lock_bh(dev->net); 1103 1020 usbnet_start_xmit(NULL, dev->net); ··· 1234 1149 struct usb_cdc_ncm_dpe16 *dpe16; 1235 1150 int ndpoffset; 1236 1151 int loopcount = 50; /* arbitrary max preventing infinite loop */ 1152 + u32 payload = 0; 1237 1153 1238 1154 ndpoffset = cdc_ncm_rx_verify_nth16(ctx, skb_in); 1239 1155 if (ndpoffset < 0) ··· 1287 1201 skb->data = ((u8 *)skb_in->data) + offset; 1288 1202 skb_set_tail_pointer(skb, len); 1289 1203 usbnet_skb_return(dev, skb); 1204 + payload += len; /* count payload bytes in this NTB */ 1290 1205 } 1291 1206 } 1292 1207 err_ndp: ··· 1295 1208 ndpoffset = le16_to_cpu(ndp16->wNextNdpIndex); 1296 1209 if (ndpoffset && loopcount--) 1297 1210 goto next_ndp; 1211 + 1212 + /* update stats */ 1213 + ctx->rx_overhead += skb_in->len - payload; 1214 + ctx->rx_ntbs++; 1298 1215 1299 1216 return 1; 1300 1217 error:
+11
include/linux/usb/cdc_ncm.h
··· 116 116 u16 rx_seq; 117 117 u16 connected; 118 118 u16 min_tx_pkt; 119 + 120 + /* statistics */ 121 + u32 tx_curr_frame_payload; 122 + u32 tx_reason_ntb_full; 123 + u32 tx_reason_ndp_full; 124 + u32 tx_reason_timeout; 125 + u32 tx_reason_max_datagram; 126 + u64 tx_overhead; 127 + u64 tx_ntbs; 128 + u64 rx_overhead; 129 + u64 rx_ntbs; 119 130 }; 120 131 121 132 u8 cdc_ncm_select_altsetting(struct usb_interface *intf);