From d616982cb3eb53b250759c4de4ad06db5a598d45 Mon Sep 17 00:00:00 2001 From: Rafal Slawik Date: Wed, 19 Jun 2019 17:59:50 +0100 Subject: [PATCH] Read process allocation sizes on system ion heap We are planning to use this to attribute low-memory kills to processes. Assume that each represents an allocation. Compute values for all fields in the ProcessSystemIonHeapSize atom. This code is intented to run only on userdebug or eng builds and it has to be enabled via a statsd metric from the server only for devices with required SELinux policies. It extends SystemIonHeapSize atom to provide per-process ion allocations attribution. Bug: 130526489 Test: atest MemoryStatUtilTest Test: statsd_testdrive returns non-zero values Change-Id: I1c1f31e2739dc493e8f4b3f47d73f5bbaadc8bdf (cherry picked from commit 5fe706c7db35e3709375acf16212b74b4e11fe22) --- .../com/android/server/am/MemoryStatUtil.java | 90 ++++++++++++ .../server/stats/StatsCompanionService.java | 13 +- .../android/server/am/MemoryStatUtilTest.java | 135 ++++++++++++++---- 3 files changed, 212 insertions(+), 26 deletions(-) diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java index 58d9965ed0c6b..95eb2c69024e6 100644 --- a/services/core/java/com/android/server/am/MemoryStatUtil.java +++ b/services/core/java/com/android/server/am/MemoryStatUtil.java @@ -26,12 +26,17 @@ import android.os.SystemProperties; import android.system.Os; import android.system.OsConstants; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -76,6 +81,8 @@ public final class MemoryStatUtil { private static final Pattern ION_HEAP_SIZE_IN_BYTES = Pattern.compile("\n\\s*total\\s*(\\d+)\\s*\n"); + private static final Pattern PROCESS_ION_HEAP_SIZE_IN_BYTES = + Pattern.compile("\n\\s+\\S+\\s+(\\d+)\\s+(\\d+)"); private static final int PGFAULT_INDEX = 9; private static final int PGMAJFAULT_INDEX = 11; @@ -147,6 +154,16 @@ public final class MemoryStatUtil { return parseIonHeapSizeFromDebugfs(readFileContents(DEBUG_SYSTEM_ION_HEAP_FILE)); } + /** + * Reads process allocation sizes on the system ion heap from debugfs. + * + * Returns values of allocation sizes in bytes on the system ion heap from + * /sys/kernel/debug/ion/heaps/system. + */ + public static List readProcessSystemIonHeapSizesFromDebugfs() { + return parseProcessIonHeapSizesFromDebugfs(readFileContents(DEBUG_SYSTEM_ION_HEAP_FILE)); + } + private static String readFileContents(String path) { final File file = new File(path); if (!file.exists()) { @@ -262,6 +279,43 @@ public final class MemoryStatUtil { return tryParseLong(ION_HEAP_SIZE_IN_BYTES, contents); } + /** + * Parses per-process allocation sizes on the ion heap from the contents of a file under + * /sys/kernel/debug/ion/heaps in debugfs. + */ + @VisibleForTesting + static List parseProcessIonHeapSizesFromDebugfs(String contents) { + if (contents == null || contents.isEmpty()) { + return Collections.emptyList(); + } + + final Matcher m = PROCESS_ION_HEAP_SIZE_IN_BYTES.matcher(contents); + final SparseArray entries = new SparseArray<>(); + while (m.find()) { + try { + final int pid = Integer.parseInt(m.group(1)); + final long sizeInBytes = Long.parseLong(m.group(2)); + IonAllocations allocations = entries.get(pid); + if (allocations == null) { + allocations = new IonAllocations(); + entries.put(pid, allocations); + } + allocations.pid = pid; + allocations.totalSizeInBytes += sizeInBytes; + allocations.count += 1; + allocations.maxSizeInBytes = Math.max(allocations.maxSizeInBytes, sizeInBytes); + } catch (NumberFormatException e) { + Slog.e(TAG, "Failed to parse value", e); + } + } + + final List result = new ArrayList<>(entries.size()); + for (int i = 0; i < entries.size(); i++) { + result.add(entries.valueAt(i)); + } + return result; + } + /** * Returns whether per-app memcg is available on device. */ @@ -299,4 +353,40 @@ public final class MemoryStatUtil { /** Device time when the processes started. */ public long startTimeNanos; } + + /** Summary information about process ion allocations. */ + public static final class IonAllocations { + /** PID these allocations belong to. */ + public int pid; + /** Size of all individual allocations added together. */ + public long totalSizeInBytes; + /** Number of allocations. */ + public int count; + /** Size of the largest allocation. */ + public long maxSizeInBytes; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + IonAllocations that = (IonAllocations) o; + return pid == that.pid && totalSizeInBytes == that.totalSizeInBytes + && count == that.count && maxSizeInBytes == that.maxSizeInBytes; + } + + @Override + public int hashCode() { + return Objects.hash(pid, totalSizeInBytes, count, maxSizeInBytes); + } + + @Override + public String toString() { + return "IonAllocations{" + + "pid=" + pid + + ", totalSizeInBytes=" + totalSizeInBytes + + ", count=" + count + + ", maxSizeInBytes=" + maxSizeInBytes + + '}'; + } + } } diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 2f95944f54f11..c76bbb05a3595 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -27,6 +27,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import static com.android.server.am.MemoryStatUtil.readCmdlineFromProcfs; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromProcfs; +import static com.android.server.am.MemoryStatUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.am.MemoryStatUtil.readRssHighWaterMarkFromProcfs; import static com.android.server.am.MemoryStatUtil.readSystemIonHeapSizeFromDebugfs; @@ -137,6 +138,7 @@ import com.android.server.BinderCallsStatsService; import com.android.server.LocalServices; import com.android.server.SystemService; import com.android.server.SystemServiceManager; +import com.android.server.am.MemoryStatUtil.IonAllocations; import com.android.server.am.MemoryStatUtil.MemoryStat; import com.android.server.role.RoleManagerInternal; import com.android.server.storage.DiskStatsFileLogger; @@ -1274,7 +1276,16 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { private void pullProcessSystemIonHeapSize( int tagId, long elapsedNanos, long wallClockNanos, List pulledData) { - // TODO(b/130526489): Read from debugfs. + List result = readProcessSystemIonHeapSizesFromDebugfs(); + for (IonAllocations allocations : result) { + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeInt(getUidForPid(allocations.pid)); + e.writeString(readCmdlineFromProcfs(allocations.pid)); + e.writeInt((int) (allocations.totalSizeInBytes / 1024)); + e.writeInt(allocations.count); + e.writeInt((int) (allocations.maxSizeInBytes / 1024)); + pulledData.add(e); + } } private void pullBinderCallsStats( diff --git a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java index 213c939494c25..6678a7833f9a5 100644 --- a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java +++ b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java @@ -23,13 +23,18 @@ import static com.android.server.am.MemoryStatUtil.parseCmdlineFromProcfs; import static com.android.server.am.MemoryStatUtil.parseIonHeapSizeFromDebugfs; import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg; import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs; +import static com.android.server.am.MemoryStatUtil.parseProcessIonHeapSizesFromDebugfs; import static com.android.server.am.MemoryStatUtil.parseVmHWMFromProcfs; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import androidx.test.filters.SmallTest; +import com.android.server.am.MemoryStatUtil.IonAllocations; + import org.junit.Test; import java.io.ByteArrayOutputStream; @@ -178,32 +183,70 @@ public class MemoryStatUtilTest { + "voluntary_ctxt_switches:\t903\n" + "nonvoluntary_ctxt_switches:\t104\n"; + // Repeated lines have been removed. private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS = String.join( - " client pid size\n", - "----------------------------------------------------\n", - " audio@2.0-servi 765 4096\n", - " audio@2.0-servi 765 61440\n", - " audio@2.0-servi 765 4096\n", - " voip_client 96 8192\n", - " voip_client 96 4096\n", - " system_server 1232 16728064\n", - " surfaceflinger 611 50642944\n", - "----------------------------------------------------\n", - "orphaned allocations (info is from last known client):\n", - "----------------------------------------------------\n", - " total orphaned 0\n", - " total 55193600\n", - " deferred free 0\n", - "----------------------------------------------------\n", - "0 order 4 highmem pages in uncached pool = 0 total\n", - "0 order 4 lowmem pages in uncached pool = 0 total\n", - "1251 order 4 lowmem pages in cached pool = 81985536 total\n", - "VMID 8: 0 order 4 highmem pages in secure pool = 0 total\n", - "VMID 8: 0 order 4 lowmem pages in secure pool = 0 total\n", - "--------------------------------------------\n", - "uncached pool = 4096 cached pool = 83566592 secure pool = 0\n", - "pool total (uncached + cached + secure) = 83570688\n", - "--------------------------------------------\n"); + "\n", + " client pid size", + "----------------------------------------------------", + " audio@2.0-servi 765 4096", + " audio@2.0-servi 765 61440", + " audio@2.0-servi 765 4096", + " voip_client 96 8192", + " voip_client 96 4096", + " system_server 1232 16728064", + " surfaceflinger 611 50642944", + "----------------------------------------------------", + "orphaned allocations (info is from last known client):", + "----------------------------------------------------", + " total orphaned 0", + " total 55193600", + " deferred free 0", + "----------------------------------------------------", + "0 order 4 highmem pages in uncached pool = 0 total", + "0 order 4 lowmem pages in uncached pool = 0 total", + "1251 order 4 lowmem pages in cached pool = 81985536 total", + "VMID 8: 0 order 4 highmem pages in secure pool = 0 total", + "VMID 8: 0 order 4 lowmem pages in secure pool = 0 total", + "--------------------------------------------", + "uncached pool = 4096 cached pool = 83566592 secure pool = 0", + "pool total (uncached + cached + secure) = 83570688", + "--------------------------------------------"); + + // Repeated lines have been removed. + private static final String DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO = String.join( + "\n", + " client pid size page counts" + + "-------------------------------------------------- 4K 8K " + + "16K 32K 64K 128K 256K 512K 1M 2M " + + "4M >=8M", + " system_server 1705 58097664 13120 532 " + + "0 0 0 0 0 0 0 0 " + + "0 0M", + " audio@2.0-servi 851 16384 0 2 0 " + + "0 0 0 0 0 0 0 " + + "0 0M", + " audio@2.0-servi 851 4096 1 0 0 " + + " 0 0 0 0 0 0 0 0 " + + "0M", + " audio@2.0-servi 851 4096 1 0 " + + " 0 0 0 0 0 0 0 0 " + + "0 0M", + "----------------------------------------------------", + "orphaned allocations (info is from last known client):", + "----------------------------------------------------", + " total orphaned 0", + " total 159928320", + " deferred free 0", + "----------------------------------------------------", + "0 order 4 highmem pages in uncached pool = 0 total", + "0 order 4 lowmem pages in uncached pool = 0 total", + "1251 order 4 lowmem pages in cached pool = 81985536 total", + "VMID 8: 0 order 4 highmem pages in secure pool = 0 total", + "VMID 8: 0 order 4 lowmem pages in secure pool = 0 total", + "--------------------------------------------", + "uncached pool = 4096 cached pool = 83566592 secure pool = 0", + "pool total (uncached + cached + secure) = 83570688", + "--------------------------------------------"); @Test public void testParseMemoryStatFromMemcg_parsesCorrectValues() { @@ -323,5 +366,47 @@ public class MemoryStatUtilTest { @Test public void testParseIonHeapSizeFromDebugfs_correctValue() { assertEquals(55193600, parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS)); + + assertEquals(159928320, parseIonHeapSizeFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO)); + } + + @Test + public void testParseProcessIonHeapSizesFromDebugfs_emptyContents() { + assertEquals(0, parseProcessIonHeapSizesFromDebugfs("").size()); + + assertEquals(0, parseProcessIonHeapSizesFromDebugfs(null).size()); + } + + @Test + public void testParseProcessIonHeapSizesFromDebugfs_invalidValue() { + assertEquals(0, parseProcessIonHeapSizesFromDebugfs("<>").size()); + } + + @Test + public void testParseProcessIonHeapSizesFromDebugfs_correctValue1() { + assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS)) + .containsExactly( + createIonAllocations(765, 61440 + 4096 + 4096, 3, 61440), + createIonAllocations(96, 8192 + 4096, 2, 8192), + createIonAllocations(1232, 16728064, 1, 16728064), + createIonAllocations(611, 50642944, 1, 50642944)); + } + + @Test + public void testParseProcessIonHeapSizesFromDebugfs_correctValue2() { + assertThat(parseProcessIonHeapSizesFromDebugfs(DEBUG_SYSTEM_ION_HEAP_CONTENTS_SARGO)) + .containsExactly( + createIonAllocations(1705, 58097664, 1, 58097664), + createIonAllocations(851, 16384 + 4096 + 4096, 3, 16384)); + } + + private static IonAllocations createIonAllocations(int pid, long totalSizeInBytes, int count, + long maxSizeInBytes) { + IonAllocations allocations = new IonAllocations(); + allocations.pid = pid; + allocations.totalSizeInBytes = totalSizeInBytes; + allocations.count = count; + allocations.maxSizeInBytes = maxSizeInBytes; + return allocations; } }