/* * 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; // These values are in 1kB increments (not 4kB like you'd expect). 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 totalHeap() { return javaHeapSize() + (int) nativeHeapSize; } int javaPagesInK() { return javaSharedPages + javaPrivatePages; } int nativePagesInK() { return nativeSharedPages + nativePrivatePages; } int otherPagesInK() { return otherSharedPages + otherPrivatePages; } int totalPages() { return javaSharedPages + javaPrivatePages + nativeSharedPages + nativePrivatePages + otherSharedPages + otherPrivatePages; } /** * 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", "shell", "dalvikvm", CLASS_PATH, "LoadClass" }; /** * Measures memory usage for the given class. */ static MemoryUsage forClass(String className) { 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 commandList = new ArrayList( 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 + "; command was " + Arrays.toString(commands)); 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(); } } /** Measures memory usage information and stores it in the model. */ public static void main(String[] args) throws IOException, ClassNotFoundException { Root root = Root.fromFile(args[0]); root.baseline = baseline(); for (LoadedClass loadedClass : root.loadedClasses.values()) { if (loadedClass.systemClass) { loadedClass.measureMemoryUsage(); } } root.toFile(args[0]); } }