From 89c496491e02ae9b78cd988c5ef41ca23a213884 Mon Sep 17 00:00:00 2001 From: Gopinath Elanchezhian Date: Tue, 17 Mar 2020 15:10:04 -0700 Subject: DO NOT MERGE: Backport rss metric collector changes. Bug: b/151336935 Test: RssSnapshotHelperTest, RssSnapshotListenerTest Change-Id: If1aa6282abefb9c697aff97f294134ba29a63bf0 Exclude merging into *-plus-aosp branches, since they already include the change. Merged-In: Ic9101a35c137b9e480c6183b0ef249e9b2b17a6f Merged-In: Ic571023b68fd4c398a57d10b9b4e80c7676eabd1 Merged-In: I75c3d7ce90d5edb995b79a3faa85f4244bc2f5e7 --- .../src/com/android/helpers/RssSnapshotHelper.java | 323 +++++++++++++++++++++ .../helpers/tests/RssSnapshotHelperTest.java | 156 ++++++++++ .../device/collectors/RssSnapshotListener.java | 99 +++++++ .../device/collectors/RssSnapshotListenerTest.java | 91 ++++++ 4 files changed, 669 insertions(+) create mode 100644 libraries/collectors-helper/memory/src/com/android/helpers/RssSnapshotHelper.java create mode 100644 libraries/collectors-helper/memory/test/src/com/android/helpers/tests/RssSnapshotHelperTest.java create mode 100644 libraries/device-collectors/src/main/java/android/device/collectors/RssSnapshotListener.java create mode 100644 libraries/device-collectors/src/test/java/android/device/collectors/RssSnapshotListenerTest.java diff --git a/libraries/collectors-helper/memory/src/com/android/helpers/RssSnapshotHelper.java b/libraries/collectors-helper/memory/src/com/android/helpers/RssSnapshotHelper.java new file mode 100644 index 00000000..fc947a75 --- /dev/null +++ b/libraries/collectors-helper/memory/src/com/android/helpers/RssSnapshotHelper.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2019 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.helpers; + +import static com.android.helpers.MetricUtility.constructKey; + +import android.support.test.uiautomator.UiDevice; +import android.util.Log; +import androidx.test.InstrumentationRegistry; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.InputMismatchException; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Scanner; +import java.util.Set; +import java.util.UUID; + +/** + * Helper to collect rss snapshot for a list of processes. + */ +public class RssSnapshotHelper implements ICollectorHelper { + private static final String TAG = RssSnapshotHelper.class.getSimpleName(); + + private static final String DROP_CACHES_CMD = "echo %d > /proc/sys/vm/drop_caches"; + private static final String PIDOF_CMD = "pidof %s"; + public static final String ALL_PROCESSES_CMD = "ps -A"; + private static final String SHOWMAP_CMD = "showmap -v %d"; + + public static final String RSS_METRIC_PREFIX = "showmap_rss_bytes"; + public static final String OUTPUT_FILE_PATH_KEY = "showmap_output_file"; + public static final String RSS_PROCESS_COUNT = "rss_process_count"; + + private String[] mProcessNames = null; + private String mTestOutputDir = null; + private String mTestOutputFile = null; + + private int mDropCacheOption; + private boolean mCollectForAllProcesses = false; + private UiDevice mUiDevice; + + // Map to maintain per-process rss. + private Map mRssMap = new HashMap<>(); + + public void setUp(String testOutputDir, String... processNames) { + mProcessNames = processNames; + mTestOutputDir = testOutputDir; + mDropCacheOption = 0; + mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); + } + + @Override + public boolean startCollecting() { + if (mTestOutputDir == null) { + Log.e(TAG, String.format("Invalid test setup")); + return false; + } + + File directory = new File(mTestOutputDir); + String filePath = + String.format("%s/rss_snapshot%d.txt", mTestOutputDir, UUID.randomUUID().hashCode()); + File file = new File(filePath); + + // Make sure directory exists and file does not + if (directory.exists()) { + if (file.exists() && !file.delete()) { + Log.e(TAG, String.format("Failed to delete result output file %s", filePath)); + return false; + } + } else { + if (!directory.mkdirs()) { + Log.e(TAG, String.format("Failed to create result output directory %s", mTestOutputDir)); + return false; + } + } + + // Create an empty file to fail early in case there are no write permissions + try { + if (!file.createNewFile()) { + // This should not happen unless someone created the file right after we deleted it + Log.e(TAG, String.format("Race with another user of result output file %s", filePath)); + return false; + } + } catch (IOException e) { + Log.e(TAG, String.format("Failed to create result output file %s", filePath), e); + return false; + } + + mTestOutputFile = filePath; + return true; + } + + @Override + public Map getMetrics() { + try { + // Drop cache if requested + if (mDropCacheOption > 0) { + dropCache(mDropCacheOption); + } + + if (mCollectForAllProcesses) { + Log.i(TAG, "Collecting RSS metrics for all processes."); + mProcessNames = getAllProcessNames(); + } else if (mProcessNames.length > 0) { + Log.i(TAG, "Collecting RSS only for given list of process"); + } else if (mProcessNames.length == 0) { + // No processes specified, just return empty map + return mRssMap; + } + + FileWriter writer = new FileWriter(new File(mTestOutputFile), true); + for (String processName : mProcessNames) { + List pids = new ArrayList<>(); + + long totalrss = 0; + // Collect required data + try { + pids = getPids(processName); + + for (Integer pid: pids) { + String showmapOutput = execShowMap(processName, pid); + long rss = extractTotalRss(processName, showmapOutput); + // Track the total rss for the processes with the same process name. + totalrss += rss; + // Store showmap output into file. If there are more than one process + // with same name write the individual showmap associated with pid. + storeToFile(mTestOutputFile, processName, pid, showmapOutput, writer); + } + } catch (RuntimeException e) { + Log.e(TAG, e.getMessage(), e.getCause()); + // Skip this process and continue with the next one + continue; + } + + // Store metrics + mRssMap.put(constructKey(RSS_METRIC_PREFIX, processName), Long.toString(totalrss * 1024)); + // Store the unique process count. + mRssMap.put(RSS_PROCESS_COUNT, Integer.toString(mProcessNames.length)); + } + writer.close(); + mRssMap.put(OUTPUT_FILE_PATH_KEY, mTestOutputFile); + } catch (RuntimeException e) { + Log.e(TAG, e.getMessage(), e.getCause()); + } catch (IOException e) { + Log.e(TAG, String.format("Failed to write output file %s", mTestOutputFile), e); + } + + return mRssMap; + } + + @Override + public boolean stopCollecting() { + return true; + } + + /** + * Set drop cache option. + * + * @param dropCacheOption drop pagecache (1), slab (2) or all (3) cache + * @return true on success, false if input option is invalid + */ + public boolean setDropCacheOption(int dropCacheOption) { + // Valid values are 1..3 + if (dropCacheOption < 1 || dropCacheOption > 3) { + return false; + } + + mDropCacheOption = dropCacheOption; + return true; + } + + /** + * Drops kernel memory cache. + * + * @param cacheOption drop pagecache (1), slab (2) or all (3) caches + */ + private void dropCache(int cacheOption) throws RuntimeException { + try { + mUiDevice.executeShellCommand(String.format(DROP_CACHES_CMD, cacheOption)); + } catch (IOException e) { + throw new RuntimeException("Unable to drop caches", e); + } + } + + /** + * Get pid's of the process with {@code processName} name. + * + * @param processName name of the process to get pid + * @return pid's of the specified process + */ + private List getPids(String processName) throws RuntimeException { + try { + String pidofOutput = mUiDevice.executeShellCommand(String.format(PIDOF_CMD, processName)); + + // Sample output for the process with more than 1 pid. + // Sample command : "pidof init" + // Sample output : 1 559 + String[] pids = pidofOutput.split("\\s+"); + List pidList = new ArrayList<>(); + for (String pid: pids) { + pidList.add(Integer.parseInt(pid.trim())); + } + return pidList; + } catch (IOException e) { + throw new RuntimeException(String.format("Unable to get pid of %s ", processName), e); + } + } + + /** + * Executes showmap command for the process with {@code processName} name and {@code pid} pid. + * + * @param processName name of the process to run showmap for + * @param pid pid of the process to run showmap for + * @return the output of showmap command + */ + private String execShowMap(String processName, long pid) throws IOException { + try { + return mUiDevice.executeShellCommand(String.format(SHOWMAP_CMD, pid)); + } catch (IOException e) { + throw new RuntimeException( + String.format("Unable to execute showmap command for %s ", processName), e); + } + } + + /** + * Extract total RSS from showmap command output for the process with {@code processName} name. + * + * @param processName name of the process to extract RSS for + * @param showmapOutput showmap command output + * @return total RSS of the process + */ + private long extractTotalRss(String processName, String showmapOutput) throws RuntimeException { + try { + int pos = showmapOutput.lastIndexOf("----"); + Scanner sc = new Scanner(showmapOutput.substring(pos)); + sc.next(); + sc.nextLong(); + return sc.nextLong(); + } catch (IndexOutOfBoundsException | InputMismatchException e) { + throw new RuntimeException( + String.format("Unexpected showmap format for %s ", processName), e); + } + } + + /** + * Store test results for one process into file. + * + * @param fileName name of the file being written + * @param processName name of the process + * @param pid pid of the process + * @param showmapOutput showmap command output + * @param writer file writer to write the data + */ + private void storeToFile(String fileName, String processName, long pid, String showmapOutput, + FileWriter writer) throws RuntimeException { + try { + writer.write(String.format(">>> %s (%d) <<<\n", processName, pid)); + writer.write(showmapOutput); + writer.write('\n'); + } catch (IOException e) { + throw new RuntimeException(String.format("Unable to write file %s ", fileName), e); + } + } + + /** + * Enables RSS collection for all processes. + */ + public void setAllProcesses() { + mCollectForAllProcesses = true; + } + + /** + * Get all process names running in the system. + */ + private String[] getAllProcessNames() { + Set allProcessNames = new LinkedHashSet<>(); + try { + String psOutput = mUiDevice.executeShellCommand(ALL_PROCESSES_CMD); + // Split the lines + String allProcesses[] = psOutput.split("\\n"); + for (String invidualProcessDetails : allProcesses) { + Log.i(TAG, String.format("Process detail: %s", invidualProcessDetails)); + // Sample process detail line + // system 603 1 41532 5396 SyS_epoll+ 0 S servicemanager + String processSplit[] = invidualProcessDetails.split("\\s+"); + // Parse process name + String processName = processSplit[processSplit.length - 1].trim(); + // Include the process name which are not enclosed in []. + if (!processName.startsWith("[") && !processName.endsWith("]")) { + // Skip the first (i.e header) line from "ps -A" output. + if (processName.equalsIgnoreCase("NAME")) { + continue; + } + Log.i(TAG, String.format("Including the process %s", processName)); + allProcessNames.add(processName); + } + } + } catch (IOException ioe) { + throw new RuntimeException( + String.format("Unable execute all processes command %s ", ALL_PROCESSES_CMD), + ioe); + } + return allProcessNames.toArray(new String[0]); + } +} diff --git a/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/RssSnapshotHelperTest.java b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/RssSnapshotHelperTest.java new file mode 100644 index 00000000..6a1e1792 --- /dev/null +++ b/libraries/collectors-helper/memory/test/src/com/android/helpers/tests/RssSnapshotHelperTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2019 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.helpers.tests; + +import static com.android.helpers.MetricUtility.constructKey; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import androidx.test.runner.AndroidJUnit4; +import com.android.helpers.RssSnapshotHelper; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Android Unit tests for {@link RssSnapshotHelper}. + * + * To run: + * atest CollectorsHelperTest:com.android.helpers.tests.RssSnapshotHelperTest + */ +@RunWith(AndroidJUnit4.class) +public class RssSnapshotHelperTest { + private static final String TAG = RssSnapshotHelperTest.class.getSimpleName(); + + // Valid output file + private static final String VALID_OUTPUT_DIR = "/sdcard/test_results"; + // Invalid output file (no permissions to write) + private static final String INVALID_OUTPUT_DIR = "/data/local/tmp"; + + // Lists of process names + private static final String[] EMPTY_PROCESS_LIST = {}; + private static final String[] ONE_PROCESS_LIST = {"com.android.systemui"}; + private static final String[] TWO_PROCESS_LIST = {"com.android.systemui", "system_server"}; + private static final String[] NO_PROCESS_LIST = {null}; + + private RssSnapshotHelper mRssSnapshotHelper; + + @Before + public void setUp() { + mRssSnapshotHelper = new RssSnapshotHelper(); + } + + /** + * Test start collecting returns false if the helper has not been properly set up. + */ + @Test + public void testSetUpNotCalled() { + assertFalse(mRssSnapshotHelper.startCollecting()); + } + + /** + * Test invalid options for drop cache flag. + */ + @Test + public void testInvalidDropCacheOptions() { + assertFalse(mRssSnapshotHelper.setDropCacheOption(-1)); + assertFalse(mRssSnapshotHelper.setDropCacheOption(0)); + assertFalse(mRssSnapshotHelper.setDropCacheOption(4)); + } + + /** + * Test invalid options for drop cache flag. + */ + @Test + public void testValidDropCacheOptions() { + assertTrue(mRssSnapshotHelper.setDropCacheOption(1)); + assertTrue(mRssSnapshotHelper.setDropCacheOption(2)); + assertTrue(mRssSnapshotHelper.setDropCacheOption(3)); + } + + /** + * Test no metrics are sampled if process name is empty. + */ + @Test + public void testEmptyProcessName() { + mRssSnapshotHelper.setUp(VALID_OUTPUT_DIR, EMPTY_PROCESS_LIST); + Map metrics = mRssSnapshotHelper.getMetrics(); + assertTrue(metrics.isEmpty()); + } + + /** + * Test sampling on a valid and running process. + */ + @Test + public void testValidFile() { + mRssSnapshotHelper.setUp(VALID_OUTPUT_DIR, ONE_PROCESS_LIST); + assertTrue(mRssSnapshotHelper.startCollecting()); + } + + /** + * Test sampling on using an invalid output file. + */ + @Test + public void testInvalidFile() { + mRssSnapshotHelper.setUp(INVALID_OUTPUT_DIR, ONE_PROCESS_LIST); + assertFalse(mRssSnapshotHelper.startCollecting()); + } + + /** + * Test getting metrics from one process. + */ + @Test + public void testGetMetrics_OneProcess() { + testProcessList(ONE_PROCESS_LIST); + } + + /** + * Test getting metrics from multiple processes process. + */ + @Test + public void testGetMetrics_MultipleProcesses() { + testProcessList(TWO_PROCESS_LIST); + } + + /** + * Test all process flag return more than 2 processes metrics atleast. + */ + @Test + public void testGetMetrics_AllProcess() { + mRssSnapshotHelper.setUp(VALID_OUTPUT_DIR, NO_PROCESS_LIST); + mRssSnapshotHelper.setAllProcesses(); + assertTrue(mRssSnapshotHelper.startCollecting()); + Map metrics = mRssSnapshotHelper.getMetrics(); + assertTrue(metrics.size() > 2); + assertTrue(metrics.containsKey(RssSnapshotHelper.OUTPUT_FILE_PATH_KEY)); + + } + + + private void testProcessList(String... processNames) { + mRssSnapshotHelper.setUp(VALID_OUTPUT_DIR, processNames); + assertTrue(mRssSnapshotHelper.startCollecting()); + Map metrics = mRssSnapshotHelper.getMetrics(); + assertFalse(metrics.isEmpty()); + for (String processName : processNames) { + assertTrue( + metrics.containsKey(constructKey(RssSnapshotHelper.RSS_METRIC_PREFIX, processName))); + } + assertTrue(metrics.containsKey(RssSnapshotHelper.OUTPUT_FILE_PATH_KEY)); + } +} diff --git a/libraries/device-collectors/src/main/java/android/device/collectors/RssSnapshotListener.java b/libraries/device-collectors/src/main/java/android/device/collectors/RssSnapshotListener.java new file mode 100644 index 00000000..fc82512c --- /dev/null +++ b/libraries/device-collectors/src/main/java/android/device/collectors/RssSnapshotListener.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 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 android.device.collectors; + +import android.device.collectors.annotations.OptionClass; +import android.os.Bundle; +import android.util.Log; +import androidx.annotation.VisibleForTesting; +import com.android.helpers.RssSnapshotHelper; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link RssSnapshotListener} that takes a snapshot of Rss sizes for the list of + * specified processes. + * + * Options: + * -e process-names [processNames] : a comma-separated list of processes + * -e drop-cache [pagecache | slab | all] : drop cache flag + * -e test-output-dir [path] : path to the output directory + */ +@OptionClass(alias = "rsssnapshot-collector") +public class RssSnapshotListener extends BaseCollectionListener { + private static final String TAG = RssSnapshotListener.class.getSimpleName(); + private static final String DEFAULT_OUTPUT_DIR = "/sdcard/test_results"; + + @VisibleForTesting static final String PROCESS_SEPARATOR = ","; + @VisibleForTesting static final String PROCESS_NAMES_KEY = "process-names"; + @VisibleForTesting static final String DROP_CACHE_KEY = "drop-cache"; + @VisibleForTesting static final String OUTPUT_DIR_KEY = "test-output-dir"; + + private RssSnapshotHelper mRssSnapshotHelper = new RssSnapshotHelper(); + private final Map dropCacheValues = new HashMap() { + { + put("pagecache", 1); + put("slab", 2); + put("all", 3); + } + }; + + public RssSnapshotListener() { + createHelperInstance(mRssSnapshotHelper); + } + + /** + * Constructor to simulate receiving the instrumentation arguments. Should not be used except + * for testing. + */ + @VisibleForTesting + public RssSnapshotListener(Bundle args, RssSnapshotHelper helper) { + super(args, helper); + mRssSnapshotHelper = helper; + createHelperInstance(mRssSnapshotHelper); + } + + /** + * Adds the options for rss snapshot collector. + */ + @Override + public void setupAdditionalArgs() { + Bundle args = getArgsBundle(); + String testOutputDir = args.getString(OUTPUT_DIR_KEY, DEFAULT_OUTPUT_DIR); + // Collect for all processes if process list is empty or null. + String procsString = args.getString(PROCESS_NAMES_KEY); + + String[] procs = null; + if (procsString == null || procsString.isEmpty()) { + mRssSnapshotHelper.setAllProcesses(); + } else { + procs = procsString.split(PROCESS_SEPARATOR); + } + + mRssSnapshotHelper.setUp(testOutputDir, procs); + + String dropCacheValue = args.getString(DROP_CACHE_KEY); + if (dropCacheValue != null) { + if (dropCacheValues.containsKey(dropCacheValue)) { + mRssSnapshotHelper.setDropCacheOption(dropCacheValues.get(dropCacheValue)); + } else { + Log.e(TAG, "Value for \"" + DROP_CACHE_KEY + "\" parameter is invalid"); + return; + } + } + } +} diff --git a/libraries/device-collectors/src/test/java/android/device/collectors/RssSnapshotListenerTest.java b/libraries/device-collectors/src/test/java/android/device/collectors/RssSnapshotListenerTest.java new file mode 100644 index 00000000..883a0588 --- /dev/null +++ b/libraries/device-collectors/src/test/java/android/device/collectors/RssSnapshotListenerTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2018 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 android.device.collectors; + +import static android.device.collectors.RssSnapshotListener.DROP_CACHE_KEY; +import static android.device.collectors.RssSnapshotListener.OUTPUT_DIR_KEY; +import static android.device.collectors.RssSnapshotListener.PROCESS_NAMES_KEY; +import static android.device.collectors.RssSnapshotListener.PROCESS_SEPARATOR; +import static org.mockito.Mockito.verify; + +import android.app.Instrumentation; +import android.os.Bundle; +import androidx.test.runner.AndroidJUnit4; +import com.android.helpers.RssSnapshotHelper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Android Unit tests for {@link RssSnapshotListener}. + * + * To run: + * atest CollectorDeviceLibTest:android.device.collectors.RssSnapshotListenerTest + */ +@RunWith(AndroidJUnit4.class) +public class RssSnapshotListenerTest { + @Mock private Instrumentation mInstrumentation; + @Mock private RssSnapshotHelper mRssSnapshotHelper; + + private RssSnapshotListener mListener; + private Description mRunDesc; + + private static final String VALID_OUTPUT_DIR = "/data/local/tmp"; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mRunDesc = Description.createSuiteDescription("run"); + } + + private RssSnapshotListener initListener(Bundle b) { + RssSnapshotListener listener = new RssSnapshotListener(b, mRssSnapshotHelper); + listener.setInstrumentation(mInstrumentation); + return listener; + } + + @Test + public void testHelperReceivesProcessNames() throws Exception { + Bundle b = new Bundle(); + b.putString(PROCESS_NAMES_KEY, "process1" + PROCESS_SEPARATOR + "process2"); + b.putString(OUTPUT_DIR_KEY, VALID_OUTPUT_DIR); + + mListener = initListener(b); + + mListener.testRunStarted(mRunDesc); + + verify(mRssSnapshotHelper).setUp(VALID_OUTPUT_DIR, "process1", "process2"); + } + + @Test + public void testAdditionalOptions() throws Exception { + Bundle b = new Bundle(); + b.putString(PROCESS_NAMES_KEY, "process1"); + b.putString(OUTPUT_DIR_KEY, VALID_OUTPUT_DIR); + b.putString(DROP_CACHE_KEY, "all"); + mListener = initListener(b); + + mListener.testRunStarted(mRunDesc); + + verify(mRssSnapshotHelper).setUp(VALID_OUTPUT_DIR, "process1"); + // DROP_CACHE_KEY values: "pagecache" = 1, "slab" = 2, "all" = 3 + verify(mRssSnapshotHelper).setDropCacheOption(3); + } +} -- cgit v1.2.3