Proc.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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    /** Parent process. */
47    final Proc parent;
48
49    /** Process ID. */
50    final int id;
51
52    /**
53     * Name of this process. We may not have the correct name at first, i.e.
54     * some classes could have been loaded before the process name was set.
55     */
56    String name;
57
58    /** Child processes. */
59    final List<Proc> children = new ArrayList<Proc>();
60
61    /** Maps thread ID to operation stack. */
62    transient final Map<Integer, LinkedList<Operation>> stacks
63            = new HashMap<Integer, LinkedList<Operation>>();
64
65    /** Number of operations. */
66    int operationCount;
67
68    /** Sequential list of operations that happened in this process. */
69    final List<Operation> operations = new ArrayList<Operation>();
70
71    /** List of past process names. */
72    final List<String> nameHistory = new ArrayList<String>();
73
74    /** Constructs a new process. */
75    Proc(Proc parent, int id) {
76        this.parent = parent;
77        this.id = id;
78    }
79
80    /** Sets name of this process. */
81    void setName(String name) {
82        if (!name.equals(this.name)) {
83            if (this.name != null) {
84                nameHistory.add(this.name);
85            }
86            this.name = name;
87        }
88    }
89
90    /**
91     * Returns the percentage of time we should cut by preloading for this
92     * app.
93     */
94    int percentageToPreload() {
95        return PERCENTAGE_TO_PRELOAD;
96    }
97
98    /**
99     * Returns a list of classes which should be preloaded.
100     *
101     * @param takeAllClasses forces all classes to be taken (irrespective of ranking)
102     */
103    List<LoadedClass> highestRankedClasses(boolean takeAllClasses) {
104        if (!isApplication()) {
105            return Collections.emptyList();
106        }
107
108        // Sort by rank.
109        Operation[] ranked = new Operation[operations.size()];
110        ranked = operations.toArray(ranked);
111        Arrays.sort(ranked, new ClassRank());
112
113        // The percentage of time to save by preloading.
114        int timeToSave = totalTimeMicros() * percentageToPreload() / 100;
115        int timeSaved = 0;
116
117        boolean service = Policy.isService(this.name);
118
119        List<LoadedClass> highest = new ArrayList<LoadedClass>();
120        for (Operation operation : ranked) {
121
122            // These are actual ranking decisions, which can be overridden
123            if (!takeAllClasses) {
124                if (highest.size() >= MAX_TO_PRELOAD) {
125                    System.out.println(name + " got "
126                            + (timeSaved * 100 / timeToSave) + "% through");
127                    break;
128                }
129
130                if (timeSaved >= timeToSave) {
131                    break;
132                }
133            }
134
135            // The remaining rules apply even to wired-down processes
136            if (!Policy.isPreloadableClass(operation.loadedClass.name)) {
137                continue;
138            }
139
140            if (!operation.loadedClass.systemClass) {
141                continue;
142            }
143
144            // Only load java.* class for services.
145            if (!service || operation.loadedClass.name.startsWith("java.")) {
146                highest.add(operation.loadedClass);
147            }
148
149            // For services, still count the time even if it's not in java.*
150            timeSaved += operation.medianExclusiveTimeMicros();
151        }
152
153        return highest;
154    }
155
156    /**
157     * Total time spent class loading and initializing.
158     */
159    int totalTimeMicros() {
160        int totalTime = 0;
161        for (Operation operation : operations) {
162            totalTime += operation.medianExclusiveTimeMicros();
163        }
164        return totalTime;
165    }
166
167    /**
168     * Returns true if this process is an app.
169     *
170     * TODO: Replace the hardcoded list with a walk up the parent chain looking for zygote.
171     */
172    public boolean isApplication() {
173        return Policy.isFromZygote(name);
174    }
175
176    /**
177     * Starts an operation.
178     *
179     * @param threadId thread the operation started in
180     * @param loadedClass class operation happened to
181     * @param time the operation started
182     */
183    void startOperation(int threadId, LoadedClass loadedClass, long time,
184            Operation.Type type) {
185        Operation o = new Operation(
186                this, loadedClass, time, operationCount++, type);
187        operations.add(o);
188
189        LinkedList<Operation> stack = stacks.get(threadId);
190        if (stack == null) {
191            stack = new LinkedList<Operation>();
192            stacks.put(threadId, stack);
193        }
194
195        if (!stack.isEmpty()) {
196            stack.getLast().subops.add(o);
197        }
198
199        stack.add(o);
200    }
201
202    /**
203     * Ends an operation.
204     *
205     * @param threadId thread the operation ended in
206     * @param loadedClass class operation happened to
207     * @param time the operation ended
208     */
209    Operation endOperation(int threadId, String className,
210            LoadedClass loadedClass, long time) {
211        LinkedList<Operation> stack = stacks.get(threadId);
212
213        if (stack == null || stack.isEmpty()) {
214            didNotStart(className);
215            return null;
216        }
217
218        Operation o = stack.getLast();
219        if (loadedClass != o.loadedClass) {
220            didNotStart(className);
221            return null;
222        }
223
224        stack.removeLast();
225
226        o.endTimeNanos = time;
227        return o;
228    }
229
230    /**
231     * Prints an error indicating that we saw the end of an operation but not
232     * the start. A bug in the logging framework which results in dropped logs
233     * causes this.
234     */
235    private static void didNotStart(String name) {
236        System.err.println("Warning: An operation ended on " + name
237            + " but it never started!");
238    }
239
240    /**
241     * Prints this process tree to stdout.
242     */
243    void print() {
244        print("");
245    }
246
247    /**
248     * Prints a child proc to standard out.
249     */
250    private void print(String prefix) {
251        System.out.println(prefix + "id=" + id + ", name=" + name);
252        for (Proc child : children) {
253            child.print(prefix + "    ");
254        }
255    }
256
257    @Override
258    public String toString() {
259        return this.name;
260    }
261}
262