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
17package com.android.settings.applications;
18
19import android.app.ActivityManager;
20import android.content.Context;
21import android.content.pm.PackageManager;
22import android.os.ParcelFileDescriptor;
23import android.os.RemoteException;
24import android.os.ServiceManager;
25import android.os.SystemClock;
26import android.text.format.Formatter;
27import android.util.ArrayMap;
28import android.util.Log;
29import android.util.SparseArray;
30
31import com.android.internal.app.ProcessMap;
32import com.android.internal.app.procstats.DumpUtils;
33import com.android.internal.app.procstats.IProcessStats;
34import com.android.internal.app.procstats.ProcessState;
35import com.android.internal.app.procstats.ProcessStats;
36import com.android.internal.app.procstats.ProcessStats.ProcessDataCollection;
37import com.android.internal.app.procstats.ProcessStats.TotalMemoryUseCollection;
38import com.android.internal.app.procstats.ServiceState;
39import com.android.internal.util.MemInfoReader;
40import com.android.settings.R;
41import com.android.settings.Utils;
42
43import java.io.IOException;
44import java.io.InputStream;
45import java.util.ArrayList;
46import java.util.Comparator;
47import java.util.List;
48
49public 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
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
87    public void setTotalTime(int totalTime) {
88        memTotalTime = totalTime;
89    }
90
91    public void xferStats() {
92        sStatsXfer = mStats;
93    }
94
95    public void setMemStates(int[] memStates) {
96        mMemStates = memStates;
97        refreshStats(false);
98    }
99
100    public void setStats(int[] stats) {
101        this.mStates = stats;
102        refreshStats(false);
103    }
104
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
116    public MemInfo getMemInfo() {
117        return mMemInfo;
118    }
119
120    public long getElapsedTime() {
121        return mStats.mTimePeriodEndRealtime - mStats.mTimePeriodStartRealtime;
122    }
123
124    public void setDuration(long duration) {
125        if (duration != mDuration) {
126            mDuration = duration;
127            refreshStats(true);
128        }
129    }
130
131    public long getDuration() {
132        return mDuration;
133    }
134
135    public List<ProcStatsPackageEntry> getEntries() {
136        return pkgEntries;
137    }
138
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
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
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
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
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
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
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
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