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 30 import com.android.internal.annotations.VisibleForTesting; 31 32 import java.io.File; 33 import java.io.IOException; 34 import java.util.Locale; 35 import java.util.regex.Matcher; 36 import java.util.regex.Pattern; 37 38 /** 39 * Static utility methods related to {@link MemoryStat}. 40 */ 41 public final class MemoryStatUtil { 42 static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE); 43 44 private static final String TAG = TAG_WITH_CLASS_NAME ? "MemoryStatUtil" : TAG_AM; 45 46 /** True if device has per-app memcg */ 47 private static final boolean DEVICE_HAS_PER_APP_MEMCG = 48 SystemProperties.getBoolean("ro.config.per_app_memcg", false); 49 50 /** Path to memory stat file for logging app start memory state */ 51 private static final String MEMORY_STAT_FILE_FMT = "/dev/memcg/apps/uid_%d/pid_%d/memory.stat"; 52 /** Path to procfs stat file for logging app start memory state */ 53 private static final String PROC_STAT_FILE_FMT = "/proc/%d/stat"; 54 55 private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)"); 56 private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)"); 57 private static final Pattern RSS_IN_BYTES = Pattern.compile("total_rss (\\d+)"); 58 private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)"); 59 private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)"); 60 61 private static final int PGFAULT_INDEX = 9; 62 private static final int PGMAJFAULT_INDEX = 11; 63 private static final int RSS_IN_PAGES_INDEX = 23; 64 MemoryStatUtil()65 private MemoryStatUtil() {} 66 67 /** 68 * Reads memory stat for a process. 69 * 70 * Reads from per-app memcg if available on device, else fallback to procfs. 71 * Returns null if no stats can be read. 72 */ 73 @Nullable readMemoryStatFromFilesystem(int uid, int pid)74 public static MemoryStat readMemoryStatFromFilesystem(int uid, int pid) { 75 return hasMemcg() ? readMemoryStatFromMemcg(uid, pid) : readMemoryStatFromProcfs(pid); 76 } 77 78 /** 79 * Reads memory.stat of a process from memcg. 80 * 81 * Returns null if file is not found in memcg or if file has unrecognized contents. 82 */ 83 @Nullable readMemoryStatFromMemcg(int uid, int pid)84 static MemoryStat readMemoryStatFromMemcg(int uid, int pid) { 85 final String statPath = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid); 86 return parseMemoryStatFromMemcg(readFileContents(statPath)); 87 } 88 89 /** 90 * Reads memory stat of a process from procfs. 91 * 92 * Returns null if file is not found in procfs or if file has unrecognized contents. 93 */ 94 @Nullable readMemoryStatFromProcfs(int pid)95 public static MemoryStat readMemoryStatFromProcfs(int pid) { 96 final String statPath = String.format(Locale.US, PROC_STAT_FILE_FMT, pid); 97 return parseMemoryStatFromProcfs(readFileContents(statPath)); 98 } 99 readFileContents(String path)100 private static String readFileContents(String path) { 101 final File file = new File(path); 102 if (!file.exists()) { 103 if (DEBUG_METRICS) Slog.i(TAG, path + " not found"); 104 return null; 105 } 106 107 try { 108 return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */); 109 } catch (IOException e) { 110 Slog.e(TAG, "Failed to read file:", e); 111 return null; 112 } 113 } 114 115 /** 116 * Parses relevant statistics out from the contents of a memory.stat file in memcg. 117 */ 118 @VisibleForTesting 119 @Nullable parseMemoryStatFromMemcg(String memoryStatContents)120 static MemoryStat parseMemoryStatFromMemcg(String memoryStatContents) { 121 if (memoryStatContents == null || memoryStatContents.isEmpty()) { 122 return null; 123 } 124 125 final MemoryStat memoryStat = new MemoryStat(); 126 memoryStat.pgfault = tryParseLong(PGFAULT, memoryStatContents); 127 memoryStat.pgmajfault = tryParseLong(PGMAJFAULT, memoryStatContents); 128 memoryStat.rssInBytes = tryParseLong(RSS_IN_BYTES, memoryStatContents); 129 memoryStat.cacheInBytes = tryParseLong(CACHE_IN_BYTES, memoryStatContents); 130 memoryStat.swapInBytes = tryParseLong(SWAP_IN_BYTES, memoryStatContents); 131 return memoryStat; 132 } 133 134 /** 135 * Parses relevant statistics out from the contents of the /proc/pid/stat file in procfs. 136 */ 137 @VisibleForTesting 138 @Nullable parseMemoryStatFromProcfs(String procStatContents)139 static MemoryStat parseMemoryStatFromProcfs(String procStatContents) { 140 if (procStatContents == null || procStatContents.isEmpty()) { 141 return null; 142 } 143 final String[] splits = procStatContents.split(" "); 144 if (splits.length < 24) { 145 return null; 146 } 147 try { 148 final MemoryStat memoryStat = new MemoryStat(); 149 memoryStat.pgfault = Long.parseLong(splits[PGFAULT_INDEX]); 150 memoryStat.pgmajfault = Long.parseLong(splits[PGMAJFAULT_INDEX]); 151 memoryStat.rssInBytes = Long.parseLong(splits[RSS_IN_PAGES_INDEX]) * PAGE_SIZE; 152 return memoryStat; 153 } catch (NumberFormatException e) { 154 Slog.e(TAG, "Failed to parse value", e); 155 return null; 156 } 157 } 158 159 /** 160 * Returns whether per-app memcg is available on device. 161 */ hasMemcg()162 static boolean hasMemcg() { 163 return DEVICE_HAS_PER_APP_MEMCG; 164 } 165 166 /** 167 * Parses a long from the input using the pattern. Returns 0 if the captured value is not 168 * parsable. The pattern must have a single capturing group. 169 */ tryParseLong(Pattern pattern, String input)170 private static long tryParseLong(Pattern pattern, String input) { 171 final Matcher m = pattern.matcher(input); 172 try { 173 return m.find() ? Long.parseLong(m.group(1)) : 0; 174 } catch (NumberFormatException e) { 175 Slog.e(TAG, "Failed to parse value", e); 176 return 0; 177 } 178 } 179 180 public static final class MemoryStat { 181 /** Number of page faults */ 182 public long pgfault; 183 /** Number of major page faults */ 184 public long pgmajfault; 185 /** For memcg stats, the anon rss + swap cache size. Otherwise total RSS. */ 186 public long rssInBytes; 187 /** Number of bytes of page cache memory. Only present for memcg stats. */ 188 public long cacheInBytes; 189 /** Number of bytes of swap usage */ 190 public long swapInBytes; 191 } 192 } 193