From 7efcefb5d759222ba01c179ac9bc3b729508eb4a Mon Sep 17 00:00:00 2001 From: Rafal Slawik Date: Fri, 20 Sep 2019 11:16:20 +0100 Subject: [PATCH] Add ProcessMemorySnapshot atom Introduce new atom to replace existing ProcessMemoryState and NativeProcessMemoryState ones for telemetry purposes. This change is the first step to decouple memory stats from the framework internals. Follow-up CLs will remove the telemetry parts from the existing atoms. Test: atest ProcfsMemoryUtilTest Test: statsd_testdrive 10064 Change-Id: I7959c6e473201c10e7da17fa49d864bb03a2403c --- cmds/statsd/src/atoms.proto | 43 +++++++++++++- .../src/external/StatsPullerManager.cpp | 3 + .../server/stats/ProcfsMemoryUtil.java | 58 +++++++++++++++---- .../server/stats/StatsCompanionService.java | 47 +++++++++++++++ .../server/stats/ProcfsMemoryUtilTest.java | 31 ++++++++++ 5 files changed, 171 insertions(+), 11 deletions(-) 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(); + } }