1/*
2 * Copyright (C) 2013 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.content.pm.ApplicationInfo;
20import android.content.pm.PackageManager;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.util.ArrayMap;
24import android.util.Log;
25import android.util.SparseArray;
26
27import com.android.internal.app.procstats.ProcessStats;
28import com.android.internal.app.procstats.ProcessState;
29import com.android.internal.app.procstats.ServiceState;
30
31import java.util.ArrayList;
32import java.util.Collections;
33import java.util.Comparator;
34
35public final class ProcStatsEntry implements Parcelable {
36    private static final String TAG = "ProcStatsEntry";
37    private static boolean DEBUG = ProcessStatsUi.DEBUG;
38
39    final String mPackage;
40    final int mUid;
41    final String mName;
42    public CharSequence mLabel;
43    final ArrayList<String> mPackages = new ArrayList<String>();
44    final long mBgDuration;
45    final long mAvgBgMem;
46    final long mMaxBgMem;
47    final double mBgWeight;
48    final long mRunDuration;
49    final long mAvgRunMem;
50    final long mMaxRunMem;
51    final double mRunWeight;
52
53    String mBestTargetPackage;
54
55    ArrayMap<String, ArrayList<Service>> mServices = new ArrayMap<String, ArrayList<Service>>(1);
56
57    public ProcStatsEntry(ProcessState proc, String packageName,
58            ProcessStats.ProcessDataCollection tmpBgTotals,
59            ProcessStats.ProcessDataCollection tmpRunTotals, boolean useUss) {
60        proc.computeProcessData(tmpBgTotals, 0);
61        proc.computeProcessData(tmpRunTotals, 0);
62        mPackage = proc.getPackage();
63        mUid = proc.getUid();
64        mName = proc.getName();
65        mPackages.add(packageName);
66        mBgDuration = tmpBgTotals.totalTime;
67        mAvgBgMem = useUss ? tmpBgTotals.avgUss : tmpBgTotals.avgPss;
68        mMaxBgMem = useUss ? tmpBgTotals.maxUss : tmpBgTotals.maxPss;
69        mBgWeight = mAvgBgMem * (double) mBgDuration;
70        mRunDuration = tmpRunTotals.totalTime;
71        mAvgRunMem = useUss ? tmpRunTotals.avgUss : tmpRunTotals.avgPss;
72        mMaxRunMem = useUss ? tmpRunTotals.maxUss : tmpRunTotals.maxPss;
73        mRunWeight = mAvgRunMem * (double) mRunDuration;
74        if (DEBUG) Log.d(TAG, "New proc entry " + proc.getName() + ": dur=" + mBgDuration
75                + " avgpss=" + mAvgBgMem + " weight=" + mBgWeight);
76    }
77
78    public ProcStatsEntry(String pkgName, int uid, String procName, long duration, long mem,
79            long memDuration) {
80        mPackage = pkgName;
81        mUid = uid;
82        mName = procName;
83        mBgDuration = mRunDuration = duration;
84        mAvgBgMem = mMaxBgMem = mAvgRunMem = mMaxRunMem = mem;
85        mBgWeight = mRunWeight = ((double)memDuration) * mem;
86        if (DEBUG) Log.d(TAG, "New proc entry " + procName + ": dur=" + mBgDuration
87                + " avgpss=" + mAvgBgMem + " weight=" + mBgWeight);
88    }
89
90    public ProcStatsEntry(Parcel in) {
91        mPackage = in.readString();
92        mUid = in.readInt();
93        mName = in.readString();
94        in.readStringList(mPackages);
95        mBgDuration = in.readLong();
96        mAvgBgMem = in.readLong();
97        mMaxBgMem = in.readLong();
98        mBgWeight = in.readDouble();
99        mRunDuration = in.readLong();
100        mAvgRunMem = in.readLong();
101        mMaxRunMem = in.readLong();
102        mRunWeight = in.readDouble();
103        mBestTargetPackage = in.readString();
104        final int N = in.readInt();
105        if (N > 0) {
106            mServices.ensureCapacity(N);
107            for (int i=0; i<N; i++) {
108                String key = in.readString();
109                ArrayList<Service> value = new ArrayList<Service>();
110                in.readTypedList(value, Service.CREATOR);
111                mServices.append(key, value);
112            }
113        }
114    }
115
116    public void addPackage(String packageName) {
117        mPackages.add(packageName);
118    }
119
120    public void evaluateTargetPackage(PackageManager pm, ProcessStats stats,
121            ProcessStats.ProcessDataCollection bgTotals,
122            ProcessStats.ProcessDataCollection runTotals, Comparator<ProcStatsEntry> compare,
123            boolean useUss) {
124        mBestTargetPackage = null;
125        if (mPackages.size() == 1) {
126            if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": single pkg " + mPackages.get(0));
127            mBestTargetPackage = mPackages.get(0);
128            return;
129        }
130
131        // If one of the packages is the framework itself, that wins.
132        // See if there is one significant package that was running here.
133        for (int ipkg=0; ipkg<mPackages.size(); ipkg++) {
134            if ("android".equals(mPackages.get(ipkg))) {
135                mBestTargetPackage = mPackages.get(ipkg);
136                return;
137            }
138        }
139
140        // Collect information about each package running in the process.
141        ArrayList<ProcStatsEntry> subProcs = new ArrayList<>();
142        for (int ipkg=0; ipkg<mPackages.size(); ipkg++) {
143            SparseArray<ProcessStats.PackageState> vpkgs
144                    = stats.mPackages.get(mPackages.get(ipkg), mUid);
145            for (int ivers=0;  ivers<vpkgs.size(); ivers++) {
146                ProcessStats.PackageState pkgState = vpkgs.valueAt(ivers);
147                if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ", pkg "
148                        + pkgState + ":");
149                if (pkgState == null) {
150                    Log.w(TAG, "No package state found for " + mPackages.get(ipkg) + "/"
151                            + mUid + " in process " + mName);
152                    continue;
153                }
154                ProcessState pkgProc = pkgState.mProcesses.get(mName);
155                if (pkgProc == null) {
156                    Log.w(TAG, "No process " + mName + " found in package state "
157                            + mPackages.get(ipkg) + "/" + mUid);
158                    continue;
159                }
160                subProcs.add(new ProcStatsEntry(pkgProc, pkgState.mPackageName, bgTotals,
161                        runTotals, useUss));
162            }
163        }
164
165        if (subProcs.size() > 1) {
166            Collections.sort(subProcs, compare);
167            if (subProcs.get(0).mRunWeight > (subProcs.get(1).mRunWeight *3)) {
168                if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": best pkg "
169                        + subProcs.get(0).mPackage + " weight " + subProcs.get(0).mRunWeight
170                        + " better than " + subProcs.get(1).mPackage
171                        + " weight " + subProcs.get(1).mRunWeight);
172                mBestTargetPackage = subProcs.get(0).mPackage;
173                return;
174            }
175            // Couldn't find one that is best by weight, let's decide on best another
176            // way: the one that has the longest running service, accounts for at least
177            // half of the maximum weight, and has specified an explicit app icon.
178            double maxWeight = subProcs.get(0).mRunWeight;
179            long bestRunTime = -1;
180            boolean bestPersistent = false;
181            for (int i=0; i<subProcs.size(); i++) {
182                final ProcStatsEntry subProc = subProcs.get(i);
183                if (subProc.mRunWeight < (maxWeight/2)) {
184                    if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
185                            + subProc.mPackage + " weight " + subProc.mRunWeight
186                            + " too small");
187                    continue;
188                }
189                try {
190                    ApplicationInfo ai = pm.getApplicationInfo(subProc.mPackage, 0);
191                    if (ai.icon == 0) {
192                        if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
193                                + subProc.mPackage + " has no icon");
194                        continue;
195                    }
196                    if ((ai.flags&ApplicationInfo.FLAG_PERSISTENT) != 0) {
197                        long thisRunTime = subProc.mRunDuration;
198                        if (!bestPersistent || thisRunTime > bestRunTime) {
199                            if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
200                                    + subProc.mPackage + " new best pers run time "
201                                    + thisRunTime);
202                            bestRunTime = thisRunTime;
203                            bestPersistent = true;
204                        } else {
205                            if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
206                                    + subProc.mPackage + " pers run time " + thisRunTime
207                                    + " not as good as last " + bestRunTime);
208                        }
209                        continue;
210                    } else if (bestPersistent) {
211                        if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
212                                + subProc.mPackage + " is not persistent");
213                        continue;
214                    }
215                } catch (PackageManager.NameNotFoundException e) {
216                    if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
217                            + subProc.mPackage + " failed finding app info");
218                    continue;
219                }
220                ArrayList<Service> subProcServices = null;
221                for (int isp=0, NSP=mServices.size(); isp<NSP; isp++) {
222                    ArrayList<Service> subServices = mServices.valueAt(isp);
223                    if (subServices.get(0).mPackage.equals(subProc.mPackage)) {
224                        subProcServices = subServices;
225                        break;
226                    }
227                }
228                long thisRunTime = 0;
229                if (subProcServices != null) {
230                    for (int iss=0, NSS=subProcServices.size(); iss<NSS; iss++) {
231                        Service service = subProcServices.get(iss);
232                        if (service.mDuration > thisRunTime) {
233                            if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
234                                    + subProc.mPackage + " service " + service.mName
235                                    + " run time is " + service.mDuration);
236                            thisRunTime = service.mDuration;
237                            break;
238                        }
239                    }
240                }
241                if (thisRunTime > bestRunTime) {
242                    if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
243                            + subProc.mPackage + " new best run time " + thisRunTime);
244                    mBestTargetPackage = subProc.mPackage;
245                    bestRunTime = thisRunTime;
246                } else {
247                    if (DEBUG) Log.d(TAG, "Eval pkg of " + mName + ": pkg "
248                            + subProc.mPackage + " run time " + thisRunTime
249                            + " not as good as last " + bestRunTime);
250                }
251            }
252        } else if (subProcs.size() == 1) {
253            mBestTargetPackage = subProcs.get(0).mPackage;
254        }
255    }
256
257    public void addService(ServiceState svc) {
258        ArrayList<Service> services = mServices.get(svc.getPackage());
259        if (services == null) {
260            services = new ArrayList<Service>();
261            mServices.put(svc.getPackage(), services);
262        }
263        services.add(new Service(svc));
264    }
265
266    @Override
267    public int describeContents() {
268        return 0;
269    }
270
271    @Override
272    public void writeToParcel(Parcel dest, int flags) {
273        dest.writeString(mPackage);
274        dest.writeInt(mUid);
275        dest.writeString(mName);
276        dest.writeStringList(mPackages);
277        dest.writeLong(mBgDuration);
278        dest.writeLong(mAvgBgMem);
279        dest.writeLong(mMaxBgMem);
280        dest.writeDouble(mBgWeight);
281        dest.writeLong(mRunDuration);
282        dest.writeLong(mAvgRunMem);
283        dest.writeLong(mMaxRunMem);
284        dest.writeDouble(mRunWeight);
285        dest.writeString(mBestTargetPackage);
286        final int N = mServices.size();
287        dest.writeInt(N);
288        for (int i=0; i<N; i++) {
289            dest.writeString(mServices.keyAt(i));
290            dest.writeTypedList(mServices.valueAt(i));
291        }
292    }
293
294    public static final Parcelable.Creator<ProcStatsEntry> CREATOR
295            = new Parcelable.Creator<ProcStatsEntry>() {
296        public ProcStatsEntry createFromParcel(Parcel in) {
297            return new ProcStatsEntry(in);
298        }
299
300        public ProcStatsEntry[] newArray(int size) {
301            return new ProcStatsEntry[size];
302        }
303    };
304
305    public static final class Service implements Parcelable {
306        final String mPackage;
307        final String mName;
308        final String mProcess;
309        final long mDuration;
310
311        public Service(ServiceState service) {
312            mPackage = service.getPackage();
313            mName = service.getName();
314            mProcess = service.getProcessName();
315            mDuration = service.dumpTime(null, null,
316                    ServiceState.SERVICE_RUN, ProcessStats.STATE_NOTHING, 0, 0);
317        }
318
319        public Service(Parcel in) {
320            mPackage = in.readString();
321            mName = in.readString();
322            mProcess = in.readString();
323            mDuration = in.readLong();
324        }
325
326        @Override
327        public int describeContents() {
328            return 0;
329        }
330
331        @Override
332        public void writeToParcel(Parcel dest, int flags) {
333            dest.writeString(mPackage);
334            dest.writeString(mName);
335            dest.writeString(mProcess);
336            dest.writeLong(mDuration);
337        }
338
339        public static final Parcelable.Creator<Service> CREATOR
340                = new Parcelable.Creator<Service>() {
341            public Service createFromParcel(Parcel in) {
342                return new Service(in);
343            }
344
345            public Service[] newArray(int size) {
346                return new Service[size];
347            }
348        };
349    }
350}
351