最近在做一场流量性能测试,期望得到的结果,既不是应用关键场景需要使用的流量总量,也不是应用跑起来后的平均带宽值。而是一个叫带宽峰值的玩意儿,它代表一段时间内,这个应用1s内最高会收发多少数据。有什么特别的呢?
流量测试一般的测试方法:定义关键场景,关键场景前流量值– 场景后流量值= 流量总量。流量总量/时长 = 平均带宽。
测试手段:
在接到需求后,认真分析了上面的测试方法和测试手段,发现只有GT或Emmagee基本能够满足,因为带宽峰值的含义就是一段时间内,带宽值曲线上的最高点。
一开始我是很信赖GT的,毕竟是我大腾讯同事出品。直到测试开始,它给了我类似以下两组数据(一是负数,二是带宽一点点增加)。
GT输出的带宽测试报告
现有的工具无法满足需求,只能自己动手,丰衣足食了。首先理清楚我们可以从哪些地方拿到实时的流量数据:
Proc文件中的流量数据
a) getUidRxBytes(int uid)和getUidTxBytes(int uid);
b) getTotalRxBytes()和getTotalTxBytes();
c) getRxBytes(String iface)和getTxBytes(String iface);
需要准确获取一个应用的实时带宽,从字面意义上看,似乎选择以上第1点中的方法,或者第2点中的a更加合适,一个文件读取,一个系统接口获取,都是直接取出了对应uid的流量数据。于是根据1,我迅速的码出了如下代码:
static FileReader mReader = null;
static BufferedReader mBufReader = null;
public static long getTrafficInfo1(int uid) {
String fileName = "/proc/net/xt_qtaguid/stats";
long ret = 0;
try{
mReader = new FileReader(fileName);
mBufReader = new BufferedReader(mReader);
String line = null;
int count = 0;
while ( (line = mBufReader.readLine()) != null){
count++;
if(count == 1 ) continue;
String[] vecTemp = line.split("[:\\s]+");
if(vecTemp[1].equals("wlan0") && Integer.valueOf(vecTemp[3]) == uid){
ret += Long.valueOf(vecTemp[5]) + Long.valueOf(vecTemp[7]);
}
}
}catch (IOException e){
Log.e(LOG_TAG,e.getMessage());
}finally {
try {
if (mBufReader != null) {
mBufReader.close();
}
if (mReader != null) {
mReader.close();
}
}catch (IOException e){
Log.e(LOG_TAG,e.getMessage());
}
}
return ret;
}
然后悲催的发现,虽然通过命令:
adb shell cat /proc/net/xt_qtaguid/stats
能够看到一行行漂亮的数据,在应用程序中却始终都只能读取到title行。这个地方暂时没找到为什么,大概怀疑点是Android权限的问题。
当然我们可以写个运行在PC端的脚本程序,然后adb连接着被测手机,cat出该文件然后再分析,然而带宽峰值的测试,依赖于大样本量,连着ADB跑,这个就局限了,只能我一个人测试,想找其他人帮忙太麻烦。
第一种方法不行,马上换。TrafficStats中的getUidRxBytes(int uid)和getUidTxBytes(int uid)这两个接口是否可行呢?看了Emmagee的源码,关于流量的测试,它还真是用这两个接口实现的。
public static long getTrafficInfo(int uid) {
Log.i(LOG_TAG, "get traffic information");
long rcvTraffic = UNSUPPORTED;
long sndTraffic = UNSUPPORTED;
// Use getUidRxBytes and getUidTxBytes to get network traffic,these API
// return both tcp and udp usage
rcvTraffic = TrafficStats.getUidRxBytes(uid);
sndTraffic = TrafficStats.getUidTxBytes(uid);
if (rcvTraffic == UNSUPPORTED || sndTraffic == UNSUPPORTED) {
return UNSUPPORTED;
} else
return rcvTraffic + sndTraffic;
}
Copy好代码,然后冒出一个问号,为什么要特意标记一个UNSUPPORTED呢?瞄一眼源码,从注释和代码中可以看出,4.3以下这个接口是没有的;7.0及以上,这个接口只能用来拿应用本身的Traffic数据;要拿其他人的?详情请看NetworkStatsManager。
/**
* Return number of bytes transmitted by the given UID since device boot.
* Counts packets across all network interfaces, and always increases
* monotonically since device boot. Statistics are measured at the network
* layer, so they include both TCP and UDP usage.
* <p>
* Before {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}, this may
* return {@link #UNSUPPORTED} on devices where statistics aren't available.
* <p>
* Starting in {@link android.os.Build.VERSION_CODES#N} this will only
* report traffic statistics for the calling UID. It will return
* {@link #UNSUPPORTED} for all other UIDs for privacy reasons. To access
* historical network statistics belonging to other UIDs, use
* {@link NetworkStatsManager}.
*
* @see android.os.Process#myUid()
* @see android.content.pm.ApplicationInfo#uid
*/
public static long getUidTxBytes(int uid) {
// This isn't actually enforcing any security; it just returns the
// unsupported value. The real filtering is done at the kernel level.
final int callingUid = android.os.Process.myUid();
if (callingUid == android.os.Process.SYSTEM_UID || callingUid == uid) {
return nativeGetUidStat(uid, TYPE_TX_BYTES);
} else {
return UNSUPPORTED;
}
}
看来这是Android版本碎片化带来的坑,瞬间体会到了开发同学做兼容适配的痛苦。
由于项目急需用了,最终决定不再纠结于用哪个API。直接使用以下两种方法来做,只是在测试前先杀掉其他应用,如果有手机管家的话,开启管家的禁止联网功能,保证只有被测应用在联网:
具体代码实现:
第一个比较简单,直接调用即可。第二个因为getRxBytes(String iface)是隐藏方法,所以需要通过反射拿到该方法进行invoke。
static {
try {
Class<?> claxx = Class.forName("android.net.TrafficStats");
mgetRxBytesMethod = claxx.getDeclaredMethod("getRxBytes", String.class);
mgetTxBytesMethod = claxx.getDeclaredMethod("getTxBytes", String.class);
mgetRxBytesMethod.setAccessible(true);
mgetTxBytesMethod.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
public static long getRxBytes(String iface){
long ret = -1;
try {
ret = Long.valueOf(mgetRxBytesMethod.invoke(null,"wlan0").toString());
}catch (Exception e){
e.printStackTrace();
}
return ret;
}
public static long getTxBytes(String iface){
long ret = -1;
try {
ret = Long.valueOf(mgetTxBytesMethod.invoke(null,"wlan0").toString());
}catch (Exception e){
e.printStackTrace();
}
return ret;
}
计算逻辑:
class CountRunnable implements Runnable {
@Override
public void run() {
String dataSavePath = DataSaveHandler.getFilePath("band");
//通知界面刷新
notifyStart(dataSavePath);
long current = 0;
long last = 0;
long lastSnd = 0; //上次发
long lastRcv = 0; //上次收
long currentSnd = 0;
long currentRcv = 0;
int count = 0;
while (!isGoingToStop) {
count++;
//获取所有网卡的流量
// currentSnd = TrafficStats.getTotalTxBytes();
// currentRcv = TrafficStats.getTotalRxBytes();
//获取wifi网卡上的流量
currentSnd = TrafficInfo.getRxBytes("wlan0");
currentRcv = TrafficInfo.getTxBytes("wlan0");
current = System.currentTimeMillis();
SystemClock.sleep(100);
if (count == 1) {
last = current;
lastSnd = currentSnd;
lastRcv = currentRcv;
continue; //首次只收集current数据
}
if (current - last < 1000) { continue; }
last = current;
long sndBand = currentSnd - lastSnd;
long rcvBand = currentRcv - lastRcv;
//每次都刷新并保存该文件,能够保证即使不停止,也会有数据
DataSaveHandler.openFileWriter(dataSavePath);
float bandInKB = ((float) (sndBand + rcvBand))/1000;
DecimalFormat df = new DecimalFormat();
df.applyPattern("0.000");
DataSaveHandler.writeNewLine(mLogSdf.format(current) + "," + sndBand + "," + rcvBand+","+df.format(bandInKB));
DataSaveHandler.saveAndCloseFile();
lastSnd = currentSnd;
lastRcv = currentRcv;
}
//通知界面刷新
notifyStop(dataSavePath);
}
}
将代码输出为一个应用,实时收集流量数据,写到csv文件中。将这个测试APP发给大家。测试步骤:
这个方法虽然没有直接获取被测APP的流量,但是简单快捷,Android各个版本上的兼容性高,能够快速收集到较多的样本。当然也可以再结合NetworkStatsManager来做兼容适配。
http://blog.csdn.net/w7849516230/article/details/71705835
文章来源于:腾讯移动品质中心 TMQ
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。