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
38    // These values are in 1kB increments (not 4kB like you'd expect).
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 totalHeap() {
127        return javaHeapSize() + (int) nativeHeapSize;
128    }
129
130    int javaPagesInK() {
131        return javaSharedPages + javaPrivatePages;
132    }
133
134    int nativePagesInK() {
135        return nativeSharedPages + nativePrivatePages;
136    }
137    int otherPagesInK() {
138        return otherSharedPages + otherPrivatePages;
139    }
140
141    int totalPages() {
142        return javaSharedPages + javaPrivatePages + nativeSharedPages +
143                nativePrivatePages + otherSharedPages + otherPrivatePages;
144    }
145
146    /**
147     * Was this information available?
148     */
149    boolean isAvailable() {
150        return nativeSharedPages != -1;
151    }
152
153    /**
154     * Measures baseline memory usage.
155     */
156    static MemoryUsage baseline() {
157        return forClass(null);
158    }
159
160    private static final String CLASS_PATH = "-Xbootclasspath"
161            + ":/system/framework/core.jar"
162            + ":/system/framework/ext.jar"
163            + ":/system/framework/framework.jar"
164            + ":/system/framework/framework-tests.jar"
165            + ":/system/framework/services.jar"
166            + ":/system/framework/loadclass.jar";
167
168    private static final String[] GET_DIRTY_PAGES = {
169        "adb", "shell", "dalvikvm", CLASS_PATH, "LoadClass" };
170
171    /**
172     * Measures memory usage for the given class.
173     */
174    static MemoryUsage forClass(String className) {
175        MeasureWithTimeout measurer = new MeasureWithTimeout(className);
176
177        new Thread(measurer).start();
178
179        synchronized (measurer) {
180            if (measurer.memoryUsage == null) {
181                // Wait up to 10s.
182                try {
183                    measurer.wait(30000);
184                } catch (InterruptedException e) {
185                    System.err.println("Interrupted waiting for measurement.");
186                    e.printStackTrace();
187                    return NOT_AVAILABLE;
188                }
189
190                // If it's still null.
191                if (measurer.memoryUsage == null) {
192                    System.err.println("Timed out while measuring "
193                            + className + ".");
194                    return NOT_AVAILABLE;
195                }
196            }
197
198            System.err.println("Got memory usage for " + className + ".");
199            return measurer.memoryUsage;
200        }
201    }
202
203    static class MeasureWithTimeout implements Runnable {
204
205        final String className;
206        MemoryUsage memoryUsage = null;
207
208        MeasureWithTimeout(String className) {
209            this.className = className;
210        }
211
212        public void run() {
213            MemoryUsage measured = measure();
214
215            synchronized (this) {
216                memoryUsage = measured;
217                notifyAll();
218            }
219        }
220
221        private MemoryUsage measure() {
222            String[] commands = GET_DIRTY_PAGES;
223            if (className != null) {
224                List<String> commandList = new ArrayList<String>(
225                        GET_DIRTY_PAGES.length + 1);
226                commandList.addAll(Arrays.asList(commands));
227                commandList.add(className);
228                commands = commandList.toArray(new String[commandList.size()]);
229            }
230
231            try {
232                final Process process = Runtime.getRuntime().exec(commands);
233
234                final InputStream err = process.getErrorStream();
235
236                // Send error output to stderr.
237                Thread errThread = new Thread() {
238                    @Override
239                    public void run() {
240                        copy(err, System.err);
241                    }
242                };
243                errThread.setDaemon(true);
244                errThread.start();
245
246                BufferedReader in = new BufferedReader(
247                        new InputStreamReader(process.getInputStream()));
248                String line = in.readLine();
249                if (line == null || !line.startsWith("DECAFBAD,")) {
250                    System.err.println("Got bad response for " + className
251                            + ": " + line + "; command was " + Arrays.toString(commands));
252                    errorCount += 1;
253                    return NOT_AVAILABLE;
254                }
255
256                in.close();
257                err.close();
258                process.destroy();
259
260                return new MemoryUsage(line);
261            } catch (IOException e) {
262                System.err.println("Error getting stats for "
263                        + className + ".");
264                e.printStackTrace();
265                return NOT_AVAILABLE;
266            }
267        }
268
269    }
270
271    /**
272     * Copies from one stream to another.
273     */
274    private static void copy(InputStream in, OutputStream out) {
275        byte[] buffer = new byte[1024];
276        int read;
277        try {
278            while ((read = in.read(buffer)) > -1) {
279                out.write(buffer, 0, read);
280            }
281        } catch (IOException e) {
282            e.printStackTrace();
283        }
284    }
285
286    /** Measures memory usage information and stores it in the model. */
287    public static void main(String[] args) throws IOException,
288            ClassNotFoundException {
289        Root root = Root.fromFile(args[0]);
290        root.baseline = baseline();
291        for (LoadedClass loadedClass : root.loadedClasses.values()) {
292            if (loadedClass.systemClass) {
293                loadedClass.measureMemoryUsage();
294            }
295        }
296        root.toFile(args[0]);
297    }
298}
299