【转载】源码解读腾讯 GT 的性能测试方案

来源:互联网 发布:电竞耳机 知乎 编辑:程序博客网 时间:2024/05/15 23:52

前言

本文将整理腾讯GT各个性能测试项的测试方法,目的是为了帮助移动性能专项测试同学快速过一遍腾讯GT各个性能数据是如何获取的。
另外对腾讯GT还不了解或者不知道它能做什么的同学可以看看这篇文章:
GT-非官方介绍 · TesterHome

一.GT性能测试方案之CPU测试

1.简要流程

初始化cpu的数据
提供了两种方法获取CPU数据 getCpuUsage: 整机的CPU使用水平,主要用于实时刷新GT上的CPU数据。通过读取/proc/stat的数据,将每一个核的cpu使用跟闲置数据提取。使用率永远是增量式计算。计算方法为100*(cpu忙时增量-cpu整体增量),从计算方法来看,可能会导致负数出现。 getProcessCpuUsage:计算进程的CPU使用率,主要通过”/proc/” + pid + “/stat”来计算,在这里回京过一系列计算,拿到进程的CPU时间片
2.代码流程

cpu数据初始化 经过初始化,让CPU整体使用跟进程的CPU占用都为0
`public CpuUtils() {
initCpuData();
}

private void initCpuData() {
pCpu = o_pCpu = 0.0;
aCpu = o_aCpu = 0.0;

}`
通过不同的调用栈,来监控不同的CPU数据
整体CPU使用率:由于getCpuUsage是通过后台线程不断刷新来实现的,因此,o_cpu/o_idle数据不断在实时更新

