diff --git a/README.md b/README.md
index be544a5..4218e8e 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,13 @@
##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
diff --git a/res/layout/settings.xml b/res/layout/settings.xml
index c6fa242..0d6d62f 100644
--- a/res/layout/settings.xml
+++ b/res/layout/settings.xml
@@ -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" >
@@ -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" />
+
+
+
+
+
+
+
+
+
+
+
+
栈顶Activity名称
GBK
+
+ Dalvik heap alloc/size(KB)
+ Native heap alloc/size(KB)
+
+ 统计Heap数据
+ (需要root)
+
+ 无法获取root权限,请确认手机是否已经root过
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0c1ee35..fd47d40 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -77,4 +77,10 @@
Top Activity Name
UTF-8
+ Dalvik heap alloc/size(KB)
+ Native heap alloc/size(KB)
+
+ Collect Heap
+ (root is necessary)
+ Fail to get root permission, please check if this phone is rooted
diff --git a/src/com/netease/qa/emmagee/activity/MainPageActivity.java b/src/com/netease/qa/emmagee/activity/MainPageActivity.java
index 518818a..a02b19c 100644
--- a/src/com/netease/qa/emmagee/activity/MainPageActivity.java
+++ b/src/com/netease/qa/emmagee/activity/MainPageActivity.java
@@ -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.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 void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.mainpage);
+
initTitleLayout();
processInfo = new ProcessInfo();
btnTest.setOnClickListener(new OnClickListener() {
diff --git a/src/com/netease/qa/emmagee/activity/SettingsActivity.java b/src/com/netease/qa/emmagee/activity/SettingsActivity.java
index 4d7bcb9..cc0fe01 100644
--- a/src/com/netease/qa/emmagee/activity/SettingsActivity.java
+++ b/src/com/netease/qa/emmagee/activity/SettingsActivity.java
@@ -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.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 @@
*/
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 void onCreate(Bundle savedInstanceState) {
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 void onCreate(Bundle savedInstanceState) {
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 void onClick(View arg0) {
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 @@ protected void onDestroy() {
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) {
+ }
+ }
+ }
}
diff --git a/src/com/netease/qa/emmagee/service/EmmageeService.java b/src/com/netease/qa/emmagee/service/EmmageeService.java
index 081556a..caf0121 100644
--- a/src/com/netease/qa/emmagee/service/EmmageeService.java
+++ b/src/com/netease/qa/emmagee/service/EmmageeService.java
@@ -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 @@ private void readSettingInfo() {
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 @@ private void createResultCsv() {
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 @@ private void createResultCsv() {
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 @@ private void createResultCsv() {
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 @@ private void dataRefresh() {
} catch (Exception e) {
currentBatt = Constants.NA;
}
- ArrayList processInfo = cpuInfo.getCpuRatioInfo(totalBatt, currentBatt, temperature, voltage);
+ ArrayList processInfo = cpuInfo.getCpuRatioInfo(totalBatt, currentBatt, temperature, voltage,isRoot);
if (isFloating) {
String processCpuRatio = "0.00";
String totalCpuRatio = "0.00";
@@ -521,12 +527,10 @@ public void onDestroy() {
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 @@ private void replaceFileString(String filePath, String replaceType, String repla
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) {
diff --git a/src/com/netease/qa/emmagee/utils/CpuInfo.java b/src/com/netease/qa/emmagee/utils/CpuInfo.java
index 21110e3..ea9627b 100644
--- a/src/com/netease/qa/emmagee/utils/CpuInfo.java
+++ b/src/com/netease/qa/emmagee/utils/CpuInfo.java
@@ -226,8 +226,9 @@ public ArrayList getCpuList() {
* @return network traffic ,used ratio of process CPU and total CPU in
* certain interval
*/
- public ArrayList getCpuRatioInfo(String totalBatt, String currentBatt, String temperature, String voltage) {
+ public ArrayList 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 ArrayList getCpuRatioInfo(String totalBatt, String currentBatt, S
} 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);
diff --git a/src/com/netease/qa/emmagee/utils/MemoryInfo.java b/src/com/netease/qa/emmagee/utils/MemoryInfo.java
index c285fee..24a1517 100644
--- a/src/com/netease/qa/emmagee/utils/MemoryInfo.java
+++ b/src/com/netease/qa/emmagee/utils/MemoryInfo.java
@@ -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 @@
*/
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 long getTotalMemory() {
*/
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 long getFreeMemorySize(Context context) {
* @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 String getSDKVersion() {
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;
+ }
}
diff --git a/src/com/netease/qa/emmagee/utils/Settings.java b/src/com/netease/qa/emmagee/utils/Settings.java
index b71de11..1e9e50c 100644
--- a/src/com/netease/qa/emmagee/utils/Settings.java
+++ b/src/com/netease/qa/emmagee/utils/Settings.java
@@ -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);
}