| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import java.io.Serializable; |
| import java.io.IOException; |
| import java.io.BufferedReader; |
| import java.io.InputStreamReader; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.List; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| |
| /** |
| * Memory usage information. |
| */ |
| class MemoryUsage implements Serializable { |
| |
| private static final long serialVersionUID = 0; |
| |
| static final MemoryUsage NOT_AVAILABLE = new MemoryUsage(); |
| |
| static int errorCount = 0; |
| static final int MAXIMUM_ERRORS = 10; // give up after this many fails |
| |
| final int nativeSharedPages; |
| final int javaSharedPages; |
| final int otherSharedPages; |
| final int nativePrivatePages; |
| final int javaPrivatePages; |
| final int otherPrivatePages; |
| |
| final int allocCount; |
| final int allocSize; |
| final int freedCount; |
| final int freedSize; |
| final long nativeHeapSize; |
| |
| public MemoryUsage(String line) { |
| String[] parsed = line.split(","); |
| |
| nativeSharedPages = Integer.parseInt(parsed[1]); |
| javaSharedPages = Integer.parseInt(parsed[2]); |
| otherSharedPages = Integer.parseInt(parsed[3]); |
| nativePrivatePages = Integer.parseInt(parsed[4]); |
| javaPrivatePages = Integer.parseInt(parsed[5]); |
| otherPrivatePages = Integer.parseInt(parsed[6]); |
| allocCount = Integer.parseInt(parsed[7]); |
| allocSize = Integer.parseInt(parsed[8]); |
| freedCount = Integer.parseInt(parsed[9]); |
| freedSize = Integer.parseInt(parsed[10]); |
| nativeHeapSize = Long.parseLong(parsed[11]); |
| } |
| |
| MemoryUsage() { |
| nativeSharedPages = -1; |
| javaSharedPages = -1; |
| otherSharedPages = -1; |
| nativePrivatePages = -1; |
| javaPrivatePages = -1; |
| otherPrivatePages = -1; |
| |
| allocCount = -1; |
| allocSize = -1; |
| freedCount = -1; |
| freedSize = -1; |
| nativeHeapSize = -1; |
| } |
| |
| MemoryUsage(int nativeSharedPages, |
| int javaSharedPages, |
| int otherSharedPages, |
| int nativePrivatePages, |
| int javaPrivatePages, |
| int otherPrivatePages, |
| int allocCount, |
| int allocSize, |
| int freedCount, |
| int freedSize, |
| long nativeHeapSize) { |
| this.nativeSharedPages = nativeSharedPages; |
| this.javaSharedPages = javaSharedPages; |
| this.otherSharedPages = otherSharedPages; |
| this.nativePrivatePages = nativePrivatePages; |
| this.javaPrivatePages = javaPrivatePages; |
| this.otherPrivatePages = otherPrivatePages; |
| this.allocCount = allocCount; |
| this.allocSize = allocSize; |
| this.freedCount = freedCount; |
| this.freedSize = freedSize; |
| this.nativeHeapSize = nativeHeapSize; |
| } |
| |
| MemoryUsage subtract(MemoryUsage baseline) { |
| return new MemoryUsage( |
| nativeSharedPages - baseline.nativeSharedPages, |
| javaSharedPages - baseline.javaSharedPages, |
| otherSharedPages - baseline.otherSharedPages, |
| nativePrivatePages - baseline.nativePrivatePages, |
| javaPrivatePages - baseline.javaPrivatePages, |
| otherPrivatePages - baseline.otherPrivatePages, |
| allocCount - baseline.allocCount, |
| allocSize - baseline.allocSize, |
| freedCount - baseline.freedCount, |
| freedSize - baseline.freedSize, |
| nativeHeapSize - baseline.nativeHeapSize); |
| } |
| |
| int javaHeapSize() { |
| return allocSize - freedSize; |
| } |
| |
| int javaPagesInK() { |
| return (javaSharedPages + javaPrivatePages) * 4; |
| } |
| |
| int nativePagesInK() { |
| return (nativeSharedPages + nativePrivatePages) * 4; |
| } |
| int otherPagesInK() { |
| return (otherSharedPages + otherPrivatePages) * 4; |
| } |
| |
| /** |
| * Was this information available? |
| */ |
| boolean isAvailable() { |
| return nativeSharedPages != -1; |
| } |
| |
| /** |
| * Measures baseline memory usage. |
| */ |
| static MemoryUsage baseline() { |
| return forClass(null); |
| } |
| |
| private static final String CLASS_PATH = "-Xbootclasspath" |
| + ":/system/framework/core.jar" |
| + ":/system/framework/ext.jar" |
| + ":/system/framework/framework.jar" |
| + ":/system/framework/framework-tests.jar" |
| + ":/system/framework/services.jar" |
| + ":/system/framework/loadclass.jar"; |
| |
| private static final String[] GET_DIRTY_PAGES = { |
| "adb", "-e", "shell", "dalvikvm", CLASS_PATH, "LoadClass" }; |
| |
| /** |
| * Measures memory usage for the given class. |
| */ |
| static MemoryUsage forClass(String className) { |
| |
| // This is a coarse approximation for determining that no device is connected, |
| // or that the communication protocol has changed, but we'll keep going and stop whining. |
| if (errorCount >= MAXIMUM_ERRORS) { |
| return NOT_AVAILABLE; |
| } |
| |
| MeasureWithTimeout measurer = new MeasureWithTimeout(className); |
| |
| new Thread(measurer).start(); |
| |
| synchronized (measurer) { |
| if (measurer.memoryUsage == null) { |
| // Wait up to 10s. |
| try { |
| measurer.wait(30000); |
| } catch (InterruptedException e) { |
| System.err.println("Interrupted waiting for measurement."); |
| e.printStackTrace(); |
| return NOT_AVAILABLE; |
| } |
| |
| // If it's still null. |
| if (measurer.memoryUsage == null) { |
| System.err.println("Timed out while measuring " |
| + className + "."); |
| return NOT_AVAILABLE; |
| } |
| } |
| |
| System.err.println("Got memory usage for " + className + "."); |
| return measurer.memoryUsage; |
| } |
| } |
| |
| static class MeasureWithTimeout implements Runnable { |
| |
| final String className; |
| MemoryUsage memoryUsage = null; |
| |
| MeasureWithTimeout(String className) { |
| this.className = className; |
| } |
| |
| public void run() { |
| MemoryUsage measured = measure(); |
| |
| synchronized (this) { |
| memoryUsage = measured; |
| notifyAll(); |
| } |
| } |
| |
| private MemoryUsage measure() { |
| String[] commands = GET_DIRTY_PAGES; |
| if (className != null) { |
| List<String> commandList = new ArrayList<String>( |
| GET_DIRTY_PAGES.length + 1); |
| commandList.addAll(Arrays.asList(commands)); |
| commandList.add(className); |
| commands = commandList.toArray(new String[commandList.size()]); |
| } |
| |
| try { |
| final Process process = Runtime.getRuntime().exec(commands); |
| |
| final InputStream err = process.getErrorStream(); |
| |
| // Send error output to stderr. |
| Thread errThread = new Thread() { |
| @Override |
| public void run() { |
| copy(err, System.err); |
| } |
| }; |
| errThread.setDaemon(true); |
| errThread.start(); |
| |
| BufferedReader in = new BufferedReader( |
| new InputStreamReader(process.getInputStream())); |
| String line = in.readLine(); |
| if (line == null || !line.startsWith("DECAFBAD,")) { |
| System.err.println("Got bad response for " + className |
| + ": " + line); |
| errorCount += 1; |
| return NOT_AVAILABLE; |
| } |
| |
| in.close(); |
| err.close(); |
| process.destroy(); |
| |
| return new MemoryUsage(line); |
| } catch (IOException e) { |
| System.err.println("Error getting stats for " |
| + className + "."); |
| e.printStackTrace(); |
| return NOT_AVAILABLE; |
| } |
| } |
| |
| } |
| |
| /** |
| * Copies from one stream to another. |
| */ |
| private static void copy(InputStream in, OutputStream out) { |
| byte[] buffer = new byte[1024]; |
| int read; |
| try { |
| while ((read = in.read(buffer)) > -1) { |
| out.write(buffer, 0, read); |
| } |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |