/* * Copyright (C) 2015 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.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.text.format.Formatter; import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import com.android.internal.app.ProcessMap; import com.android.internal.app.procstats.DumpUtils; import com.android.internal.app.procstats.IProcessStats; import com.android.internal.app.procstats.ProcessState; import com.android.internal.app.procstats.ProcessStats; import com.android.internal.app.procstats.ProcessStats.ProcessDataCollection; import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection; import com.android.internal.app.procstats.ServiceState; import com.android.internal.util.MemInfoReader; import com.android.settings.R; import com.android.settings.Utils; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Comparator; import java.util.List; public class ProcStatsData { private static final String TAG = "ProcStatsManager"; private static final boolean DEBUG = ProcessStatsUi.DEBUG; private static ProcessStats sStatsXfer; private PackageManager mPm; private Context mContext; private long memTotalTime; private IProcessStats mProcessStats; private ProcessStats mStats; private boolean mUseUss; private long mDuration; private int[] mMemStates; private int[] mStates; private MemInfo mMemInfo; private ArrayList<ProcStatsPackageEntry> pkgEntries; public ProcStatsData(Context context, boolean useXfer) { mContext = context; mPm = context.getPackageManager(); mProcessStats = IProcessStats.Stub.asInterface( ServiceManager.getService(ProcessStats.SERVICE_NAME)); mMemStates = ProcessStats.ALL_MEM_ADJ; mStates = ProcessStats.BACKGROUND_PROC_STATES; if (useXfer) { mStats = sStatsXfer; } } public void setTotalTime(int totalTime) { memTotalTime = totalTime; } public void xferStats() { sStatsXfer = mStats; } public void setMemStates(int[] memStates) { mMemStates = memStates; refreshStats(false); } public void setStats(int[] stats) { this.mStates = stats; refreshStats(false); } public int getMemState() { int factor = mStats.mMemFactor; if (factor == ProcessStats.ADJ_NOTHING) { return ProcessStats.ADJ_MEM_FACTOR_NORMAL; } if (factor >= ProcessStats.ADJ_SCREEN_ON) { factor -= ProcessStats.ADJ_SCREEN_ON; } return factor; } public MemInfo getMemInfo() { return mMemInfo; } public long getElapsedTime() { return mStats.mTimePeriodEndRealtime - mStats.mTimePeriodStartRealtime; } public void setDuration(long duration) { if (duration != mDuration) { mDuration = duration; refreshStats(true); } } public long getDuration() { return mDuration; } public List<ProcStatsPackageEntry> getEntries() { return pkgEntries; } public void refreshStats(boolean forceLoad) { if (mStats == null || forceLoad) { load(); } pkgEntries = new ArrayList<>(); long now = SystemClock.uptimeMillis(); memTotalTime = DumpUtils.dumpSingleTime(null, null, mStats.mMemFactorDurations, mStats.mMemFactor, mStats.mStartTime, now); ProcessStats.TotalMemoryUseCollection totalMem = new ProcessStats.TotalMemoryUseCollection( ProcessStats.ALL_SCREEN_ADJ, mMemStates); mStats.computeTotalMemoryUse(totalMem, now); mMemInfo = new MemInfo(mContext, totalMem, memTotalTime); ProcessDataCollection bgTotals = new ProcessDataCollection( ProcessStats.ALL_SCREEN_ADJ, mMemStates, mStates); ProcessDataCollection runTotals = new ProcessDataCollection( ProcessStats.ALL_SCREEN_ADJ, mMemStates, ProcessStats.NON_CACHED_PROC_STATES); createPkgMap(getProcs(bgTotals, runTotals), bgTotals, runTotals); if (totalMem.sysMemZRamWeight > 0 && !totalMem.hasSwappedOutPss) { distributeZRam(totalMem.sysMemZRamWeight); } ProcStatsPackageEntry osPkg = createOsEntry(bgTotals, runTotals, totalMem, mMemInfo.baseCacheRam); pkgEntries.add(osPkg); } private void createPkgMap(ArrayList<ProcStatsEntry> procEntries, ProcessDataCollection bgTotals, ProcessDataCollection runTotals) { // Combine processes into packages. ArrayMap<String, ProcStatsPackageEntry> pkgMap = new ArrayMap<>(); for (int i = procEntries.size() - 1; i >= 0; i--) { ProcStatsEntry proc = procEntries.get(i); proc.evaluateTargetPackage(mPm, mStats, bgTotals, runTotals, sEntryCompare, mUseUss); ProcStatsPackageEntry pkg = pkgMap.get(proc.mBestTargetPackage); if (pkg == null) { pkg = new ProcStatsPackageEntry(proc.mBestTargetPackage, memTotalTime); pkgMap.put(proc.mBestTargetPackage, pkg); pkgEntries.add(pkg); } pkg.addEntry(proc); } } private void distributeZRam(double zramWeight) { // Distribute kernel's Z-Ram across processes, based on how much they have been running. // The idea is that the memory used by the kernel for this is not really the kernel's // responsibility, but that of whoever got swapped in to it... and we will take how // much a process runs for as a sign of the proportion of Z-Ram it is responsible for. long zramMem = (long) (zramWeight / memTotalTime); long totalTime = 0; for (int i = pkgEntries.size() - 1; i >= 0; i--) { ProcStatsPackageEntry entry = pkgEntries.get(i); for (int j = entry.mEntries.size() - 1; j >= 0; j--) { ProcStatsEntry proc = entry.mEntries.get(j); totalTime += proc.mRunDuration; } } for (int i = pkgEntries.size() - 1; i >= 0 && totalTime > 0; i--) { ProcStatsPackageEntry entry = pkgEntries.get(i); long pkgRunTime = 0; long maxRunTime = 0; for (int j = entry.mEntries.size() - 1; j >= 0; j--) { ProcStatsEntry proc = entry.mEntries.get(j); pkgRunTime += proc.mRunDuration; if (proc.mRunDuration > maxRunTime) { maxRunTime = proc.mRunDuration; } } long pkgZRam = (zramMem*pkgRunTime)/totalTime; if (pkgZRam > 0) { zramMem -= pkgZRam; totalTime -= pkgRunTime; ProcStatsEntry procEntry = new ProcStatsEntry(entry.mPackage, 0, mContext.getString(R.string.process_stats_os_zram), maxRunTime, pkgZRam, memTotalTime); procEntry.evaluateTargetPackage(mPm, mStats, null, null, sEntryCompare, mUseUss); entry.addEntry(procEntry); } } } private ProcStatsPackageEntry createOsEntry(ProcessDataCollection bgTotals, ProcessDataCollection runTotals, TotalMemoryUseCollection totalMem, long baseCacheRam) { // Add in fake entry representing the OS itself. ProcStatsPackageEntry osPkg = new ProcStatsPackageEntry("os", memTotalTime); ProcStatsEntry osEntry; if (totalMem.sysMemNativeWeight > 0) { osEntry = new ProcStatsEntry(Utils.OS_PKG, 0, mContext.getString(R.string.process_stats_os_native), memTotalTime, (long) (totalMem.sysMemNativeWeight / memTotalTime), memTotalTime); osEntry.evaluateTargetPackage(mPm, mStats, bgTotals, runTotals, sEntryCompare, mUseUss); osPkg.addEntry(osEntry); } if (totalMem.sysMemKernelWeight > 0) { osEntry = new ProcStatsEntry(Utils.OS_PKG, 0, mContext.getString(R.string.process_stats_os_kernel), memTotalTime, (long) (totalMem.sysMemKernelWeight / memTotalTime), memTotalTime); osEntry.evaluateTargetPackage(mPm, mStats, bgTotals, runTotals, sEntryCompare, mUseUss); osPkg.addEntry(osEntry); } /* Turned off now -- zram is being distributed across running apps. if (totalMem.sysMemZRamWeight > 0) { osEntry = new ProcStatsEntry(Utils.OS_PKG, 0, mContext.getString(R.string.process_stats_os_zram), memTotalTime, (long) (totalMem.sysMemZRamWeight / memTotalTime)); osEntry.evaluateTargetPackage(mPm, mStats, bgTotals, runTotals, sEntryCompare, mUseUss); osPkg.addEntry(osEntry); } */ if (baseCacheRam > 0) { osEntry = new ProcStatsEntry(Utils.OS_PKG, 0, mContext.getString(R.string.process_stats_os_cache), memTotalTime, baseCacheRam / 1024, memTotalTime); osEntry.evaluateTargetPackage(mPm, mStats, bgTotals, runTotals, sEntryCompare, mUseUss); osPkg.addEntry(osEntry); } return osPkg; } private ArrayList<ProcStatsEntry> getProcs(ProcessDataCollection bgTotals, ProcessDataCollection runTotals) { final ArrayList<ProcStatsEntry> procEntries = new ArrayList<>(); if (DEBUG) Log.d(TAG, "-------------------- PULLING PROCESSES"); final ProcessMap<ProcStatsEntry> entriesMap = new ProcessMap<ProcStatsEntry>(); for (int ipkg = 0, N = mStats.mPackages.getMap().size(); ipkg < N; ipkg++) { final SparseArray<SparseArray<ProcessStats.PackageState>> pkgUids = mStats.mPackages .getMap().valueAt(ipkg); for (int iu = 0; iu < pkgUids.size(); iu++) { final SparseArray<ProcessStats.PackageState> vpkgs = pkgUids.valueAt(iu); for (int iv = 0; iv < vpkgs.size(); iv++) { final ProcessStats.PackageState st = vpkgs.valueAt(iv); for (int iproc = 0; iproc < st.mProcesses.size(); iproc++) { final ProcessState pkgProc = st.mProcesses.valueAt(iproc); final ProcessState proc = mStats.mProcesses.get(pkgProc.getName(), pkgProc.getUid()); if (proc == null) { Log.w(TAG, "No process found for pkg " + st.mPackageName + "/" + st.mUid + " proc name " + pkgProc.getName()); continue; } ProcStatsEntry ent = entriesMap.get(proc.getName(), proc.getUid()); if (ent == null) { ent = new ProcStatsEntry(proc, st.mPackageName, bgTotals, runTotals, mUseUss); if (ent.mRunWeight > 0) { if (DEBUG) Log.d(TAG, "Adding proc " + proc.getName() + "/" + proc.getUid() + ": time=" + ProcessStatsUi.makeDuration(ent.mRunDuration) + " (" + ((((double) ent.mRunDuration) / memTotalTime) * 100) + "%)" + " pss=" + ent.mAvgRunMem); entriesMap.put(proc.getName(), proc.getUid(), ent); procEntries.add(ent); } } else { ent.addPackage(st.mPackageName); } } } } } if (DEBUG) Log.d(TAG, "-------------------- MAPPING SERVICES"); // Add in service info. for (int ip = 0, N = mStats.mPackages.getMap().size(); ip < N; ip++) { SparseArray<SparseArray<ProcessStats.PackageState>> uids = mStats.mPackages.getMap() .valueAt(ip); for (int iu = 0; iu < uids.size(); iu++) { SparseArray<ProcessStats.PackageState> vpkgs = uids.valueAt(iu); for (int iv = 0; iv < vpkgs.size(); iv++) { ProcessStats.PackageState ps = vpkgs.valueAt(iv); for (int is = 0, NS = ps.mServices.size(); is < NS; is++) { ServiceState ss = ps.mServices.valueAt(is); if (ss.getProcessName() != null) { ProcStatsEntry ent = entriesMap.get(ss.getProcessName(), uids.keyAt(iu)); if (ent != null) { if (DEBUG) Log.d(TAG, "Adding service " + ps.mPackageName + "/" + ss.getName() + "/" + uids.keyAt(iu) + " to proc " + ss.getProcessName()); ent.addService(ss); } else { Log.w(TAG, "No process " + ss.getProcessName() + "/" + uids.keyAt(iu) + " for service " + ss.getName()); } } } } } } return procEntries; } private void load() { try { 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); } } public static class MemInfo { public double realUsedRam; public double realFreeRam; public double realTotalRam; long baseCacheRam; double[] mMemStateWeights = new double[ProcessStats.STATE_COUNT]; double freeWeight; double usedWeight; double weightToRam; double totalRam; double totalScale; long memTotalTime; private MemInfo(Context context, ProcessStats.TotalMemoryUseCollection totalMem, long memTotalTime) { this.memTotalTime = memTotalTime; calculateWeightInfo(context, totalMem, memTotalTime); double usedRam = (usedWeight * 1024) / memTotalTime; double freeRam = (freeWeight * 1024) / memTotalTime; totalRam = usedRam + freeRam; totalScale = realTotalRam / totalRam; weightToRam = totalScale / memTotalTime * 1024; realUsedRam = usedRam * totalScale; realFreeRam = freeRam * totalScale; if (DEBUG) { Log.i(TAG, "Scaled Used RAM: " + Formatter.formatShortFileSize(context, (long) realUsedRam)); Log.i(TAG, "Scaled Free RAM: " + Formatter.formatShortFileSize(context, (long) realFreeRam)); } if (DEBUG) { Log.i(TAG, "Adj Scaled Used RAM: " + Formatter.formatShortFileSize(context, (long) realUsedRam)); Log.i(TAG, "Adj Scaled Free RAM: " + Formatter.formatShortFileSize(context, (long) realFreeRam)); } ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo( memInfo); if (memInfo.hiddenAppThreshold >= realFreeRam) { realUsedRam = freeRam; realFreeRam = 0; baseCacheRam = (long) realFreeRam; } else { realUsedRam += memInfo.hiddenAppThreshold; realFreeRam -= memInfo.hiddenAppThreshold; baseCacheRam = memInfo.hiddenAppThreshold; } } private void calculateWeightInfo(Context context, TotalMemoryUseCollection totalMem, long memTotalTime) { MemInfoReader memReader = new MemInfoReader(); memReader.readMemInfo(); realTotalRam = memReader.getTotalSize(); freeWeight = totalMem.sysMemFreeWeight + totalMem.sysMemCachedWeight; usedWeight = totalMem.sysMemKernelWeight + totalMem.sysMemNativeWeight; if (!totalMem.hasSwappedOutPss) { usedWeight += totalMem.sysMemZRamWeight; } for (int i = 0; i < ProcessStats.STATE_COUNT; i++) { if (i == ProcessStats.STATE_SERVICE_RESTARTING) { // These don't really run. mMemStateWeights[i] = 0; } else { mMemStateWeights[i] = totalMem.processStateWeight[i]; if (i >= ProcessStats.STATE_HOME) { freeWeight += totalMem.processStateWeight[i]; } else { usedWeight += totalMem.processStateWeight[i]; } } } if (DEBUG) { Log.i(TAG, "Used RAM: " + Formatter.formatShortFileSize(context, (long) ((usedWeight * 1024) / memTotalTime))); Log.i(TAG, "Free RAM: " + Formatter.formatShortFileSize(context, (long) ((freeWeight * 1024) / memTotalTime))); Log.i(TAG, "Total RAM: " + Formatter.formatShortFileSize(context, (long) (((freeWeight + usedWeight) * 1024) / memTotalTime))); } } } final static Comparator<ProcStatsEntry> sEntryCompare = new Comparator<ProcStatsEntry>() { @Override public int compare(ProcStatsEntry lhs, ProcStatsEntry rhs) { if (lhs.mRunWeight < rhs.mRunWeight) { return 1; } else if (lhs.mRunWeight > rhs.mRunWeight) { return -1; } else if (lhs.mRunDuration < rhs.mRunDuration) { return 1; } else if (lhs.mRunDuration > rhs.mRunDuration) { return -1; } return 0; } }; }