Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 63ea81eb authored by Andreas Gampe's avatar Andreas Gampe Committed by android-build-merger
Browse files

Merge "Frameworks/base: New preload tool" am: f2c7983d

am: f2eb8b23

* commit 'f2eb8b23':
  Frameworks/base: New preload tool
parents 80dc34e3 f2eb8b23
Loading
Loading
Loading
Loading
+32 −0
Original line number Original line Diff line number Diff line
LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk

# To connect to devices (and take hprof dumps).
LOCAL_STATIC_JAVA_LIBRARIES := ddmlib-prebuilt

# To process hprof dumps.
LOCAL_STATIC_JAVA_LIBRARIES += perflib-prebuilt trove-prebuilt guavalib

# For JDWP access we use the framework in the JDWP tests from Apache Harmony, for
# convenience (and to not depend on internal JDK APIs).
LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit

LOCAL_MODULE:= preload2

include $(BUILD_HOST_JAVA_LIBRARY)

# Copy the preload-tool shell script to the host's bin directory.
include $(CLEAR_VARS)
LOCAL_IS_HOST_MODULE := true
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_CLASS := EXECUTABLES
LOCAL_MODULE := preload-tool
include $(BUILD_SYSTEM)/base_rules.mk
$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/preload-tool $(ACP)
	@echo "Copy: $(PRIVATE_MODULE) ($@)"
	$(copy-file-to-new-target)
	$(hide) chmod 755 $@
+37 −0
Original line number Original line Diff line number Diff line
# Copyright (C) 2015 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.

# This script is used on the host only. It uses a common subset
# shell dialect that should work well. It is partially derived
# from art/tools/art.

function follow_links() {
  if [ z"$BASH_SOURCE" != z ]; then
    file="$BASH_SOURCE"
  else
    file="$0"
  fi
  while [ -h "$file" ]; do
    # On Mac OS, readlink -f doesn't work.
    file="$(readlink "$file")"
  done
  echo "$file"
}


PROG_NAME="$(follow_links)"
PROG_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
ANDROID_ROOT=$PROG_DIR/..

java -cp $ANDROID_ROOT/framework/preload2.jar com.android.preload.Main
+224 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2015 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.
 */

package com.android.preload;

import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
import com.android.ddmlib.Client;
import com.android.ddmlib.IDevice;

/**
 * Helper class for common communication with a Client (the ddms name for a running application).
 *
 * Instances take a default timeout parameter that's applied to all functions without explicit
 * timeout. Timeouts are in milliseconds.
 */
public class ClientUtils {

    private int defaultTimeout;

    public ClientUtils() {
        this(10000);
    }

    public ClientUtils(int defaultTimeout) {
        this.defaultTimeout = defaultTimeout;
    }

    /**
     * Shortcut for findClient with default timeout.
     */
    public Client findClient(IDevice device, String processName, int processPid) {
        return findClient(device, processName, processPid, defaultTimeout);
    }

    /**
     * Find the client with the given process name or process id. The name takes precedence over
     * the process id (if valid). Stop looking after the given timeout.
     *
     * @param device The device to communicate with.
     * @param processName The name of the process. May be null.
     * @param processPid The pid of the process. Values less than or equal to zero are ignored.
     * @param timeout The amount of milliseconds to wait, at most.
     * @return The client, if found. Otherwise null.
     */
    public Client findClient(IDevice device, String processName, int processPid, int timeout) {
        WaitForClient wfc = new WaitForClient(device, processName, processPid, timeout);
        return wfc.get();
    }

    /**
     * Shortcut for findAllClients with default timeout.
     */
    public Client[] findAllClients(IDevice device) {
        return findAllClients(device, defaultTimeout);
    }

    /**
     * Retrieve all clients known to the given device. Wait at most the given timeout.
     *
     * @param device The device to investigate.
     * @param timeout The amount of milliseconds to wait, at most.
     * @return An array of clients running on the given device. May be null depending on the
     *         device implementation.
     */
    public Client[] findAllClients(IDevice device, int timeout) {
        if (device.hasClients()) {
            return device.getClients();
        }
        WaitForClients wfc = new WaitForClients(device, timeout);
        return wfc.get();
    }

