content: /*
   * 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]);
      }
  }
  