| /* | 
 |  * 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<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 + "; 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]); | 
 |     } | 
 | } |