Android 功能测试自动化框架较多,UIAutomator,Robotium,Appium等。Case执行过程中,可能希望收集手机的性能指标,包括内存、cpu、流量等。使用java+shell+bat简单实现了android手机性能收集。
简述
过程很简单:
- 在启动自动化case前,执行收集信息的命令。
其实就是一些adb shell命令,如下:
adb shell top -n 1| awk '{print $3" "$10}' >> cpu.dat adb shell ps | awk '{print $5" "$9}' >> mem.dat //android sdk level 大于16 adb shell cat /proc/uid_stat/$uid/tcp_rcv >> $uid"_recv.dat" adb shell cat /proc/uid_stat/$uid/tcp_snd >> $uid"_snd.dat" //android sdk level 小于16 adb shell cat /proc/$pid/net/dev | grep wlan | awk '{print $2" "$10}' >> $pid"_net.dat"
- 执行自动化case,可能需要很久。这个过程中,上面的命令在不断地执行
- case执行结束,kill掉第一步中的命令
- 分析收集到的dat文件,使用jscharts绘出走势图
脚本
CPU
使用top命令不断查看各进程的cpu占用
linux
get-android-cpu.sh
#!/bin/sh
#path--target/android-info/mem/dat
cd ../../../
mkdir -p target/android-info/cpu/dat
mkdir -p target/android-info/cpu/html
cp src/main/resources/jscharts.js target/android-info/cpu/html
cd target/android-info/cpu/dat
while true
do
adb shell top -n 1| awk '{print $3" "$10}' >> cpu.dat
sleep 15
done
windows
get-android-cpu.bat
cd ..\..\jenkins\workspace\android-info-end3
mkdir target\android-info\cpu\dat
mkdir target\android-info\cpu\html
copy src\main\resources\jscharts.js target\android-info\cpu\html
cd target\android-info\cpu\dat
:run
adb shell top -n 1 | awk "{print $3\" \"$10}" >> cpu.dat
ping 127.0.0.1 -n 15 > null
goto run
MEMORY
使用ps命令不断查看各进程的内存占用
linux
get-android-mem.sh
#!/bin/sh
#path--target/android-info/mem/dat
cd ../../../
mkdir -p target/android-info/mem/dat
mkdir -p target/android-info/mem/html
cp src/main/resources/jscharts.js target/android-info/mem/html
cd target/android-info/mem/dat
while true
do
adb shell ps | awk '{print $5" "$9}' >> mem.dat
sleep 15
done
windows
cd ..\..\jenkins\workspace\android-info-end3
mkdir target\android-info\mem\dat
mkdir target\android-info\mem\html
copy src\main\resources\jscharts.js target\android-info\mem\html
cd target\android-info\mem\dat
:run
adb shell ps | awk "{print $5\" \"$9}" >> mem.dat
ping 127.0.0.1 -n 15 > null
goto run
流量
android 4.0以上版本可以用/proc/uid_stat/$uid/tcp_rcv
和/proc/uid_stat/$uid/tcp_snd
来获取某个程序的上下行流量;而4.0以下版本要用cat /proc/$pid/net/dev
来查看上下行流量。uid和pid的关系,可以从/data/system/packages.list
这个文件中获取。
linux
判断android api level:
#!/bin/sh
#android 4.0以上和4.0以下方法不同
#get android sdk level
apileveltemp=`adb shell getprop | grep ro.build.version.sdk`
apilevel=${apileveltemp:25:2}
chmod +x *.sh
echo "android api level:"$apilevel
if [ $apilevel -gt 14 ]
then
./get-android-net-gt-4.0.sh
elif [ $apilevel -lt 14 ]
then
./get-android-net-lt-4.0.sh
fi
get-android-net-gt-4.0.sh
#!/bin/sh
#android api level great than 14(android 4.0)
#path--target/android-info/net/dat
cd ../../../
mkdir -p target/android-info/net/dat
mkdir -p target/android-info/net/html
cp src/main/resources/jscharts.js target/android-info/net/html
cd target/android-info/net/dat
cd ..
echo "adb pull/data/system/packages.list--start"
adb pull /data/system/packages.list
cd dat
while true
do
echo "get net info from /proc/uid-stat/$uid"
for i in `adb shell ls /proc/uid_stat`
do
#delete the Enter character
uid=`echo $i | tr -d ["\r\n"]`
adb shell cat /proc/uid_stat/$uid/tcp_rcv >> $uid"_recv.dat"
adb shell cat /proc/uid_stat/$uid/tcp_snd >> $uid"_snd.dat"
done
sleep 15
done
get-android-net-lt-4.0.sh
#!/bin/sh
#path--target/android-info/net/dat
cd ../../../
mkdir -p target/android-info/net/dat
mkdir -p target/android-info/net/html
cp src/main/resources/jscharts.js target/android-info/net/html
cd target/android-info/net/dat
#get /proc/$pid/net/dev
while true
do
#get pid
for i in `adb shell ps | awk '{print $2}'`
do
pid=`echo $i | tr -d ["\r\n"]`
echo $pid
adb shell cat /proc/$pid/net/dev | grep wlan | awk '{print $2" "$10}' >> $pid"_net.dat"
done
sleep 15
done
windows
bat命令不熟,不知道如何在windows下实现linux下地反转义,这里用java代码实现。
import java.io.*;
/**
* Created by Xuemeng Wang on 14-9-15.
* api Level > 16
*/
public class GetNetInfo {
public static void main(String[] args) {
String uidString = execCmd("adb shell ls /proc/uid_stat");
String[] uidArray = uidString.split("\n");
int length = uidArray.length-1;
for(int i=0;i<=length-1;i++)
{
String contentRcv = execCmd("adb shell cat /proc/uid_stat/"+uidArray[i]+"/tcp_rcv");
String contentSnd = execCmd("adb shell cat /proc/uid_stat/"+uidArray[i]+"/tcp_snd");
System.out.println(System.getProperty("user.dir"));
try {
File file = new File(uidArray[i]+"_recv.dat");
if(!file.exists()) file.createNewFile();
File file2 = new File(uidArray[i]+"_snd.dat");
if(!file2.exists()) file2.createNewFile();
FileWriter fileWriter1 = new FileWriter(file, true);
FileWriter fileWriter2 = new FileWriter(file2, true);
fileWriter1.write(contentRcv);
fileWriter2.write(contentSnd);
fileWriter1.close();
fileWriter2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static String execCmd(String command)
{
BufferedReader br = null;
StringBuffer stringBuffer = new StringBuffer();
try {
Process p = Runtime.getRuntime().exec(command);
br = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {
if("".equals(line.trim())) continue;
stringBuffer.append(line+"\n");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return stringBuffer.toString();
}
}
api level小于16的情况,我没用到,略掉:)。
杀掉进程
linux: ps -ef | grep get-android- | grep -v grep | awk '{print $2}' | xargs kill -9
windows(后台运行,其实是cmd进程,注意下面会杀掉所有的cmd进程):
taskkill /F /IM cmd.exe
taskkill /F /IM adb.exe
结果收集
使用java分析dat,给出分析cpu的代码,工程已上传到github,https://github.com/yeetrack/android-performance。
CpuInfo.java
package com.meilishuo.android.performance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
/**
* Created by victor on 14-9-9.
*/
public class CpuInfo {
private static final Logger logger = LoggerFactory.getLogger(MemInfo.class);
private static final String JSCHARTPATH = "target/android-info/cpu/html/";
private static final String MEMFILEPATH = "target/android-info/cpu/dat/cpu.dat";
TreeMap<String, List<String>> cpuMap = new TreeMap<String, List<String>>();
/**
* 解析cpu.dat存储到map中
*/
public void parseCpuFile()
{
File file = new File(MEMFILEPATH);
if(null==file) return;
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
String line = null;
while((line=bufferedReader.readLine())!=null)
{
String[] array = line.trim().split(" "); //空格分隔
if(null==array || array.length!=2 || !array[0].contains("%") || "".equals(array[1]) || "Name".equals(array[1]))
continue;
if(cpuMap.size()==0 || !cpuMap.containsKey(array[1]))
{
List<String> memList = new ArrayList<String>();
memList.add(array[0].substring(0, array[0].indexOf("%")));
cpuMap.put(array[1], memList);
}
else
{
cpuMap.get(array[1]).add(array[0].substring(0, array[0].indexOf("%")));
}
}
System.out.println(cpuMap.size());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 将map中的数据写到到xml中
*/
public void writeXmlFromMap()
{
if(null==cpuMap || cpuMap.size()==0)
return;
Iterator<String> it = cpuMap.keySet().iterator();
while(it.hasNext())
{
String key = it.next();
List<String> value = cpuMap.get(key);
if(null==value || value.size()<=1)
continue;
//写入xml
File file = new File(JSCHARTPATH+key.replace("/", "_").replace(":", "_")+"_cpu.xml");
try {
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
bufferedWriter.write(
"<?xml version=\"1.0\"?>\n" +
"<JSChart>\n" +
"\t<dataset type=\"line\">");
int pos = 1;
for(String index : value)
{
bufferedWriter.write("<data unit=\""+pos+"\" value=\""+index+"\"/>\n");
pos++;
}
bufferedWriter.write(
"</dataset>\n" +
"\t<optionset>\n" +
"\t\t<option set=\"setLineColor\" value=\"'#8D9386'\"/>\n" +
"\t\t<option set=\"setLineWidth\" value=\"4\"/>\n" +
"\t\t<option set=\"setTitleColor\" value=\"'#7D7D7D'\"/>\n" +
"\t\t<option set=\"setAxisColor\" value=\"'#9F0505'\"/>\n" +
"\t\t<option set=\"setGridColor\" value=\"'#a4a4a4'\"/>\n" +
"\t\t<option set=\"setAxisValuesColor\" value=\"'#333639'\"/>\n" +
"\t\t<option set=\"setAxisNameColor\" value=\"'#333639'\"/>\n" +
"\t\t<option set=\"setTextPaddingLeft\" value=\"0\"/>\n" +
"\t</optionset>\n " +
"</JSChart>");
bufferedWriter.flush();
bufferedWriter.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 生成jschart html文件
*/
public void writeJsHtml()
{
File file = new File(JSCHARTPATH);
File[] xmlFiles = file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith("_cpu.xml");
}
});
if(null== xmlFiles || xmlFiles.length==0) return;
for(File index : xmlFiles)
{
File htmlFile = new File(index.getAbsolutePath().replace(".xml", ".html"));
try {
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(htmlFile));
bufferedWriter.write(
"<html>\n" +
"<head>\n" +
"\n" +
"<title>JSChart</title>\n" +
"\n" +
"<script type=\"text/javascript\" src=\"jscharts.js\"></script>\n" +
"\n" +
"</head>\n" +
"<body>\n" +
"<div id=\"result\">\n" +
"<h3>"+htmlFile.getName().replace(".xml", "")+"</h3>" +
"<div id=\"graph\">Loading graph...</div>\n" +
"\n" +
"<script type=\"text/javascript\">\n" +
"\t\n" +
"\tvar myChart = new JSChart('graph', 'line');\n" +
"\tmyChart.setDataXML(\""+index.getName()+"\");\n" +
"\tmyChart.draw();\n" +
"\t\n" +
"</script>\n" +
"\n" +
"\n" +
"</div>\n" +
"</body>\n" +
"</html>\n"
);
bufferedWriter.flush();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void writeAllJsHtml()
{
File[] files = new File(JSCHARTPATH).listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
return pathname.getName().endsWith("_cpu.html");
}
});
if(files.length==0) return;
File allHtmlFile = new File(JSCHARTPATH+"all_cpu.html");
if(allHtmlFile.exists())
allHtmlFile.delete();
try {
allHtmlFile.createNewFile();
BufferedWriter out = new BufferedWriter(new FileWriter(allHtmlFile, true));
String header =
"<!DOCTYPE html>\n" +
"<html>\n" +
"<head lang=\"en\">\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>cpu使用率</title>\n" +
"</head>\n" +
"<body>\n" +
"<h1> cpu使用率</h1>\n";
out.write(header);
for(File index : files)
{
String fileName = index.getName();
out.write("<iframe src=\""+fileName+"\" width=\"100%\" height=\"500\"></iframe><br>\n");
}
out.write("</body>\n</html>");
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void generateCpuHtmlInfo()
{
CpuInfo cpuInfo = new CpuInfo();
cpuInfo.parseCpuFile();
cpuInfo.writeXmlFromMap();
cpuInfo.writeJsHtml();
cpuInfo.writeAllJsHtml();
}
}
jenkins问题
- 执行自动化前,调用shell、bat文件必须在后台运行,默认情况下,jenkins启动的进程会阻塞后面进程执行。jenkins支持后台启动进程,参考链接:Jenkins启动守护进程后台持续运行
- windows在使用at启动定时任务时,当前的路径是C:\Windows\system32(其实就是cmd.exe的路径),当时这个忽略,各种路径问题。
- windows使用at命令执行bat脚本,发现
java
,javac
等命令执行无反应,不知道为何(环境变量配了,jenkins可以直接调用java等命令),后来发现将路径写后执行成功。 - 最后注意一点,一定要记得kill掉后台的进程,否则文件写满硬盘,:)。
附上生成的html示例地址:https://github.com/yeetrack/android-performance/tree/master/target/android-info/cpu/html
版权声明
本站文章、图片、视频等(除转载外),均采用知识共享署名 4.0 国际许可协议(CC BY-NC-SA 4.0),转载请注明出处、非商业性使用、并且以相同协议共享。
© 空空博客,本文链接:https://www.yeetrack.com/?p=970
近期评论