移动性能测试 关于用 adb 命令统计手机所有 app 流量的优化

绵绵不绝 · November 13, 2017 · Last by keitwotest replied at March 01, 2019 · 3042 hits

最近参考了大家一些关于流量的测试方法,
https://testerhome.com/topics/2643
https://testerhome.com/topics/2068

我自己也写了一个统计手机所有 app 流量的脚本,但是执行时间太慢了,手机大概 200 个 package,统计一次流量要将近 50 秒
其中 adb shell dumpsys package '+package+' | find "userId" 这条命令大概要执行 200 次 耗时 30 秒
adb shell cat /proc/net/xt_qtaguid/stats | find "'+uid+'" 这条命令执行 100 多次,耗时 10 秒

想请大家帮忙看下有没有什么优化的方法,比如:使用 adb 命令一次性获取手机里面所有 App 的 uid 和 packageName,或者脚本的逻辑优化

# coding:utf-8
import subprocess
import time

def get_packages():
    '''
    获取所有的包名
    :return:
    '''
    #存储包名
    packages=[]
    #获取所有的包
    cmd='adb shell pm list package'
    proc=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
    #以换行来划分
    output=proc.communicate()[0].split('\n')
    for package in output:
        #去掉package:字符串
        if 'package:' in package:
            #去掉\r
            package=package[8:].replace('\r','')
            packages.append(package)
    return packages

def get_uid(package):
    '''
    获取uid
    :param package:
    :return:
    '''
    uid=''
    #获取uid
    cmd='adb shell dumpsys package '+package+' | find "userId"'
    proc=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
    #保留返回结果第一行
    output=proc.communicate()[0].split('\n')[0]
    #去掉空格
    output=output.replace(' ','')
    #判断结果里面是否有userId
    if 'userId=' in output:
        #找到 userId= 的下标
        index=output.find('userId=')
        #从 userId= 的下标+7,开始遍历,直到字符串结束
        for j in range((index+7),len(output)):
            #如果是数字,就拼接到uid
            if output[j].isdigit():
                uid=uid+output[j]
            else:
                #不是数字的时候,就停止拼接
                break
        return uid
    else:
        print('Not found uid')
        return '0'

def remove_duplicate_pid():
    '''
    去掉重复的pid,获得整个设备的uid,package
    :return:
    '''
    packages=get_packages()
    uid_and_packages=[]
    for package in packages:
        #单个uid和package
        uid_package=[]
        #判定是否有重复的uid的标志位
        flag=0
        uid_package.append(get_uid(package))
        uid_package.append(package)
        for i in uid_and_packages:
            #如果要加入的pid与已经存在相同,标志位置为1
            if uid_package[0] == i[0]:
                flag=1
        #如果有重复的就去掉重复的,不添加
        if flag==0:
            uid_and_packages.append(uid_package)
    return uid_and_packages

def get_per_flow(uid):
    '''
    根据uid获取流量
    :param uid:
    :return:
    '''
    #获取当前时间
    #ltime=time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
    #获取流量的adb命令
    cmd='adb shell cat /proc/net/xt_qtaguid/stats | find "'+uid+'"'
    proc=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
    output=proc.communicate()[0].split('\n')
    #数据流量
    rmnet=0
    #wifi流量
    wlan=0
    for line in output:
        #第6列数据是下行/接收流量,第八列数据是上行/发送流量
        if 'rmnet' in line:
            rmnet=rmnet+int(line.split(' ')[5])+int(line.split(' ')[7])
        elif 'wlan' in line:
            wlan=wlan+int(line.split(' ')[5])+int(line.split(' ')[7])
    return rmnet,wlan

def get_flows(packages):
    '''
    获取所有的包名和对应流量
    :return:返回数据为 [[包名1,数据流量1,wifi流量1],[包名2,数据流量2,wifi流量2],[包名3,数据流量3,wifi流量3]...]
    '''
    package_flows=[]
    for uid_package in packages:
        rmnet=0
        wlan=0
        package_flow=[]
        #根据每一个uid查到对应的数据流量和wifi流量
        rmnet,wlan=get_per_flow(uid_package[0])
        #如果数据流量和wifi流量都为0,就不统计
        if rmnet==0 and wlan==0:
            pass
        else:
            # print(uid_package[1]+':\n'+'data:'+str(rmnet)+',wifi:'+str(wlan)+'\n')
            #添加包
            package_flow.append(uid_package[1])
            #添加数据流量
            package_flow.append(str(rmnet))
            #添加wifi流量
            package_flow.append(str(wlan))
            #把这个app的包名,数据流量,wifi流量加入到list里面
            package_flows.append(package_flow)
    print(package_flows)
    return package_flows

