aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Desprez <jdesprez@google.com>2017-09-14 00:07:12 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2017-09-14 00:07:12 +0000
commitcc53aaf552184f73a1ce70717fed0b1327771aca (patch)
treeff5e320522581a6ea92b977e4f3784f2fec6a5f9
parent0f93a550de4cf9433086dae4a76b1525ba54d05d (diff)
parentd767032c5a9a7d0d48f86ff478e7aac00ec88f40 (diff)
downloadtradefederation-cc53aaf552184f73a1ce70717fed0b1327771aca.tar.gz
Merge "Create a system-server heap dump utility" into oc-dev
-rw-r--r--src/com/android/tradefed/device/INativeDevice.java3
-rw-r--r--src/com/android/tradefed/device/ITestDevice.java12
-rw-r--r--src/com/android/tradefed/device/NativeDevice.java30
-rw-r--r--src/com/android/tradefed/device/TestDevice.java35
-rw-r--r--tests/src/com/android/tradefed/device/TestDeviceTest.java64
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
+ }
+ }
}