MemoryUsage.java revision d24b8183b93e781080b2c16c487e60d51c12da31
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.io.Serializable;
18import java.io.IOException;
19import java.io.BufferedReader;
20import java.io.InputStreamReader;
21import java.io.InputStream;
22import java.io.OutputStream;
23import java.util.List;
24import java.util.ArrayList;
25import java.util.Arrays;
26
27/**
28 * Memory usage information.
29 */
30class MemoryUsage implements Serializable {
31
32    private static final long serialVersionUID = 0;
33
34    static final MemoryUsage NOT_AVAILABLE = new MemoryUsage();
35
36    static int errorCount = 0;
37    static final int MAXIMUM_ERRORS = 10;        // give up after this many fails
38
39    final int nativeSharedPages;
40    final int javaSharedPages;
41    final int otherSharedPages;
42    final int nativePrivatePages;
43    final int javaPrivatePages;
44    final int otherPrivatePages;
45
46    final int allocCount;
47    final int allocSize;
48    final int freedCount;
49    final int freedSize;
50    final long nativeHeapSize;
51
52    public MemoryUsage(String line) {
53        String[] parsed = line.split(",");
54
55        nativeSharedPages = Integer.parseInt(parsed[1]);
56        javaSharedPages = Integer.parseInt(parsed[2]);
57        otherSharedPages = Integer.parseInt(parsed[3]);
58        nativePrivatePages = Integer.parseInt(parsed[4]);
59        javaPrivatePages = Integer.parseInt(parsed[5]);
60        otherPrivatePages = Integer.parseInt(parsed[6]);
61        allocCount = Integer.parseInt(parsed[7]);
62        allocSize = Integer.parseInt(parsed[8]);
63        freedCount = Integer.parseInt(parsed[9]);
64        freedSize = Integer.parseInt(parsed[10]);
65        nativeHeapSize = Long.parseLong(parsed[11]);
66    }
67
68    MemoryUsage() {
69        nativeSharedPages = -1;
70        javaSharedPages = -1;
71        otherSharedPages = -1;
72        nativePrivatePages = -1;
73        javaPrivatePages = -1;
74        otherPrivatePages = -1;
75
76        allocCount = -1;
77        allocSize = -1;
78        freedCount = -1;
79        freedSize = -1;
80        nativeHeapSize = -1;
81    }
82
83    MemoryUsage(int nativeSharedPages,
84            int javaSharedPages,
85            int otherSharedPages,
86            int nativePrivatePages,
87            int javaPrivatePages,
88            int otherPrivatePages,
89            int allocCount,
90            int allocSize,
91            int freedCount,
92            int freedSize,
93            long nativeHeapSize) {
94        this.nativeSharedPages = nativeSharedPages;
95        this.javaSharedPages = javaSharedPages;
96        this.otherSharedPages = otherSharedPages;
97        this.nativePrivatePages = nativePrivatePages;
98        this.javaPrivatePages = javaPrivatePages;
99        this.otherPrivatePages = otherPrivatePages;
100        this.allocCount = allocCount;
101        this.allocSize = allocSize;
102        this.freedCount = freedCount;
103        this.freedSize = freedSize;
104        this.nativeHeapSize = nativeHeapSize;
105    }
106
107    MemoryUsage subtract(MemoryUsage baseline) {
108        return new MemoryUsage(
109                nativeSharedPages - baseline.nativeSharedPages,
110                javaSharedPages - baseline.javaSharedPages,
111                otherSharedPages - baseline.otherSharedPages,
112                nativePrivatePages - baseline.nativePrivatePages,
113                javaPrivatePages - baseline.javaPrivatePages,
114                otherPrivatePages - baseline.otherPrivatePages,
115                allocCount - baseline.allocCount,
116                allocSize - baseline.allocSize,
117                freedCount - baseline.freedCount,
118                freedSize - baseline.freedSize,
119                nativeHeapSize - baseline.nativeHeapSize);
120    }
121
122    int javaHeapSize() {
123        return allocSize - freedSize;
124    }
125
126    int javaPagesInK() {
127        return (javaSharedPages + javaPrivatePages) * 4;
128    }
129
130    int nativePagesInK() {
131        return (nativeSharedPages + nativePrivatePages) * 4;
132    }
133    int otherPagesInK() {
134        return (otherSharedPages + otherPrivatePages) * 4;
135    }
136
137    /**
138     * Was this information available?
139     */
140    boolean isAvailable() {
141        return nativeSharedPages != -1;
142    }
143
144    /**
145     * Measures baseline memory usage.
146     */
147    static MemoryUsage baseline() {
148        return forClass(null);
149    }
150
151    private static final String CLASS_PATH = "-Xbootclasspath"
152            + ":/system/framework/core.jar"
153            + ":/system/framework/ext.jar"
154            + ":/system/framework/framework.jar"
155            + ":/system/framework/framework-tests.jar"
156            + ":/system/framework/services.jar"
157            + ":/system/framework/loadclass.jar";
158
159    private static final String[] GET_DIRTY_PAGES = {
160        "adb", "-e", "shell", "dalvikvm", CLASS_PATH, "LoadClass" };
161
162    /**
163     * Measures memory usage for the given class.
164     */
165    static MemoryUsage forClass(String className) {
166
167        // This is a coarse approximation for determining that no device is connected,
168        // or that the communication protocol has changed, but we'll keep going and stop whining.
169        if (errorCount >= MAXIMUM_ERRORS) {
170            return NOT_AVAILABLE;
171        }
172
173        MeasureWithTimeout measurer = new MeasureWithTimeout(className);
174
175        new Thread(measurer).start();
176
177        synchronized (measurer) {
178            if (measurer.memoryUsage == null) {
179                // Wait up to 10s.
180                try {
181                    measurer.wait(30000);
182                } catch (InterruptedException e) {
183                    System.err.println("Interrupted waiting for measurement.");
184                    e.printStackTrace();
185                    return NOT_AVAILABLE;
186                }
187
188                // If it's still null.
189                if (measurer.memoryUsage == null) {
190                    System.err.println("Timed out while measuring "
191                            + className + ".");
192                    return NOT_AVAILABLE;
193                }
194            }
195
196            System.err.println("Got memory usage for " + className + ".");
197            return measurer.memoryUsage;
198        }
199    }
200
201    static class MeasureWithTimeout implements Runnable {
202
203        final String className;
204        MemoryUsage memoryUsage = null;
205
206        MeasureWithTimeout(String className) {
207            this.className = className;
208        }
209
210        public void run() {
211            MemoryUsage measured = measure();
212
213            synchronized (this) {
214                memoryUsage = measured;
215                notifyAll();
216            }
217        }
218
219        private MemoryUsage measure() {
220            String[] commands = GET_DIRTY_PAGES;
221            if (className != null) {
222                List<String> commandList = new ArrayList<String>(
223                        GET_DIRTY_PAGES.length + 1);
224                commandList.addAll(Arrays.asList(commands));
225                commandList.add(className);
226                commands = commandList.toArray(new String[commandList.size()]);
227            }
228
229            try {
230                final Process process = Runtime.getRuntime().exec(commands);
231
232                final InputStream err = process.getErrorStream();
233
234                // Send error output to stderr.
235                Thread errThread = new Thread() {
236                    @Override
237                    public void run() {
238                        copy(err, System.err);
239                    }
240                };
241                errThread.setDaemon(true);
242                errThread.start();
243
244                BufferedReader in = new BufferedReader(
245                        new InputStreamReader(process.getInputStream()));
246                String line = in.readLine();
247                if (line == null || !line.startsWith("DECAFBAD,")) {
248                    System.err.println("Got bad response for " + className
249                            + ": " + line);
250                    errorCount += 1;
251                    return NOT_AVAILABLE;
252                }
253
254                in.close();
255                err.close();
256                process.destroy();
257
258                return new MemoryUsage(line);
259            } catch (IOException e) {
260                System.err.println("Error getting stats for "
261                        + className + ".");
262                e.printStackTrace();
263                return NOT_AVAILABLE;
264            }
265        }
266
267    }
268
269    /**
270     * Copies from one stream to another.
271     */
272    private static void copy(InputStream in, OutputStream out) {
273        byte[] buffer = new byte[1024];
274        int read;
275        try {
276            while ((read = in.read(buffer)) > -1) {
277                out.write(buffer, 0, read);
278            }
279        } catch (IOException e) {
280            e.printStackTrace();
281        }
282    }
283}
284