机智的防爬虫标识
原创博客地址:https://testerhome.com/topics/9094
博客求关注: http://www.cnblogs.com/alexkn
本文将整理腾讯 GT 各个性能测试项的测试方法,目的是为了帮助移动性能专项测试同学快速过一遍腾讯 GT 各个性能数据是如何获取的。
另外对腾讯 GT 还不了解或者不知道它能做什么的同学可以看看这篇文章:https://testerhome.com/topics/9092
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;
}
内存测试主要通过 handleMessage 来触发,根据 msg 参数来决定任务执行
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;
}
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!");
}
}
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!");
}
}
内存填充,主要是通过调用系统的 malloc 来分配内存。内存释放,则是通过系统 free 来释放。
const int BASE_SIZE = 1024*1024; // 1M
int 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;
}
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();
}
FPS 数据收集是一个定时任务(4.3 后 1s 一次),通过异步线程中不断获取 FPS 数据来刷新到前端页面。而广播模式调用,则直接从缓存的 field 中获取数据即可。
在这里 GT 获取 fps 数据,也是通过采用 surfaceflinger 来获取,但我感觉好像是有问题的。因为,一般 surfaceflinger 数据获取的命令是adb shell dumpsys SurfaceFlinger --latency <window name>
,在这里直接定义了把"service call SurfaceFlinger 1013"字符串写到流里,没看明白这个操作跟帧率获取有什么关系。刚去了解了下,Runtime.getRuntime()
原来执行多条命令时后续只要拿到process
的DataOutputStream
对象,继续writeBytes
就可以保证是在同一个上下文中执行多条命令了。
if (! GTFrameUtils.isHasSu())
{
return;
}
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;
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;
}
腾讯的流畅度测试比较简单粗暴,测试方式是通过初始化 choreographer 日志级别,生成 Choreographer 日志来得到当前操作的丢帧。通过一系列计算后来计算流畅度。
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
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();
}
}
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);
}
流量测试有三种方案,默认采用方案 1
"/proc/uid_stat/" + uid + "/tcp_snd"
获取发送跟接收流量api:TrafficStats.getUidTxBytes(uid)
来获取流量数据(该方法号称是获取到指定 uid 发送流量的总和,但实测情况是只有 tcp 层的流量)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;
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 一次整个生命周期如下,当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();
}
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);
@Override
public 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);
}
}