1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.applications;
18 
19 import android.app.ActivityManager;
20 import android.content.Context;
21 import android.content.pm.PackageManager;
22 import android.os.ParcelFileDescriptor;
23 import android.os.RemoteException;
24 import android.os.ServiceManager;
25 import android.os.SystemClock;
26 import android.text.format.Formatter;
27 import android.util.ArrayMap;
28 import android.util.Log;
29 import android.util.SparseArray;
30 
31 import com.android.internal.app.ProcessMap;
32 import com.android.internal.app.procstats.DumpUtils;
33 import com.android.internal.app.procstats.IProcessStats;
34 import com.android.internal.app.procstats.ProcessState;
35 import com.android.internal.app.procstats.ProcessStats;
36 import com.android.internal.app.procstats.ProcessStats.ProcessDataCollection;
37 import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection;
38 import com.android.internal.app.procstats.ServiceState;
39 import com.android.internal.util.MemInfoReader;
40 import com.android.settings.R;
41 import com.android.settings.Utils;
42 
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.util.ArrayList;
46 import java.util.Comparator;
47 import java.util.List;
48 
49 public class ProcStatsData {
50 
51     private static final String TAG = "ProcStatsManager";
52 
53     private static final boolean DEBUG = ProcessStatsUi.DEBUG;
54 
55     private static ProcessStats sStatsXfer;
56 
57     private PackageManager mPm;
58     private Context mContext;
59     private long memTotalTime;
60 
61     private IProcessStats mProcessStats;
62     private ProcessStats mStats;
63 
64     private boolean mUseUss;
65     private long mDuration;
66 
67     private int[] mMemStates;
68 
69     private int[] mStates;
70 
71     private MemInfo mMemInfo;
72 
73     private ArrayList<ProcStatsPackageEntry> pkgEntries;
74 
ProcStatsData(Context context, boolean useXfer)75     public ProcStatsData(Context context, boolean useXfer) {
76         mContext = context;
77         mPm = context.getPackageManager();
78         mProcessStats = IProcessStats.Stub.asInterface(
79                 ServiceManager.getService(ProcessStats.SERVICE_NAME));
80         mMemStates = ProcessStats.ALL_MEM_ADJ;
81         mStates = ProcessStats.BACKGROUND_PROC_STATES;
82         if (useXfer) {
83             mStats = sStatsXfer;
84         }
85     }
86 
setTotalTime(int totalTime)87     public void setTotalTime(int totalTime) {
88         memTotalTime = totalTime;
89     }
90 
xferStats()91     public void xferStats() {
92         sStatsXfer = mStats;
93     }
94 
setMemStates(int[] memStates)95     public void setMemStates(int[] memStates) {
96         mMemStates = memStates;
97         refreshStats(false);
98     }
99 
setStats(int[] stats)100     public void setStats(int[] stats) {
101         this.mStates = stats;
102         refreshStats(false);
103     }
104 
getMemState()105     public int getMemState() {
106         int factor = mStats.mMemFactor;
107         if (factor == ProcessStats.ADJ_NOTHING) {
108             return ProcessStats.ADJ_MEM_FACTOR_NORMAL;
109         }
110         if (factor >= ProcessStats.ADJ_SCREEN_ON) {
111             factor -= ProcessStats.ADJ_SCREEN_ON;
112         }
113         return factor;
114     }
115 
getMemInfo()116     public MemInfo getMemInfo() {
117         return mMemInfo;
118     }
119 
getElapsedTime()120     public long getElapsedTime() {
121         return mStats.mTimePeriodEndRealtime - mStats.mTimePeriodStartRealtime;
122     }
123 
setDuration(long duration)124     public void setDuration(long duration) {
125         if (duration != mDuration) {
126             mDuration = duration;
127             refreshStats(true);
128         }
129     }
130 
getDuration()131     public long getDuration() {
132         return mDuration;
133     }
134 
getEntries()135     public List<ProcStatsPackageEntry> getEntries() {
136         return pkgEntries;
137     }
138 
refreshStats(boolean forceLoad)139     public void refreshStats(boolean forceLoad) {
140         if (mStats == null || forceLoad) {
141             load();
142         }
143 
144         pkgEntries = new ArrayList<>();
145 
146         long now = SystemClock.uptimeMillis();
147 
148         memTotalTime = DumpUtils.dumpSingleTime(null, null, mStats.mMemFactorDurations,
149                 mStats.mMemFactor, mStats.mStartTime, now);
150 
151         ProcessStats.TotalMemoryUseCollection totalMem = new ProcessStats.TotalMemoryUseCollection(
152                 ProcessStats.ALL_SCREEN_ADJ, mMemStates);
153         mStats.computeTotalMemoryUse(totalMem, now);
154 
155         mMemInfo = new MemInfo(mContext, totalMem, memTotalTime);
156 
157         ProcessDataCollection bgTotals = new ProcessDataCollection(
158                 ProcessStats.ALL_SCREEN_ADJ, mMemStates, mStates);
159         ProcessDataCollection runTotals = new ProcessDataCollection(
160                 ProcessStats.ALL_SCREEN_ADJ, mMemStates, ProcessStats.NON_CACHED_PROC_STATES);
161 
162         createPkgMap(getProcs(bgTotals, runTotals), bgTotals, runTotals);
163         if (totalMem.sysMemZRamWeight > 0 && !totalMem.hasSwappedOutPss) {
164             distributeZRam(totalMem.sysMemZRamWeight);
165         }
166 
167         ProcStatsPackageEntry osPkg = createOsEntry(bgTotals, runTotals, totalMem,
168                 mMemInfo.baseCacheRam);
169         pkgEntries.add(osPkg);
170     }
171 
createPkgMap(ArrayList<ProcStatsEntry> procEntries, ProcessDataCollection bgTotals, ProcessDataCollection runTotals)172     private void createPkgMap(ArrayList<ProcStatsEntry> procEntries, ProcessDataCollection bgTotals,
173             ProcessDataCollection runTotals) {
174         // Combine processes into packages.
175         ArrayMap<String, ProcStatsPackageEntry> pkgMap = new ArrayMap<>();
176         for (int i = procEntries.size() - 1; i >= 0; i--) {
177             ProcStatsEntry proc = procEntries.get(i);
178             proc.evaluateTargetPackage(mPm, mStats, bgTotals, runTotals, sEntryCompare, mUseUss);
179             ProcStatsPackageEntry pkg = pkgMap.get(proc.mBestTargetPackage);
180             if (pkg == null) {
181                 pkg = new ProcStatsPackageEntry(proc.mBestTargetPackage, memTotalTime);
182                 pkgMap.put(proc.mBestTargetPackage, pkg);
183                 pkgEntries.add(pkg);
184             }
185             pkg.addEntry(proc);
186         }
187     }
188 
distributeZRam(double zramWeight)189     private void distributeZRam(double zramWeight) {
190         // Distribute kernel's Z-Ram across processes, based on how much they have been running.
191         // The idea is that the memory used by the kernel for this is not really the kernel's
192         // responsibility, but that of whoever got swapped in to it...  and we will take how
193         // much a process runs for as a sign of the proportion of Z-Ram it is responsible for.
194 
195         long zramMem = (long) (zramWeight / memTotalTime);
196         long totalTime = 0;
197         for (int i = pkgEntries.size() - 1; i >= 0; i--) {
198             ProcStatsPackageEntry entry = pkgEntries.get(i);
199             for (int j = entry.mEntries.size() - 1; j >= 0; j--) {
200                 ProcStatsEntry proc = entry.mEntries.get(j);
201                 totalTime += proc.mRunDuration;
202             }
203         }
204         for (int i = pkgEntries.size() - 1; i >= 0 && totalTime > 0; i--) {
205             ProcStatsPackageEntry entry = pkgEntries.get(i);
206             long pkgRunTime = 0;
207             long maxRunTime = 0;
208             for (int j = entry.mEntries.size() - 1; j >= 0; j--) {
209                 ProcStatsEntry proc = entry.mEntries.get(j);
210                 pkgRunTime += proc.mRunDuration;
211                 if (proc.mRunDuration > maxRunTime) {
212                     maxRunTime = proc.mRunDuration;
213                 }
214             }
215             long pkgZRam = (zramMem*pkgRunTime)/totalTime;
216             if (pkgZRam > 0) {
217                 zramMem -= pkgZRam;
218                 totalTime -= pkgRunTime;
219                 ProcStatsEntry procEntry = new ProcStatsEntry(entry.mPackage, 0,
220                         mContext.getString(R.string.process_stats_os_zram), maxRunTime,
221                         pkgZRam, memTotalTime);
222                 procEntry.evaluateTargetPackage(mPm, mStats, null, null, sEntryCompare, mUseUss);
223                 entry.addEntry(procEntry);
224             }
225         }
226     }
227 
createOsEntry(ProcessDataCollection bgTotals, ProcessDataCollection runTotals, TotalMemoryUseCollection totalMem, long baseCacheRam)228     private ProcStatsPackageEntry createOsEntry(ProcessDataCollection bgTotals,
229             ProcessDataCollection runTotals, TotalMemoryUseCollection totalMem, long baseCacheRam) {
230         // Add in fake entry representing the OS itself.
231         ProcStatsPackageEntry osPkg = new ProcStatsPackageEntry("os", memTotalTime);
232         ProcStatsEntry osEntry;
233         if (totalMem.sysMemNativeWeight > 0) {
234             osEntry = new ProcStatsEntry(Utils.OS_PKG, 0,
235                     mContext.getString(R.string.process_stats_os_native), memTotalTime,
236                     (long) (totalMem.sysMemNativeWeight / memTotalTime), memTotalTime);
237             osEntry.evaluateTargetPackage(mPm, mStats, bgTotals, runTotals, sEntryCompare, mUseUss);
238             osPkg.addEntry(osEntry);
239         }
240         if (totalMem.sysMemKernelWeight > 0) {
241             osEntry = new ProcStatsEntry(Utils.OS_PKG, 0,
242                     mContext.getString(R.string.process_stats_os_kernel), memTotalTime,
243                     (long) (totalMem.sysMemKernelWeight / memTotalTime), memTotalTime);
244             osEntry.evaluateTargetPackage(mPm, mStats, bgTotals, runTotals, sEntryCompare, mUseUss);
245             osPkg.addEntry(osEntry);
246         }
247         /*  Turned off now -- zram is being distributed across running apps.
248         if (totalMem.sysMemZRamWeight > 0) {
249             osEntry = new ProcStatsEntry(Utils.OS_PKG, 0,
250                     mContext.getString(R.string.process_stats_os_zram), memTotalTime,
251                     (long) (totalMem.sysMemZRamWeight / memTotalTime));
252             osEntry.evaluateTargetPackage(mPm, mStats, bgTotals, runTotals, sEntryCompare, mUseUss);
253             osPkg.addEntry(osEntry);
254         }
255         */
256         if (baseCacheRam > 0) {
257             osEntry = new ProcStatsEntry(Utils.OS_PKG, 0,
258                     mContext.getString(R.string.process_stats_os_cache), memTotalTime,
259                     baseCacheRam / 1024, memTotalTime);
260             osEntry.evaluateTargetPackage(mPm, mStats, bgTotals, runTotals, sEntryCompare, mUseUss);
261             osPkg.addEntry(osEntry);
262         }
263         return osPkg;
264     }
265 
getProcs(ProcessDataCollection bgTotals, ProcessDataCollection runTotals)266     private ArrayList<ProcStatsEntry> getProcs(ProcessDataCollection bgTotals,
267             ProcessDataCollection runTotals) {
268         final ArrayList<ProcStatsEntry> procEntries = new ArrayList<>();
269         if (DEBUG) Log.d(TAG, "-------------------- PULLING PROCESSES");
270 
271         final ProcessMap<ProcStatsEntry> entriesMap = new ProcessMap<ProcStatsEntry>();
272         for (int ipkg = 0, N = mStats.mPackages.getMap().size(); ipkg < N; ipkg++) {
273             final SparseArray<SparseArray<ProcessStats.PackageState>> pkgUids = mStats.mPackages
274                     .getMap().valueAt(ipkg);
275             for (int iu = 0; iu < pkgUids.size(); iu++) {
276                 final SparseArray<ProcessStats.PackageState> vpkgs = pkgUids.valueAt(iu);
277                 for (int iv = 0; iv < vpkgs.size(); iv++) {
278                     final ProcessStats.PackageState st = vpkgs.valueAt(iv);
279                     for (int iproc = 0; iproc < st.mProcesses.size(); iproc++) {
280                         final ProcessState pkgProc = st.mProcesses.valueAt(iproc);
281                         final ProcessState proc = mStats.mProcesses.get(pkgProc.getName(),
282                                 pkgProc.getUid());
283                         if (proc == null) {
284                             Log.w(TAG, "No process found for pkg " + st.mPackageName
285                                     + "/" + st.mUid + " proc name " + pkgProc.getName());
286                             continue;
287                         }
288                         ProcStatsEntry ent = entriesMap.get(proc.getName(), proc.getUid());
289                         if (ent == null) {
290                             ent = new ProcStatsEntry(proc, st.mPackageName, bgTotals, runTotals,
291                                     mUseUss);
292                             if (ent.mRunWeight > 0) {
293                                 if (DEBUG) Log.d(TAG, "Adding proc " + proc.getName() + "/"
294                                             + proc.getUid() + ": time="
295                                             + ProcessStatsUi.makeDuration(ent.mRunDuration) + " ("
296                                             + ((((double) ent.mRunDuration) / memTotalTime) * 100)
297                                             + "%)"
298                                             + " pss=" + ent.mAvgRunMem);
299                                 entriesMap.put(proc.getName(), proc.getUid(), ent);
300                                 procEntries.add(ent);
301                             }
302                         } else {
303                             ent.addPackage(st.mPackageName);
304                         }
305                     }
306                 }
307             }
308         }
309 
310         if (DEBUG) Log.d(TAG, "-------------------- MAPPING SERVICES");
311 
312         // Add in service info.
313         for (int ip = 0, N = mStats.mPackages.getMap().size(); ip < N; ip++) {
314             SparseArray<SparseArray<ProcessStats.PackageState>> uids = mStats.mPackages.getMap()
315                     .valueAt(ip);
316             for (int iu = 0; iu < uids.size(); iu++) {
317                 SparseArray<ProcessStats.PackageState> vpkgs = uids.valueAt(iu);
318                 for (int iv = 0; iv < vpkgs.size(); iv++) {
319                     ProcessStats.PackageState ps = vpkgs.valueAt(iv);
320                     for (int is = 0, NS = ps.mServices.size(); is < NS; is++) {
321                         ServiceState ss = ps.mServices.valueAt(is);
322                         if (ss.getProcessName() != null) {
323                             ProcStatsEntry ent = entriesMap.get(ss.getProcessName(),
324                                     uids.keyAt(iu));
325                             if (ent != null) {
326                                 if (DEBUG) Log.d(TAG, "Adding service " + ps.mPackageName
327                                             + "/" + ss.getName() + "/" + uids.keyAt(iu)
328                                             + " to proc " + ss.getProcessName());
329                                 ent.addService(ss);
330                             } else {
331                                 Log.w(TAG, "No process " + ss.getProcessName() + "/"
332                                         + uids.keyAt(iu) + " for service " + ss.getName());
333                             }
334                         }
335                     }
336                 }
337             }
338         }
339 
340         return procEntries;
341     }
342 
load()343     private void load() {
344         try {
345             ParcelFileDescriptor pfd = mProcessStats.getStatsOverTime(mDuration);
346             mStats = new ProcessStats(false);
347             InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
348             mStats.read(is);
349             try {
350                 is.close();
351             } catch (IOException e) {
352             }
353             if (mStats.mReadError != null) {
354                 Log.w(TAG, "Failure reading process stats: " + mStats.mReadError);
355             }
356         } catch (RemoteException e) {
357             Log.e(TAG, "RemoteException:", e);
358         }
359     }
360 
361     public static class MemInfo {
362         public double realUsedRam;
363         public double realFreeRam;
364         public double realTotalRam;
365         long baseCacheRam;
366 
367         double[] mMemStateWeights = new double[ProcessStats.STATE_COUNT];
368         double freeWeight;
369         double usedWeight;
370         double weightToRam;
371         double totalRam;
372         double totalScale;
373         long memTotalTime;
374 
MemInfo(Context context, ProcessStats.TotalMemoryUseCollection totalMem, long memTotalTime)375         private MemInfo(Context context, ProcessStats.TotalMemoryUseCollection totalMem,
376                 long memTotalTime) {
377             this.memTotalTime = memTotalTime;
378             calculateWeightInfo(context, totalMem, memTotalTime);
379 
380             double usedRam = (usedWeight * 1024) / memTotalTime;
381             double freeRam = (freeWeight * 1024) / memTotalTime;
382             totalRam = usedRam + freeRam;
383             totalScale = realTotalRam / totalRam;
384             weightToRam = totalScale / memTotalTime * 1024;
385 
386             realUsedRam = usedRam * totalScale;
387             realFreeRam = freeRam * totalScale;
388             if (DEBUG) {
389                 Log.i(TAG, "Scaled Used RAM: " + Formatter.formatShortFileSize(context,
390                         (long) realUsedRam));
391                 Log.i(TAG, "Scaled Free RAM: " + Formatter.formatShortFileSize(context,
392                         (long) realFreeRam));
393             }
394             if (DEBUG) {
395                 Log.i(TAG, "Adj Scaled Used RAM: " + Formatter.formatShortFileSize(context,
396                         (long) realUsedRam));
397                 Log.i(TAG, "Adj Scaled Free RAM: " + Formatter.formatShortFileSize(context,
398                         (long) realFreeRam));
399             }
400 
401             ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
402             ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(
403                     memInfo);
404             if (memInfo.hiddenAppThreshold >= realFreeRam) {
405                 realUsedRam = freeRam;
406                 realFreeRam = 0;
407                 baseCacheRam = (long) realFreeRam;
408             } else {
409                 realUsedRam += memInfo.hiddenAppThreshold;
410                 realFreeRam -= memInfo.hiddenAppThreshold;
411                 baseCacheRam = memInfo.hiddenAppThreshold;
412             }
413         }
414 
calculateWeightInfo(Context context, TotalMemoryUseCollection totalMem, long memTotalTime)415         private void calculateWeightInfo(Context context, TotalMemoryUseCollection totalMem,
416                 long memTotalTime) {
417             MemInfoReader memReader = new MemInfoReader();
418             memReader.readMemInfo();
419             realTotalRam = memReader.getTotalSize();
420             freeWeight = totalMem.sysMemFreeWeight + totalMem.sysMemCachedWeight;
421             usedWeight = totalMem.sysMemKernelWeight + totalMem.sysMemNativeWeight;
422             if (!totalMem.hasSwappedOutPss) {
423                 usedWeight += totalMem.sysMemZRamWeight;
424             }
425             for (int i = 0; i < ProcessStats.STATE_COUNT; i++) {
426                 if (i == ProcessStats.STATE_SERVICE_RESTARTING) {
427                     // These don't really run.
428                     mMemStateWeights[i] = 0;
429                 } else {
430                     mMemStateWeights[i] = totalMem.processStateWeight[i];
431                     if (i >= ProcessStats.STATE_HOME) {
432                         freeWeight += totalMem.processStateWeight[i];
433                     } else {
434                         usedWeight += totalMem.processStateWeight[i];
435                     }
436                 }
437             }
438             if (DEBUG) {
439                 Log.i(TAG, "Used RAM: " + Formatter.formatShortFileSize(context,
440                         (long) ((usedWeight * 1024) / memTotalTime)));
441                 Log.i(TAG, "Free RAM: " + Formatter.formatShortFileSize(context,
442                         (long) ((freeWeight * 1024) / memTotalTime)));
443                 Log.i(TAG, "Total RAM: " + Formatter.formatShortFileSize(context,
444                         (long) (((freeWeight + usedWeight) * 1024) / memTotalTime)));
445             }
446         }
447     }
448 
449     final static Comparator<ProcStatsEntry> sEntryCompare = new Comparator<ProcStatsEntry>() {
450         @Override
451         public int compare(ProcStatsEntry lhs, ProcStatsEntry rhs) {
452             if (lhs.mRunWeight < rhs.mRunWeight) {
453                 return 1;
454             } else if (lhs.mRunWeight > rhs.mRunWeight) {
455                 return -1;
456             } else if (lhs.mRunDuration < rhs.mRunDuration) {
457                 return 1;
458             } else if (lhs.mRunDuration > rhs.mRunDuration) {
459                 return -1;
460             }
461             return 0;
462         }
463     };
464 }
465