Loading tests/AppMemoryTest/app/src/android/app/memory/tests/helper/EmptyActivity.java +0 −109 Original line number Diff line number Diff line Loading @@ -17,120 +17,11 @@ package android.app.memory.testhelper; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Debug; import android.os.SystemClock; import android.util.Log; import org.apache.harmony.dalvik.ddmc.DdmVmInternal; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; public class EmptyActivity extends Activity { private static final String TAG = "AppMemoryTest"; // A local string, whose value must be visible in the heap dump. If the value is not found, // the heap dump is not being created correctly. private String mSentinelString; // A large array that is used to calibrate the heap profile walker. private int[] mExtraBytes; // Configure for a heap dump private static void configureForHeap() { DdmVmInternal.setRecentAllocationsTrackingEnabled(true); } // Configure allocation tracking in the constructor. public EmptyActivity() { configureForHeap(); } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); Intent intent = getIntent(); if (intent == null) { Log.e(TAG, "no intent to process"); throw new RuntimeException("no intent to process"); } String dumpPath = intent.getStringExtra("dump-path"); if (dumpPath == null) { Log.e(TAG, "no dump path available"); throw new RuntimeException("no dump path available"); } long soakDelay = intent.getLongExtra("soak-delay", -1); if (soakDelay < 0) { Log.e(TAG, "no soak delay available"); throw new RuntimeException("no soak delay available"); } int extraSize = intent.getIntExtra("extra-size", 0); if (extraSize > 0) { mExtraBytes = new int[extraSize]; } // Create a sentinel string. If "a sample string" is not found in the heap dump, something // is wrong. StringBuilder b; b = new StringBuilder(); b.append("a").append(" sample").append(" string"); mSentinelString = b.toString(); // Delay for the soak time. The purpose is to give the background tasks time to // stabilize. final long alarm = SystemClock.uptimeMillis() + soakDelay; while (SystemClock.uptimeMillis() < alarm) { try { Thread.sleep(alarm - SystemClock.uptimeMillis()); } catch (InterruptedException e) { Log.e(TAG, "interrupted during wait for boot-up"); } } final long allocated = android.os.Debug.getGlobalAllocSize(); try { Debug.dumpHprofData(dumpPath + ".hprof"); } catch (IOException e) { Log.e(TAG, "failed to create hprof " + dumpPath + ": ", e); } // The remaining logic occurs after the heap dump. Log.i(TAG, "heap dump=" + dumpPath + " soak-delay=" + soakDelay + " extra-size=" + extraSize); File statFile = new File(dumpPath + ".stats"); try (PrintWriter out = new PrintWriter(statFile)) { // Write a header that identifies the start of the file. The current time is used by // report generators to sequence statistics files from multiple runs on the same // build. out.format("memory.testhelper.runtime %d\n", System.currentTimeMillis() / 1000); out.format("art.gc.bytes-allocated %d\n", allocated); out.format("extra-size.length %d\n", (mExtraBytes != null) ? mExtraBytes.length : 0); out.format("sentinel.string %s\n", mSentinelString); } catch (FileNotFoundException e) { Log.e(TAG, "cannot write stats file", e); throw new RuntimeException(e.toString()); } // It is not necessary to throw for the following errors. The main test app will detect // that the result files (statistics and heap dump) are not readable and will fail the test. if (!statFile.setReadable(true, false)) { Log.e(TAG, "failed to make stats file readable"); } else if (!statFile.setWritable(true, false)) { Log.e(TAG, "failed to make stats file writable"); } // Make the heap dump world-readable so that the main test can read it. File dumpFile = new File(dumpPath + ".hprof"); if (!dumpFile.setReadable(true, false)) { Log.e(TAG, "failed to make dump readable"); } } } tests/AppMemoryTest/src/android/app/memory/tests/AppMemoryTest.java +43 −48 Original line number Diff line number Diff line Loading @@ -16,8 +16,8 @@ package android.app.memory.tests; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNotNull; import android.app.Activity; import android.app.Instrumentation; Loading @@ -36,13 +36,9 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; @RunWith(Parameterized.class) public class AppMemoryTest { Loading Loading @@ -104,6 +100,7 @@ public class AppMemoryTest { @After public void tearDown() { runShellCommandWithResult("setprop debug.allocTracker.stackDepth 16"); runShellCommandWithResult("am force-stop " + HELPER); uninstallHelper(); } Loading Loading @@ -140,66 +137,64 @@ public class AppMemoryTest { // Compute the suffix for this particular test. The suffix is just the amount of extra // memory divided by 1024. final String suffix = String.format("-%04d", extra / 1024); final String path = mRootPath + "jheap" + suffix; final String profilePath = mRootPath + "jheap" + suffix + ".hprof"; final File profileFile = new File(profilePath); // Where the dump will be found. First verify that it is not present. final File profile = new File(path + ".hprof"); assertFalse(profile.exists()); // Ensure clean state profileFile.delete(); String cmd = new String("am start-activity -S --track-allocation ") + String.format("-n %s/.EmptyActivity ", HELPER) + String.format("--es dump-path %s ", path) + String.format("--el soak-delay %d ", delay) + String.format("--ei extra-size %d", extra); // -S stops any existing activity before starting new, ensuring cold start // -W waits until activity has drawn its first frame String cmd = new String("am start-activity -S -W --track-allocation ") + String.format("-n %s/.EmptyActivity ", HELPER); Log.i(TAG, "starting helper activity"); Log.i(TAG, cmd); runShellCommandWithResult(cmd); Log.i(TAG, "waiting for helper activity"); // Wait up to 120 seconds for the profile to be created. for (int i = 0; i < 120; i++) { if (profile.exists() && profile.canRead()) break; Thread.sleep(1000); } // without this sleep, pidof errors out, meaning maybe when we wait with -W // perhaps when the first frame is drawn the pid is still not assigned? // also serves as a soak delay Thread.sleep(15 * 1000); // get PID of the helper app String pid = runShellCommandWithResult("pidof " + HELPER).trim(); assertNotNull("Could not get PID for package " + HELPER, pid); Log.i(TAG, "Got PID for " + HELPER + ": " + pid); assertTrue("PID is not a valid number: " + pid, pid.matches("\\d+")); Log.i(TAG, "Got PID for " + HELPER + ": " + pid); // trigger the heap dump cmd = String.format("am dumpheap %s %s", pid, profilePath); Log.i(TAG, "Executing heap dump command: " + cmd); runShellCommandWithResult(cmd); // Now verify that the dump was created and is readable. assertTrue("heap profile does not exist", profile.exists()); assertTrue("heap profile is not readable", profile.canRead()); // Read the summary file. Let exceptions roll up and fail the test. HashMap<String, String> appStats = new HashMap<>(); try (BufferedReader ifile = new BufferedReader(new FileReader(path + ".stats"))) { String line; while ((line = ifile.readLine()) != null) { String[] keyval = line.split(" "); if (keyval.length == 2) { appStats.put(keyval[0], keyval[1]); } } } // make file readable runShellCommandWithResult("chmod 666 " + profilePath); // verify dump exists and readable assertTrue("Heap profile does not exist: " + profilePath, profileFile.exists()); assertTrue("Heap profile is not readable: " + profilePath, profileFile.canRead()); Log.i(TAG, "Heap dump successfully created at: " + profilePath); // without this sleep, am dumpheap creates a file but it is still writing to it. // if Profile() reads it too early, then readVersion() triggers an overflow error. Thread.sleep(15 * 1000); // Extract metrics and report them. Profile p = new Profile(profile); // Extract metrics and report them Profile p = new Profile(profileFile); final long p_allocated = p.size(); final long d_allocated = Long.parseLong(appStats.get("art.gc.bytes-allocated")); // Log the sizes for debugging purposes. Log.i(TAG, String.format("allocated p:%d d:%d", p_allocated, d_allocated)); try (FileWriter ofile = new FileWriter(path + ".stats", true)) { ofile.write("profile.heap " + p_allocated + "\n"); } // Log the parsed size for debugging purposes. Log.i(TAG, "Profile-parsed heap size (PSize): " + p_allocated); // Send metrics to the automation system. Bundle stats = new Bundle(); String key; key = "PSize" + suffix; String key = "PSize" + suffix; stats.putLong(key, p_allocated); stats.putString(Instrumentation.REPORT_KEY_STREAMRESULT, key + ": " + p_allocated); key = "DSize" + suffix; stats.putLong(key, d_allocated); stats.putString(Instrumentation.REPORT_KEY_STREAMRESULT, key + ": " + d_allocated); InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, stats); } Loading Loading
tests/AppMemoryTest/app/src/android/app/memory/tests/helper/EmptyActivity.java +0 −109 Original line number Diff line number Diff line Loading @@ -17,120 +17,11 @@ package android.app.memory.testhelper; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.Debug; import android.os.SystemClock; import android.util.Log; import org.apache.harmony.dalvik.ddmc.DdmVmInternal; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; public class EmptyActivity extends Activity { private static final String TAG = "AppMemoryTest"; // A local string, whose value must be visible in the heap dump. If the value is not found, // the heap dump is not being created correctly. private String mSentinelString; // A large array that is used to calibrate the heap profile walker. private int[] mExtraBytes; // Configure for a heap dump private static void configureForHeap() { DdmVmInternal.setRecentAllocationsTrackingEnabled(true); } // Configure allocation tracking in the constructor. public EmptyActivity() { configureForHeap(); } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); Intent intent = getIntent(); if (intent == null) { Log.e(TAG, "no intent to process"); throw new RuntimeException("no intent to process"); } String dumpPath = intent.getStringExtra("dump-path"); if (dumpPath == null) { Log.e(TAG, "no dump path available"); throw new RuntimeException("no dump path available"); } long soakDelay = intent.getLongExtra("soak-delay", -1); if (soakDelay < 0) { Log.e(TAG, "no soak delay available"); throw new RuntimeException("no soak delay available"); } int extraSize = intent.getIntExtra("extra-size", 0); if (extraSize > 0) { mExtraBytes = new int[extraSize]; } // Create a sentinel string. If "a sample string" is not found in the heap dump, something // is wrong. StringBuilder b; b = new StringBuilder(); b.append("a").append(" sample").append(" string"); mSentinelString = b.toString(); // Delay for the soak time. The purpose is to give the background tasks time to // stabilize. final long alarm = SystemClock.uptimeMillis() + soakDelay; while (SystemClock.uptimeMillis() < alarm) { try { Thread.sleep(alarm - SystemClock.uptimeMillis()); } catch (InterruptedException e) { Log.e(TAG, "interrupted during wait for boot-up"); } } final long allocated = android.os.Debug.getGlobalAllocSize(); try { Debug.dumpHprofData(dumpPath + ".hprof"); } catch (IOException e) { Log.e(TAG, "failed to create hprof " + dumpPath + ": ", e); } // The remaining logic occurs after the heap dump. Log.i(TAG, "heap dump=" + dumpPath + " soak-delay=" + soakDelay + " extra-size=" + extraSize); File statFile = new File(dumpPath + ".stats"); try (PrintWriter out = new PrintWriter(statFile)) { // Write a header that identifies the start of the file. The current time is used by // report generators to sequence statistics files from multiple runs on the same // build. out.format("memory.testhelper.runtime %d\n", System.currentTimeMillis() / 1000); out.format("art.gc.bytes-allocated %d\n", allocated); out.format("extra-size.length %d\n", (mExtraBytes != null) ? mExtraBytes.length : 0); out.format("sentinel.string %s\n", mSentinelString); } catch (FileNotFoundException e) { Log.e(TAG, "cannot write stats file", e); throw new RuntimeException(e.toString()); } // It is not necessary to throw for the following errors. The main test app will detect // that the result files (statistics and heap dump) are not readable and will fail the test. if (!statFile.setReadable(true, false)) { Log.e(TAG, "failed to make stats file readable"); } else if (!statFile.setWritable(true, false)) { Log.e(TAG, "failed to make stats file writable"); } // Make the heap dump world-readable so that the main test can read it. File dumpFile = new File(dumpPath + ".hprof"); if (!dumpFile.setReadable(true, false)) { Log.e(TAG, "failed to make dump readable"); } } }
tests/AppMemoryTest/src/android/app/memory/tests/AppMemoryTest.java +43 −48 Original line number Diff line number Diff line Loading @@ -16,8 +16,8 @@ package android.app.memory.tests; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertNotNull; import android.app.Activity; import android.app.Instrumentation; Loading @@ -36,13 +36,9 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; @RunWith(Parameterized.class) public class AppMemoryTest { Loading Loading @@ -104,6 +100,7 @@ public class AppMemoryTest { @After public void tearDown() { runShellCommandWithResult("setprop debug.allocTracker.stackDepth 16"); runShellCommandWithResult("am force-stop " + HELPER); uninstallHelper(); } Loading Loading @@ -140,66 +137,64 @@ public class AppMemoryTest { // Compute the suffix for this particular test. The suffix is just the amount of extra // memory divided by 1024. final String suffix = String.format("-%04d", extra / 1024); final String path = mRootPath + "jheap" + suffix; final String profilePath = mRootPath + "jheap" + suffix + ".hprof"; final File profileFile = new File(profilePath); // Where the dump will be found. First verify that it is not present. final File profile = new File(path + ".hprof"); assertFalse(profile.exists()); // Ensure clean state profileFile.delete(); String cmd = new String("am start-activity -S --track-allocation ") + String.format("-n %s/.EmptyActivity ", HELPER) + String.format("--es dump-path %s ", path) + String.format("--el soak-delay %d ", delay) + String.format("--ei extra-size %d", extra); // -S stops any existing activity before starting new, ensuring cold start // -W waits until activity has drawn its first frame String cmd = new String("am start-activity -S -W --track-allocation ") + String.format("-n %s/.EmptyActivity ", HELPER); Log.i(TAG, "starting helper activity"); Log.i(TAG, cmd); runShellCommandWithResult(cmd); Log.i(TAG, "waiting for helper activity"); // Wait up to 120 seconds for the profile to be created. for (int i = 0; i < 120; i++) { if (profile.exists() && profile.canRead()) break; Thread.sleep(1000); } // without this sleep, pidof errors out, meaning maybe when we wait with -W // perhaps when the first frame is drawn the pid is still not assigned? // also serves as a soak delay Thread.sleep(15 * 1000); // get PID of the helper app String pid = runShellCommandWithResult("pidof " + HELPER).trim(); assertNotNull("Could not get PID for package " + HELPER, pid); Log.i(TAG, "Got PID for " + HELPER + ": " + pid); assertTrue("PID is not a valid number: " + pid, pid.matches("\\d+")); Log.i(TAG, "Got PID for " + HELPER + ": " + pid); // trigger the heap dump cmd = String.format("am dumpheap %s %s", pid, profilePath); Log.i(TAG, "Executing heap dump command: " + cmd); runShellCommandWithResult(cmd); // Now verify that the dump was created and is readable. assertTrue("heap profile does not exist", profile.exists()); assertTrue("heap profile is not readable", profile.canRead()); // Read the summary file. Let exceptions roll up and fail the test. HashMap<String, String> appStats = new HashMap<>(); try (BufferedReader ifile = new BufferedReader(new FileReader(path + ".stats"))) { String line; while ((line = ifile.readLine()) != null) { String[] keyval = line.split(" "); if (keyval.length == 2) { appStats.put(keyval[0], keyval[1]); } } } // make file readable runShellCommandWithResult("chmod 666 " + profilePath); // verify dump exists and readable assertTrue("Heap profile does not exist: " + profilePath, profileFile.exists()); assertTrue("Heap profile is not readable: " + profilePath, profileFile.canRead()); Log.i(TAG, "Heap dump successfully created at: " + profilePath); // without this sleep, am dumpheap creates a file but it is still writing to it. // if Profile() reads it too early, then readVersion() triggers an overflow error. Thread.sleep(15 * 1000); // Extract metrics and report them. Profile p = new Profile(profile); // Extract metrics and report them Profile p = new Profile(profileFile); final long p_allocated = p.size(); final long d_allocated = Long.parseLong(appStats.get("art.gc.bytes-allocated")); // Log the sizes for debugging purposes. Log.i(TAG, String.format("allocated p:%d d:%d", p_allocated, d_allocated)); try (FileWriter ofile = new FileWriter(path + ".stats", true)) { ofile.write("profile.heap " + p_allocated + "\n"); } // Log the parsed size for debugging purposes. Log.i(TAG, "Profile-parsed heap size (PSize): " + p_allocated); // Send metrics to the automation system. Bundle stats = new Bundle(); String key; key = "PSize" + suffix; String key = "PSize" + suffix; stats.putLong(key, p_allocated); stats.putString(Instrumentation.REPORT_KEY_STREAMRESULT, key + ": " + p_allocated); key = "DSize" + suffix; stats.putLong(key, d_allocated); stats.putString(Instrumentation.REPORT_KEY_STREAMRESULT, key + ": " + d_allocated); InstrumentationRegistry.getInstrumentation().sendStatus(Activity.RESULT_OK, stats); } Loading