提交 d8153479 编写于 作者: A andrewleo

Added: add setting options: collect heap data, require root permission

上级 fd1ebf25
##Emmagee - a practical, handy performance test tool for specified Android App
Emmagee is a practical, handy performance test tool for specified Android App, which can monitor CPU, memory,
network traffic, battery current and status([Some devices are not supported](https://github.com/NetEase/Emmagee/wiki/Some-devices-are-not-supported)). Additionally, it also provides several cool features such as customizing interval of collecting data,
network traffic, battery current and status([Some devices are not supported](https://github.com/NetEase/Emmagee/wiki/Some-devices-are-not-supported)), new features such as top activity and heap size if rooted([Root Toast may continously show](https://github.com/NetEase/Emmagee/wiki/FAQ)), are also supported in the [latest version](https://github.com/NetEase/Emmagee/releases). Additionally, it also provides several cool features such as customizing interval of collecting data,
rendering real-time process status in a floating window, and much more.
* Homepage: https://github.com/NetEase/Emmagee
* Wiki: https://github.com/NetEase/Emmagee/wiki
* Issues: https://github.com/NetEase/Emmagee/issues
* FAQ: https://github.com/NetEase/Emmagee/wiki/FAQ
* Tags: Android, Java
<img src="https://github.com/andrewleo/pictures/blob/master/Emmagee/V2.0/homepage.png" width="180px" />
......
......@@ -58,8 +58,8 @@
android:layout_width="50sp"
android:layout_height="wrap_content"
android:gravity="center_vertical|right"
android:textSize="@dimen/text_size"
android:textColor="@color/black" >
android:textColor="@color/black"
android:textSize="@dimen/text_size" >
</TextView>
</LinearLayout>
</LinearLayout>
......@@ -72,9 +72,9 @@
android:max="59"
android:maxHeight="4.0dip"
android:minHeight="4.0dip"
android:paddingBottom="@dimen/layout_vertical_margin_small"
android:paddingLeft="16.0dip"
android:paddingRight="16.0dip"
android:paddingBottom="@dimen/layout_vertical_margin_small"
android:progress="5"
android:progressDrawable="@drawable/custom_seekbar"
android:thumb="@drawable/seekbar_thumb" />
......@@ -107,10 +107,49 @@
android:background="@drawable/custom_checkbox"
android:button="@null"
android:checked="true"
android:paddingRight="@dimen/image_padding"
android:paddingLeft="@dimen/image_padding" />
android:paddingLeft="@dimen/image_padding"
android:paddingRight="@dimen/image_padding" />
</RelativeLayout>
<LinearLayout
android:id="@+id/heap_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/layout_vertical_margin_small" >
<TextView
android:id="@+id/collect_heap"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/collect_heap"
android:textColor="@color/black"
android:textSize="@dimen/text_size" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/root_notification"
android:textColor="@color/gray"
android:textSize="@dimen/text_size" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="right" >
<CheckBox
android:id="@+id/is_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:background="@drawable/custom_checkbox"
android:button="@null"
android:checked="false"
android:gravity="right"
android:paddingLeft="@dimen/image_padding"
android:paddingRight="@dimen/image_padding" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
......
......@@ -76,4 +76,12 @@
<string name="top_activity">栈顶Activity名称</string>
<string name="csv_encoding">GBK</string>
<string name="dalvik_heap">Dalvik heap alloc/size(KB)</string>
<string name="native_heap">Native heap alloc/size(KB)</string>
<string name="collect_heap">统计Heap数据</string>
<string name="root_notification">(需要root)</string>
<string name="root_failed_notification">无法获取root权限,请确认手机是否已经root过</string>
</resources>
......@@ -77,4 +77,10 @@
<string name="top_activity">Top Activity Name</string>
<string name="csv_encoding">UTF-8</string>
<string name="dalvik_heap">Dalvik heap alloc/size(KB)</string>
<string name="native_heap">Native heap alloc/size(KB)</string>
<string name="collect_heap">Collect Heap</string>
<string name="root_notification">(root is necessary)</string>
<string name="root_failed_notification">Fail to get root permission, please check if this phone is rooted</string>
</resources>
......@@ -16,6 +16,7 @@
*/
package com.netease.qa.emmagee.activity;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.List;
......@@ -31,13 +32,22 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.*;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
import com.netease.qa.emmagee.R;
import com.netease.qa.emmagee.service.EmmageeService;
import com.netease.qa.emmagee.utils.ProcessInfo;
import com.netease.qa.emmagee.utils.Programe;
import com.netease.qa.emmagee.R;
/**
* Main Page of Emmagee
......@@ -71,6 +81,7 @@ public class MainPageActivity extends Activity {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mainpage);
initTitleLayout();
processInfo = new ProcessInfo();
btnTest.setOnClickListener(new OnClickListener() {
......
......@@ -16,6 +16,11 @@
*/
package com.netease.qa.emmagee.activity;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
......@@ -31,6 +36,7 @@ import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import com.netease.qa.emmagee.R;
import com.netease.qa.emmagee.utils.Settings;
......@@ -42,14 +48,15 @@ import com.netease.qa.emmagee.utils.Settings;
*/
public class SettingsActivity extends Activity {
private static final String LOG_TAG = "Emmagee-" + SettingsActivity.class.getSimpleName();
private static final String LOG_TAG = "Emmagee-" + SettingsActivity.class.getSimpleName();
private CheckBox chkFloat;
private CheckBox chkRoot;
private TextView tvTime;
private LinearLayout about;
private LinearLayout mailSettings;
private SharedPreferences preferences;
private SharedPreferences preferences;
@Override
public void onCreate(Bundle savedInstanceState) {
......@@ -59,6 +66,7 @@ public class SettingsActivity extends Activity {
setContentView(R.layout.settings);
chkFloat = (CheckBox) findViewById(R.id.floating);
chkRoot = (CheckBox) findViewById(R.id.is_root);
tvTime = (TextView) findViewById(R.id.time);
about = (LinearLayout) findViewById(R.id.about);
mailSettings = (LinearLayout) findViewById(R.id.mail_settings);
......@@ -66,14 +74,19 @@ public class SettingsActivity extends Activity {
ImageView btnSave = (ImageView) findViewById(R.id.btn_set);
RelativeLayout floatingItem = (RelativeLayout) findViewById(R.id.floating_item);
LinearLayout layGoBack = (LinearLayout) findViewById(R.id.lay_go_back);
LinearLayout layHeapItem = (LinearLayout) findViewById(R.id.heap_item);
btnSave.setVisibility(ImageView.INVISIBLE);
preferences = Settings.getDefaultSharedPreferences(getApplicationContext());
int interval = preferences.getInt(Settings.KEY_INTERVAL, 5);
boolean isfloat = preferences.getBoolean(Settings.KEY_ISFLOAT, true);
boolean isRoot = preferences.getBoolean(Settings.KEY_ROOT, false);
tvTime.setText(String.valueOf(interval));
chkFloat.setChecked(isfloat);
chkRoot.setChecked(isRoot);
timeBar.setProgress(interval);
timeBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
......@@ -124,8 +137,34 @@ public class SettingsActivity extends Activity {
floatingItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
chkFloat.setChecked(!chkFloat.isChecked());
preferences.edit().putBoolean(Settings.KEY_ISFLOAT, chkFloat.isChecked()).commit();
boolean isChecked = chkFloat.isChecked();
chkFloat.setChecked(!isChecked);
preferences.edit().putBoolean(Settings.KEY_ISFLOAT, !isChecked).commit();
}
});
// get root permission
layHeapItem.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// if root checkbox is checked, change status to
// opposite;otherwise, try to upgrade app to root
boolean isChecked = chkRoot.isChecked();
if (isChecked) {
chkRoot.setChecked(!isChecked);
preferences.edit().putBoolean(Settings.KEY_ROOT, !isChecked).commit();
} else {
boolean root = upgradeRootPermission(getPackageCodePath());
if (root) {
Log.d(LOG_TAG, "root succeed");
chkRoot.setChecked(!isChecked);
preferences.edit().putBoolean(Settings.KEY_ROOT, !isChecked).commit();
} else {
// if root failed, tell user to check if phone is rooted
Toast.makeText(getBaseContext(), getString(R.string.root_failed_notification), Toast.LENGTH_LONG).show();
}
}
}
});
}
......@@ -140,4 +179,38 @@ public class SettingsActivity extends Activity {
super.onDestroy();
}
/**
* upgrade app to get root permission
*
* @return is root successfully
*/
public static boolean upgradeRootPermission(String pkgCodePath) {
Process process = null;
DataOutputStream os = null;
try {
String cmd = "chmod 777 " + pkgCodePath;
process = Runtime.getRuntime().exec("su"); // 切换到root帐号
os = new DataOutputStream(process.getOutputStream());
os.writeBytes(cmd + "\n");
os.writeBytes("exit\n");
os.flush();
int existValue = process.waitFor();
if (existValue == 0) {
return true;
} else {
return false;
}
} catch (Exception e) {
Log.w(LOG_TAG, "upgradeRootPermission exception=" + e.getMessage());
return false;
} finally {
try {
if (os != null) {
os.close();
}
process.destroy();
} catch (Exception e) {
}
}
}
}
......@@ -98,6 +98,7 @@ public class EmmageeService extends Service {
private Handler handler = new Handler();
private CpuInfo cpuInfo;
private boolean isFloating;
private boolean isRoot;
private String processName, packageName, startActivity;
private int pid, uid;
private boolean isServiceStop = false;
......@@ -231,6 +232,7 @@ public class EmmageeService extends Service {
recipients = preferences.getString(Settings.KEY_RECIPIENTS, BLANK_STRING);
receivers = recipients.split("\\s+");
smtp = preferences.getString(Settings.KEY_SMTP, BLANK_STRING);
isRoot = preferences.getBoolean(Settings.KEY_ROOT, false);
}
/**
......@@ -240,6 +242,7 @@ public class EmmageeService extends Service {
Calendar cal = Calendar.getInstance();
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
String mDateTime;
String heapData = "";
if ((Build.MODEL.equals("sdk")) || (Build.MODEL.equals("google_sdk")))
mDateTime = formatter.format(cal.getTime().getTime() + 8 * 60 * 60 * 1000);
else
......@@ -257,7 +260,7 @@ public class EmmageeService extends Service {
File resultFile = new File(resultFilePath);
resultFile.createNewFile();
out = new FileOutputStream(resultFile);
osw = new OutputStreamWriter(out, getString(R.string.csv_encoding));
osw = new OutputStreamWriter(out);
bw = new BufferedWriter(osw);
long totalMemorySize = memoryInfo.getTotalMemory();
String totalMemory = fomart.format((double) totalMemorySize / 1024);
......@@ -277,7 +280,10 @@ public class EmmageeService extends Service {
if (isGrantedReadLogsPermission()) {
bw.write(START_TIME);
}
bw.write(getString(R.string.timestamp) + Constants.COMMA + getString(R.string.top_activity) + Constants.COMMA
if(isRoot){
heapData = getString(R.string.native_heap) + Constants.COMMA+getString(R.string.dalvik_heap) + Constants.COMMA;
}
bw.write(getString(R.string.timestamp) + Constants.COMMA + getString(R.string.top_activity) + Constants.COMMA+heapData
+ getString(R.string.used_mem_PSS) + Constants.COMMA + getString(R.string.used_mem_ratio) + Constants.COMMA
+ getString(R.string.mobile_free_mem) + Constants.COMMA + getString(R.string.app_used_cpu_ratio) + Constants.COMMA
+ getString(R.string.total_used_cpu_ratio) + multiCpuTitle + Constants.COMMA + getString(R.string.traffic) + Constants.COMMA
......@@ -439,7 +445,7 @@ public class EmmageeService extends Service {
} catch (Exception e) {
currentBatt = Constants.NA;
}
ArrayList<String> processInfo = cpuInfo.getCpuRatioInfo(totalBatt, currentBatt, temperature, voltage);
ArrayList<String> processInfo = cpuInfo.getCpuRatioInfo(totalBatt, currentBatt, temperature, voltage,isRoot);
if (isFloating) {
String processCpuRatio = "0.00";
String totalCpuRatio = "0.00";
......@@ -521,12 +527,10 @@ public class EmmageeService extends Service {
handler.removeCallbacks(task);
closeOpenedStream();
// replace the start time in file
if (isGrantedReadLogsPermission()) {
if (!BLANK_STRING.equals(startTime)) {
replaceFileString(resultFilePath, START_TIME, getString(R.string.start_time) + startTime + Constants.LINE_END);
} else {
replaceFileString(resultFilePath, START_TIME, BLANK_STRING);
}
if (!BLANK_STRING.equals(startTime)) {
replaceFileString(resultFilePath, START_TIME, getString(R.string.start_time) + startTime + Constants.LINE_END);
} else {
replaceFileString(resultFilePath, START_TIME, BLANK_STRING);
}
isStop = true;
unregisterReceiver(batteryBroadcast);
......@@ -566,7 +570,7 @@ public class EmmageeService extends Service {
reader.close();
// replace a word in a file
String newtext = oldtext.replaceAll(replaceType, replaceString);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath), "UTF-8"));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(filePath), getString(R.string.csv_encoding)));
writer.write(newtext);
writer.close();
} catch (IOException e) {
......
......@@ -226,8 +226,9 @@ public class CpuInfo {
* @return network traffic ,used ratio of process CPU and total CPU in
* certain interval
*/
public ArrayList<String> getCpuRatioInfo(String totalBatt, String currentBatt, String temperature, String voltage) {
public ArrayList<String> getCpuRatioInfo(String totalBatt, String currentBatt, String temperature, String voltage,boolean isRoot) {
String heapData = "";
DecimalFormat fomart = new DecimalFormat();
fomart.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
fomart.setGroupingUsed(false);
......@@ -308,7 +309,11 @@ public class CpuInfo {
} else {
trafValue = String.valueOf(traffic);
}
EmmageeService.bw.write(mDateTime2 + Constants.COMMA + ProcessInfo.getTopActivity(context) + Constants.COMMA + pMemory
if(isRoot){
String[][] heapArray = MemoryInfo.getHeapSize(pid, context);
heapData = heapArray[0][1]+"/"+heapArray[0][0]+Constants.COMMA+heapArray[1][1]+"/"+heapArray[1][0]+Constants.COMMA;
}
EmmageeService.bw.write(mDateTime2 + Constants.COMMA + ProcessInfo.getTopActivity(context) + Constants.COMMA +heapData+ pMemory
+ Constants.COMMA + percent + Constants.COMMA + fMemory + Constants.COMMA + processCpuRatio + Constants.COMMA
+ totalCpuBuffer.toString() + trafValue + Constants.COMMA + totalBatt + Constants.COMMA + currentBatt + Constants.COMMA
+ temperature + Constants.COMMA + voltage + Constants.LINE_END);
......
......@@ -17,8 +17,11 @@
package com.netease.qa.emmagee.utils;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import android.app.ActivityManager;
import android.content.Context;
......@@ -32,8 +35,9 @@ import android.util.Log;
*/
public class MemoryInfo {
private static final String LOG_TAG = "Emmagee-"
+ MemoryInfo.class.getSimpleName();
private static final String LOG_TAG = "Emmagee-" + MemoryInfo.class.getSimpleName();
private static Process process;
/**
* get total memory of certain device.
......@@ -73,8 +77,7 @@ public class MemoryInfo {
*/
public long getFreeMemorySize(Context context) {
ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
am.getMemoryInfo(outInfo);
long avaliMem = outInfo.availMem;
return avaliMem / 1024;
......@@ -90,8 +93,7 @@ public class MemoryInfo {
* @return memory usage of certain process
*/
public int getPidMemorySize(int pid, Context context) {
ActivityManager am = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
int[] myMempid = new int[] { pid };
Debug.MemoryInfo[] memoryInfo = am.getProcessMemoryInfo(myMempid);
memoryInfo[0].getTotalSharedDirty();
......@@ -116,4 +118,80 @@ public class MemoryInfo {
public String getPhoneType() {
return android.os.Build.MODEL;
}
/**
* get app heap size, it is more importance than total memory
*
* @return heap size
*/
public static String[][] getHeapSize(int pid, Context context) {
String[][] heapData = parseMeminfo(pid);
return heapData;
}
/**
* dumpsys meminfo, and parse the result to get native and heap data
*
* @param pid
* process id
* @return native and heap data
*/
public static String[][] parseMeminfo(int pid) {
boolean infoStart = false;
// [][],00:native heap size,01:native heap alloc;10: dalvik heap
// size,11: dalvik heap alloc
String[][] heapData = new String[2][2];
try {
Runtime runtime = Runtime.getRuntime();
process = runtime.exec("su");
DataOutputStream os = new DataOutputStream(process.getOutputStream());
os.writeBytes("dumpsys meminfo " + pid + "\n");
os.writeBytes("exit\n");
os.flush();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = "";
while ((line = bufferedReader.readLine()) != null) {
line = line.trim();
if (line.contains("Permission Denial")) {
break;
} else {
// 当读取到MEMINFO in pid 这一行时,下一行就是需要获取的数据
if (line.contains("MEMINFO in pid")) {
infoStart = true;
} else if (infoStart) {
String[] lineItems = line.split("\\s+");
int length = lineItems.length;
if (line.startsWith("size")) {
heapData[0][0] = lineItems[1];
heapData[1][0] = lineItems[2];
} else if (line.startsWith("allocated")) {
heapData[0][1] = lineItems[1];
heapData[1][1] = lineItems[2];
break;
} else if (line.startsWith("Native")) {
Log.d(LOG_TAG, "Native");
Log.d(LOG_TAG, "lineItems[4]=" + lineItems[4]);
Log.d(LOG_TAG, "lineItems[5]=" + lineItems[5]);
heapData[0][0] = lineItems[length-3];
heapData[0][1] = lineItems[length-2];
} else if (line.startsWith("Dalvik")) {
Log.d(LOG_TAG, "Dalvik");
Log.d(LOG_TAG, "lineItems[4]=" + lineItems[4]);
Log.d(LOG_TAG, "lineItems[5]=" + lineItems[5]);
heapData[1][0] = lineItems[length-3];
heapData[1][1] = lineItems[length-2];
break;
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
return heapData;
}
}
......@@ -18,7 +18,8 @@ public final class Settings {
public static final String KEY_SMTP = "smtp";
public static final String KEY_ISFLOAT = "isfloat";
public static final String KEY_INTERVAL = "interval";
public static final String KEY_ROOT = "root";
public static SharedPreferences getDefaultSharedPreferences(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context);
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册