if __name__=='__main__':
    print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
    packages=remove_duplicate_pid()
    print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
    get_flows(packages)
    print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))

==================分割线============================
以下内容 2017/11/19 更新

根据 @sandman 的建议,用 adb 命令一次性抓取所有的 uid 和 package,命令为:adb shell dumpsys package packages,重新调整了一下脚本,时间直接缩短到 10 秒

下面是更新后的代码:

# coding:utf-8
import subprocess
import time

def get_uid_and_packages():
    uids=[]
    uid=''
    packages=[]
    package=''
    cmd='adb shell dumpsys package packages | findstr /c:"userId" /c:"Package ["'
    proc=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
    output=proc.communicate()[0]
    for line in output.split('\n'):
        #判断结果里面是否有userId
        if 'userId=' in line:
            #找到 userId= 的下标
            index=line.find('userId=')
            #从 userId= 的下标+7,开始遍历,直到字符串结束
            for i in range((index+7),len(line)):
                #如果是数字,就拼接到uid
                if line[i].isdigit():
                    uid=uid+line[i]
                else:
                    #不是数字的时候,就停止拼接,初始化uid
                    uids.append(uid)
                    uid=''
                    break
        #判断结果里面是否有Package [
        elif 'Package [' in line:
            #找到 Package [ 的下标
            index_start=line.find('[')
            index_end=line.find(']')
            #从"["到"]"这里面的是package
            package=line[index_start+1:index_end]
            packages.append(package)
    return [uids,packages]

def remove_duplicate_pid():
    '''
    去掉重复的pid,获得整个设备的uid,package
    :return:
    '''
    uids=get_uid_and_packages()[0]
    packages=get_uid_and_packages()[1]
    uid_and_packages=[]
    for i in range(len(packages)):
        #单个uid和package
        uid_package=[]
        #判定是否有重复的uid的标志位
        flag=0
        uid_package.append(uids[i])
        uid_package.append(packages[i])
        for up in uid_and_packages:
            #如果要加入的pid与已经存在相同,标志位置为1
            if up[0] == uids[i]:
                flag=1
        #如果有重复的就去掉重复的,不添加
        if flag==0:
            uid_and_packages.append(uid_package)
    return uid_and_packages

def get_per_flow(uid):
    '''
    根据uid获取流量
    :param uid:
    :return:
    '''
    #获取流量的adb命令
    cmd='adb shell cat /proc/net/xt_qtaguid/stats | find "'+uid+'"'
    proc=subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
    output=proc.communicate()[0].split('\n')
    #数据流量
    rmnet=0
    #wifi流量
    wlan=0
    for line in output:
        #第6列数据是下行/接收流量,第八列数据是上行/发送流量
        if 'rmnet' in line:
            rmnet=rmnet+int(line.split(' ')[5])+int(line.split(' ')[7])
        elif 'wlan' in line:
            wlan=wlan+int(line.split(' ')[5])+int(line.split(' ')[7])
    return rmnet,wlan

def get_flows():
    '''
    获取所有的包名和对应流量
    :return:返回数据为 [[包名1,数据流量1,wifi流量1],[包名2,数据流量2,wifi流量2],[包名3,数据流量3,wifi流量3]...]
    '''
    package_flows=[]
    for uid_package in remove_duplicate_pid():
        rmnet=0
        wlan=0
        package_flow=[]
        #根据每一个uid查到对应的数据流量和wifi流量
        rmnet,wlan=get_per_flow(uid_package[0])
        #如果数据流量和wifi流量都为0,就不统计
        if rmnet==0 and wlan==0:
            pass
        else:
            # print(uid_package[1]+':\n'+'data:'+str(rmnet)+',wifi:'+str(wlan)+'\n')
            #添加包
            package_flow.append(uid_package[1])
            #添加数据流量
            package_flow.append(str(rmnet))
            #添加wifi流量
            package_flow.append(str(wlan))
            #把这个app的包名,数据流量,wifi流量加入到list里面
            package_flows.append(package_flow)
    print(package_flows)
    print(len(package_flows))
    return package_flows