RandomAccessFile reader = null;try {reader = new RandomAccessFile("/proc/stat", "r");String load;load = reader.readLine();String[] toks = load.split(" ");double c_idle = Double.parseDouble(toks[5]);double c_cpu = Double.parseDouble(toks[2])     + Double.parseDouble(toks[3])     + Double.parseDouble(toks[4])     + Double.parseDouble(toks[6])     + Double.parseDouble(toks[8])     + Double.parseDouble(toks[7]);if (0 != ((c_cpu + c_idle) - (o_cpu + o_idle))) {  // double value = (100.00 * ((c_cpu - o_cpu) ) / ((c_cpu +  // c_idle) - (o_cpu + o_idle)));  usage = DoubleUtils.div((100.00 * ((c_cpu - o_cpu))),        ((c_cpu + c_idle) - (o_cpu + o_idle)), 2);  // Log.d("CPU", "usage: " + usage);  if (usage < 0) {     usage = 0;  }  else if (usage > 100)  {     usage = 100;  }  // BigDecimal b = new BigDecimal(Double.toString(value));  // usage = b.setScale(2,  // BigDecimal.ROUND_HALF_UP).doubleValue();  // Log.d("CPU", "usage: " + usage);}o_cpu = c_cpu;o_idle = c_idle;} catch (IOException e) {e.printStackTrace();} finally {FileUtil.closeRandomAccessFile(reader);}

进程的CPU使用时间片获取

public String getProcessCpuUsage(int pid) {   String result = "";   String[] result1 = null;   String[] result2 = null;   if (pid >= 0) {      result1 = getProcessCpuAction(pid);      if (null != result1) {         pCpu = Double.parseDouble(result1[1])               + Double.parseDouble(result1[2]);      }      result2 = getCpuAction();      if (null != result2) {         aCpu = 0.0;         for (int i = 2; i < result2.length; i++) {            aCpu += Double.parseDouble(result2[i]);         }      }      double usage = 0.0;      if ((aCpu - o_aCpu) != 0) {         usage = DoubleUtils.div(((pCpu - o_pCpu) * 100.00),               (aCpu - o_aCpu), 2);         if (usage < 0) {            usage = 0;         }         else if (usage > 100)         {            usage = 100;         }      }      o_pCpu = pCpu;      o_aCpu = aCpu;      result = String.valueOf(usage) + "%";   }   p_jif = pCpu;   return result;}

二.GT性能测试方案之内存测试

1.简要流程

内存测试主要通过handleMessage来触发,根据msg参数来决定任务执行

内存数据获取:仅仅简单通过dumpsys meminfo来获取内存数据,然后通过解析来得到pss_Native/naticeHeapSize/naticeAllocated/pss_OtherDev/pss_graphics/pss_gl/pss_UnKnown/pss_total数据
dumpHeap:通过am dumpheap 来获取heap文件
GC:直接kill -10 $pid 来完成GC
2.测试方法

内存数据获取

public static MemInfo getMemInfo(String packageName){   MemInfo result = null;   String resultString = null;   try {      resultString = runCMD("dumpsys meminfo " + packageName);   } catch (Exception e) {      e.printStackTrace();      return MemInfo.EMPTY;   }   if(Env.API < 14)   {      result = parseMemInfoFrom2x(resultString);   }   else if (Env.API < 19)   {      result = parseMemInfoFrom4x(resultString);   }   else   {      result = parseMemInfoFrom44(resultString);   }   return result;}

dumpHeap

private void dumpHeap() {   String pid = String.valueOf(ProcessUtils         .getProcessPID(AUTManager.pkn.toString()));   if (!pid.equals("-1")) {      boolean isSucess = true;      ProcessBuilder pb = null;      String sFolder = Env.S_ROOT_DUMP_FOLDER + AUTManager.pkn.toString() + "/";      File folder = new File(sFolder);      if (!folder.exists())      {         folder.mkdirs();      }      String cmd = "am dumpheap " + pid + " "// 命令            + Env.S_ROOT_DUMP_FOLDER + AUTManager.pkn.toString() + "/"// 输出路径            + "dump_" + pid + "_" + GTUtils.getSaveDate() + ".hprof"; // 输出文件名      pb = new ProcessBuilder("su", "-c", cmd);      Process exec = null;      pb.redirectErrorStream(true);      try {         exec = pb.start();         InputStream is = exec.getInputStream();         BufferedReader reader = new BufferedReader(               new InputStreamReader(is));         while ((reader.readLine()) != null) {            isSucess = false;         }      } catch (Exception e) {         e.printStackTrace();         isSucess = false;      }      // 至此命令算是执行成功      if (isSucess)      {         handler.sendEmptyMessage(6);      }   } else {      Log.d("dump error", "pid not found!");   }}

GC

private void gc() {   String pid = String.valueOf(ProcessUtils         .getProcessPID(AUTManager.pkn.toString()));   if (!pid.equals("-1")) {      boolean isSucess = true;      ProcessBuilder pb = null;      String cmd = "kill -10 " + pid;      pb = new ProcessBuilder("su", "-c", cmd);      Process exec = null;      pb.redirectErrorStream(true);      try {         exec = pb.start();         InputStream is = exec.getInputStream();         BufferedReader reader = new BufferedReader(               new InputStreamReader(is));         while ((reader.readLine()) != null) {            isSucess = false;         }      } catch (Exception e) {         e.printStackTrace();         isSucess = false;      }      // 至此命令算是执行成功      if (isSucess)      {         handler.sendEmptyMessage(5);      }   } else {      Log.d("gc error", "pid not found!");   }}

三.GT性能测试方案之内存填充

1.简要流程

内存填充,主要是通过调用系统的malloc来分配内存。内存释放,则是通过系统free来释放。

  1. 代码流程

内存分配以及释放的函数

const int BASE_SIZE = 1024*1024; // 1Mint fill(int blockNum){    int memSize = blockNum * BASE_SIZE;    p = (char *)malloc(memSize);    int i;    for (i = 0; i < memSize; i++)    {        p[i] = 0;    }    return 0;}int freeMem(){    free(p);    return 0;}

加载com_tencent_wstt_gt_api_utils_MemFillTool.c,并提供度应对操作接口

public class MemFillTool {   public MemFillTool() {   }   public static MemFillTool instance = null;   public static MemFillTool getInstance() {      if (instance == null) {         System.loadLibrary("mem_fill_tool");         instance = new MemFillTool();      }      return instance;   }   // 填充xxxMB内存   public native int fillMem(int blockNum);   // 释放刚才填充的内存   public native int freeMem();}

四.GT性能测试方案之帧率测试

1.简要流程

FPS数据收集是一个定时任务(4.3后1s一次),通过异步线程中不断获取FPS数据来刷新到前端页面。而广播模式调用,则直接从缓存的field中获取数据即可。
在这里GT获取fps数据,也是通过采用surfaceflinger来获取(执行”service call SurfaceFlinger 1013”),题外话:Runtime.getRuntime()原来执行多条命令时后续只要拿到process的DataOutputStream对象,继续writeBytes就可以保证是在同一个上下文中执行多条命令了。

2.代码流程

判断当前root状态,如果没有root直接返回,避免消耗系统资源

if (! GTFrameUtils.isHasSu()){   return;}

计算一个周期内的帧率数据,由于比较简单(除了在1中surfaceflinger数据存疑外),直接把核心代码发出来

startTime = System.nanoTime();if (testCount == 0) {   try {      lastFrameNum = getFrameNum();   } catch (IOException e) {      e.printStackTrace();   }}int currentFrameNum = 0;try {   currentFrameNum = getFrameNum();} catch (IOException e) {   e.printStackTrace();}int FPS = currentFrameNum - lastFrameNum;if (realCostTime > 0.0F) {   int fpsResult = (int) (FPS * 1000 / realCostTime);   defaultClient.setOutPara("FPS", fpsResult);}lastFrameNum = currentFrameNum;testCount += 1;

帧率获取的部分也发一下。另外感谢@codeskyblue 的指点,service call SurfaceFlinger 1013这个命令是获取系统的总的刷新帧率(返回的是16进制)

public static synchronized int getFrameNum() throws IOException {   String frameNumString = "";   String getFps40 = "service call SurfaceFlinger 1013";   if (process == null)   {      process = Runtime.getRuntime().exec("su");      os = new DataOutputStream(process.getOutputStream());      ir = new BufferedReader(            new InputStreamReader(process.getInputStream()));   }   os.writeBytes(getFps40 + "\n");   os.flush();   String str = "";   int index1 = 0;   int index2 = 0;   while ((str = ir.readLine()) != null) {      if (str.indexOf("(") != -1) {         index1 = str.indexOf("(");         index2 = str.indexOf("  ");         frameNumString = str.substring(index1 + 1, index2);         break;      }   }   int frameNum;   if (!frameNumString.equals("")) {      frameNum = Integer.parseInt(frameNumString, 16);   } else {      frameNum = 0;   }   return frameNum;}

五.GT性能测试方案之流畅度测试

1.简要流程

腾讯的流畅度测试比较简单粗暴,测试方式是通过初始化choreographer日志级别,生成Choreographer日志来得到当前操作的丢帧。通过一系列计算后来计算流畅度。

2.测试方法

执行setprop debug.choreographer.skipwarning 1

View.OnClickListener button_write_property = new View.OnClickListener() {        @Override        public void onClick(View v) {            String cmd = "setprop debug.choreographer.skipwarning 1";            ProcessBuilder execBuilder = new ProcessBuilder("su", "-c", cmd);            execBuilder.redirectErrorStream(true);            try {                execBuilder.start();            } catch (IOException e) {                e.printStackTrace();            }        }    };

执行getprop debug.choreographer.skipwarning判断,为1则可以进行测试

View.OnClickListener button_check_status = new View.OnClickListener() {        @Override        public void onClick(View v) {            String cmd = "getprop debug.choreographer.skipwarning";            ProcessBuilder execBuilder = new ProcessBuilder("sh", "-c", cmd);            execBuilder.redirectErrorStream(true);            try {                TextView textview = (TextView) findViewById(R.id.textviewInformation);                Process p = execBuilder.start();                InputStream is = p.getInputStream();                InputStreamReader isr = new InputStreamReader(is);                BufferedReader br = new BufferedReader(isr);                Boolean flag = false;                String line;                while ((line = br.readLine()) != null) {                    if (line.compareTo("1") == 0) {                        flag = true;                        break;                    }                }                if (flag) {                    textview.setText("OK");                } else {                    textview.setText("NOT OK");                }            } catch (IOException e) {                e.printStackTrace();            }        }    };

执行adb logcat -v time -s Choreographer:I *:S
过滤获取当前pid丢帧值

protected void onHandleIntent(Intent intent) {        try {            String str = intent.getStringExtra("pid");            int pid = Integer.parseInt(str);            List<String> args = new ArrayList<String>(Arrays.asList("logcat", "-v", "time", "Choreographer:I", "*:S"));            dumpLogcatProcess = RuntimeHelper.exec(args);            reader = new BufferedReader(new InputStreamReader(dumpLogcatProcess.getInputStream()), 8192);            String line;            while ((line = reader.readLine()) != null && !killed) {                // filter "The application may be doing too much work on its main thread."                if (!line.contains("uch work on its main t")) {                    continue;                }                int pID = LogLine.newLogLine(line, false).getProcessId();                if (pID != pid){                    continue;                }                line = line.substring(50, line.length() - 71);                Integer value = Integer.parseInt(line.trim());                SMServiceHelper.getInstance().dataQueue.offer(value);            }        } catch (IOException e) {            Log.e(TAG, e.toString() + "unexpected exception");        } finally {            killProcess();        }    }

数据处理得到sm值 腾讯这边的处理方案是:当丢帧<60时,流畅度SM =60-frame; 当丢帧frame>60时,流畅度SM = 60-frame%60。不过这种处理方式是有问题的。在这里要先说下流畅度计算的原理:
VSync机制可以通过其Loop来了解当前App最高绘制能力,固定每隔16.6ms执行一次,这样最高的刷新的帧率就控制在60FPS以内,Choreographer日志可以打印当前丢帧数,因此通过计算,得到当前APP的流畅度。
而计算这样来计算可能会更加准确(个人看法,欢迎讨论): SM= 60-丢帧frame/每两行同一线程的丢帧时间差(单位:s),如果只关心UI线程,那就只需要统计UI线程即可。

while (true) {    if (pause) {        break;    }    int x = count.getAndSet(0);    // 卡顿大于60时,要将之前几次SM计数做修正    if (x > 60) {        int n = x / 60;        int v = x % 60;        TagTimeEntry tte = OpPerfBridge.getProfilerData(key);        int len = tte.getRecordSize();        // 补偿参数        int p = n;        //Math.min(len, n);        /*        * n > len是刚启动测试的情况,日志中的亡灵作祟,这种情况不做补偿;        * 并且本次也记为60。本逻辑在两次测试间会清理数据的情况生效。        */        if (n > len) {            globalClient.setOutPara(key, 60);//          globalClient.setOutPara(SFKey, 0);        } else {            for (int i = 0; i < p; i++) {            TimeEntry te = tte.getRecord(len - 1 - i);            te.reduce = 0;            }        globalClient.setOutPara(key, v);//      globalClient.setOutPara(SFKey, 60 - v);        }    } else {        int sm = 60 - x;        globalClient.setOutPara(key, sm);//      globalClient.setOutPara(SFKey, x);    }

六.GT性能测试方案之流量测试

1.简要流程

流量测试有三种方案,默认采用方案1

通过读取”/proc/uid_stat/” + uid + “/tcp_snd”获取发送跟接收流量
直接调用android的api:TrafficStats.getUidTxBytes(uid)来获取流量数据(该方法号称是获取到指定 uid 发送流量的总和,但实测情况是只有 tcp 层的流量)
第三种方案居然空在那里,那实际上只有两种方案
2.代码流程

初始化流量

public void initProcessNetValue(String pName) {   p_t_base = getOutOctets(pName);   p_r_base = getInOctets(pName);   p_t_add = 0;   p_r_add = 0;}

其中getOutOctets/getInOctets具体对应什么方法,需要看设备是不是支持uid流量数据获取

获取增加的流量

public String getProcessNetValue(String pName) {   StringBuffer sb = new StringBuffer();   java.text.DecimalFormat df = new java.text.DecimalFormat("#.##");   p_t_cur = getOutOctets(pName);   p_r_cur = getInOctets(pName);   p_t_add = (p_t_cur - p_t_base) / B2K;   p_r_add = (p_r_cur - p_r_base) / B2K;   sb.append("t");   sb.append(df.format(p_t_add));   sb.append("KB|r");   sb.append(df.format(p_r_add));   sb.append("KB");   return sb.toString();}

矫正处理

// modify on 20120616 过滤有的手机进程流量偶尔输出负数的情况if ((nowT != lastT || nowR != lastR) && nowT >= 0 && nowR >= 0) {   OpPerfBridge.addHistory(op, value, new long[]{(long) nowT, (long) nowR});}return value;

七.GT性能测试方案之电量测试

1.简单流程

关注指标:
电量测试关注的指标有四个: 电流,电压,电量跟温度。
数据获取方式:
通过ReadPowerTimerTask任务去set关注的电量指标,当update方法调用时,才把数据set进去。电量数据调用的系统命令/sys/class/power_supply/battery/uevent
具体流程:
接收”com.tencent.wstt.gt.plugin.battery.startTest”广播后,update各个指标
注册跟设置出参,并设置刷新频率跟初始化屏幕电量
开启定时任务ReadPowerTimerTask,这个任务的作用就是去获取/sys/class/power_supply/battery/uevent下的电量数据并解析,最后设置出参。刷新频率默认是250ms一次
最后stop时,销毁异步定时任务
2.代码流程

整个生命周期如下,当BATTERY_START_TEST行为被捕获时,开始执行电量测试

String action = intent.getAction();if (action == null) return;if (action.equals(BATTERY_START_TEST)) {   int refreshRate = intent.getIntExtra("refreshRate", 250);   int brightness = intent.getIntExtra("brightness", 100);   boolean updateI = intent.getBooleanExtra("I", true);   GTBatteryEngine.getInstance().updateI(updateI);   boolean updateU = intent.getBooleanExtra("U", false);   GTBatteryEngine.getInstance().updateU(updateU);   boolean updateT = intent.getBooleanExtra("T", false);   GTBatteryEngine.getInstance().updateT(updateT);   boolean updateP = intent.getBooleanExtra("P", false);   GTBatteryEngine.getInstance().updateP(updateP);   GTBatteryEngine.getInstance().doStart(refreshRate, brightness);} else if (action.equals(BATTERY_END_TEST)) {   GTBatteryEngine.getInstance().doStop();}

update操作很简单,只是注册跟set出参

public void updateI(boolean isChecked){   if (isChecked)   {      globalClient.registerOutPara(GTBatteryEngine.OPI, "I");      globalClient.setOutparaMonitor(GTBatteryEngine.OPI, true);   }   else   {      globalClient.unregisterOutPara(GTBatteryEngine.OPI);   }   state_cb_I = isChecked;   GTPref.getGTPref().edit().putBoolean(GTBatteryEngine.KEY_I, isChecked).commit();   for (BatteryPluginListener listener : listeners)   {      listener.onUpdateI(isChecked);   }}

初始化操作略过,这里展示异步任务,处理流程直接看下面的关键代码即可,方法在GTBatteryEngine.java中

timer = new Timer(true);timer.schedule(new ReadPowerTimerTask(), refreshRate, refreshRate);@Overridepublic void run() {   BufferedReader br = null;   try {      FileReader fr = new FileReader(f);      br = new BufferedReader(fr);      String line = "";      while((line = br.readLine()) != null){         int found = 0;         if (line.startsWith("POWER_SUPPLY_VOLTAGE_NOW="))         {            U = line.substring(line.lastIndexOf("=") + 1);            // since 2.1.1 从μV转成mV            long volt = Long.parseLong(U) / 1000;            globalClient.setOutPara(OPU, volt + "mV");            OutPara op = globalClient.getOutPara(OPU);            if (null != op)            {               OpPerfBridge.addHistory(op, U, volt);            }            found++;         }         if (line.startsWith("POWER_SUPPLY_CURRENT_NOW="))         {            I = line.substring(line.lastIndexOf("=") + 1);            // since 2.1.1 从μA转成mA since 2.2.4 华为本身就是mA            long current = Long.parseLong(I);            if (isHuawei)            {               current = -current;            }            else if (isLGg3)            {               current = current >> 1; // 经验值估算LG g3的数据除以2后比较接近真实            }            else            {               current = current / 1000;            }            globalClient.setOutPara(OPI, current + "mA");            OutPara op = globalClient.getOutPara(OPI);            if (null != op)            {               OpPerfBridge.addHistory(op, I, current);            }            found++;         }         if (line.startsWith("POWER_SUPPLY_CAPACITY="))         {            String lastBattery = POW;            POW =  line.substring(line.lastIndexOf("=") + 1);            if (! lastBattery.equals(POW)) // 电池百分比变化了            {               if (startBattry != -1)               {                  lastBatteryChangeTime = (System.currentTimeMillis() - startBattry)/1000 + "s";                  String tempValue = POW + "% | -1% time:" + lastBatteryChangeTime;                  globalClient.setOutPara(OPPow, tempValue);                  GTLog.logI(LOG_TAG, tempValue);                  // 将电量加入历史记录                  OutPara op = globalClient.getOutPara(OPPow);                  if (null != op)                  {                     OpPerfBridge.addHistory(op, tempValue, Long.parseLong(POW));                  }               }               startBattry = System.currentTimeMillis();            }            globalClient.setOutPara(OPPow, POW + "% | -1% time:" + lastBatteryChangeTime);            found++;         }         if (line.startsWith("POWER_SUPPLY_TEMP="))         {            TEMP = line.substring(line.lastIndexOf("=") + 1);            int iTemp = Integer.parseInt(TEMP);            iTemp = iTemp/10;            if (iTemp > -273)            {               TEMP = iTemp + "℃";            }            globalClient.setOutPara(OPTemp, TEMP);            OutPara op = globalClient.getOutPara(OPTemp);            if (null != op && iTemp != INT_TEMP)            {               OpPerfBridge.addHistory(op, TEMP, iTemp);               GTLog.logI(LOG_TAG, TEMP);               INT_TEMP = iTemp;            }            found++;         }         if (found >= 4)         {            return;         }      }   } catch (Exception e) {      doStop();   }   finally   {      FileUtil.closeReader(br);   }}

转载自:https://zhuanlan.zhihu.com/p/27697524

原创粉丝点击