diff options
7 files changed, 150 insertions, 18 deletions
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index ffd7d3161202..448608e9ece7 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -2220,6 +2220,11 @@ message ProcessMemoryState { // SWAP optional int64 swap_in_bytes = 8; + + // RSS high watermark. + // Peak RSS usage of the process. Value is read from the VmHWM field in /proc/PID/status or + // from memory.max_usage_in_bytes under /dev/memcg if the device uses per-app memory cgroups. + optional int64 rss_high_watermark_in_bytes = 9; } /* diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 5a0172b22301..66392f80f1fe 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -169,7 +169,7 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}}, // process_memory_state {android::util::PROCESS_MEMORY_STATE, - {{4, 5, 6, 7, 8}, + {{4, 5, 6, 7, 8, 9}, {2, 3}, 1 * NS_PER_SEC, new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}}, diff --git a/core/java/android/app/ProcessMemoryState.java b/core/java/android/app/ProcessMemoryState.java index e0aed16b8abf..9bfdae0d8c03 100644 --- a/core/java/android/app/ProcessMemoryState.java +++ b/core/java/android/app/ProcessMemoryState.java @@ -32,10 +32,11 @@ public final class ProcessMemoryState implements Parcelable { public final long rssInBytes; public final long cacheInBytes; public final long swapInBytes; + public final long rssHighWatermarkInBytes; public ProcessMemoryState(int uid, String processName, int oomScore, long pgfault, long pgmajfault, long rssInBytes, long cacheInBytes, - long swapInBytes) { + long swapInBytes, long rssHighWatermarkInBytes) { this.uid = uid; this.processName = processName; this.oomScore = oomScore; @@ -44,6 +45,7 @@ public final class ProcessMemoryState implements Parcelable { this.rssInBytes = rssInBytes; this.cacheInBytes = cacheInBytes; this.swapInBytes = swapInBytes; + this.rssHighWatermarkInBytes = rssHighWatermarkInBytes; } private ProcessMemoryState(Parcel in) { @@ -55,6 +57,7 @@ public final class ProcessMemoryState implements Parcelable { rssInBytes = in.readLong(); cacheInBytes = in.readLong(); swapInBytes = in.readLong(); + rssHighWatermarkInBytes = in.readLong(); } public static final Creator<ProcessMemoryState> CREATOR = new Creator<ProcessMemoryState>() { @@ -84,5 +87,6 @@ public final class ProcessMemoryState implements Parcelable { parcel.writeLong(rssInBytes); parcel.writeLong(cacheInBytes); parcel.writeLong(swapInBytes); + parcel.writeLong(rssHighWatermarkInBytes); } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8c7fc849b79e..6e3ea9fad599 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -20892,7 +20892,8 @@ public class ActivityManagerService extends IActivityManager.Stub memoryStat.pgmajfault, memoryStat.rssInBytes, memoryStat.cacheInBytes, - memoryStat.swapInBytes); + memoryStat.swapInBytes, + memoryStat.rssHighWatermarkInBytes); processMemoryStates.add(processMemoryState); } } diff --git a/services/core/java/com/android/server/am/MemoryStatUtil.java b/services/core/java/com/android/server/am/MemoryStatUtil.java index aad890b8bd74..228c71d91b8f 100644 --- a/services/core/java/com/android/server/am/MemoryStatUtil.java +++ b/services/core/java/com/android/server/am/MemoryStatUtil.java @@ -37,18 +37,25 @@ import java.util.regex.Pattern; * Static utility methods related to {@link MemoryStat}. */ final class MemoryStatUtil { + static final int BYTES_IN_KILOBYTE = 1024; + private static final String TAG = TAG_WITH_CLASS_NAME ? "MemoryStatUtil" : TAG_AM; /** True if device has per-app memcg */ - private static final Boolean DEVICE_HAS_PER_APP_MEMCG = + private static final boolean DEVICE_HAS_PER_APP_MEMCG = SystemProperties.getBoolean("ro.config.per_app_memcg", false); /** Path to check if device has memcg */ private static final String MEMCG_TEST_PATH = "/dev/memcg/apps/memory.stat"; /** Path to memory stat file for logging app start memory state */ private static final String MEMORY_STAT_FILE_FMT = "/dev/memcg/apps/uid_%d/pid_%d/memory.stat"; + /** Path to memory max usage file for logging app memory state */ + private static final String MEMORY_MAX_USAGE_FILE_FMT = + "/dev/memcg/apps/uid_%d/pid_%d/memory.max_usage_in_bytes"; /** Path to procfs stat file for logging app start memory state */ private static final String PROC_STAT_FILE_FMT = "/proc/%d/stat"; + /** Path to procfs status file for logging app memory state */ + private static final String PROC_STATUS_FILE_FMT = "/proc/%d/status"; private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)"); private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)"); @@ -56,6 +63,9 @@ final class MemoryStatUtil { private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)"); private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)"); + private static final Pattern RSS_HIGH_WATERMARK_IN_BYTES = + Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB"); + private static final int PGFAULT_INDEX = 9; private static final int PGMAJFAULT_INDEX = 11; private static final int RSS_IN_BYTES_INDEX = 23; @@ -80,8 +90,15 @@ final class MemoryStatUtil { */ @Nullable static MemoryStat readMemoryStatFromMemcg(int uid, int pid) { - final String path = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid); - return parseMemoryStatFromMemcg(readFileContents(path)); + final String statPath = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid); + MemoryStat stat = parseMemoryStatFromMemcg(readFileContents(statPath)); + if (stat == null) { + return null; + } + String maxUsagePath = String.format(Locale.US, MEMORY_MAX_USAGE_FILE_FMT, uid, pid); + stat.rssHighWatermarkInBytes = parseMemoryMaxUsageFromMemCg( + readFileContents(maxUsagePath)); + return stat; } /** @@ -91,8 +108,14 @@ final class MemoryStatUtil { */ @Nullable static MemoryStat readMemoryStatFromProcfs(int pid) { - final String path = String.format(Locale.US, PROC_STAT_FILE_FMT, pid); - return parseMemoryStatFromProcfs(readFileContents(path)); + final String statPath = String.format(Locale.US, PROC_STAT_FILE_FMT, pid); + MemoryStat stat = parseMemoryStatFromProcfs(readFileContents(statPath)); + if (stat == null) { + return null; + } + final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid); + stat.rssHighWatermarkInBytes = parseVmHWMFromProcfs(readFileContents(statusPath)); + return stat; } private static String readFileContents(String path) { @@ -113,7 +136,7 @@ final class MemoryStatUtil { /** * Parses relevant statistics out from the contents of a memory.stat file in memcg. */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @VisibleForTesting @Nullable static MemoryStat parseMemoryStatFromMemcg(String memoryStatContents) { if (memoryStatContents == null || memoryStatContents.isEmpty()) { @@ -135,10 +158,18 @@ final class MemoryStatUtil { return memoryStat; } + @VisibleForTesting + static long parseMemoryMaxUsageFromMemCg(String memoryMaxUsageContents) { + if (memoryMaxUsageContents == null || memoryMaxUsageContents.isEmpty()) { + return 0; + } + return Long.valueOf(memoryMaxUsageContents); + } + /** - * Parses relevant statistics out from the contents of a /proc/pid/stat file in procfs. + * Parses relevant statistics out from the contents of the /proc/pid/stat file in procfs. */ - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + @VisibleForTesting @Nullable static MemoryStat parseMemoryStatFromProcfs(String procStatContents) { if (procStatContents == null || procStatContents.isEmpty()) { @@ -158,6 +189,20 @@ final class MemoryStatUtil { } /** + * Parses RSS high watermark out from the contents of the /proc/pid/status file in procfs. The + * returned value is in bytes. + */ + @VisibleForTesting + static long parseVmHWMFromProcfs(String procStatusContents) { + if (procStatusContents == null || procStatusContents.isEmpty()) { + return 0; + } + Matcher m = RSS_HIGH_WATERMARK_IN_BYTES.matcher(procStatusContents); + // Convert value read from /proc/pid/status from kilobytes to bytes. + return m.find() ? Long.valueOf(m.group(1)) * BYTES_IN_KILOBYTE : 0; + } + + /** * Returns whether per-app memcg is available on device. */ static boolean hasMemcg() { @@ -175,5 +220,7 @@ final class MemoryStatUtil { long cacheInBytes; /** Number of bytes of swap usage */ long swapInBytes; + /** Number of bytes of peak anonymous and swap cache memory */ + long rssHighWatermarkInBytes; } } diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index bfa03ca9f2be..c6e6449313c0 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -966,6 +966,7 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { e.writeLong(processMemoryState.rssInBytes); e.writeLong(processMemoryState.cacheInBytes); e.writeLong(processMemoryState.swapInBytes); + e.writeLong(processMemoryState.rssHighWatermarkInBytes); pulledData.add(e); } } 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 06c74371eb51..e8a824a12300 100644 --- a/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java +++ b/services/tests/servicestests/src/com/android/server/am/MemoryStatUtilTest.java @@ -16,9 +16,12 @@ package com.android.server.am; +import static com.android.server.am.MemoryStatUtil.BYTES_IN_KILOBYTE; import static com.android.server.am.MemoryStatUtil.MemoryStat; +import static com.android.server.am.MemoryStatUtil.parseMemoryMaxUsageFromMemCg; import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromMemcg; import static com.android.server.am.MemoryStatUtil.parseMemoryStatFromProcfs; +import static com.android.server.am.MemoryStatUtil.parseVmHWMFromProcfs; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -32,7 +35,7 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) @SmallTest public class MemoryStatUtilTest { - private String MEMORY_STAT_CONTENTS = String.join( + private static final String MEMORY_STAT_CONTENTS = String.join( "\n", "cache 96", // keep different from total_cache to catch reading wrong value "rss 97", // keep different from total_rss to catch reading wrong value @@ -67,7 +70,7 @@ public class MemoryStatUtilTest { "total_active_file 81920", "total_unevictable 0"); - private String PROC_STAT_CONTENTS = String.join( + private static final String PROC_STAT_CONTENTS = String.join( " ", "1040", "(system_server)", @@ -122,14 +125,61 @@ public class MemoryStatUtilTest { "3198889956", "0"); + private static final String PROC_STATUS_CONTENTS = "Name:\tandroid.youtube\n" + + "State:\tS (sleeping)\n" + + "Tgid:\t12088\n" + + "Pid:\t12088\n" + + "PPid:\t723\n" + + "TracerPid:\t0\n" + + "Uid:\t10083\t10083\t10083\t10083\n" + + "Gid:\t10083\t10083\t10083\t10083\n" + + "Ngid:\t0\n" + + "FDSize:\t128\n" + + "Groups:\t3003 9997 20083 50083 \n" + + "VmPeak:\t 4546844 kB\n" + + "VmSize:\t 4542636 kB\n" + + "VmLck:\t 0 kB\n" + + "VmPin:\t 0 kB\n" + + "VmHWM:\t 137668 kB\n" // RSS high watermark + + "VmRSS:\t 126776 kB\n" + + "RssAnon:\t 37860 kB\n" + + "RssFile:\t 88764 kB\n" + + "RssShmem:\t 152 kB\n" + + "VmData:\t 4125112 kB\n" + + "VmStk:\t 8192 kB\n" + + "VmExe:\t 24 kB\n" + + "VmLib:\t 102432 kB\n" + + "VmPTE:\t 1300 kB\n" + + "VmPMD:\t 36 kB\n" + + "VmSwap:\t 0 kB\n" + + "Threads:\t95\n" + + "SigQ:\t0/13641\n" + + "SigPnd:\t0000000000000000\n" + + "ShdPnd:\t0000000000000000\n" + + "SigBlk:\t0000000000001204\n" + + "SigIgn:\t0000000000000001\n" + + "SigCgt:\t00000006400084f8\n" + + "CapInh:\t0000000000000000\n" + + "CapPrm:\t0000000000000000\n" + + "CapEff:\t0000000000000000\n" + + "CapBnd:\t0000000000000000\n" + + "CapAmb:\t0000000000000000\n" + + "Seccomp:\t2\n" + + "Cpus_allowed:\tff\n" + + "Cpus_allowed_list:\t0-7\n" + + "Mems_allowed:\t1\n" + + "Mems_allowed_list:\t0\n" + + "voluntary_ctxt_switches:\t903\n" + + "nonvoluntary_ctxt_switches:\t104\n"; + @Test public void testParseMemoryStatFromMemcg_parsesCorrectValues() throws Exception { MemoryStat stat = parseMemoryStatFromMemcg(MEMORY_STAT_CONTENTS); - assertEquals(stat.pgfault, 1); - assertEquals(stat.pgmajfault, 2); - assertEquals(stat.rssInBytes, 3); - assertEquals(stat.cacheInBytes, 4); - assertEquals(stat.swapInBytes, 5); + assertEquals(1, stat.pgfault); + assertEquals(2, stat.pgmajfault); + assertEquals(3, stat.rssInBytes); + assertEquals(4, stat.cacheInBytes); + assertEquals(5, stat.swapInBytes); } @Test @@ -142,6 +192,18 @@ public class MemoryStatUtilTest { } @Test + public void testParseMemoryMaxUsageFromMemCg_parsesCorrectValue() { + assertEquals(1234, parseMemoryMaxUsageFromMemCg("1234")); + } + + @Test + public void testParseMemoryMaxUsageFromMemCg_emptyContents() { + assertEquals(0, parseMemoryMaxUsageFromMemCg("")); + + assertEquals(0, parseMemoryMaxUsageFromMemCg(null)); + } + + @Test public void testParseMemoryStatFromProcfs_parsesCorrectValues() throws Exception { MemoryStat stat = parseMemoryStatFromProcfs(PROC_STAT_CONTENTS); assertEquals(1, stat.pgfault); @@ -159,4 +221,16 @@ public class MemoryStatUtilTest { stat = parseMemoryStatFromProcfs(null); assertNull(stat); } + + @Test + public void testParseVmHWMFromProcfs_parsesCorrectValue() { + assertEquals(137668, parseVmHWMFromProcfs(PROC_STATUS_CONTENTS) / BYTES_IN_KILOBYTE); + } + + @Test + public void testParseVmHWMFromProcfs_emptyContents() { + assertEquals(0, parseVmHWMFromProcfs("")); + + assertEquals(0, parseVmHWMFromProcfs(null)); + } } |