if __name__=='__main__':
    print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
    get_flows()
    print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime()))
共收到 15 条回复 时间 点赞

你头像好炫酷

如果用 java 就可以直接用 api 获取了 速度应该是毫秒级的

卡斯 回复

嗯,有空了用 java 去改下

杀手carry 回复

你也可以换这样的啊😂

善用 busybox 的文本流处理工具,如 awk,取/proc/net/xt_qtaguid/stats 并格式化输出耗时 20ms;取所有 app 的 uid 耗时 200ms

步骤一,busbox 官网下个安卓设备对应的 busybox
步骤二、push 到/data/local/tmp 下并付权限 755
adb push busbox /data/local/tmp
adb shell chmod 755 /data/local/tmp/busybox
步骤三、adb shell 下获取
adb shell
bb=/data/local/tmp/busybox
获取所有 app uid 以下命令是一行
dumpsys package packages|$bb grep -E "Package |userId"|$bb awk -v OFS="," '{if($1=="Package"){P=substr($2,2,length($2)-2)}else{if(substr($1,1,6)=="userId") print P,substr($1,8,length($1)-7)}}'
获取流量
$bb awk -v OFS=, 'NR>1{if($2=="wlan0"){wr[$4]+=$6;wt[$4]+=$8}else{if($2=="rmnet0"){rr[$4]+=$6;rt[$4]+=$8}}}END{for(i in wr){print i,wr[i],wt[i],"wifi"};for(i in rr){print i,rr[i],rt[i],"data"}}' /proc/net/xt_qtaguid/stats

ps: 结果格式化为 csv 格式,可按需自己修改;awk 中也可以对统计数据直接做浮点运算转换流量单位。

6Floor has deleted


用 java 改了下,根据 uid 获取到的值是-1,获取总流量确实正常数值,坑

浮云 回复

这个东西不了解,我自己用 python 多线程优化了下

绵绵不绝 回复

你试下这几行命令就清楚了,再有就是取数据能一次获取的不要循环抓,dumpsys package packages 可一次性拿到所有 app 的 uid,就是文本得后处理下

绵绵不绝 回复

再有就是 python 一样,建议注意下文本流处理的意识,逐行过一遍文本信息直接通过逻辑处理拿到最终结果。awk 就是这个思路,逐行处理中已完成逻辑判定和处理。

浮云 回复

厉害👍 用了你教的方法
时间直接缩短到 10 秒了

hi,我通过 uid 统计的流量之和,发现与实际的流量相差很大,差不多在两倍左右(实际流量我是通过开发在代码中打印请求响应大小的 log),所以我怀疑使用此方法统计流量时有重复累加的可能。查找到这个文章http://www.voidcn.com/article/p-tolukrhb-vz.html

其中 acct_tag_hex 为 0x0 的一行为某应用开机以来产生的总流量



所以,这样的话,我想如果把包含 uid 的所有行里的数据累加,可能会出现重复统计,差不多刚好一倍,因此我在代码中把 0x0 过滤掉了

if 'wlan' in line and '0x0' not in line:

之后,再统计的结果中,下行流量总和与开发给出的结果很相近。

  • adb 统计:
  • log 打印:
    但不清楚我的猜测是否正确,请多多指教
prettfool 回复

你搞反了,0x0 是总流量 tag、其他的 tag 是线程流量。不是去掉 0x0 而是只统计这个 tag。如果想了解详情才分别去看是哪些线程使用了流量。

@zhizhizhi2017 这个没有值是什么原因呢?

aabbcc 回复

抱歉,信息太多现在才看到。
没有值可能是因为脚本适配的问题,有的地方字符串截取不对,我调试脚本只适配了两个机型😂
自己可以调试一下,打印关键信息,看打印有没有问题


运行提示这个?

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up