blob: 9cda89a17766bb2d46646d5088de1d22de2ebceb [file] [log] [blame]
Ng Zhi Anbbefdec2018-01-30 17:12:39 -08001/*
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
17package com.android.server.am;
18
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080019import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
20import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
Rafal Slawikc3762622018-11-07 12:07:26 +000021import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080022
23import android.annotation.Nullable;
24import android.os.FileUtils;
Rafal Slawikba944072018-09-13 11:34:24 +010025import android.os.SystemProperties;
Rafal Slawikbf67d072018-10-23 11:07:54 +010026import android.system.Os;
27import android.system.OsConstants;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080028import android.util.Slog;
29
30import com.android.internal.annotations.VisibleForTesting;
31
32import java.io.File;
33import java.io.IOException;
Rajeev Kumar22d92b72018-02-07 18:38:36 -080034import java.util.Locale;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080035import java.util.regex.Matcher;
36import java.util.regex.Pattern;
37
38/**
39 * Static utility methods related to {@link MemoryStat}.
40 */
Wale Ogunwale387b34c2018-10-25 19:59:40 -070041public final class MemoryStatUtil {
Rafal Slawikaaf60892018-09-12 13:04:30 +010042 static final int BYTES_IN_KILOBYTE = 1024;
Rafal Slawikc886af92018-10-01 16:06:39 +010043 static final int PAGE_SIZE = 4096;
Rafal Slawikbf67d072018-10-23 11:07:54 +010044 static final long JIFFY_NANOS = 1_000_000_000 / Os.sysconf(OsConstants._SC_CLK_TCK);
Rafal Slawikaaf60892018-09-12 13:04:30 +010045
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080046 private static final String TAG = TAG_WITH_CLASS_NAME ? "MemoryStatUtil" : TAG_AM;
47
Rafal Slawikba944072018-09-13 11:34:24 +010048 /** True if device has per-app memcg */
Rafal Slawikaaf60892018-09-12 13:04:30 +010049 private static final boolean DEVICE_HAS_PER_APP_MEMCG =
Rafal Slawikba944072018-09-13 11:34:24 +010050 SystemProperties.getBoolean("ro.config.per_app_memcg", false);
51
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080052 /** 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 Kumarbfcd9202018-03-02 22:42:13 +000054 /** Path to procfs stat file for logging app start memory state */
55 private static final String PROC_STAT_FILE_FMT = "/proc/%d/stat";
Rafal Slawikaaf60892018-09-12 13:04:30 +010056 /** Path to procfs status file for logging app memory state */
57 private static final String PROC_STATUS_FILE_FMT = "/proc/%d/status";
Rafal Slawik08621582018-10-15 14:53:07 +010058 /** Path to procfs cmdline file. Used with pid: /proc/pid/cmdline. */
59 private static final String PROC_CMDLINE_FILE_FMT = "/proc/%d/cmdline";
Rafal Slawikd4e87572019-03-12 13:08:38 +000060 /** Path to debugfs file for the system ion heap. */
61 private static final String DEBUG_SYSTEM_ION_HEAP_FILE = "/sys/kernel/debug/ion/heaps/system";
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080062
63 private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)");
64 private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)");
65 private static final Pattern RSS_IN_BYTES = Pattern.compile("total_rss (\\d+)");
66 private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)");
67 private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)");
Rafal Slawikaaf60892018-09-12 13:04:30 +010068 private static final Pattern RSS_HIGH_WATERMARK_IN_BYTES =
69 Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
Rafal Slawikd4e87572019-03-12 13:08:38 +000070 private static final Pattern ION_HEAP_SIZE_IN_BYTES =
71 Pattern.compile("\n\\s*total\\s*(\\d+)\\s*\n");
Rafal Slawikaaf60892018-09-12 13:04:30 +010072
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000073 private static final int PGFAULT_INDEX = 9;
74 private static final int PGMAJFAULT_INDEX = 11;
Rafal Slawikbf67d072018-10-23 11:07:54 +010075 private static final int START_TIME_INDEX = 21;
Rafal Slawikc886af92018-10-01 16:06:39 +010076 private static final int RSS_IN_PAGES_INDEX = 23;
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000077
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080078 private MemoryStatUtil() {}
79
80 /**
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000081 * Reads memory stat for a process.
82 *
Rafal Slawikba944072018-09-13 11:34:24 +010083 * Reads from per-app memcg if available on device, else fallback to procfs.
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000084 * Returns null if no stats can be read.
85 */
86 @Nullable
Wale Ogunwale387b34c2018-10-25 19:59:40 -070087 public static MemoryStat readMemoryStatFromFilesystem(int uid, int pid) {
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000088 return hasMemcg() ? readMemoryStatFromMemcg(uid, pid) : readMemoryStatFromProcfs(pid);
89 }
90
91 /**
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080092 * Reads memory.stat of a process from memcg.
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000093 *
94 * Returns null if file is not found in memcg or if file has unrecognized contents.
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080095 */
Rajeev Kumar22d92b72018-02-07 18:38:36 -080096 @Nullable
97 static MemoryStat readMemoryStatFromMemcg(int uid, int pid) {
Rafal Slawikaaf60892018-09-12 13:04:30 +010098 final String statPath = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid);
Rafal Slawikd03ae422018-11-20 11:27:45 +000099 return parseMemoryStatFromMemcg(readFileContents(statPath));
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000100 }
101
102 /**
103 * Reads memory stat of a process from procfs.
104 *
105 * Returns null if file is not found in procfs or if file has unrecognized contents.
106 */
107 @Nullable
Rafal Slawikc3762622018-11-07 12:07:26 +0000108 public static MemoryStat readMemoryStatFromProcfs(int pid) {
Rafal Slawikaaf60892018-09-12 13:04:30 +0100109 final String statPath = String.format(Locale.US, PROC_STAT_FILE_FMT, pid);
Rafal Slawikd03ae422018-11-20 11:27:45 +0000110 return parseMemoryStatFromProcfs(readFileContents(statPath));
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000111 }
112
Rafal Slawik08621582018-10-15 14:53:07 +0100113 /**
Rafal Slawik3bea8952018-11-15 12:39:33 +0000114 * Reads RSS high-water mark of a process from procfs. Returns value of the VmHWM field in
115 * /proc/PID/status in bytes or 0 if not available.
116 */
117 public static long readRssHighWaterMarkFromProcfs(int pid) {
118 final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid);
119 return parseVmHWMFromProcfs(readFileContents(statusPath));
120 }
121
122 /**
Rafal Slawik08621582018-10-15 14:53:07 +0100123 * Reads cmdline of a process from procfs.
124 *
125 * Returns content of /proc/pid/cmdline (e.g. /system/bin/statsd) or an empty string
126 * if the file is not available.
127 */
Rafal Slawikc3762622018-11-07 12:07:26 +0000128 public static String readCmdlineFromProcfs(int pid) {
Rafal Slawik9dc56ac2018-12-12 15:21:44 +0000129 final String path = String.format(Locale.US, PROC_CMDLINE_FILE_FMT, pid);
130 return parseCmdlineFromProcfs(readFileContents(path));
Rafal Slawik08621582018-10-15 14:53:07 +0100131 }
132
Rafal Slawikd4e87572019-03-12 13:08:38 +0000133 /**
134 * Reads size of the system ion heap from debugfs.
135 *
136 * Returns value of the total size in bytes of the system ion heap from
137 * /sys/kernel/debug/ion/heaps/system.
138 */
139 public static long readSystemIonHeapSizeFromDebugfs() {
140 return parseIonHeapSizeFromDebugfs(readFileContents(DEBUG_SYSTEM_ION_HEAP_FILE));
141 }
142
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000143 private static String readFileContents(String path) {
144 final File file = new File(path);
145 if (!file.exists()) {
146 if (DEBUG_METRICS) Slog.i(TAG, path + " not found");
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800147 return null;
148 }
149
150 try {
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000151 return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */);
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800152 } catch (IOException e) {
153 Slog.e(TAG, "Failed to read file:", e);
154 return null;
155 }
156 }
157
158 /**
159 * Parses relevant statistics out from the contents of a memory.stat file in memcg.
160 */
Rafal Slawikaaf60892018-09-12 13:04:30 +0100161 @VisibleForTesting
Rajeev Kumar22d92b72018-02-07 18:38:36 -0800162 @Nullable
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000163 static MemoryStat parseMemoryStatFromMemcg(String memoryStatContents) {
164 if (memoryStatContents == null || memoryStatContents.isEmpty()) {
165 return null;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800166 }
167
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000168 final MemoryStat memoryStat = new MemoryStat();
Rafal Slawikcde178c2019-03-14 12:39:54 +0000169 memoryStat.pgfault = tryParseLong(PGFAULT, memoryStatContents);
170 memoryStat.pgmajfault = tryParseLong(PGMAJFAULT, memoryStatContents);
171 memoryStat.rssInBytes = tryParseLong(RSS_IN_BYTES, memoryStatContents);
172 memoryStat.cacheInBytes = tryParseLong(CACHE_IN_BYTES, memoryStatContents);
173 memoryStat.swapInBytes = tryParseLong(SWAP_IN_BYTES, memoryStatContents);
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800174 return memoryStat;
175 }
176
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000177 /**
Rafal Slawikaaf60892018-09-12 13:04:30 +0100178 * Parses relevant statistics out from the contents of the /proc/pid/stat file in procfs.
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000179 */
Rafal Slawikaaf60892018-09-12 13:04:30 +0100180 @VisibleForTesting
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000181 @Nullable
182 static MemoryStat parseMemoryStatFromProcfs(String procStatContents) {
183 if (procStatContents == null || procStatContents.isEmpty()) {
184 return null;
185 }
186
187 final String[] splits = procStatContents.split(" ");
188 if (splits.length < 24) {
189 return null;
190 }
191
Rafal Slawikc886af92018-10-01 16:06:39 +0100192 try {
193 final MemoryStat memoryStat = new MemoryStat();
194 memoryStat.pgfault = Long.parseLong(splits[PGFAULT_INDEX]);
195 memoryStat.pgmajfault = Long.parseLong(splits[PGMAJFAULT_INDEX]);
196 memoryStat.rssInBytes = Long.parseLong(splits[RSS_IN_PAGES_INDEX]) * PAGE_SIZE;
Rafal Slawikbf67d072018-10-23 11:07:54 +0100197 memoryStat.startTimeNanos = Long.parseLong(splits[START_TIME_INDEX]) * JIFFY_NANOS;
Rafal Slawikc886af92018-10-01 16:06:39 +0100198 return memoryStat;
199 } catch (NumberFormatException e) {
200 Slog.e(TAG, "Failed to parse value", e);
201 return null;
202 }
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000203 }
204
205 /**
Rafal Slawikaaf60892018-09-12 13:04:30 +0100206 * Parses RSS high watermark out from the contents of the /proc/pid/status file in procfs. The
207 * returned value is in bytes.
208 */
209 @VisibleForTesting
210 static long parseVmHWMFromProcfs(String procStatusContents) {
211 if (procStatusContents == null || procStatusContents.isEmpty()) {
212 return 0;
213 }
Rafal Slawikaaf60892018-09-12 13:04:30 +0100214 // Convert value read from /proc/pid/status from kilobytes to bytes.
Rafal Slawikcde178c2019-03-14 12:39:54 +0000215 return tryParseLong(RSS_HIGH_WATERMARK_IN_BYTES, procStatusContents) * BYTES_IN_KILOBYTE;
Rafal Slawikaaf60892018-09-12 13:04:30 +0100216 }
217
Rafal Slawik9dc56ac2018-12-12 15:21:44 +0000218
219 /**
220 * Parses cmdline out of the contents of the /proc/pid/cmdline file in procfs.
221 *
222 * Parsing is required to strip anything after first null byte.
223 */
224 @VisibleForTesting
225 static String parseCmdlineFromProcfs(String cmdline) {
226 if (cmdline == null) {
227 return "";
228 }
229 int firstNullByte = cmdline.indexOf("\0");
230 if (firstNullByte == -1) {
231 return cmdline;
232 }
233 return cmdline.substring(0, firstNullByte);
234 }
235
Rafal Slawikaaf60892018-09-12 13:04:30 +0100236 /**
Rafal Slawikd4e87572019-03-12 13:08:38 +0000237 * Parses the ion heap size from the contents of a file under /sys/kernel/debug/ion/heaps in
238 * debugfs. The returned value is in bytes.
239 */
240 @VisibleForTesting
241 static long parseIonHeapSizeFromDebugfs(String contents) {
242 if (contents == null || contents.isEmpty()) {
243 return 0;
244 }
Rafal Slawikcde178c2019-03-14 12:39:54 +0000245 return tryParseLong(ION_HEAP_SIZE_IN_BYTES, contents);
Rafal Slawikd4e87572019-03-12 13:08:38 +0000246 }
247
248 /**
Rafal Slawikba944072018-09-13 11:34:24 +0100249 * Returns whether per-app memcg is available on device.
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000250 */
251 static boolean hasMemcg() {
Rafal Slawikba944072018-09-13 11:34:24 +0100252 return DEVICE_HAS_PER_APP_MEMCG;
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000253 }
254
Rafal Slawikcde178c2019-03-14 12:39:54 +0000255 /**
256 * Parses a long from the input using the pattern. Returns 0 if the captured value is not
257 * parsable. The pattern must have a single capturing group.
258 */
259 private static long tryParseLong(Pattern pattern, String input) {
260 final Matcher m = pattern.matcher(input);
261 try {
262 return m.find() ? Long.parseLong(m.group(1)) : 0;
263 } catch (NumberFormatException e) {
264 Slog.e(TAG, "Failed to parse value", e);
265 return 0;
266 }
267 }
268
Wale Ogunwale387b34c2018-10-25 19:59:40 -0700269 public static final class MemoryStat {
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800270 /** Number of page faults */
Wale Ogunwale59507092018-10-29 09:00:30 -0700271 public long pgfault;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800272 /** Number of major page faults */
Wale Ogunwale59507092018-10-29 09:00:30 -0700273 public long pgmajfault;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800274 /** Number of bytes of anonymous and swap cache memory */
Wale Ogunwale59507092018-10-29 09:00:30 -0700275 public long rssInBytes;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800276 /** Number of bytes of page cache memory */
Wale Ogunwale59507092018-10-29 09:00:30 -0700277 public long cacheInBytes;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800278 /** Number of bytes of swap usage */
Wale Ogunwale59507092018-10-29 09:00:30 -0700279 public long swapInBytes;
Rafal Slawikbf67d072018-10-23 11:07:54 +0100280 /** Device time when the processes started. */
Wale Ogunwale59507092018-10-29 09:00:30 -0700281 public long startTimeNanos;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800282 }
Ng Zhi Anf39efa12018-02-22 14:06:33 -0800283}