net-snmpとlinuxの64bitカウンタ実装

誤動作の調査で調べたのでついでに覚え書き。
snmpdで取得できるネットワークインターフェイスカウンタの値は
/proc/net/devを元にしている。

中身はこんなの。

# cat /proc/net/dev
Inter-|   Receive                                                |  Transmit ...
 face |bytes    packets errs drop fifo frame compressed multicast|bytes      ...
    lo:  268198    1760    0    0    0     0          0         0   268198   ...
  eth0:1438145878 2202918421    0    0     0     0          0         6 36330...

出力している箇所は以下。(@net/core/dev.c)
net_device_statsに格納されているカウンタ値がunsigned longなので32bitカーネルだと
32bitカウンタ値までしか取得できないが、64bitカーネルの場合はlongが64bitになるため
そのまま64bitカウンタが取得できる。(LP64等の場合)

static void dev_seq_printf_stats(struct seq_file *seq, struct net_device *dev)
{
        if (dev->get_stats) {
                struct net_device_stats *stats = dev->get_stats(dev);

                seq_printf(seq, "%6s:%8lu %7lu %4lu %4lu %4lu %5lu %10lu %9lu "
                                "%8lu %7lu %4lu %4lu %4lu %5lu %7lu %10lu\n",
                           dev->name, stats->rx_bytes, stats->rx_packets,
                           stats->rx_errors,
                           stats->rx_dropped + stats->rx_missed_errors,
                           stats->rx_fifo_errors,
                           stats->rx_length_errors + stats->rx_over_errors +
                             stats->rx_crc_errors + stats->rx_frame_errors,
                           stats->rx_compressed, stats->multicast,
                           stats->tx_bytes, stats->tx_packets,
                           stats->tx_errors, stats->tx_dropped,
                           stats->tx_fifo_errors, stats->collisions,
                           stats->tx_carrier_errors +
                             stats->tx_aborted_errors +
                             stats->tx_window_errors +
                             stats->tx_heartbeat_errors,
                           stats->tx_compressed);
        } else
                seq_printf(seq, "%6s: No statistics available.\n", dev->name);
}

net_device_statsへ値を格納しているのはドライバ側で用意している関数で
bnx2(Broadcom NetXtreme II)のドライバを見てみると以下のような感じ。
32bitモードで動いてる限り上位がそのまま切り捨てられている。
(@drivers/net/bnx2.c)

#define GET_NET_STATS64(ctr)                                    \
        (unsigned long) ((unsigned long) (ctr##_hi) << 32) +    \
        (unsigned long) (ctr##_lo)

#define GET_NET_STATS32(ctr)            \
        (ctr##_lo)

#if (BITS_PER_LONG == 64)
#define GET_NET_STATS   GET_NET_STATS64
#else
#define GET_NET_STATS   GET_NET_STATS32
#endif
...
static struct net_device_stats *
bnx2_get_stats(struct net_device *dev)
...
        net_stats->rx_bytes =
                GET_NET_STATS(stats_blk->stat_IfHCInOctets);

        net_stats->tx_bytes =
                GET_NET_STATS(stats_blk->stat_IfHCOutOctets);
...


で、実際の所はsnmpdがクライアントから問い合わせが無い場合も定期的に
インターフェイスの情報をポーリングしており、32bitカーネルでも取りこぼしが
起きないようになっている。(内部的には上位、下位32bitでそれぞれ管理)
これにより32bitカーネルの場合でも64bitカウンタを擬似的に作り出せているが
当然のことながら、カーネルからは下位32bitしか取得できないのでsnmpdを落とすと
上位32bitはリセットされる。
64bitカーネルの場合はそのまま値が拾えるので何も問題なし。