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); }