Android流量统计

最近做个需求要在native层的某个应用中加入流量统计,所以研究了一下Android自带的流量统计代码,将JNI中的代码直接移植到了我需要的地方

Android提供的流量统计功能在TrafficStats.java类中,包含以下几个:

  • public static long getMobileTxPackets() //Return number of packets transmitted across mobile networks since device boot
  • public static long getMobileRxPackets() //Return number of packets received across mobile networks since device boot
  • public static long getMobileTxBytes() //Return number of bytes transmitted across mobile networks since device boot
  • public static long getMobileRxBytes() //Return number of bytes received across mobile networks since device boot
  • public static long getMobileTcpRxPackets() //tcp received
  • public static long getMobileTcpTxPackets() //tcp transmitted
  • public static long getTotalTxPackets //Return number of packets transmitted since device boot
  • public static long getTotalRxPackets() //Return number of packets received since device boot
  • public static long getTotalTxBytes() //Return number of bytes transmitted since device boot
  • public static long getTotalRxBytes() //Return number of bytes received since device boot
  • public static long getUidTxBytes(int uid) //Return number of bytes transmitted by the given UID since device boot
  • public static long getUidRxBytes(int uid) //Return number of bytes received by the given UID since device boot
  • public static long getUidTxPackets(int uid) //Return number of packets transmitted by the given UID since device boot
  • public static long getUidRxPackets(int uid) //Return number of packets received by the given UID since device boot

 

已其中一个为例,getTotalTxBytes

679     public static long getTotalTxBytes() {
680         try {
681             return getStatsService().getTotalStats(TYPE_TX_BYTES);
682         } catch (RemoteException e) {
683             throw e.rethrowFromSystemServer();
684         }
685     }

getTotalTxBytes这里获取的是NetworkStatsService代理,通过binder调用到NetworkStatsService

 947     @Override
 948     public long getTotalStats(int type) {
 949         return nativeGetTotalStat(type, checkBpfStatsEnable());
 950     }

checkBpfStatsEnable函数是检测一个系统文件是否存在

 952     private boolean checkBpfStatsEnable() {
 953         return mUseBpfTrafficStats;
 954     }

mUseBpfTrafficStats = new File("/sys/fs/bpf/traffic_uid_stats_map").exists();

native方法进入JNI

153 static jlong getTotalStat(JNIEnv* env, jclass clazz, jint type, jboolean useBpfStats) {
154     struct Stats stats;
155     memset(&stats, 0, sizeof(Stats));
156 
157     if (useBpfStats) {
158         if (bpfGetIfaceStats(NULL, &stats) == 0) {
159             return getStatsType(&stats, (StatsType) type);
160         } else {
161             return UNKNOWN;
162         }
163     }
164 
165     if (parseIfaceStats(NULL, &stats) == 0) {
166         return getStatsType(&stats, (StatsType) type);
167     } else {
168         return UNKNOWN;
169     }
170 }

我们发现如果上面的/sys/fs/bpf/traffic_uid_stats_map文件如果存在,就会进入bpfGetIfaceStats(NULL, &stats),不存在则会进入parseIfaceStats(NULL, &stats)

先来看bpfGetIfaceStats(NULL, &stats)

 95 int bpfGetIfaceStats(const char* iface, Stats* stats) {
        //IFACE_STATS_MAP_PATH = /sys/fs/bpf/traffic_iface_stats_map
 96     BpfMap<uint32_t, StatsValue> ifaceStatsMap(mapRetrieve(IFACE_STATS_MAP_PATH, BPF_OPEN_FLAGS));
 97     int ret;
 98     if (!ifaceStatsMap.isValid()) {
 99         ret = -errno;
100         ALOGE("get ifaceStats map fd failed: %s", strerror(errno));
101         return ret;
102     }
103     BpfMap<uint32_t, IfaceValue> ifaceIndexNameMap(
104         mapRetrieve(IFACE_INDEX_NAME_MAP_PATH, BPF_OPEN_FLAGS));
105     if (!ifaceIndexNameMap.isValid()) {
106         ret = -errno;
107         ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
108         return ret;
109     }
110     return bpfGetIfaceStatsInternal(iface, stats, ifaceStatsMap, ifaceIndexNameMap);
111 }

什么是bpf?伯克利包过滤器(Berkeley Packet Filter,简称BPF)以协议无关的方式提供指向数据链路层的原始接口,网络上的所有数据包都可以通过这种机制进行访问。伯克利包过滤器以一种字符设备存在,如"/dev/bpf0""/dev/bpf1"等,这些字符设备的文件描述符都通过BIOCSETIF ioctl与特定的网络接口进行绑定,每一个网络接口都可以和多个绑定的监听者共享,因此每个文件描述符下的过滤器可以看到被过滤出的包。

不太懂,但简单看应该是从traffic_iface_stats_map这个文件中读取数值,至于traffic_iface_stats_map这个文件中的数值怎么来的,比较深奥不清楚,这里就不深究了

 

再看parseIfaceStats(NULL, &stats)

 struct Stats {
     uint64_t rxBytes;
     uint64_t rxPackets;
     uint64_t txBytes;
     uint64_t txPackets;
     uint64_t tcpRxPackets;
     uint64_t tcpTxPackets;
 };

 77 static int parseIfaceStats(const char* iface, struct Stats* stats) {
        // QTAGUID_IFACE_STATS = /proc/net/xt_qtaguid/iface_stat_fmt
 78     FILE *fp = fopen(QTAGUID_IFACE_STATS, "r");
 79     if (fp == NULL) {
 80         return -1;
 81     }
 82 
 83     char buffer[384];
 84     char cur_iface[32];
 85     bool foundTcp = false;
 86     uint64_t rxBytes, rxPackets, txBytes, txPackets, tcpRxPackets, tcpTxPackets;
 87 
 88     while (fgets(buffer, sizeof(buffer), fp) != NULL) {
 89         int matched = sscanf(buffer, "%31s %" SCNu64 " %" SCNu64 " %" SCNu64
 90                 " %" SCNu64 " " "%*u %" SCNu64 " %*u %*u %*u %*u "
 91                 "%*u %" SCNu64 " %*u %*u %*u %*u", cur_iface, &rxBytes,
 92                 &rxPackets, &txBytes, &txPackets, &tcpRxPackets, &tcpTxPackets);
 93         if (matched >= 5) {
 94             if (matched == 7) {
 95                 foundTcp = true;
 96             }
 97             if (!iface || !strcmp(iface, cur_iface)) {
 98                 stats->rxBytes += rxBytes;
 99                 stats->rxPackets += rxPackets;
100                 stats->txBytes += txBytes;
101                 stats->txPackets += txPackets;
102                 if (matched == 7) {
103                     stats->tcpRxPackets += tcpRxPackets;
104                     stats->tcpTxPackets += tcpTxPackets;
105                 }
106             }
107         }
108     }
109 
110     if (!foundTcp) {
111         stats->tcpRxPackets = UNKNOWN;
112         stats->tcpTxPackets = UNKNOWN;
113     }
114 
115     if (fclose(fp) != 0) {
116         return -1;
117     }
118     return 0;
119 }

这个就比较直接了,sscanf按特定的格式读取文件iface_stat_fmt中的字符串,也就是我们需要的流量数据

看下iface_stat_fmt这个文件里面是什么?

各个interface的流量数据值,从中统计出来最终结果


版权声明:本文为cc0410原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。