From d767032c5a9a7d0d48f86ff478e7aac00ec88f40 Mon Sep 17 00:00:00 2001 From: jdesprez Date: Wed, 30 Aug 2017 14:12:28 -0700 Subject: Create a system-server heap dump utility In order to investigate some issues collecting heap dump is quite useful. Making a basic utility that can be improved later for this. Test: unit tests Bug: 64826998 Change-Id: I0948115094a397a10053036e7af00c94df42501a --- src/com/android/tradefed/device/INativeDevice.java | 3 ++ src/com/android/tradefed/device/ITestDevice.java | 12 ++++++++ src/com/android/tradefed/device/NativeDevice.java | 30 ++++++++++++++++++- src/com/android/tradefed/device/TestDevice.java | 35 ++++++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/com/android/tradefed/device/INativeDevice.java b/src/com/android/tradefed/device/INativeDevice.java index 87c5bad23..388b52bc4 100644 --- a/src/com/android/tradefed/device/INativeDevice.java +++ b/src/com/android/tradefed/device/INativeDevice.java @@ -1085,4 +1085,7 @@ public interface INativeDevice { * @return ProcessInfo of given processName */ public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException; + + /** Returns the pid of the service or null if something went wrong. */ + public String getProcessPid(String process) throws DeviceNotAvailableException; } diff --git a/src/com/android/tradefed/device/ITestDevice.java b/src/com/android/tradefed/device/ITestDevice.java index cbc8c7c3b..746b2c6df 100644 --- a/src/com/android/tradefed/device/ITestDevice.java +++ b/src/com/android/tradefed/device/ITestDevice.java @@ -621,4 +621,16 @@ public interface ITestDevice extends INativeDevice { * on non-secure ones only) */ public void disableKeyguard() throws DeviceNotAvailableException; + + /** + * Attempt to dump the heap from the system_server. It is the caller responsibility to clean up + * the dumped file. + * + * @param process the name of the device process to dumpheap on. + * @param devicePath the path on the device where to put the dump. This must be a location where + * permissions allow it. + * @return the {@link File} containing the report. Null if something failed. + * @throws DeviceNotAvailableException + */ + public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException; } diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java index 5c59d5bab..0c9221c9f 100644 --- a/src/com/android/tradefed/device/NativeDevice.java +++ b/src/com/android/tradefed/device/NativeDevice.java @@ -156,7 +156,6 @@ public class NativeDevice implements IManagedTestDevice { static final String MAC_ADDRESS_PATTERN = "([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}"; static final String MAC_ADDRESS_COMMAND = "su root cat /sys/class/net/wlan0/address"; - /** The network monitoring interval in ms. */ private static final int NETWORK_MONITOR_INTERVAL = 10 * 1000; @@ -3831,4 +3830,33 @@ public class NativeDevice implements IManagedTestDevice { return null; } } + + @Override + public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException { + throw new UnsupportedOperationException("dumpHeap is not supported."); + } + + @Override + public String getProcessPid(String process) throws DeviceNotAvailableException { + String output = executeShellCommand(String.format("pidof %s", process)).trim(); + if (checkValidPid(output)) { + return output; + } + CLog.e("Failed to find a valid pid for process."); + return null; + } + + /** Validate that pid is an integer and not empty. */ + private boolean checkValidPid(String output) { + if (output.isEmpty()) { + return false; + } + try { + Integer.parseInt(output); + } catch (NumberFormatException e) { + CLog.e(e); + return false; + } + return true; + } } diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java index 66285f4ec..e5ab1d43e 100644 --- a/src/com/android/tradefed/device/TestDevice.java +++ b/src/com/android/tradefed/device/TestDevice.java @@ -30,6 +30,7 @@ import com.android.tradefed.util.RunUtil; import com.android.tradefed.util.StreamUtil; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; import java.awt.Image; import java.awt.image.BufferedImage; @@ -89,6 +90,11 @@ public class TestDevice extends NativeDevice { /** Timeout to wait for a screenshot before giving up to avoid hanging forever */ private static final long MAX_SCREENSHOT_TIMEOUT = 5 * 60 * 1000; // 5 min + /** adb shell am dumpheap */ + private static final String DUMPHEAP_CMD = "am dumpheap %s %s"; + /** Time given to a file to be dumped on device side */ + private static final long DUMPHEAP_TIME = 5000l; + /** * @param device * @param stateMonitor @@ -1215,4 +1221,33 @@ public class TestDevice extends NativeDevice { + "Must be API %d.", feature, getSerialNumber(), strictMinLevel)); } } + + @Override + public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException { + if (Strings.isNullOrEmpty(devicePath) || Strings.isNullOrEmpty(process)) { + throw new IllegalArgumentException("devicePath or process cannot be null or empty."); + } + String pid = getProcessPid(process); + if (pid == null) { + return null; + } + File dump = dumpAndPullHeap(pid, devicePath); + // Clean the device. + executeShellCommand(String.format("rm %s", devicePath)); + return dump; + } + + /** Dump the heap file and pull it from the device. */ + private File dumpAndPullHeap(String pid, String devicePath) throws DeviceNotAvailableException { + executeShellCommand(String.format(DUMPHEAP_CMD, pid, devicePath)); + // Allow a little bit of time for the file to populate on device side. + int attempt = 0; + // TODO: add an API to check device file size + while (!doesFileExist(devicePath) && attempt < 3) { + getRunUtil().sleep(DUMPHEAP_TIME); + attempt++; + } + File dumpFile = pullFile(devicePath); + return dumpFile; + } } -- cgit v1.2.3