diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto index 78a6609e5e0d7..4cdc38370ebef 100644 --- a/cmds/statsd/src/atoms.proto +++ b/cmds/statsd/src/atoms.proto @@ -340,7 +340,7 @@ message Atom { } // Pulled events will start at field 10000. - // Next: 10064 + // Next: 10065 oneof pulled { WifiBytesTransfer wifi_bytes_transfer = 10000; WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001; @@ -406,6 +406,7 @@ message Atom { ProcessSystemIonHeapSize process_system_ion_heap_size = 10061; SurfaceflingerStatsGlobalInfo surfaceflinger_stats_global_info = 10062; SurfaceflingerStatsLayerInfo surfaceflinger_stats_layer_info = 10063; + ProcessMemorySnapshot process_memory_snapshot = 10064; } // DO NOT USE field numbers above 100,000 in AOSP. @@ -4107,6 +4108,46 @@ message ProcessMemoryHighWaterMark { optional int32 rss_high_water_mark_in_kilobytes = 4; } +/* + * Logs the memory stats for a process. + * + * Pulled from StatsCompanionService for all managed processes (from ActivityManagerService) + * and for selected native processes. + */ +message ProcessMemorySnapshot { + // The uid if available. -1 means not available. + optional int32 uid = 1 [(is_uid) = true]; + + // The process name. + // Usually package name or process cmdline. + // Provided by ActivityManagerService or read from /proc/PID/cmdline. + optional string process_name = 2; + + // The pid of the process. + // Allows to disambiguate instances of the process. + optional int32 pid = 3; + + // The current OOM score adjustment value. + // Read from ProcessRecord for managed processes. + // Placeholder -1001 (OOM_SCORE_ADJ_MIN - 1, outside of allowed range) for native ones. + optional int32 oom_score_adj = 4; + + // The current RSS of the process. + // VmRSS from /proc/pid/status. + optional int32 rss_in_kilobytes = 5; + + // The current anon RSS of the process. + // RssAnon from /proc/pid/status. + optional int32 anon_rss_in_kilobytes = 6; + + // The current swap size of the process. + // VmSwap from /proc/pid/status. + optional int32 swap_in_kilobytes = 7; + + // The sum of rss_in_kilobytes and swap_in_kilobytes. + optional int32 anon_rss_and_swap_in_kilobytes = 8; +} + /* * Elapsed real time from SystemClock. */ diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp index 475f18a9b0b8d..f69e2d09ad23a 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -153,6 +153,9 @@ std::map StatsPullerManager::kAllPullAtomInfo = { {.additiveFields = {3}, .puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}}, + // process_memory_snapshot + {android::util::PROCESS_MEMORY_SNAPSHOT, + {.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}}, // system_ion_heap_size {android::util::SYSTEM_ION_HEAP_SIZE, {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}}, diff --git a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java index 3076284a80edb..d49b9589cb157 100644 --- a/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java +++ b/services/core/java/com/android/server/stats/ProcfsMemoryUtil.java @@ -34,6 +34,12 @@ final class ProcfsMemoryUtil { private static final Pattern RSS_HIGH_WATER_MARK_IN_KILOBYTES = Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB"); + private static final Pattern RSS_IN_KILOBYTES = + Pattern.compile("VmRSS:\\s*(\\d+)\\s*kB"); + private static final Pattern ANON_RSS_IN_KILOBYTES = + Pattern.compile("RssAnon:\\s*(\\d+)\\s*kB"); + private static final Pattern SWAP_IN_KILOBYTES = + Pattern.compile("VmSwap:\\s*(\\d+)\\s*kB"); private ProcfsMemoryUtil() {} @@ -52,16 +58,25 @@ final class ProcfsMemoryUtil { */ @VisibleForTesting static int parseVmHWMFromStatus(String contents) { - if (contents.isEmpty()) { - return 0; - } - final Matcher matcher = RSS_HIGH_WATER_MARK_IN_KILOBYTES.matcher(contents); - try { - return matcher.find() ? Integer.parseInt(matcher.group(1)) : 0; - } catch (NumberFormatException e) { - Slog.e(TAG, "Failed to parse value", e); - return 0; - } + return tryParseInt(contents, RSS_HIGH_WATER_MARK_IN_KILOBYTES); + } + + /** + * Reads memory stat of a process from procfs. Returns values of the VmRss, AnonRSS, VmSwap + * fields in /proc/pid/status in kilobytes or 0 if not available. + */ + static MemorySnapshot readMemorySnapshotFromProcfs(int pid) { + final String statusPath = String.format(Locale.US, STATUS_FILE_FMT, pid); + return parseMemorySnapshotFromStatus(readFile(statusPath)); + } + + @VisibleForTesting + static MemorySnapshot parseMemorySnapshotFromStatus(String contents) { + final MemorySnapshot snapshot = new MemorySnapshot(); + snapshot.rssInKilobytes = tryParseInt(contents, RSS_IN_KILOBYTES); + snapshot.anonRssInKilobytes = tryParseInt(contents, ANON_RSS_IN_KILOBYTES); + snapshot.swapInKilobytes = tryParseInt(contents, SWAP_IN_KILOBYTES); + return snapshot; } private static String readFile(String path) { @@ -72,4 +87,27 @@ final class ProcfsMemoryUtil { return ""; } } + + private static int tryParseInt(String contents, Pattern pattern) { + if (contents.isEmpty()) { + return 0; + } + final Matcher matcher = pattern.matcher(contents); + try { + return matcher.find() ? Integer.parseInt(matcher.group(1)) : 0; + } catch (NumberFormatException e) { + Slog.e(TAG, "Failed to parse value", e); + return 0; + } + } + + static final class MemorySnapshot { + public int rssInKilobytes; + public int anonRssInKilobytes; + public int swapInKilobytes; + + boolean isEmpty() { + return (anonRssInKilobytes + swapInKilobytes) == 0; + } + } } diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java index 19b80556a779f..e1a48ed3b5502 100644 --- a/services/core/java/com/android/server/stats/StatsCompanionService.java +++ b/services/core/java/com/android/server/stats/StatsCompanionService.java @@ -29,6 +29,7 @@ import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromProcfs; import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; +import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs; import static com.android.server.stats.ProcfsMemoryUtil.readRssHighWaterMarkFromProcfs; import android.annotation.NonNull; @@ -141,6 +142,7 @@ import com.android.server.SystemServiceManager; import com.android.server.am.MemoryStatUtil.MemoryStat; import com.android.server.role.RoleManagerInternal; import com.android.server.stats.IonMemoryUtil.IonAllocations; +import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot; import com.android.server.storage.DiskStatsFileLogger; import com.android.server.storage.DiskStatsLoggingService; @@ -1270,6 +1272,47 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { SystemProperties.set("sys.rss_hwm_reset.on", "1"); } + private void pullProcessMemorySnapshot( + int tagId, long elapsedNanos, long wallClockNanos, + List pulledData) { + List managedProcessList = + LocalServices.getService( + ActivityManagerInternal.class).getMemoryStateForProcesses(); + for (ProcessMemoryState managedProcess : managedProcessList) { + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeInt(managedProcess.uid); + e.writeString(managedProcess.processName); + e.writeInt(managedProcess.pid); + e.writeInt(managedProcess.oomScore); + final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(managedProcess.pid); + if (snapshot.isEmpty()) { + continue; + } + e.writeInt(snapshot.rssInKilobytes); + e.writeInt(snapshot.anonRssInKilobytes); + e.writeInt(snapshot.swapInKilobytes); + e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes); + pulledData.add(e); + } + int[] pids = getPidsForCommands(MEMORY_INTERESTING_NATIVE_PROCESSES); + for (int pid : pids) { + StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos); + e.writeInt(getUidForPid(pid)); + e.writeString(readCmdlineFromProcfs(pid)); + e.writeInt(pid); + e.writeInt(-1001); // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1. + final MemorySnapshot snapshot = readMemorySnapshotFromProcfs(pid); + if (snapshot.isEmpty()) { + continue; + } + e.writeInt(snapshot.rssInKilobytes); + e.writeInt(snapshot.anonRssInKilobytes); + e.writeInt(snapshot.swapInKilobytes); + e.writeInt(snapshot.anonRssInKilobytes + snapshot.swapInKilobytes); + pulledData.add(e); + } + } + private void pullSystemIonHeapSize( int tagId, long elapsedNanos, long wallClockNanos, List pulledData) { @@ -2352,6 +2395,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub { pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret); break; } + case StatsLog.PROCESS_MEMORY_SNAPSHOT: { + pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret); + break; + } case StatsLog.SYSTEM_ION_HEAP_SIZE: { pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret); break; diff --git a/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java index 4fb533f726b2f..ae57774035280 100644 --- a/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java +++ b/services/tests/servicestests/src/com/android/server/stats/ProcfsMemoryUtilTest.java @@ -15,12 +15,15 @@ */ package com.android.server.stats; +import static com.android.server.stats.ProcfsMemoryUtil.parseMemorySnapshotFromStatus; import static com.android.server.stats.ProcfsMemoryUtil.parseVmHWMFromStatus; import static com.google.common.truth.Truth.assertThat; import androidx.test.filters.SmallTest; +import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot; + import org.junit.Test; /** @@ -90,4 +93,32 @@ public class ProcfsMemoryUtilTest { public void testParseVmHWMFromStatus_emptyContents() { assertThat(parseVmHWMFromStatus("")).isEqualTo(0); } + + @Test + public void testParseMemorySnapshotFromStatus_parsesCorrectValue() { + MemorySnapshot snapshot = parseMemorySnapshotFromStatus(STATUS_CONTENTS); + assertThat(snapshot.rssInKilobytes).isEqualTo(126776); + assertThat(snapshot.anonRssInKilobytes).isEqualTo(37860); + assertThat(snapshot.swapInKilobytes).isEqualTo(22); + assertThat(snapshot.isEmpty()).isFalse(); + } + + @Test + public void testParseMemorySnapshotFromStatus_invalidValue() { + MemorySnapshot snapshot = + parseMemorySnapshotFromStatus("test\nVmRSS:\tx0x0x\nVmSwap:\t1 kB\ntest"); + assertThat(snapshot.rssInKilobytes).isEqualTo(0); + assertThat(snapshot.anonRssInKilobytes).isEqualTo(0); + assertThat(snapshot.swapInKilobytes).isEqualTo(1); + assertThat(snapshot.isEmpty()).isFalse(); + } + + @Test + public void testParseMemorySnapshotFromStatus_emptyContents() { + MemorySnapshot snapshot = parseMemorySnapshotFromStatus(""); + assertThat(snapshot.rssInKilobytes).isEqualTo(0); + assertThat(snapshot.anonRssInKilobytes).isEqualTo(0); + assertThat(snapshot.swapInKilobytes).isEqualTo(0); + assertThat(snapshot.isEmpty()).isTrue(); + } }