Proc.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
1/*
2 * Copyright (C) 2008 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
17import java.util.Set;
18import java.util.HashSet;
19import java.util.Arrays;
20import java.util.List;
21import java.util.ArrayList;
22import java.util.LinkedList;
23import java.util.Map;
24import java.util.HashMap;
25import java.util.Collections;
26import java.util.TreeSet;
27import java.io.Serializable;
28
29/**
30 * A Dalvik process.
31 */
32class Proc implements Serializable {
33
34    private static final long serialVersionUID = 0;
35
36    /**
37     * Default percentage of time to cut off of app class loading times.
38     */
39    static final int PERCENTAGE_TO_PRELOAD = 75;
40
41    /**
42     * Maximum number of classes to preload for a given process.
43     */
44    static final int MAX_TO_PRELOAD = 100;
45
46    /** Name of system server process. */
47    private static final String SYSTEM_SERVER = "system_server";
48
49    /** Names of non-application processes. */
50    private static final Set<String> NOT_FROM_ZYGOTE
51            = new HashSet<String>(Arrays.asList(
52                    "zygote",
53                    "dexopt",
54                    "unknown",
55                    SYSTEM_SERVER,
56                    "com.android.development",
57                    "app_process" // am
58            ));
59
60    /** Long running services. */
61    private static final Set<String> SERVICES
62            = new HashSet<String>(Arrays.asList(
63                    SYSTEM_SERVER,
64                    "com.android.home",
65// Commented out to make sure DefaultTimeZones gets preloaded.
66//                    "com.android.phone",
67                    "com.google.process.content",
68                    "com.android.process.media"
69            ));
70
71    /**
72     * Classes which we shouldn't load from the Zygote.
73     */
74    static final Set<String> EXCLUDED_CLASSES
75            = new HashSet<String>(Arrays.asList(
76        // Binders
77        "android.app.AlarmManager",
78        "android.app.SearchManager",
79        "android.os.FileObserver",
80        "com.android.server.PackageManagerService$AppDirObserver",
81
82        // Threads
83        "java.lang.ProcessManager",
84
85        // This class was deleted.
86        "java.math.Elementary"
87    ));
88
89    /** Parent process. */
90    final Proc parent;
91
92    /** Process ID. */
93    final int id;
94
95    /**
96     * Name of this process. We may not have the correct name at first, i.e.
97     * some classes could have been loaded before the process name was set.
98     */
99    String name;
100
101    /** Child processes. */
102    final List<Proc> children = new ArrayList<Proc>();
103
104    /** Maps thread ID to operation stack. */
105    transient final Map<Integer, LinkedList<Operation>> stacks
106            = new HashMap<Integer, LinkedList<Operation>>();
107
108    /** Number of operations. */
109    int operationCount;
110
111    /** Sequential list of operations that happened in this process. */
112    final List<Operation> operations = new ArrayList<Operation>();
113
114    /** List of past process names. */
115    final List<String> nameHistory = new ArrayList<String>();
116
117    /** Constructs a new process. */
118    Proc(Proc parent, int id) {
119        this.parent = parent;
120        this.id = id;
121    }
122
123    /** Sets name of this process. */
124    void setName(String name) {
125        if (!name.equals(this.name)) {
126            if (this.name != null) {
127                nameHistory.add(this.name);
128            }
129            this.name = name;
130        }
131    }
132
133    /**
134     * Returns the percentage of time we should cut by preloading for this
135     * app.
136     */
137    int percentageToPreload() {
138        return PERCENTAGE_TO_PRELOAD;
139    }
140
141    /**
142     * Is this a long running process?
143     */
144    boolean isService() {
145        return SERVICES.contains(this.name);
146    }
147
148    /**
149     * Returns a list of classes which should be preloaded.
150     */
151    List<LoadedClass> highestRankedClasses() {
152        if (NOT_FROM_ZYGOTE.contains(this.name)) {
153            return Collections.emptyList();
154        }
155
156        // Sort by rank.
157        Operation[] ranked = new Operation[operations.size()];
158        ranked = operations.toArray(ranked);
159        Arrays.sort(ranked, new ClassRank());
160
161        // The percentage of time to save by preloading.
162        int timeToSave = totalTimeMicros() * percentageToPreload() / 100;
163        int timeSaved = 0;
164
165        boolean service = isService();
166
167        List<LoadedClass> highest = new ArrayList<LoadedClass>();
168        for (Operation operation : ranked) {
169            if (highest.size() >= MAX_TO_PRELOAD) {
170                System.out.println(name + " got "
171                        + (timeSaved * 100 / timeToSave) + "% through");
172
173                break;
174            }
175
176            if (timeSaved >= timeToSave) {
177                break;
178            }
179
180            if (EXCLUDED_CLASSES.contains(operation.loadedClass.name)
181                    || !operation.loadedClass.systemClass) {
182                continue;
183            }
184
185            // Only load java.* class for services.
186            if (!service || operation.loadedClass.name.startsWith("java.")) {
187                highest.add(operation.loadedClass);
188            }
189
190            // For services, still count the time even if it's not in java.*
191            timeSaved += operation.medianExclusiveTimeMicros();
192        }
193
194        return highest;
195    }
196
197    /**
198     * Total time spent class loading and initializing.
199     */
200    int totalTimeMicros() {
201        int totalTime = 0;
202        for (Operation operation : operations) {
203            totalTime += operation.medianExclusiveTimeMicros();
204        }
205        return totalTime;
206    }
207
208    /** Returns true if this process is an app. */
209    public boolean isApplication() {
210        return !NOT_FROM_ZYGOTE.contains(name);
211    }
212
213    /**
214     * Starts an operation.
215     *
216     * @param threadId thread the operation started in
217     * @param loadedClass class operation happened to
218     * @param time the operation started
219     */
220    void startOperation(int threadId, LoadedClass loadedClass, long time,
221            Operation.Type type) {
222        Operation o = new Operation(
223                this, loadedClass, time, operationCount++, type);
224        operations.add(o);
225
226        LinkedList<Operation> stack = stacks.get(threadId);
227        if (stack == null) {
228            stack = new LinkedList<Operation>();
229            stacks.put(threadId, stack);
230        }
231
232        if (!stack.isEmpty()) {
233            stack.getLast().subops.add(o);
234        }
235
236        stack.add(o);
237    }
238
239    /**
240     * Ends an operation.
241     *
242     * @param threadId thread the operation ended in
243     * @param loadedClass class operation happened to
244     * @param time the operation ended
245     */
246    Operation endOperation(int threadId, String className,
247            LoadedClass loadedClass, long time) {
248        LinkedList<Operation> stack = stacks.get(threadId);
249
250        if (stack == null || stack.isEmpty()) {
251            didNotStart(className);
252            return null;
253        }
254
255        Operation o = stack.getLast();
256        if (loadedClass != o.loadedClass) {
257            didNotStart(className);
258            return null;
259        }
260
261        stack.removeLast();
262
263        o.endTimeNanos = time;
264        return o;
265    }
266
267    /**
268     * Prints an error indicating that we saw the end of an operation but not
269     * the start. A bug in the logging framework which results in dropped logs
270     * causes this.
271     */
272    private static void didNotStart(String name) {
273        System.err.println("Warning: An operation ended on " + name
274            + " but it never started!");
275    }
276
277    /**
278     * Prints this process tree to stdout.
279     */
280    void print() {
281        print("");
282    }
283
284    /**
285     * Prints a child proc to standard out.
286     */
287    private void print(String prefix) {
288        System.out.println(prefix + "id=" + id + ", name=" + name);
289        for (Proc child : children) {
290            child.print(prefix + "    ");
291        }
292    }
293
294    @Override
295    public String toString() {
296        return this.name;
297    }
298}
299