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