    private static class WaitForClient implements IClientChangeListener {

        private IDevice device;
        private String processName;
        private int processPid;
        private long timeout;
        private Client result;

        public WaitForClient(IDevice device, String processName, int processPid, long timeout) {
            this.device = device;
            this.processName = processName;
            this.processPid = processPid;
            this.timeout = timeout;
            this.result = null;
        }

        public Client get() {
            synchronized (this) {
                AndroidDebugBridge.addClientChangeListener(this);

                // Maybe it's already there.
                if (result == null) {
                    result = searchForClient(device);
                }

                if (result == null) {
                    try {
                        wait(timeout);
                    } catch (InterruptedException e) {
                        // Note: doesn't guard for spurious wakeup.
                    }
                }
            }

            AndroidDebugBridge.removeClientChangeListener(this);
            return result;
        }

        private Client searchForClient(IDevice device) {
            if (processName != null) {
                Client tmp = device.getClient(processName);
                if (tmp != null) {
                    return tmp;
                }
            }
            if (processPid > 0) {
                String name = device.getClientName(processPid);
                if (name != null && !name.isEmpty()) {
                    Client tmp = device.getClient(name);
                    if (tmp != null) {
                        return tmp;
                    }
                }
            }
            if (processPid > 0) {
                // Try manual search.
                for (Client cl : device.getClients()) {
                    if (cl.getClientData().getPid() == processPid
                            && cl.getClientData().getClientDescription() != null) {
                        return cl;
                    }
                }
            }
            return null;
        }

        private boolean isTargetClient(Client c) {
            if (processPid > 0 && c.getClientData().getPid() == processPid) {
                return true;
            }
            if (processName != null
                    && processName.equals(c.getClientData().getClientDescription())) {
                return true;
            }
            return false;
        }

        @Override
        public void clientChanged(Client arg0, int arg1) {
            synchronized (this) {
                if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
                    if (isTargetClient(arg0)) {
                        result = arg0;
                        notifyAll();
                    }
                }
            }
        }
    }

    private static class WaitForClients implements IClientChangeListener {

        private IDevice device;
        private long timeout;

        public WaitForClients(IDevice device, long timeout) {
            this.device = device;
            this.timeout = timeout;
        }

        public Client[] get() {
            synchronized (this) {
                AndroidDebugBridge.addClientChangeListener(this);

                if (device.hasClients()) {
                    return device.getClients();
                }

                try {
                    wait(timeout); // Note: doesn't guard for spurious wakeup.
                } catch (InterruptedException exc) {
                }

                // We will be woken up when the first client data arrives. Sleep a little longer
                // to give (hopefully all of) the rest of the clients a chance to become available.
                // Note: a loop with timeout is brittle as well and complicated, just accept this
                //       for now.
                try {
                    Thread.sleep(500);
                } catch (InterruptedException exc) {
                }
            }

            AndroidDebugBridge.removeClientChangeListener(this);

            return device.getClients();
        }

        @Override
        public void clientChanged(Client arg0, int arg1) {
            synchronized (this) {
                if ((arg1 & Client.CHANGE_INFO) != 0 && (arg0.getDevice() == device)) {
                    notifyAll();
                }
            }
        }
    }
}
+390 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2015 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.
 */

package com.android.preload;

import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.preload.classdataretrieval.hprof.Hprof;
import com.android.ddmlib.DdmPreferences;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.IShellOutputReceiver;

import java.util.Date;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * Helper class for some device routines.
 */
public class DeviceUtils {

  public static void init(int debugPort) {
    DdmPreferences.setSelectedDebugPort(debugPort);

    Hprof.init();

    AndroidDebugBridge.init(true);

    AndroidDebugBridge.createBridge();
  }

