Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2018 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.server.am; |
| 18 | |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 19 | import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; |
| 20 | import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; |
Rafal Slawik | c376262 | 2018-11-07 12:07:26 +0000 | [diff] [blame] | 21 | import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 22 | |
| 23 | import android.annotation.Nullable; |
| 24 | import android.os.FileUtils; |
Rafal Slawik | ba94407 | 2018-09-13 11:34:24 +0100 | [diff] [blame] | 25 | import android.os.SystemProperties; |
Rafal Slawik | bf67d07 | 2018-10-23 11:07:54 +0100 | [diff] [blame] | 26 | import android.system.Os; |
| 27 | import android.system.OsConstants; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 28 | import android.util.Slog; |
| 29 | |
| 30 | import com.android.internal.annotations.VisibleForTesting; |
| 31 | |
| 32 | import java.io.File; |
| 33 | import java.io.IOException; |
Rajeev Kumar | 22d92b7 | 2018-02-07 18:38:36 -0800 | [diff] [blame] | 34 | import java.util.Locale; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 35 | import java.util.regex.Matcher; |
| 36 | import java.util.regex.Pattern; |
| 37 | |
| 38 | /** |
| 39 | * Static utility methods related to {@link MemoryStat}. |
| 40 | */ |
Wale Ogunwale | 387b34c | 2018-10-25 19:59:40 -0700 | [diff] [blame] | 41 | public final class MemoryStatUtil { |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 42 | static final int BYTES_IN_KILOBYTE = 1024; |
Rafal Slawik | c886af9 | 2018-10-01 16:06:39 +0100 | [diff] [blame] | 43 | static final int PAGE_SIZE = 4096; |
Rafal Slawik | bf67d07 | 2018-10-23 11:07:54 +0100 | [diff] [blame] | 44 | static final long JIFFY_NANOS = 1_000_000_000 / Os.sysconf(OsConstants._SC_CLK_TCK); |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 45 | |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 46 | private static final String TAG = TAG_WITH_CLASS_NAME ? "MemoryStatUtil" : TAG_AM; |
| 47 | |
Rafal Slawik | ba94407 | 2018-09-13 11:34:24 +0100 | [diff] [blame] | 48 | /** True if device has per-app memcg */ |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 49 | private static final boolean DEVICE_HAS_PER_APP_MEMCG = |
Rafal Slawik | ba94407 | 2018-09-13 11:34:24 +0100 | [diff] [blame] | 50 | SystemProperties.getBoolean("ro.config.per_app_memcg", false); |
| 51 | |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 52 | /** Path to memory stat file for logging app start memory state */ |
| 53 | private static final String MEMORY_STAT_FILE_FMT = "/dev/memcg/apps/uid_%d/pid_%d/memory.stat"; |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 54 | /** Path to procfs stat file for logging app start memory state */ |
| 55 | private static final String PROC_STAT_FILE_FMT = "/proc/%d/stat"; |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 56 | /** Path to procfs status file for logging app memory state */ |
| 57 | private static final String PROC_STATUS_FILE_FMT = "/proc/%d/status"; |
Rafal Slawik | 0862158 | 2018-10-15 14:53:07 +0100 | [diff] [blame] | 58 | /** Path to procfs cmdline file. Used with pid: /proc/pid/cmdline. */ |
| 59 | private static final String PROC_CMDLINE_FILE_FMT = "/proc/%d/cmdline"; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 60 | |
| 61 | private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)"); |
| 62 | private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)"); |
| 63 | private static final Pattern RSS_IN_BYTES = Pattern.compile("total_rss (\\d+)"); |
| 64 | private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)"); |
| 65 | private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)"); |
| 66 | |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 67 | private static final Pattern RSS_HIGH_WATERMARK_IN_BYTES = |
| 68 | Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB"); |
| 69 | |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 70 | private static final int PGFAULT_INDEX = 9; |
| 71 | private static final int PGMAJFAULT_INDEX = 11; |
Rafal Slawik | bf67d07 | 2018-10-23 11:07:54 +0100 | [diff] [blame] | 72 | private static final int START_TIME_INDEX = 21; |
Rafal Slawik | c886af9 | 2018-10-01 16:06:39 +0100 | [diff] [blame] | 73 | private static final int RSS_IN_PAGES_INDEX = 23; |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 74 | |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 75 | private MemoryStatUtil() {} |
| 76 | |
| 77 | /** |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 78 | * Reads memory stat for a process. |
| 79 | * |
Rafal Slawik | ba94407 | 2018-09-13 11:34:24 +0100 | [diff] [blame] | 80 | * Reads from per-app memcg if available on device, else fallback to procfs. |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 81 | * Returns null if no stats can be read. |
| 82 | */ |
| 83 | @Nullable |
Wale Ogunwale | 387b34c | 2018-10-25 19:59:40 -0700 | [diff] [blame] | 84 | public static MemoryStat readMemoryStatFromFilesystem(int uid, int pid) { |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 85 | return hasMemcg() ? readMemoryStatFromMemcg(uid, pid) : readMemoryStatFromProcfs(pid); |
| 86 | } |
| 87 | |
| 88 | /** |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 89 | * Reads memory.stat of a process from memcg. |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 90 | * |
| 91 | * Returns null if file is not found in memcg or if file has unrecognized contents. |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 92 | */ |
Rajeev Kumar | 22d92b7 | 2018-02-07 18:38:36 -0800 | [diff] [blame] | 93 | @Nullable |
| 94 | static MemoryStat readMemoryStatFromMemcg(int uid, int pid) { |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 95 | final String statPath = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid); |
Rafal Slawik | d03ae42 | 2018-11-20 11:27:45 +0000 | [diff] [blame] | 96 | return parseMemoryStatFromMemcg(readFileContents(statPath)); |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 97 | } |
| 98 | |
| 99 | /** |
| 100 | * Reads memory stat of a process from procfs. |
| 101 | * |
| 102 | * Returns null if file is not found in procfs or if file has unrecognized contents. |
| 103 | */ |
| 104 | @Nullable |
Rafal Slawik | c376262 | 2018-11-07 12:07:26 +0000 | [diff] [blame] | 105 | public static MemoryStat readMemoryStatFromProcfs(int pid) { |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 106 | final String statPath = String.format(Locale.US, PROC_STAT_FILE_FMT, pid); |
Rafal Slawik | d03ae42 | 2018-11-20 11:27:45 +0000 | [diff] [blame] | 107 | return parseMemoryStatFromProcfs(readFileContents(statPath)); |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 108 | } |
| 109 | |
Rafal Slawik | 0862158 | 2018-10-15 14:53:07 +0100 | [diff] [blame] | 110 | /** |
Rafal Slawik | 3bea895 | 2018-11-15 12:39:33 +0000 | [diff] [blame] | 111 | * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in |
| 112 | * /proc/PID/status in bytes or 0 if not available. |
| 113 | */ |
| 114 | public static long readRssHighWaterMarkFromProcfs(int pid) { |
| 115 | final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid); |
| 116 | return parseVmHWMFromProcfs(readFileContents(statusPath)); |
| 117 | } |
| 118 | |
| 119 | /** |
Rafal Slawik | 0862158 | 2018-10-15 14:53:07 +0100 | [diff] [blame] | 120 | * Reads cmdline of a process from procfs. |
| 121 | * |
| 122 | * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string |
| 123 | * if the file is not available. |
| 124 | */ |
Rafal Slawik | c376262 | 2018-11-07 12:07:26 +0000 | [diff] [blame] | 125 | public static String readCmdlineFromProcfs(int pid) { |
Rafal Slawik | 9dc56ac | 2018-12-12 15:21:44 +0000 | [diff] [blame] | 126 | final String path = String.format(Locale.US, PROC_CMDLINE_FILE_FMT, pid); |
| 127 | return parseCmdlineFromProcfs(readFileContents(path)); |
Rafal Slawik | 0862158 | 2018-10-15 14:53:07 +0100 | [diff] [blame] | 128 | } |
| 129 | |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 130 | private static String readFileContents(String path) { |
| 131 | final File file = new File(path); |
| 132 | if (!file.exists()) { |
| 133 | if (DEBUG_METRICS) Slog.i(TAG, path + " not found"); |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 134 | return null; |
| 135 | } |
| 136 | |
| 137 | try { |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 138 | return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */); |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 139 | } catch (IOException e) { |
| 140 | Slog.e(TAG, "Failed to read file:", e); |
| 141 | return null; |
| 142 | } |
| 143 | } |
| 144 | |
| 145 | /** |
| 146 | * Parses relevant statistics out from the contents of a memory.stat file in memcg. |
| 147 | */ |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 148 | @VisibleForTesting |
Rajeev Kumar | 22d92b7 | 2018-02-07 18:38:36 -0800 | [diff] [blame] | 149 | @Nullable |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 150 | static MemoryStat parseMemoryStatFromMemcg(String memoryStatContents) { |
| 151 | if (memoryStatContents == null || memoryStatContents.isEmpty()) { |
| 152 | return null; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 153 | } |
| 154 | |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 155 | final MemoryStat memoryStat = new MemoryStat(); |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 156 | Matcher m; |
| 157 | m = PGFAULT.matcher(memoryStatContents); |
Rafal Slawik | c886af9 | 2018-10-01 16:06:39 +0100 | [diff] [blame] | 158 | memoryStat.pgfault = m.find() ? Long.parseLong(m.group(1)) : 0; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 159 | m = PGMAJFAULT.matcher(memoryStatContents); |
Rafal Slawik | c886af9 | 2018-10-01 16:06:39 +0100 | [diff] [blame] | 160 | memoryStat.pgmajfault = m.find() ? Long.parseLong(m.group(1)) : 0; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 161 | m = RSS_IN_BYTES.matcher(memoryStatContents); |
Rafal Slawik | c886af9 | 2018-10-01 16:06:39 +0100 | [diff] [blame] | 162 | memoryStat.rssInBytes = m.find() ? Long.parseLong(m.group(1)) : 0; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 163 | m = CACHE_IN_BYTES.matcher(memoryStatContents); |
Rafal Slawik | c886af9 | 2018-10-01 16:06:39 +0100 | [diff] [blame] | 164 | memoryStat.cacheInBytes = m.find() ? Long.parseLong(m.group(1)) : 0; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 165 | m = SWAP_IN_BYTES.matcher(memoryStatContents); |
Rafal Slawik | c886af9 | 2018-10-01 16:06:39 +0100 | [diff] [blame] | 166 | memoryStat.swapInBytes = m.find() ? Long.parseLong(m.group(1)) : 0; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 167 | return memoryStat; |
| 168 | } |
| 169 | |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 170 | /** |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 171 | * Parses relevant statistics out from the contents of the /proc/pid/stat file in procfs. |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 172 | */ |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 173 | @VisibleForTesting |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 174 | @Nullable |
| 175 | static MemoryStat parseMemoryStatFromProcfs(String procStatContents) { |
| 176 | if (procStatContents == null || procStatContents.isEmpty()) { |
| 177 | return null; |
| 178 | } |
| 179 | |
| 180 | final String[] splits = procStatContents.split(" "); |
| 181 | if (splits.length < 24) { |
| 182 | return null; |
| 183 | } |
| 184 | |
Rafal Slawik | c886af9 | 2018-10-01 16:06:39 +0100 | [diff] [blame] | 185 | try { |
| 186 | final MemoryStat memoryStat = new MemoryStat(); |
| 187 | memoryStat.pgfault = Long.parseLong(splits[PGFAULT_INDEX]); |
| 188 | memoryStat.pgmajfault = Long.parseLong(splits[PGMAJFAULT_INDEX]); |
| 189 | memoryStat.rssInBytes = Long.parseLong(splits[RSS_IN_PAGES_INDEX]) * PAGE_SIZE; |
Rafal Slawik | bf67d07 | 2018-10-23 11:07:54 +0100 | [diff] [blame] | 190 | memoryStat.startTimeNanos = Long.parseLong(splits[START_TIME_INDEX]) * JIFFY_NANOS; |
Rafal Slawik | c886af9 | 2018-10-01 16:06:39 +0100 | [diff] [blame] | 191 | return memoryStat; |
| 192 | } catch (NumberFormatException e) { |
| 193 | Slog.e(TAG, "Failed to parse value", e); |
| 194 | return null; |
| 195 | } |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 196 | } |
| 197 | |
| 198 | /** |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 199 | * Parses RSS high watermark out from the contents of the /proc/pid/status file in procfs. The |
| 200 | * returned value is in bytes. |
| 201 | */ |
| 202 | @VisibleForTesting |
| 203 | static long parseVmHWMFromProcfs(String procStatusContents) { |
| 204 | if (procStatusContents == null || procStatusContents.isEmpty()) { |
| 205 | return 0; |
| 206 | } |
| 207 | Matcher m = RSS_HIGH_WATERMARK_IN_BYTES.matcher(procStatusContents); |
| 208 | // Convert value read from /proc/pid/status from kilobytes to bytes. |
Rafal Slawik | c886af9 | 2018-10-01 16:06:39 +0100 | [diff] [blame] | 209 | return m.find() ? Long.parseLong(m.group(1)) * BYTES_IN_KILOBYTE : 0; |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 210 | } |
| 211 | |
Rafal Slawik | 9dc56ac | 2018-12-12 15:21:44 +0000 | [diff] [blame] | 212 | |
| 213 | /** |
| 214 | * Parses cmdline out of the contents of the /proc/pid/cmdline file in procfs. |
| 215 | * |
| 216 | * Parsing is required to strip anything after first null byte. |
| 217 | */ |
| 218 | @VisibleForTesting |
| 219 | static String parseCmdlineFromProcfs(String cmdline) { |
| 220 | if (cmdline == null) { |
| 221 | return ""; |
| 222 | } |
| 223 | int firstNullByte = cmdline.indexOf("\0"); |
| 224 | if (firstNullByte == -1) { |
| 225 | return cmdline; |
| 226 | } |
| 227 | return cmdline.substring(0, firstNullByte); |
| 228 | } |
| 229 | |
Rafal Slawik | aaf6089 | 2018-09-12 13:04:30 +0100 | [diff] [blame] | 230 | /** |
Rafal Slawik | ba94407 | 2018-09-13 11:34:24 +0100 | [diff] [blame] | 231 | * Returns whether per-app memcg is available on device. |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 232 | */ |
| 233 | static boolean hasMemcg() { |
Rafal Slawik | ba94407 | 2018-09-13 11:34:24 +0100 | [diff] [blame] | 234 | return DEVICE_HAS_PER_APP_MEMCG; |
Rajeev Kumar | bfcd920 | 2018-03-02 22:42:13 +0000 | [diff] [blame] | 235 | } |
| 236 | |
Wale Ogunwale | 387b34c | 2018-10-25 19:59:40 -0700 | [diff] [blame] | 237 | public static final class MemoryStat { |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 238 | /** Number of page faults */ |
Wale Ogunwale | 5950709 | 2018-10-29 09:00:30 -0700 | [diff] [blame] | 239 | public long pgfault; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 240 | /** Number of major page faults */ |
Wale Ogunwale | 5950709 | 2018-10-29 09:00:30 -0700 | [diff] [blame] | 241 | public long pgmajfault; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 242 | /** Number of bytes of anonymous and swap cache memory */ |
Wale Ogunwale | 5950709 | 2018-10-29 09:00:30 -0700 | [diff] [blame] | 243 | public long rssInBytes; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 244 | /** Number of bytes of page cache memory */ |
Wale Ogunwale | 5950709 | 2018-10-29 09:00:30 -0700 | [diff] [blame] | 245 | public long cacheInBytes; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 246 | /** Number of bytes of swap usage */ |
Wale Ogunwale | 5950709 | 2018-10-29 09:00:30 -0700 | [diff] [blame] | 247 | public long swapInBytes; |
Rafal Slawik | bf67d07 | 2018-10-23 11:07:54 +0100 | [diff] [blame] | 248 | /** Device time when the processes started. */ |
Wale Ogunwale | 5950709 | 2018-10-29 09:00:30 -0700 | [diff] [blame] | 249 | public long startTimeNanos; |
Ng Zhi An | bbefdec | 2018-01-30 17:12:39 -0800 | [diff] [blame] | 250 | } |
Ng Zhi An | f39efa1 | 2018-02-22 14:06:33 -0800 | [diff] [blame] | 251 | } |