diff options
author | Julien Desprez <jdesprez@google.com> | 2017-09-14 00:07:12 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2017-09-14 00:07:12 +0000 |
commit | cc53aaf552184f73a1ce70717fed0b1327771aca (patch) | |
tree | ff5e320522581a6ea92b977e4f3784f2fec6a5f9 | |
parent | 0f93a550de4cf9433086dae4a76b1525ba54d05d (diff) | |
parent | d767032c5a9a7d0d48f86ff478e7aac00ec88f40 (diff) | |
download | tradefederation-cc53aaf552184f73a1ce70717fed0b1327771aca.tar.gz |
Merge "Create a system-server heap dump utility" into oc-dev
-rw-r--r-- | src/com/android/tradefed/device/INativeDevice.java | 3 | ||||
-rw-r--r-- | src/com/android/tradefed/device/ITestDevice.java | 12 | ||||
-rw-r--r-- | src/com/android/tradefed/device/NativeDevice.java | 30 | ||||
-rw-r--r-- | src/com/android/tradefed/device/TestDevice.java | 35 | ||||
-rw-r--r-- | tests/src/com/android/tradefed/device/TestDeviceTest.java | 64 |
5 files changed, 143 insertions, 1 deletions
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 <service pid> <dump file path> */ + 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; + } } diff --git a/tests/src/com/android/tradefed/device/TestDeviceTest.java b/tests/src/com/android/tradefed/device/TestDeviceTest.java index 31d17f727..9af4ec6d7 100644 --- a/tests/src/com/android/tradefed/device/TestDeviceTest.java +++ b/tests/src/com/android/tradefed/device/TestDeviceTest.java @@ -3293,4 +3293,68 @@ public class TestDeviceTest extends TestCase { assertFalse(mTestDevice.isEncryptionSupported()); EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor); } + + /** Test when getting the heapdump is successful. */ + public void testGetHeapDump() throws Exception { + mTestDevice = + new TestableTestDevice() { + @Override + public File pullFile(String remoteFilePath) throws DeviceNotAvailableException { + return new File("test"); + } + }; + injectShellResponse("pidof system_server", "929"); + injectShellResponse("am dumpheap 929 /data/dump.hprof", ""); + injectShellResponse("ls \"/data/dump.hprof\"", "/data/dump.hprof"); + injectShellResponse("rm /data/dump.hprof", ""); + EasyMock.replay(mMockIDevice, mMockRunUtil); + File res = mTestDevice.dumpHeap("system_server", "/data/dump.hprof"); + assertNotNull(res); + EasyMock.verify(mMockIDevice, mMockRunUtil); + } + + /** Test when we fail to get the process pid. */ + public void testGetHeapDump_nopid() throws Exception { + injectShellResponse("pidof system_server", "\n"); + EasyMock.replay(mMockIDevice, mMockRunUtil); + File res = mTestDevice.dumpHeap("system_server", "/data/dump.hprof"); + assertNull(res); + EasyMock.verify(mMockIDevice, mMockRunUtil); + } + + public void testGetHeapDump_nullPath() throws DeviceNotAvailableException { + try { + mTestDevice.dumpHeap("system_server", null); + fail("Should have thrown an exception"); + } catch (IllegalArgumentException expected) { + // expected + } + } + + public void testGetHeapDump_emptyPath() throws DeviceNotAvailableException { + try { + mTestDevice.dumpHeap("system_server", ""); + fail("Should have thrown an exception"); + } catch (IllegalArgumentException expected) { + // expected + } + } + + public void testGetHeapDump_nullService() throws DeviceNotAvailableException { + try { + mTestDevice.dumpHeap(null, "/data/hprof"); + fail("Should have thrown an exception"); + } catch (IllegalArgumentException expected) { + // expected + } + } + + public void testGetHeapDump_emptyService() throws DeviceNotAvailableException { + try { + mTestDevice.dumpHeap("", "/data/hprof"); + fail("Should have thrown an exception"); + } catch (IllegalArgumentException expected) { + // expected + } + } } |