前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >坎坷之下出新招:记一次应用带宽峰值测试的探索历程

坎坷之下出新招:记一次应用带宽峰值测试的探索历程

原创
作者头像
陈帅
发布2017-10-12 09:26:43
1.9K0
发布2017-10-12 09:26:43
举报
文章被收录于专栏:陈帅的专栏

测试前

最近在做一场流量性能测试,期望得到的结果,既不是应用关键场景需要使用的流量总量,也不是应用跑起来后的平均带宽值。而是一个叫带宽峰值的玩意儿,它代表一段时间内,这个应用1s内最高会收发多少数据。有什么特别的呢?

流量测试一般的测试方法:定义关键场景,关键场景前流量值– 场景后流量值= 流量总量。流量总量/时长 = 平均带宽。

测试手段:

  1. 流量卡。主要是记录流量起始点流量卡的流量值,和结束点流量卡的流量值。
  2. GT工具或Emmagee工具。都是每秒都在采样带宽值。
  3. DDMS。需要被测应用是debug包。

在接到需求后,认真分析了上面的测试方法和测试手段,发现只有GT或Emmagee基本能够满足,因为带宽峰值的含义就是一段时间内,带宽值曲线上的最高点。

开始撞墙

一开始我是很信赖GT的,毕竟是我大腾讯同事出品。直到测试开始,它给了我类似以下两组数据(一是负数,二是带宽一点点增加)。

GT输出的带宽测试报告

现有的工具无法满足需求,只能自己动手,丰衣足食了。首先理清楚我们可以从哪些地方拿到实时的流量数据:

  1. 从系统文件(/proc/net/xt_qtaguid/stats)中,可以很方便的拿出每个uid的流量数据,如下图,从0开始,第3列是uid,第五列是接收数据的bytes数,第七列是发送数据的bytes数。

Proc文件中的流量数据

  1. android.net.TrafficStats这个类中提供了一大堆方法用于获取流量数据的方法。其中主要包括:

a) getUidRxBytes(int uid)和getUidTxBytes(int uid);

b) getTotalRxBytes()和getTotalTxBytes();

c) getRxBytes(String iface)和getTxBytes(String iface);

第一次撞墙

需要准确获取一个应用的实时带宽,从字面意义上看,似乎选择以上第1点中的方法,或者第2点中的a更加合适,一个文件读取,一个系统接口获取,都是直接取出了对应uid的流量数据。于是根据1,我迅速的码出了如下代码:

代码语言:javascript
复制
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的源码,关于流量的测试,它还真是用这两个接口实现的。

代码语言:javascript
复制
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。

代码语言:javascript
复制
/**
 * 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。直接使用以下两种方法来做,只是在测试前先杀掉其他应用,如果有手机管家的话,开启管家的禁止联网功能,保证只有被测应用在联网:

  1. getTotalRxBytes()和getTotalTxBytes();
  2. getRxBytes(String iface)和getTxBytes(String iface);

具体代码实现:

第一个比较简单,直接调用即可。第二个因为getRxBytes(String iface)是隐藏方法,所以需要通过反射拿到该方法进行invoke。

代码语言:javascript
复制
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;
}

计算逻辑:

代码语言:javascript
复制
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发给大家。测试步骤:

  1. 将被测应用外其他应用关闭;
  2. 开启被测应用,进入关键场景;
  3. 打开测试APP,开始收集带宽数据;
  4. 关键场景结束时,打开测试APP,点击结束;
  5. 将结果adb pull出来发给测试;
  6. 分析取带宽峰值平均值。

这个方法虽然没有直接获取被测APP的流量,但是简单快捷,Android各个版本上的兼容性高,能够快速收集到较多的样本。当然也可以再结合NetworkStatsManager来做兼容适配。

参考文献

http://blog.csdn.net/w7849516230/article/details/71705835

文章来源于:腾讯移动品质中心 TMQ

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 测试前
  • 开始撞墙
  • 第一次撞墙
  • 第二次撞墙
  • 最终方案
  • 参考文献
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档