diff options
author | Zhi An Ng <zhin@google.com> | 2018-09-24 19:38:39 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2018-09-24 19:38:39 +0000 |
commit | 4dc09a4b82b8bd063105d1376c0c8b5cd95d7ab3 (patch) | |
tree | 672b68982ef70ab43fc081d57ff4288741d3f475 | |
parent | d4cf67e2966fd5c577211f37e10a3f69eaf530cf (diff) | |
parent | 949aea7a56733521db9be03067cb375860279fa7 (diff) |
Merge "Add test to check number and memory of native processes"
3 files changed, 276 insertions, 0 deletions
diff --git a/tests/NativeProcessesMemoryTest/Android.bp b/tests/NativeProcessesMemoryTest/Android.bp new file mode 100644 index 000000000000..f2625bf2db11 --- /dev/null +++ b/tests/NativeProcessesMemoryTest/Android.bp @@ -0,0 +1,21 @@ +// 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. + +java_test_host { + name: "native-processes-memory-test", + srcs: ["src/**/*.java"], + + libs: ["tradefed"], + test_suites: ["general-tests"], +} diff --git a/tests/NativeProcessesMemoryTest/AndroidTest.xml b/tests/NativeProcessesMemoryTest/AndroidTest.xml new file mode 100644 index 000000000000..0f326fd0bd98 --- /dev/null +++ b/tests/NativeProcessesMemoryTest/AndroidTest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<configuration description="Runs the native processes memory tests"> + <option name="test-suite-tag" value="native-processes-memory-test" /> + <test class="com.android.tradefed.testtype.HostTest" > + <option name="class" value="com.android.tests.nativeprocesses.NativeProcessesMemoryTest" /> + </test> +</configuration> diff --git a/tests/NativeProcessesMemoryTest/src/com/android/tests/nativeprocesses/NativeProcessesMemoryTest.java b/tests/NativeProcessesMemoryTest/src/com/android/tests/nativeprocesses/NativeProcessesMemoryTest.java new file mode 100644 index 000000000000..ae011a0316aa --- /dev/null +++ b/tests/NativeProcessesMemoryTest/src/com/android/tests/nativeprocesses/NativeProcessesMemoryTest.java @@ -0,0 +1,234 @@ +/* + * 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 com.android.tests.nativeprocesses; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.result.ByteArrayInputStreamSource; +import com.android.tradefed.result.ITestInvocationListener; +import com.android.tradefed.result.LogDataType; +import com.android.tradefed.testtype.IDeviceTest; +import com.android.tradefed.testtype.IRemoteTest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.InputMismatchException; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Scanner; + +/** + * Test to gather native processes count and memory usage. + * + * Native processes are parsed from dumpsys meminfo --oom -c + * + * <pre> + * time,35857009,35857009 + * oom,native,331721,N/A + * proc,native,init,1,2715,N/A,e + * proc,native,init,445,1492,N/A,e + * ... + * </pre> + * + * For each native process we also look at its showmap output, and gather the PSS, VSS, and RSS. + * The showmap output is also saved to test logs. + * + * The metrics reported are: + * + * <pre> + * - number of native processes + * - total memory use of native processes + * - memory usage of each native process (one measurement for each process) + * </pre> + */ +public class NativeProcessesMemoryTest implements IDeviceTest, IRemoteTest { + // the dumpsys process comes and go as we run this test, changing pids, so ignore it + private static final List<String> PROCESSES_TO_IGNORE = Arrays.asList("dumpsys"); + + // -c gives us a compact output which is comma separated + private static final String DUMPSYS_MEMINFO_OOM_CMD = "dumpsys meminfo --oom -c"; + + private static final String SEPARATOR = ","; + private static final String LINE_SEPARATOR = "\\n"; + + // name of this test run, used for reporting + private static final String RUN_NAME = "NativeProcessesTest"; + // key used to report the number of native processes + private static final String NUM_NATIVE_PROCESSES_KEY = "Num_native_processes"; + + private final Map<String, String> mNativeProcessToMemory = new HashMap<String, String>(); + // identity for summing over MemoryMetric + private final MemoryMetric mZero = new MemoryMetric(0, 0, 0); + + private ITestDevice mTestDevice; + private ITestInvocationListener mListener; + + public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { + mListener = listener; + // showmap requires root, we enable it here for the rest of the test + mTestDevice.enableAdbRoot(); + + listener.testRunStarted(RUN_NAME, 0 /* testCount */); + + // process name -> list of pids with that name + Map<String, List<String>> nativeProcesses = collectNativeProcesses(); + sampleAndLogAllProcesses(nativeProcesses); + + // want to also record the number of native processes + mNativeProcessToMemory.put( + NUM_NATIVE_PROCESSES_KEY, Integer.toString(nativeProcesses.size())); + + listener.testRunEnded(0, mNativeProcessToMemory); + } + + /** Samples memory of all processes and logs the memory use. */ + private void sampleAndLogAllProcesses(Map<String, List<String>> nativeProcesses) + throws DeviceNotAvailableException { + for (Map.Entry<String, List<String>> entry : nativeProcesses.entrySet()) { + String processName = entry.getKey(); + if (PROCESSES_TO_IGNORE.contains(processName)) { + continue; + } + + // for all pids associated with this process name, record their memory usage + List<MemoryMetric> allMemsForProcess = new ArrayList<>(); + for (String pid : entry.getValue()) { + Optional<MemoryMetric> mem = snapMemoryUsage(processName, pid); + if (mem.isPresent()) { + allMemsForProcess.add(mem.get()); + } + } + + // if for some reason a process does not have any MemoryMetric, don't log it + if (allMemsForProcess.isEmpty()) { + continue; + } + + // combine all the memory metrics of process with the same name + MemoryMetric combined = allMemsForProcess.stream().reduce(mZero, MemoryMetric::sum); + logMemoryMetric(processName, combined); + } + } + + @Override + public void setDevice(ITestDevice device) { + mTestDevice = device; + } + + @Override + public ITestDevice getDevice() { + return mTestDevice; + } + + /** + * Query system for a list of native process. + * + * @return a map from process name to a list of pids that share the same name + */ + private Map<String, List<String>> collectNativeProcesses() throws DeviceNotAvailableException { + HashMap<String, List<String>> nativeProcesses = new HashMap<>(); + String memInfo = mTestDevice.executeShellCommand(DUMPSYS_MEMINFO_OOM_CMD); + + for (String line : memInfo.split(LINE_SEPARATOR)) { + String[] splits = line.split(SEPARATOR); + // ignore lines that don't list a native process + if (splits.length < 4 || !splits[0].equals("proc") || !splits[1].equals("native")) { + continue; + } + + String processName = splits[2]; + String pid = splits[3]; + if (nativeProcesses.containsKey(processName)) { + nativeProcesses.get(processName).add(pid); + } else { + nativeProcesses.put(processName, new ArrayList<>(Arrays.asList(pid))); + } + } + return nativeProcesses; + } + + /** Logs an entire showmap output to the test logs. */ + private void logShowmap(String label, String showmap) { + try (ByteArrayInputStreamSource source = + new ByteArrayInputStreamSource(showmap.getBytes())) { + mListener.testLog(label + "_showmap", LogDataType.TEXT, source); + } + } + + /** + * Extract VSS, PSS, and RSS from showmap of a process. + * The showmap output is also added to test logs. + */ + private Optional<MemoryMetric> snapMemoryUsage(String processName, String pid) + throws DeviceNotAvailableException { + // TODO(zhin): copied from com.android.tests.sysmem.host.Metrics#sample(), extract? + String showmap = mTestDevice.executeShellCommand("showmap " + pid); + logShowmap(processName + "_" + pid, showmap); + + // CHECKSTYLE:OFF Generated code + // The last lines of the showmap output looks like: + // ------- -------- -------- -------- -------- -------- -------- -------- -------- ---- ------------------------------ + // virtual shared shared private private + // size RSS PSS clean dirty clean dirty swap swapPSS # object + // -------- -------- -------- -------- -------- -------- -------- -------- -------- ---- ------------------------------ + // 12848 4240 1543 2852 64 36 1288 0 0 171 TOTAL + // CHECKSTYLE:ON Generated code + try { + int pos = showmap.lastIndexOf("----"); + Scanner sc = new Scanner(showmap.substring(pos)); + sc.next(); + long vss = sc.nextLong(); + long rss = sc.nextLong(); + long pss = sc.nextLong(); + return Optional.of(new MemoryMetric(pss, rss, vss)); + } catch (InputMismatchException e) { + // this might occur if we have transient processes, it was collected earlier, + // but by the time we look at showmap the process is gone + CLog.e("Unable to parse MemoryMetric from showmap of pid: " + pid + " processName: " + + processName); + CLog.e(showmap); + return Optional.empty(); + } + } + + /** Logs a MemoryMetric of a process. */ + private void logMemoryMetric(String processName, MemoryMetric memoryMetric) { + mNativeProcessToMemory.put(processName + "_pss", Long.toString(memoryMetric.pss)); + mNativeProcessToMemory.put(processName + "_rss", Long.toString(memoryMetric.rss)); + mNativeProcessToMemory.put(processName + "_vss", Long.toString(memoryMetric.vss)); + } + + /** Container of memory numbers we want to log. */ + private final class MemoryMetric { + final long pss; + final long rss; + final long vss; + + MemoryMetric(long pss, long rss, long vss) { + this.pss = pss; + this.rss = rss; + this.vss = vss; + } + + MemoryMetric sum(MemoryMetric other) { + return new MemoryMetric( + pss + other.pss, rss + other.rss, vss + other.vss); + } + } +} |