From 5635594c38fb319e050054e42109eb736f274acc Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Sun, 29 Sep 2013 17:07:49 -0700 Subject: [PATCH] Fix issue #10948509: Crash in procstats when there is no data Also make us much better about determining which app to blame for a particular running process, organize the display of services by their owning app, apply some new heuristics for deciding what goes in the process list so that we can still display interesting processes when we haven't collected pss data for them yet, etc. Change-Id: I7e87225d3dabc66cf3ff020b5db48401b14aaf47 --- AndroidManifest.xml | 3 +- .../settings/applications/ProcStatsEntry.java | 151 ++++++++++++--- .../applications/ProcessStatsDetail.java | 54 ++++-- .../applications/ProcessStatsPreference.java | 2 +- .../settings/applications/ProcessStatsUi.java | 173 +++++++++++++----- 5 files changed, 288 insertions(+), 95 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 51ff2f2dfd2..f0722a81592 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -801,7 +801,8 @@ + android:excludeFromRecents="true" + android:enabled="false"> diff --git a/src/com/android/settings/applications/ProcStatsEntry.java b/src/com/android/settings/applications/ProcStatsEntry.java index 830878438f0..0821ced2336 100644 --- a/src/com/android/settings/applications/ProcStatsEntry.java +++ b/src/com/android/settings/applications/ProcStatsEntry.java @@ -21,8 +21,8 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArrayMap; import android.util.Log; -import android.util.SparseArray; import com.android.internal.app.ProcessStats; import java.util.ArrayList; @@ -31,11 +31,12 @@ import java.util.Comparator; public final class ProcStatsEntry implements Parcelable { private static final String TAG = "ProcStatsEntry"; + private static boolean DEBUG = ProcessStatsUi.DEBUG; final String mPackage; final int mUid; final String mName; - final boolean mUnique; + final ArrayList mPackages = new ArrayList(); final long mDuration; final long mAvgPss; final long mMaxPss; @@ -45,33 +46,35 @@ public final class ProcStatsEntry implements Parcelable { String mBestTargetPackage; - ArrayList mServices = new ArrayList(2); + ArrayMap> mServices = new ArrayMap>(1); public ApplicationInfo mUiTargetApp; public String mUiLabel; public String mUiBaseLabel; public String mUiPackage; - public ProcStatsEntry(ProcessStats.ProcessState proc, + public ProcStatsEntry(ProcessStats.ProcessState proc, String packageName, ProcessStats.ProcessDataCollection tmpTotals, boolean useUss, boolean weightWithTime) { ProcessStats.computeProcessData(proc, tmpTotals, 0); mPackage = proc.mPackage; mUid = proc.mUid; mName = proc.mName; - mUnique = proc.mCommonProcess == proc; + mPackages.add(packageName); mDuration = tmpTotals.totalTime; mAvgPss = tmpTotals.avgPss; mMaxPss = tmpTotals.maxPss; mAvgUss = tmpTotals.avgUss; mMaxUss = tmpTotals.maxUss; mWeight = (weightWithTime ? mDuration : 1) * (useUss ? mAvgUss : mAvgPss); + if (DEBUG) Log.d(TAG, "New proc entry " + proc.mName + ": dur=" + mDuration + + " avgpss=" + mAvgPss + " weight=" + mWeight); } public ProcStatsEntry(Parcel in) { mPackage = in.readString(); mUid = in.readInt(); mName = in.readString(); - mUnique = in.readInt() != 0; + in.readStringList(mPackages); mDuration = in.readLong(); mAvgPss = in.readLong(); mMaxPss = in.readLong(); @@ -79,40 +82,118 @@ public final class ProcStatsEntry implements Parcelable { mMaxUss = in.readLong(); mWeight = in.readLong(); mBestTargetPackage = in.readString(); - in.readTypedList(mServices, Service.CREATOR); + final int N = in.readInt(); + if (N > 0) { + mServices.ensureCapacity(N); + for (int i=0; i value = new ArrayList(); + in.readTypedList(value, Service.CREATOR); + mServices.append(key, value); + } + } } - public void evaluateTargetPackage(ProcessStats stats, ProcessStats.ProcessDataCollection totals, - Comparator compare, boolean useUss, boolean weightWithTime) { + public void addPackage(String packageName) { + mPackages.add(packageName); + } + + public void evaluateTargetPackage(PackageManager pm, ProcessStats stats, + ProcessStats.ProcessDataCollection totals, Comparator compare, + boolean useUss, boolean weightWithTime) { mBestTargetPackage = null; - if (mUnique) { - mBestTargetPackage = mPackage; + if (mPackages.size() == 1) { + if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": single pkg " + mPackages.get(0)); + mBestTargetPackage = mPackages.get(0); } else { // See if there is one significant package that was running here. ArrayList subProcs = new ArrayList(); - for (int ipkg=0, NPKG=stats.mPackages.getMap().size(); ipkg uids - = stats.mPackages.getMap().valueAt(ipkg); - for (int iu=0, NU=uids.size(); iu 1) { Collections.sort(subProcs, compare); if (subProcs.get(0).mWeight > (subProcs.get(1).mWeight*3)) { + if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": best pkg " + + subProcs.get(0).mPackage + " weight " + subProcs.get(0).mWeight + + " better than " + subProcs.get(1).mPackage + + " weight " + subProcs.get(1).mWeight); mBestTargetPackage = subProcs.get(0).mPackage; + return; } + // Couldn't find one that is best by weight, let's decide on best another + // way: the one that has the longest running service, accounts for at least + // half of the maximum weight, and has specified an explicit app icon. + long maxWeight = subProcs.get(0).mWeight; + long bestRunTime = -1; + for (int i=0; i subProcServices = null; + for (int isp=0, NSP=mServices.size(); isp subServices = mServices.valueAt(isp); + if (subServices.get(0).mPackage.equals(subProcs.get(i).mPackage)) { + subProcServices = subServices; + break; + } + } + long thisRunTime = 0; + if (subProcServices != null) { + for (int iss=0, NSS=subProcServices.size(); iss thisRunTime) { + if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg " + + subProcs.get(i).mPackage + " service " + service.mName + + " run time is " + service.mDuration); + thisRunTime = service.mDuration; + break; + } + } + } + if (thisRunTime > bestRunTime) { + if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg " + + subProcs.get(i).mPackage + " new best run time " + thisRunTime); + mBestTargetPackage = subProcs.get(i).mPackage; + bestRunTime = thisRunTime; + } else { + if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg " + + subProcs.get(i).mPackage + " run time " + thisRunTime + + " not as good as last " + bestRunTime); + } + } + } else if (subProcs.size() == 1) { + mBestTargetPackage = subProcs.get(0).mPackage; } } } @@ -178,7 +259,12 @@ public final class ProcStatsEntry implements Parcelable { } public void addService(ProcessStats.ServiceState svc) { - mServices.add(new Service(svc)); + ArrayList services = mServices.get(svc.mPackage); + if (services == null) { + services = new ArrayList(); + mServices.put(svc.mPackage, services); + } + services.add(new Service(svc)); } @Override @@ -191,7 +277,7 @@ public final class ProcStatsEntry implements Parcelable { dest.writeString(mPackage); dest.writeInt(mUid); dest.writeString(mName); - dest.writeInt(mUnique ? 1 : 0); + dest.writeStringList(mPackages); dest.writeLong(mDuration); dest.writeLong(mAvgPss); dest.writeLong(mMaxPss); @@ -199,7 +285,12 @@ public final class ProcStatsEntry implements Parcelable { dest.writeLong(mMaxUss); dest.writeLong(mWeight); dest.writeString(mBestTargetPackage); - dest.writeTypedList(mServices); + final int N = mServices.size(); + dest.writeInt(N); + for (int i=0; i CREATOR diff --git a/src/com/android/settings/applications/ProcessStatsDetail.java b/src/com/android/settings/applications/ProcessStatsDetail.java index fad37452363..a1885a74843 100644 --- a/src/com/android/settings/applications/ProcessStatsDetail.java +++ b/src/com/android/settings/applications/ProcessStatsDetail.java @@ -168,6 +168,23 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen } } + private void addPackageHeaderItem(ViewGroup parent, String packageName) { + LayoutInflater inflater = getActivity().getLayoutInflater(); + ViewGroup item = (ViewGroup) inflater.inflate(R.layout.running_processes_item, + null); + parent.addView(item); + final ImageView icon = (ImageView) item.findViewById(R.id.icon); + TextView nameView = (TextView) item.findViewById(R.id.name); + TextView descriptionView = (TextView) item.findViewById(R.id.description); + try { + ApplicationInfo ai = mPm.getApplicationInfo(packageName, 0); + icon.setImageDrawable(ai.loadIcon(mPm)); + nameView.setText(ai.loadLabel(mPm)); + } catch (PackageManager.NameNotFoundException e) { + } + descriptionView.setText(packageName); + } + private void addDetailsItem(ViewGroup parent, CharSequence label, CharSequence value) { LayoutInflater inflater = getActivity().getLayoutInflater(); ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_detail_item_text, @@ -204,22 +221,31 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen }; private void fillServicesSection() { - LayoutInflater inflater = getActivity().getLayoutInflater(); if (mEntry.mServices.size() > 0) { - ArrayList services = - (ArrayList)mEntry.mServices.clone(); - Collections.sort(services, sServiceCompare); - for (int i=0; i= 0 && tail < (label.length()-1)) { - label = label.substring(tail+1); + boolean addPackageSections = false; + if (mEntry.mServices.size() > 1 + || !mEntry.mServices.valueAt(0).get(0).mPackage.equals(mEntry.mPackage)) { + addPackageSections = true; + } + for (int ip=0; ip services = + (ArrayList)mEntry.mServices.valueAt(ip).clone(); + Collections.sort(services, sServiceCompare); + if (addPackageSections) { + addPackageHeaderItem(mServicesParent, services.get(0).mPackage); + } + for (int is=0; is= 0 && tail < (label.length()-1)) { + label = label.substring(tail+1); + } + long duration = service.mDuration; + final double percentOfTime = (((double)duration) / mTotalTime) * 100; + addDetailsItem(mServicesParent, label, getActivity().getResources().getString( + R.string.percentage, (int) Math.ceil(percentOfTime))); } - long duration = service.mDuration; - final double percentOfTime = (((double)duration) / mTotalTime) * 100; - addDetailsItem(mServicesParent, label, getActivity().getResources().getString( - R.string.percentage, (int) Math.ceil(percentOfTime))); } } } diff --git a/src/com/android/settings/applications/ProcessStatsPreference.java b/src/com/android/settings/applications/ProcessStatsPreference.java index 2dea96a3f06..bf2676d4357 100644 --- a/src/com/android/settings/applications/ProcessStatsPreference.java +++ b/src/com/android/settings/applications/ProcessStatsPreference.java @@ -45,7 +45,7 @@ public class ProcessStatsPreference extends Preference { public void setPercent(double percentOfWeight, double percentOfTime) { mProgress = (int) Math.ceil(percentOfWeight); mProgressText = getContext().getResources().getString( - R.string.percentage, (int) Math.ceil(percentOfTime)); + R.string.percentage, (int) Math.round(percentOfTime)); notifyChanged(); } diff --git a/src/com/android/settings/applications/ProcessStatsUi.java b/src/com/android/settings/applications/ProcessStatsUi.java index b7ffcefaa19..0e27577ed79 100644 --- a/src/com/android/settings/applications/ProcessStatsUi.java +++ b/src/com/android/settings/applications/ProcessStatsUi.java @@ -19,7 +19,6 @@ package com.android.settings.applications; import android.content.Context; import android.content.pm.PackageManager; import android.os.Bundle; -import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; @@ -30,7 +29,6 @@ import android.preference.PreferenceActivity; import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; -import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import android.util.TimeUtils; @@ -39,6 +37,7 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.SubMenu; import com.android.internal.app.IProcessStats; +import com.android.internal.app.ProcessMap; import com.android.internal.app.ProcessStats; import com.android.settings.R; import com.android.settings.fuelgauge.Utils; @@ -50,8 +49,8 @@ import java.util.Collections; import java.util.Comparator; public class ProcessStatsUi extends PreferenceFragment { - private static final String TAG = "ProcessStatsUi"; - private static final boolean DEBUG = false; + static final String TAG = "ProcessStatsUi"; + static final boolean DEBUG = false; private static final String KEY_APP_LIST = "app_list"; private static final String KEY_MEM_STATUS = "mem_status"; @@ -76,6 +75,10 @@ public class ProcessStatsUi extends PreferenceFragment { return 1; } else if (lhs.mWeight > rhs.mWeight) { return -1; + } else if (lhs.mDuration < rhs.mDuration) { + return 1; + } else if (lhs.mDuration > rhs.mDuration) { + return -1; } return 0; } @@ -112,7 +115,7 @@ public class ProcessStatsUi extends PreferenceFragment { // batches of 3 hours so we want to allow the time we use to be slightly // smaller than the actual time selected instead of bumping up to 3 hours // beyond it. - private static final long DURATION_QUANTUM = 3*60*60*1000; + private static final long DURATION_QUANTUM = ProcessStats.COMMIT_PERIOD; private static long[] sDurations = new long[] { 3*60*60*1000 - DURATION_QUANTUM/2, 6*60*60*1000 - DURATION_QUANTUM/2, 12*60*60*1000 - DURATION_QUANTUM/2, 24*60*60*1000 - DURATION_QUANTUM/2 @@ -319,6 +322,12 @@ public class ProcessStatsUi extends PreferenceFragment { ProcessStats.STATE_CACHED_EMPTY }; + private String makeDuration(long time) { + StringBuilder sb = new StringBuilder(32); + TimeUtils.formatDuration(time, sb); + return sb.toString(); + } + private void refreshStats() { updateMenus(); @@ -378,6 +387,7 @@ public class ProcessStatsUi extends PreferenceFragment { mTotalTime = ProcessStats.dumpSingleTime(null, null, mStats.mMemFactorDurations, mStats.mMemFactor, mStats.mStartTime, now); + if (DEBUG) Log.d(TAG, "Total time of stats: " + makeDuration(mTotalTime)); LinearColorPreference colors = new LinearColorPreference(getActivity()); colors.setOrder(-1); @@ -396,7 +406,7 @@ public class ProcessStatsUi extends PreferenceFragment { + memTimes[ProcessStats.ADJ_MEM_FACTOR_MODERATE]) / (float)mTotalTime, memTimes[ProcessStats.ADJ_MEM_FACTOR_NORMAL] / (float)mTotalTime); - ArrayList procs = new ArrayList(); + ArrayList entries = new ArrayList(); /* ArrayList rawProcs = mStats.collectProcessesLocked( @@ -407,50 +417,43 @@ public class ProcessStatsUi extends PreferenceFragment { } */ - ArrayMap processes = new ArrayMap( - mStats.mProcesses.getMap().size()); - for (int ip=0, N=mStats.mProcesses.getMap().size(); ip uids = mStats.mProcesses.getMap().valueAt(ip); - for (int iu=0; iu entriesMap = new ProcessMap(); + for (int ipkg=0, N=mStats.mPackages.getMap().size(); ipkg pkgUids + = mStats.mPackages.getMap().valueAt(ipkg); + for (int iu=0; iu 0) { + if (DEBUG) Log.d(TAG, "Adding proc " + proc.mName + "/" + + proc.mUid + ": time=" + makeDuration(ent.mDuration) + " (" + + ((((double)ent.mDuration) / mTotalTime) * 100) + "%)" + + " pss=" + ent.mAvgPss); + entriesMap.put(proc.mName, proc.mUid, ent); + entries.add(ent); + } + } else { + ent.addPackage(st.mPackageName); + } + } } } - Collections.sort(procs, sEntryCompare); - while (procs.size() > MAX_ITEMS_TO_LIST) { - procs.remove(procs.size()-1); - } - - long maxWeight = 0; - for (int i=0, N=(procs != null ? procs.size() : 0); i (MAX_ITEMS_TO_LIST+1)) break; - } + if (DEBUG) Log.d(TAG, "-------------------- MAPPING SERVICES"); // Add in service info. if (mStatsType == MENU_TYPE_BACKGROUND) { @@ -461,13 +464,85 @@ public class ProcessStatsUi extends PreferenceFragment { for (int is=0, NS=ps.mServices.size(); is> processes + = new SparseArray>(); + for (int ip=0, N=mStats.mProcesses.getMap().size(); ip uids = mStats.mProcesses.getMap().valueAt(ip); + for (int iu=0; iu 0) { + if (DEBUG) Log.d(TAG, "Adding proc " + st.mName + "/" + st.mUid + ": time=" + + makeDuration(ent.mDuration) + " (" + + ((((double)ent.mDuration) / mTotalTime) * 100) + "%)"); + procs.add(ent); + ArrayMap uidProcs = processes.get(ent.mUid); + if (uidProcs == null) { + uidProcs = new ArrayMap(); + processes.put(ent.mUid, uidProcs); + } + uidProcs.put(ent.mName, ent); + } + } + } + */ + + Collections.sort(entries, sEntryCompare); + + long maxWeight = 1; + for (int i=0, N=(entries != null ? entries.size() : 0); i