/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.applications; import android.app.ActivityManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserManager; import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import android.text.format.Formatter; import android.util.Log; import android.util.SparseArray; import android.util.TimeUtils; import android.view.Menu; 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.internal.util.MemInfoReader; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; public class ProcessStatsUi extends PreferenceFragment implements LinearColorBar.OnRegionTappedListener { 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"; private static final int NUM_DURATIONS = 4; private static final int MENU_STATS_REFRESH = Menu.FIRST; private static final int MENU_DURATION = Menu.FIRST + 1; private static final int MENU_SHOW_SYSTEM = MENU_DURATION + NUM_DURATIONS; private static final int MENU_USE_USS = MENU_SHOW_SYSTEM + 1; private static final int MENU_TYPE_BACKGROUND = MENU_USE_USS + 1; private static final int MENU_TYPE_FOREGROUND = MENU_TYPE_BACKGROUND + 1; private static final int MENU_TYPE_CACHED = MENU_TYPE_FOREGROUND + 1; private static final int MENU_HELP = MENU_TYPE_CACHED + 1; static final int MAX_ITEMS_TO_LIST = 60; final static Comparator sEntryCompare = new Comparator() { @Override public int compare(ProcStatsEntry lhs, ProcStatsEntry rhs) { if (lhs.mWeight < rhs.mWeight) { 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; } }; private static ProcessStats sStatsXfer; IProcessStats mProcessStats; UserManager mUm; ProcessStats mStats; int mMemState; private long mDuration; private long mLastDuration; private boolean mShowSystem; private boolean mUseUss; private int mStatsType; private int mMemRegion; private MenuItem[] mDurationMenus = new MenuItem[NUM_DURATIONS]; private MenuItem mShowSystemMenu; private MenuItem mUseUssMenu; private MenuItem mTypeBackgroundMenu; private MenuItem mTypeForegroundMenu; private MenuItem mTypeCachedMenu; private PreferenceGroup mAppListGroup; private Preference mMemStatusPref; long mMaxWeight; long mTotalTime; long[] mMemTimes = new long[ProcessStats.ADJ_MEM_FACTOR_COUNT]; double[] mMemStateWeights = new double[ProcessStats.STATE_COUNT]; double mMemCachedWeight; double mMemFreeWeight; double mMemZRamWeight; double mMemKernelWeight; double mMemNativeWeight; double mMemTotalWeight; // The actual duration value to use for each duration option. Note these // are lower than the actual duration, since our durations are computed in // 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 = 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 }; private static int[] sDurationLabels = new int[] { R.string.menu_duration_3h, R.string.menu_duration_6h, R.string.menu_duration_12h, R.string.menu_duration_1d }; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); if (icicle != null) { mStats = sStatsXfer; } addPreferencesFromResource(R.xml.process_stats_summary); mProcessStats = IProcessStats.Stub.asInterface( ServiceManager.getService(ProcessStats.SERVICE_NAME)); mUm = (UserManager)getActivity().getSystemService(Context.USER_SERVICE); mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST); mMemStatusPref = mAppListGroup.findPreference(KEY_MEM_STATUS); mDuration = icicle != null ? icicle.getLong("duration", sDurations[0]) : sDurations[0]; mShowSystem = icicle != null ? icicle.getBoolean("show_system") : false; mUseUss = icicle != null ? icicle.getBoolean("use_uss") : false; mStatsType = icicle != null ? icicle.getInt("stats_type", MENU_TYPE_BACKGROUND) : MENU_TYPE_BACKGROUND; mMemRegion = icicle != null ? icicle.getInt("mem_region", LinearColorBar.REGION_GREEN) : LinearColorBar.REGION_GREEN; setHasOptionsMenu(true); } @Override public void onResume() { super.onResume(); refreshStats(); } @Override public void onPause() { super.onPause(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putLong("duration", mDuration); outState.putBoolean("show_system", mShowSystem); outState.putBoolean("use_uss", mUseUss); outState.putInt("stats_type", mStatsType); outState.putInt("mem_region", mMemRegion); } @Override public void onDestroy() { super.onDestroy(); if (getActivity().isChangingConfigurations()) { sStatsXfer = mStats; } } @Override public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { if (preference instanceof LinearColorPreference) { Bundle args = new Bundle(); args.putLongArray(ProcessStatsMemDetail.EXTRA_MEM_TIMES, mMemTimes); args.putDoubleArray(ProcessStatsMemDetail.EXTRA_MEM_STATE_WEIGHTS, mMemStateWeights); args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_CACHED_WEIGHT, mMemCachedWeight); args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_FREE_WEIGHT, mMemFreeWeight); args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_ZRAM_WEIGHT, mMemZRamWeight); args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_KERNEL_WEIGHT, mMemKernelWeight); args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_NATIVE_WEIGHT, mMemNativeWeight); args.putDouble(ProcessStatsMemDetail.EXTRA_MEM_TOTAL_WEIGHT, mMemTotalWeight); args.putBoolean(ProcessStatsMemDetail.EXTRA_USE_USS, mUseUss); args.putLong(ProcessStatsMemDetail.EXTRA_TOTAL_TIME, mTotalTime); ((SettingsActivity) getActivity()).startPreferencePanel( ProcessStatsMemDetail.class.getName(), args, R.string.mem_details_title, null, null, 0); return true; } if (!(preference instanceof ProcessStatsPreference)) { return false; } ProcessStatsPreference pgp = (ProcessStatsPreference) preference; Bundle args = new Bundle(); args.putParcelable(ProcessStatsDetail.EXTRA_ENTRY, pgp.getEntry()); args.putBoolean(ProcessStatsDetail.EXTRA_USE_USS, mUseUss); args.putLong(ProcessStatsDetail.EXTRA_MAX_WEIGHT, mMaxWeight); args.putLong(ProcessStatsDetail.EXTRA_TOTAL_TIME, mTotalTime); ((SettingsActivity) getActivity()).startPreferencePanel( ProcessStatsDetail.class.getName(), args, R.string.details_title, null, null, 0); return super.onPreferenceTreeClick(preferenceScreen, preference); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { MenuItem refresh = menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh) .setIcon(R.drawable.ic_menu_refresh_holo_dark) .setAlphabeticShortcut('r'); refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); SubMenu subMenu = menu.addSubMenu(R.string.menu_proc_stats_duration); for (int i=0; i= MENU_DURATION && id < (MENU_DURATION+NUM_DURATIONS)) { mDuration = sDurations[id-MENU_DURATION]; refreshStats(); } return false; } } @Override public void onRegionTapped(int region) { if (mMemRegion != region) { mMemRegion = region; refreshStats(); } } private void addNotAvailableMessage() { Preference notAvailable = new Preference(getActivity()); notAvailable.setTitle(R.string.power_usage_not_available); mAppListGroup.addPreference(notAvailable); } public static final int[] BACKGROUND_AND_SYSTEM_PROC_STATES = new int[] { ProcessStats.STATE_PERSISTENT, ProcessStats.STATE_IMPORTANT_FOREGROUND, ProcessStats.STATE_IMPORTANT_BACKGROUND, ProcessStats.STATE_BACKUP, ProcessStats.STATE_HEAVY_WEIGHT, ProcessStats.STATE_SERVICE, ProcessStats.STATE_SERVICE_RESTARTING, ProcessStats.STATE_RECEIVER }; public static final int[] FOREGROUND_PROC_STATES = new int[] { ProcessStats.STATE_TOP }; public static final int[] CACHED_PROC_STATES = new int[] { ProcessStats.STATE_CACHED_ACTIVITY, ProcessStats.STATE_CACHED_ACTIVITY_CLIENT, ProcessStats.STATE_CACHED_EMPTY }; public static final int[] RED_MEM_STATES = new int[] { ProcessStats.ADJ_MEM_FACTOR_CRITICAL }; public static final int[] YELLOW_MEM_STATES = new int[] { ProcessStats.ADJ_MEM_FACTOR_CRITICAL, ProcessStats.ADJ_MEM_FACTOR_LOW, ProcessStats.ADJ_MEM_FACTOR_MODERATE }; private String makeDuration(long time) { StringBuilder sb = new StringBuilder(32); TimeUtils.formatDuration(time, sb); return sb.toString(); } private void refreshStats() { updateMenus(); if (mStats == null || mLastDuration != mDuration) { load(); } int[] stats; int statsLabel; if (mStatsType == MENU_TYPE_FOREGROUND) { stats = FOREGROUND_PROC_STATES; statsLabel = R.string.process_stats_type_foreground; } else if (mStatsType == MENU_TYPE_CACHED) { stats = CACHED_PROC_STATES; statsLabel = R.string.process_stats_type_cached; } else { stats = mShowSystem ? BACKGROUND_AND_SYSTEM_PROC_STATES : ProcessStats.BACKGROUND_PROC_STATES; statsLabel = R.string.process_stats_type_background; } mAppListGroup.removeAll(); mAppListGroup.setOrderingAsAdded(false); final long elapsedTime = mStats.mTimePeriodEndRealtime-mStats.mTimePeriodStartRealtime; mMemStatusPref.setOrder(-2); mAppListGroup.addPreference(mMemStatusPref); String durationString = Utils.formatElapsedTime(getActivity(), elapsedTime, false); CharSequence memString; CharSequence[] memStatesStr = getResources().getTextArray(R.array.ram_states); if (mMemState >= 0 && mMemState < memStatesStr.length) { memString = memStatesStr[mMemState]; } else { memString = "?"; } mMemStatusPref.setTitle(getActivity().getString(R.string.process_stats_total_duration, getActivity().getString(statsLabel), durationString)); mMemStatusPref.setSummary(getActivity().getString(R.string.process_stats_memory_status, memString)); /* mMemStatusPref.setTitle(DateFormat.format(DateFormat.getBestDateTimePattern( getActivity().getResources().getConfiguration().locale, "MMMM dd, yyyy h:mm a"), mStats.mTimePeriodStartClock)); */ /* BatteryHistoryPreference hist = new BatteryHistoryPreference(getActivity(), mStats); hist.setOrder(-1); mAppListGroup.addPreference(hist); */ long now = SystemClock.uptimeMillis(); final PackageManager pm = getActivity().getPackageManager(); mTotalTime = ProcessStats.dumpSingleTime(null, null, mStats.mMemFactorDurations, mStats.mMemFactor, mStats.mStartTime, now); if (DEBUG) Log.d(TAG, "Total time of stats: " + makeDuration(mTotalTime)); for (int i=0; i= ProcessStats.STATE_HOME) { freeWeight += totalMem.processStateWeight[i]; } else { usedWeight += totalMem.processStateWeight[i]; } if (i >= ProcessStats.STATE_IMPORTANT_FOREGROUND) { backgroundWeight += totalMem.processStateWeight[i]; persBackgroundWeight += totalMem.processStateWeight[i]; } if (i == ProcessStats.STATE_PERSISTENT) { persBackgroundWeight += totalMem.processStateWeight[i]; } } } if (DEBUG) { Log.i(TAG, "Used RAM: " + Formatter.formatShortFileSize(getActivity(), (long)((usedWeight * 1024) / memTotalTime))); Log.i(TAG, "Free RAM: " + Formatter.formatShortFileSize(getActivity(), (long)((freeWeight * 1024) / memTotalTime))); Log.i(TAG, "Total RAM: " + Formatter.formatShortFileSize(getActivity(), (long)(((freeWeight+usedWeight) * 1024) / memTotalTime))); Log.i(TAG, "Background+Cached RAM: " + Formatter.formatShortFileSize(getActivity(), (long)((backgroundWeight * 1024) / memTotalTime))); } mMemTotalWeight = freeWeight + usedWeight; // For computing the ratio to show, we want to count the baseline cached RAM we // need (at which point we start killing processes) as used RAM, so that if we // reach the point of thrashing due to no RAM for any background processes we // report that as RAM being full. To do this, we need to first convert the weights // back to actual RAM... and since the RAM values we compute here won't exactly // match the real physical RAM, scale those to the actual physical RAM. No problem! double usedRam = (usedWeight*1024)/memTotalTime; double freeRam = (freeWeight*1024)/memTotalTime; double totalRam = usedRam + freeRam; MemInfoReader memReader = new MemInfoReader(); memReader.readMemInfo(); double realTotalRam = memReader.getTotalSize(); double totalScale = realTotalRam / totalRam; double realUsedRam = usedRam * totalScale; double realFreeRam = freeRam * totalScale; if (DEBUG) { Log.i(TAG, "Scaled Used RAM: " + Formatter.formatShortFileSize(getActivity(), (long)realUsedRam)); Log.i(TAG, "Scaled Free RAM: " + Formatter.formatShortFileSize(getActivity(), (long)realFreeRam)); } ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); ((ActivityManager)getActivity().getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo( memInfo); if (memInfo.hiddenAppThreshold >= realFreeRam) { realUsedRam = realFreeRam; realFreeRam = 0; } else { realUsedRam += memInfo.hiddenAppThreshold; realFreeRam -= memInfo.hiddenAppThreshold; } if (DEBUG) { Log.i(TAG, "Adj Scaled Used RAM: " + Formatter.formatShortFileSize(getActivity(), (long)realUsedRam)); Log.i(TAG, "Adj Scaled Free RAM: " + Formatter.formatShortFileSize(getActivity(), (long)realFreeRam)); } float usedRatio = (float)(realUsedRam/(realFreeRam+realUsedRam)); colors.setRatios(usedRatio, 0, 1-usedRatio); if (false) { colors.setOnRegionTappedListener(this); switch (mMemRegion) { case LinearColorBar.REGION_RED: colors.setColoredRegions(LinearColorBar.REGION_RED); memTotalTime = mMemTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL]; memStates = RED_MEM_STATES; break; case LinearColorBar.REGION_YELLOW: colors.setColoredRegions(LinearColorBar.REGION_RED | LinearColorBar.REGION_YELLOW); memTotalTime = mMemTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL] + mMemTimes[ProcessStats.ADJ_MEM_FACTOR_LOW] + mMemTimes[ProcessStats.ADJ_MEM_FACTOR_MODERATE]; memStates = YELLOW_MEM_STATES; break; default: colors.setColoredRegions(LinearColorBar.REGION_ALL); memTotalTime = mTotalTime; memStates = ProcessStats.ALL_MEM_ADJ; break; } colors.setRatios(mMemTimes[ProcessStats.ADJ_MEM_FACTOR_CRITICAL] / (float)mTotalTime, (mMemTimes[ProcessStats.ADJ_MEM_FACTOR_LOW] + mMemTimes[ProcessStats.ADJ_MEM_FACTOR_MODERATE]) / (float)mTotalTime, mMemTimes[ProcessStats.ADJ_MEM_FACTOR_NORMAL] / (float)mTotalTime); } mAppListGroup.addPreference(colors); ProcessStats.ProcessDataCollection totals = new ProcessStats.ProcessDataCollection( ProcessStats.ALL_SCREEN_ADJ, memStates, stats); ArrayList entries = new ArrayList(); /* ArrayList rawProcs = mStats.collectProcessesLocked( ProcessStats.ALL_SCREEN_ADJ, ProcessStats.ALL_MEM_ADJ, ProcessStats.BACKGROUND_PROC_STATES, now, null); for (int i=0, N=(rawProcs != null ? rawProcs.size() : 0); i entriesMap = new ProcessMap(); for (int ipkg=0, N=mStats.mPackages.getMap().size(); ipkg> pkgUids = mStats.mPackages.getMap().valueAt(ipkg); for (int iu=0; iu vpkgs = pkgUids.valueAt(iu); for (int iv=0; iv 0) { if (DEBUG) Log.d(TAG, "Adding proc " + proc.mName + "/" + proc.mUid + ": time=" + makeDuration(ent.mDuration) + " (" + ((((double)ent.mDuration) / memTotalTime) * 100) + "%)" + " pss=" + ent.mAvgPss); entriesMap.put(proc.mName, proc.mUid, ent); entries.add(ent); } } else { ent.addPackage(st.mPackageName); } } } } } if (DEBUG) Log.d(TAG, "-------------------- MAPPING SERVICES"); // Add in service info. if (mStatsType == MENU_TYPE_BACKGROUND) { for (int ip=0, N=mStats.mPackages.getMap().size(); ip> uids = mStats.mPackages.getMap().valueAt(ip); for (int iu=0; iu vpkgs = uids.valueAt(iu); for (int iv=0; iv> 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) / memTotalTime) * 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= 0) { ProcStatsEntry proc = entries.get(end); final double percentOfWeight = (((double)proc.mWeight) / mMaxWeight) * 100; final double percentOfTime = (((double)proc.mDuration) / memTotalTime) * 100; if (percentOfWeight >= 1 || percentOfTime >= 25) { break; } end--; } for (int i=0; i<=end; i++) { ProcStatsEntry proc = entries.get(i); final double percentOfWeight = (((double)proc.mWeight) / mMaxWeight) * 100; final double percentOfTime = (((double)proc.mDuration) / memTotalTime) * 100; ProcessStatsPreference pref = new ProcessStatsPreference(getActivity()); pref.init(null, proc); proc.evaluateTargetPackage(pm, mStats, totals, sEntryCompare, mUseUss, mStatsType == MENU_TYPE_BACKGROUND); proc.retrieveUiData(pm); pref.setTitle(proc.mUiLabel); if (proc.mUiTargetApp != null) { pref.setIcon(proc.mUiTargetApp.loadIcon(pm)); } pref.setOrder(i); pref.setPercent(percentOfWeight, percentOfTime); mAppListGroup.addPreference(pref); if (mStatsType == MENU_TYPE_BACKGROUND) { if (DEBUG) { Log.i(TAG, "App " + proc.mUiLabel + ": weightedRam=" + Formatter.formatShortFileSize(getActivity(), (proc.mWeight * 1024) / memTotalTime) + ", avgRam=" + Formatter.formatShortFileSize(getActivity(), (proc.mAvgPss*1024))); } } if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST+1)) { if (DEBUG) Log.d(TAG, "Done with UI, hit item limit!"); break; } } } private void load() { try { mLastDuration = mDuration; mMemState = mProcessStats.getCurrentMemoryState(); ParcelFileDescriptor pfd = mProcessStats.getStatsOverTime(mDuration); mStats = new ProcessStats(false); InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd); mStats.read(is); try { is.close(); } catch (IOException e) { } if (mStats.mReadError != null) { Log.w(TAG, "Failure reading process stats: " + mStats.mReadError); } } catch (RemoteException e) { Log.e(TAG, "RemoteException:", e); } } }