  /**
   * Run a command in the shell on the device.
   */
  public static void doShell(IDevice device, String cmdline, long timeout, TimeUnit unit) {
    doShell(device, cmdline, new NullShellOutputReceiver(), timeout, unit);
  }

  /**
   * Run a command in the shell on the device. Collects and returns the console output.
   */
  public static String doShellReturnString(IDevice device, String cmdline, long timeout,
      TimeUnit unit) {
    CollectStringShellOutputReceiver rec = new CollectStringShellOutputReceiver();
    doShell(device, cmdline, rec, timeout, unit);
    return rec.toString();
  }

  /**
   * Run a command in the shell on the device, directing all output to the given receiver.
   */
  public static void doShell(IDevice device, String cmdline, IShellOutputReceiver receiver,
      long timeout, TimeUnit unit) {
    try {
      device.executeShellCommand(cmdline, receiver, timeout, unit);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Run am start on the device.
   */
  public static void doAMStart(IDevice device, String name, String activity) {
    doShell(device, "am start -n " + name + " /." + activity, 30, TimeUnit.SECONDS);
  }

  /**
   * Find the device with the given serial. Give up after the given timeout (in milliseconds).
   */
  public static IDevice findDevice(String serial, int timeout) {
    WaitForDevice wfd = new WaitForDevice(serial, timeout);
    return wfd.get();
  }

  /**
   * Get all devices ddms knows about. Wait at most for the given timeout.
   */
  public static IDevice[] findDevices(int timeout) {
    WaitForDevice wfd = new WaitForDevice(null, timeout);
    wfd.get();
    return AndroidDebugBridge.getBridge().getDevices();
  }

  /**
   * Return the build type of the given device. This is the value of the "ro.build.type"
   * system property.
   */
  public static String getBuildType(IDevice device) {
    try {
      Future<String> buildType = device.getSystemProperty("ro.build.type");
      return buildType.get(500, TimeUnit.MILLISECONDS);
    } catch (Exception e) {
    }
    return null;
  }

  /**
   * Check whether the given device has a pre-optimized boot image. More precisely, checks
   * whether /system/framework/ * /boot.art exists.
   */
  public static boolean hasPrebuiltBootImage(IDevice device) {
    String ret =
        doShellReturnString(device, "ls /system/framework/*/boot.art", 500, TimeUnit.MILLISECONDS);

    return !ret.contains("No such file or directory");
  }

  /**
   * Remove files involved in a standard build that interfere with collecting data. This will
   * remove /etc/preloaded-classes, which determines which classes are allocated already in the
   * boot image. It also deletes any compiled boot image on the device. Then it restarts the
   * device.
   *
   * This is a potentially long-running operation, as the boot after the deletion may take a while.
   * The method will abort after the given timeout.
   */
  public static boolean removePreloaded(IDevice device, long preloadedWaitTimeInSeconds) {
    String oldContent =
        DeviceUtils.doShellReturnString(device, "cat /etc/preloaded-classes", 1, TimeUnit.SECONDS);
    if (oldContent.trim().equals("")) {
      System.out.println("Preloaded-classes already empty.");
      return true;
    }

    // Stop the system server etc.
    doShell(device, "stop", 100, TimeUnit.MILLISECONDS);

    // Remount /system, delete /etc/preloaded-classes. It would be nice to use "adb remount,"
    // but AndroidDebugBridge doesn't expose it.
    doShell(device, "mount -o remount,rw /system", 500, TimeUnit.MILLISECONDS);
    doShell(device, "rm /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);
    // We do need an empty file.
    doShell(device, "touch /etc/preloaded-classes", 100, TimeUnit.MILLISECONDS);

    // Delete the files in the dalvik cache.
    doShell(device, "rm /data/dalvik-cache/*/*boot.art", 500, TimeUnit.MILLISECONDS);

    // We'll try to use dev.bootcomplete to know when the system server is back up. But stop
    // doesn't reset it, so do it manually.
    doShell(device, "setprop dev.bootcomplete \"0\"", 500, TimeUnit.MILLISECONDS);

    // Start the system server.
    doShell(device, "start", 100, TimeUnit.MILLISECONDS);

    // Do a loop checking each second whether bootcomplete. Wait for at most the given
    // threshold.
    Date startDate = new Date();
    for (;;) {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        // Ignore spurious wakeup.
      }
      // Check whether bootcomplete.
      String ret =
          doShellReturnString(device, "getprop dev.bootcomplete", 500, TimeUnit.MILLISECONDS);
      if (ret.trim().equals("1")) {
        break;
      }
      System.out.println("Still not booted: " + ret);

      // Check whether we timed out. This is a simplistic check that doesn't take into account
      // things like switches in time.
      Date endDate = new Date();
      long seconds =
          TimeUnit.SECONDS.convert(endDate.getTime() - startDate.getTime(), TimeUnit.MILLISECONDS);
      if (seconds > preloadedWaitTimeInSeconds) {
        return false;
      }
    }

    return true;
  }

  /**
   * Enable method-tracing on device. The system should be restarted after this.
   */
  public static void enableTracing(IDevice device) {
    // Disable selinux.
    doShell(device, "setenforce 0", 100, TimeUnit.MILLISECONDS);

    // Make the profile directory world-writable.
    doShell(device, "chmod 777 /data/dalvik-cache/profiles", 100, TimeUnit.MILLISECONDS);

    // Enable streaming method tracing with a small 1K buffer.
    doShell(device, "setprop dalvik.vm.method-trace true", 100, TimeUnit.MILLISECONDS);
    doShell(device, "setprop dalvik.vm.method-trace-file "
                    + "/data/dalvik-cache/profiles/zygote.trace.bin", 100, TimeUnit.MILLISECONDS);
    doShell(device, "setprop dalvik.vm.method-trace-file-siz 1024", 100, TimeUnit.MILLISECONDS);
    doShell(device, "setprop dalvik.vm.method-trace-stream true", 100, TimeUnit.MILLISECONDS);
  }

  private static class NullShellOutputReceiver implements IShellOutputReceiver {
    @Override
    public boolean isCancelled() {
      return false;
    }

    @Override
    public void flush() {}

    @Override
    public void addOutput(byte[] arg0, int arg1, int arg2) {}
  }

  private static class CollectStringShellOutputReceiver implements IShellOutputReceiver {

    private StringBuilder builder = new StringBuilder();

    @Override
    public String toString() {
      String ret = builder.toString();
      // Strip trailing newlines. They are especially ugly because adb uses DOS line endings.
      while (ret.endsWith("\r") || ret.endsWith("\n")) {
        ret = ret.substring(0, ret.length() - 1);
      }
      return ret;
    }

    @Override
    public void addOutput(byte[] arg0, int arg1, int arg2) {
      builder.append(new String(arg0, arg1, arg2));
    }

    @Override
    public void flush() {}

    @Override
    public boolean isCancelled() {
      return false;
    }
  }

  private static class WaitForDevice {

    private String serial;
    private long timeout;
    private IDevice device;

    public WaitForDevice(String serial, long timeout) {
      this.serial = serial;
      this.timeout = timeout;
      device = null;
    }

    public IDevice get() {
      if (device == null) {
          WaitForDeviceListener wfdl = new WaitForDeviceListener(serial);
          synchronized (wfdl) {
              AndroidDebugBridge.addDeviceChangeListener(wfdl);

              // Check whether we already know about this device.
              IDevice[] devices = AndroidDebugBridge.getBridge().getDevices();
              if (serial != null) {
                  for (IDevice d : devices) {
                      if (serial.equals(d.getSerialNumber())) {
                          // Only accept if there are clients already. Else wait for the callback informing
                          // us that we now have clients.
                          if (d.hasClients()) {
                              device = d;
                          }

                          break;
                      }
                  }
              } else {
                  if (devices.length > 0) {
                      device = devices[0];
                  }
              }

              if (device == null) {
                  try {
                      wait(timeout);
                  } catch (InterruptedException e) {
                      // Ignore spurious wakeups.
                  }
                  device = wfdl.getDevice();
              }

              AndroidDebugBridge.removeDeviceChangeListener(wfdl);
          }
      }

      if (device != null) {
          // Wait for clients.
          WaitForClientsListener wfcl = new WaitForClientsListener(device);
          synchronized (wfcl) {
              AndroidDebugBridge.addDeviceChangeListener(wfcl);

              if (!device.hasClients()) {
                  try {
                      wait(timeout);
                  } catch (InterruptedException e) {
                      // Ignore spurious wakeups.
                  }
              }

              AndroidDebugBridge.removeDeviceChangeListener(wfcl);
          }
      }

      return device;
    }

    private static class WaitForDeviceListener implements IDeviceChangeListener {

        private String serial;
        private IDevice device;

        public WaitForDeviceListener(String serial) {
            this.serial = serial;
        }

        public IDevice getDevice() {
            return device;
        }

        @Override
        public void deviceChanged(IDevice arg0, int arg1) {
            // We may get a device changed instead of connected. Handle like a connection.
            deviceConnected(arg0);
        }

        @Override
        public void deviceConnected(IDevice arg0) {
            if (device != null) {
                // Ignore updates.
                return;
            }

            if (serial == null || serial.equals(arg0.getSerialNumber())) {
                device = arg0;
                synchronized (this) {
                    notifyAll();
                }
            }
        }

        @Override
        public void deviceDisconnected(IDevice arg0) {
            // Ignore disconnects.
        }

    }

    private static class WaitForClientsListener implements IDeviceChangeListener {

        private IDevice myDevice;

        public WaitForClientsListener(IDevice myDevice) {
            this.myDevice = myDevice;
        }

        @Override
        public void deviceChanged(IDevice arg0, int arg1) {
            if (arg0 == myDevice && (arg1 & IDevice.CHANGE_CLIENT_LIST) != 0) {
                // Got a client list, done here.
                synchronized (this) {
                    notifyAll();
                }
            }
        }

        @Override
        public void deviceConnected(IDevice arg0) {
        }

        @Override
        public void deviceDisconnected(IDevice arg0) {
        }

    }
  }

}
+91 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2015 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.
 */

package com.android.preload;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Holds the collected data for a process.
 */
public class DumpData {
    /**
     * Name of the package (=application).
     */
    String packageName;

    /**
     * A map of class name to a string for the classloader. This may be a toString equivalent,
     * or just a unique ID.
     */
    Map<String, String> dumpData;

    /**
     * The Date when this data was captured. Mostly for display purposes.
     */
    Date date;

    /**
     * A cached value for the number of boot classpath classes (classloader value in dumpData is
     * null).
     */
    int bcpClasses;

    public DumpData(String packageName, Map<String, String> dumpData, Date date) {
        this.packageName = packageName;
        this.dumpData = dumpData;
        this.date = date;

        countBootClassPath();
    }

    public String getPackageName() {
        return packageName;
    }

    public Date getDate() {
        return date;
    }

    public Map<String, String> getDumpData() {
        return dumpData;
    }

    public void countBootClassPath() {
        bcpClasses = 0;
        for (Map.Entry<String, String> e : dumpData.entrySet()) {
            if (e.getValue() == null) {
                bcpClasses++;
            }
        }
    }

    // Return an inverted mapping.
    public Map<String, Set<String>> invertData() {
        Map<String, Set<String>> ret = new HashMap<>();
        for (Map.Entry<String, String> e : dumpData.entrySet()) {
            if (!ret.containsKey(e.getValue())) {
                ret.put(e.getValue(), new HashSet<String>());
            }
            ret.get(e.getValue()).add(e.getKey());
        }
        return ret;
    }
}
 No newline at end of file
Loading