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 19 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 20 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 21 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS; 22 23 import android.annotation.Nullable; 24 import android.os.FileUtils; 25 import android.os.SystemProperties; 26 import android.system.Os; 27 import android.system.OsConstants; 28 import android.util.Slog; 29 import android.util.SparseArray; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 33 import java.io.File; 34 import java.io.IOException; 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.List; 38 import java.util.Locale; 39 import java.util.Objects; 40 import java.util.regex.Matcher; 41 import java.util.regex.Pattern; 42 43 /** 44 * Static utility methods related to {@link MemoryStat}. 45 */ 46 public final class MemoryStatUtil { 47 static final int BYTES_IN_KILOBYTE = 1024; 48 static final long JIFFY_NANOS = 1_000_000_000 / Os.sysconf(OsConstants._SC_CLK_TCK); 49 50 private static final String TAG = TAG_WITH_CLASS_NAME ? "MemoryStatUtil" : TAG_AM; 51 52 /** True if device has per-app memcg */ 53 private static final boolean DEVICE_HAS_PER_APP_MEMCG = 54 SystemProperties.getBoolean("ro.config.per_app_memcg", false); 55 56 /** Path to memory stat file for logging app start memory state */ 57 private static final String MEMORY_STAT_FILE_FMT = "/dev/memcg/apps/uid_%d/pid_%d/memory.stat"; 58 /** Path to procfs stat file for logging app start memory state */ 59 private static final String PROC_STAT_FILE_FMT = "/proc/%d/stat"; 60 /** Path to procfs status file for logging app memory state */ 61 private static final String PROC_STATUS_FILE_FMT = "/proc/%d/status"; 62 /** Path to procfs cmdline file. Used with pid: /proc/pid/cmdline. */ 63 private static final String PROC_CMDLINE_FILE_FMT = "/proc/%d/cmdline"; 64 /** Path to debugfs file for the system ion heap. */ 65 private static final String DEBUG_SYSTEM_ION_HEAP_FILE = "/sys/kernel/debug/ion/heaps/system"; 66 67 private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)"); 68 private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)"); 69 private static final Pattern RSS_IN_BYTES = Pattern.compile("total_rss (\\d+)"); 70 private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)"); 71 private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)"); 72 73 private static final Pattern RSS_HIGH_WATERMARK_IN_KILOBYTES = 74 Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB"); 75 private static final Pattern PROCFS_RSS_IN_KILOBYTES = 76 Pattern.compile("VmRSS:\\s*(\\d+)\\s*kB"); 77 private static final Pattern PROCFS_ANON_RSS_IN_KILOBYTES = 78 Pattern.compile("RssAnon:\\s*(\\d+)\\s*kB"); 79 private static final Pattern PROCFS_SWAP_IN_KILOBYTES = 80 Pattern.compile("VmSwap:\\s*(\\d+)\\s*kB"); 81 82 private static final Pattern ION_HEAP_SIZE_IN_BYTES = 83 Pattern.compile("\n\\s*total\\s*(\\d+)\\s*\n"); 84 private static final Pattern PROCESS_ION_HEAP_SIZE_IN_BYTES = 85 Pattern.compile("\n\\s+\\S+\\s+(\\d+)\\s+(\\d+)"); 86 87 private static final int PGFAULT_INDEX = 9; 88 private static final int PGMAJFAULT_INDEX = 11; 89 private static final int START_TIME_INDEX = 21; 90 MemoryStatUtil()91 private MemoryStatUtil() {} 92 93 /** 94 * Reads memory stat for a process. 95 * 96 * Reads from per-app memcg if available on device, else fallback to procfs. 97 * Returns null if no stats can be read. 98 */ 99 @Nullable readMemoryStatFromFilesystem(int uid, int pid)100 public static MemoryStat readMemoryStatFromFilesystem(int uid, int pid) { 101 return hasMemcg() ? readMemoryStatFromMemcg(uid, pid) : readMemoryStatFromProcfs(pid); 102 } 103 104 /** 105 * Reads memory.stat of a process from memcg. 106 * 107 * Returns null if file is not found in memcg or if file has unrecognized contents. 108 */ 109 @Nullable readMemoryStatFromMemcg(int uid, int pid)110 static MemoryStat readMemoryStatFromMemcg(int uid, int pid) { 111 final String statPath = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid); 112 return parseMemoryStatFromMemcg(readFileContents(statPath)); 113 } 114 115 /** 116 * Reads memory stat of a process from procfs. 117 * 118 * Returns null if file is not found in procfs or if file has unrecognized contents. 119 */ 120 @Nullable readMemoryStatFromProcfs(int pid)121 public static MemoryStat readMemoryStatFromProcfs(int pid) { 122 final String statPath = String.format(Locale.US, PROC_STAT_FILE_FMT, pid); 123 final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid); 124 return parseMemoryStatFromProcfs(readFileContents(statPath), readFileContents(statusPath)); 125 } 126 127 /** 128 * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in 129 * /proc/PID/status in bytes or 0 if not available. 130 */ readRssHighWaterMarkFromProcfs(int pid)131 public static long readRssHighWaterMarkFromProcfs(int pid) { 132 final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid); 133 return parseVmHWMFromProcfs(readFileContents(statusPath)); 134 } 135 136 /** 137 * Reads cmdline of a process from procfs. 138 * 139 * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string 140 * if the file is not available. 141 */ readCmdlineFromProcfs(int pid)142 public static String readCmdlineFromProcfs(int pid) { 143 final String path = String.format(Locale.US, PROC_CMDLINE_FILE_FMT, pid); 144 return parseCmdlineFromProcfs(readFileContents(path)); 145 } 146 147 /** 148 * Reads size of the system ion heap from debugfs. 149 * 150 * Returns value of the total size in bytes of the system ion heap from 151 * /sys/kernel/debug/ion/heaps/system. 152 */ readSystemIonHeapSizeFromDebugfs()153 public static long readSystemIonHeapSizeFromDebugfs() { 154 return parseIonHeapSizeFromDebugfs(readFileContents(DEBUG_SYSTEM_ION_HEAP_FILE)); 155 } 156 157 /** 158 * Reads process allocation sizes on the system ion heap from debugfs. 159 * 160 * Returns values of allocation sizes in bytes on the system ion heap from 161 * /sys/kernel/debug/ion/heaps/system. 162 */ readProcessSystemIonHeapSizesFromDebugfs()163 public static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() { 164 return parseProcessIonHeapSizesFromDebugfs(readFileContents(DEBUG_SYSTEM_ION_HEAP_FILE)); 165 } 166 readFileContents(String path)167 private static String readFileContents(String path) { 168 final File file = new File(path); 169 if (!file.exists()) { 170 if (DEBUG_METRICS) Slog.i(TAG, path + " not found"); 171 return null; 172 } 173 174 try { 175 return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */); 176 } catch (IOException e) { 177 Slog.e(TAG, "Failed to read file:", e); 178 return null; 179 } 180 } 181 182 /** 183 * Parses relevant statistics out from the contents of a memory.stat file in memcg. 184 */ 185 @VisibleForTesting 186 @Nullable parseMemoryStatFromMemcg(String memoryStatContents)187 static MemoryStat parseMemoryStatFromMemcg(String memoryStatContents) { 188 if (memoryStatContents == null || memoryStatContents.isEmpty()) { 189 return null; 190 } 191 192 final MemoryStat memoryStat = new MemoryStat(); 193 memoryStat.pgfault = tryParseLong(PGFAULT, memoryStatContents); 194 memoryStat.pgmajfault = tryParseLong(PGMAJFAULT, memoryStatContents); 195 memoryStat.rssInBytes = tryParseLong(RSS_IN_BYTES, memoryStatContents); 196 memoryStat.cacheInBytes = tryParseLong(CACHE_IN_BYTES, memoryStatContents); 197 memoryStat.swapInBytes = tryParseLong(SWAP_IN_BYTES, memoryStatContents); 198 return memoryStat; 199 } 200 201 /** 202 * Parses relevant statistics out from the contents of the /proc/pid/stat file in procfs. 203 */ 204 @VisibleForTesting 205 @Nullable parseMemoryStatFromProcfs( String procStatContents, String procStatusContents)206 static MemoryStat parseMemoryStatFromProcfs( 207 String procStatContents, String procStatusContents) { 208 if (procStatContents == null || procStatContents.isEmpty()) { 209 return null; 210 } 211 if (procStatusContents == null || procStatusContents.isEmpty()) { 212 return null; 213 } 214 215 final String[] splits = procStatContents.split(" "); 216 if (splits.length < 24) { 217 return null; 218 } 219 220 try { 221 final MemoryStat memoryStat = new MemoryStat(); 222 memoryStat.pgfault = Long.parseLong(splits[PGFAULT_INDEX]); 223 memoryStat.pgmajfault = Long.parseLong(splits[PGMAJFAULT_INDEX]); 224 memoryStat.rssInBytes = 225 tryParseLong(PROCFS_RSS_IN_KILOBYTES, procStatusContents) * BYTES_IN_KILOBYTE; 226 memoryStat.anonRssInBytes = 227 tryParseLong(PROCFS_ANON_RSS_IN_KILOBYTES, procStatusContents) * BYTES_IN_KILOBYTE; 228 memoryStat.swapInBytes = 229 tryParseLong(PROCFS_SWAP_IN_KILOBYTES, procStatusContents) * BYTES_IN_KILOBYTE; 230 memoryStat.startTimeNanos = Long.parseLong(splits[START_TIME_INDEX]) * JIFFY_NANOS; 231 return memoryStat; 232 } catch (NumberFormatException e) { 233 Slog.e(TAG, "Failed to parse value", e); 234 return null; 235 } 236 } 237 238 /** 239 * Parses RSS high watermark out from the contents of the /proc/pid/status file in procfs. The 240 * returned value is in bytes. 241 */ 242 @VisibleForTesting parseVmHWMFromProcfs(String procStatusContents)243 static long parseVmHWMFromProcfs(String procStatusContents) { 244 if (procStatusContents == null || procStatusContents.isEmpty()) { 245 return 0; 246 } 247 // Convert value read from /proc/pid/status from kilobytes to bytes. 248 return tryParseLong(RSS_HIGH_WATERMARK_IN_KILOBYTES, procStatusContents) 249 * BYTES_IN_KILOBYTE; 250 } 251 252 253 /** 254 * Parses cmdline out of the contents of the /proc/pid/cmdline file in procfs. 255 * 256 * Parsing is required to strip anything after first null byte. 257 */ 258 @VisibleForTesting parseCmdlineFromProcfs(String cmdline)259 static String parseCmdlineFromProcfs(String cmdline) { 260 if (cmdline == null) { 261 return ""; 262 } 263 int firstNullByte = cmdline.indexOf("\0"); 264 if (firstNullByte == -1) { 265 return cmdline; 266 } 267 return cmdline.substring(0, firstNullByte); 268 } 269 270 /** 271 * Parses the ion heap size from the contents of a file under /sys/kernel/debug/ion/heaps in 272 * debugfs. The returned value is in bytes. 273 */ 274 @VisibleForTesting parseIonHeapSizeFromDebugfs(String contents)275 static long parseIonHeapSizeFromDebugfs(String contents) { 276 if (contents == null || contents.isEmpty()) { 277 return 0; 278 } 279 return tryParseLong(ION_HEAP_SIZE_IN_BYTES, contents); 280 } 281 282 /** 283 * Parses per-process allocation sizes on the ion heap from the contents of a file under 284 * /sys/kernel/debug/ion/heaps in debugfs. 285 */ 286 @VisibleForTesting parseProcessIonHeapSizesFromDebugfs(String contents)287 static List<IonAllocations> parseProcessIonHeapSizesFromDebugfs(String contents) { 288 if (contents == null || contents.isEmpty()) { 289 return Collections.emptyList(); 290 } 291 292 final Matcher m = PROCESS_ION_HEAP_SIZE_IN_BYTES.matcher(contents); 293 final SparseArray<IonAllocations> entries = new SparseArray<>(); 294 while (m.find()) { 295 try { 296 final int pid = Integer.parseInt(m.group(1)); 297 final long sizeInBytes = Long.parseLong(m.group(2)); 298 IonAllocations allocations = entries.get(pid); 299 if (allocations == null) { 300 allocations = new IonAllocations(); 301 entries.put(pid, allocations); 302 } 303 allocations.pid = pid; 304 allocations.totalSizeInBytes += sizeInBytes; 305 allocations.count += 1; 306 allocations.maxSizeInBytes = Math.max(allocations.maxSizeInBytes, sizeInBytes); 307 } catch (NumberFormatException e) { 308 Slog.e(TAG, "Failed to parse value", e); 309 } 310 } 311 312 final List<IonAllocations> result = new ArrayList<>(entries.size()); 313 for (int i = 0; i < entries.size(); i++) { 314 result.add(entries.valueAt(i)); 315 } 316 return result; 317 } 318 319 /** 320 * Returns whether per-app memcg is available on device. 321 */ hasMemcg()322 static boolean hasMemcg() { 323 return DEVICE_HAS_PER_APP_MEMCG; 324 } 325 326 /** 327 * Parses a long from the input using the pattern. Returns 0 if the captured value is not 328 * parsable. The pattern must have a single capturing group. 329 */ tryParseLong(Pattern pattern, String input)330 private static long tryParseLong(Pattern pattern, String input) { 331 final Matcher m = pattern.matcher(input); 332 try { 333 return m.find() ? Long.parseLong(m.group(1)) : 0; 334 } catch (NumberFormatException e) { 335 Slog.e(TAG, "Failed to parse value", e); 336 return 0; 337 } 338 } 339 340 public static final class MemoryStat { 341 /** Number of page faults */ 342 public long pgfault; 343 /** Number of major page faults */ 344 public long pgmajfault; 345 /** For memcg stats, the anon rss + swap cache size. Otherwise total RSS. */ 346 public long rssInBytes; 347 /** Number of bytes of the anonymous RSS. Only present for non-memcg stats. */ 348 public long anonRssInBytes; 349 /** Number of bytes of page cache memory. Only present for memcg stats. */ 350 public long cacheInBytes; 351 /** Number of bytes of swap usage */ 352 public long swapInBytes; 353 /** Device time when the processes started. */ 354 public long startTimeNanos; 355 } 356 357 /** Summary information about process ion allocations. */ 358 public static final class IonAllocations { 359 /** PID these allocations belong to. */ 360 public int pid; 361 /** Size of all individual allocations added together. */ 362 public long totalSizeInBytes; 363 /** Number of allocations. */ 364 public int count; 365 /** Size of the largest allocation. */ 366 public long maxSizeInBytes; 367 368 @Override equals(Object o)369 public boolean equals(Object o) { 370 if (this == o) return true; 371 if (o == null || getClass() != o.getClass()) return false; 372 IonAllocations that = (IonAllocations) o; 373 return pid == that.pid && totalSizeInBytes == that.totalSizeInBytes 374 && count == that.count && maxSizeInBytes == that.maxSizeInBytes; 375 } 376 377 @Override hashCode()378 public int hashCode() { 379 return Objects.hash(pid, totalSizeInBytes, count, maxSizeInBytes); 380 } 381 382 @Override toString()383 public String toString() { 384 return "IonAllocations{" 385 + "pid=" + pid 386 + ", totalSizeInBytes=" + totalSizeInBytes 387 + ", count=" + count 388 + ", maxSizeInBytes=" + maxSizeInBytes 389 + '}'; 390 } 391 } 392 } 393