blob: a5849147203697cf466d51cf03ca73d2b09d4b8b [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";
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080060
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 Slawikaaf60892018-09-12 13:04:30 +010067 private static final Pattern RSS_HIGH_WATERMARK_IN_BYTES =
68 Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");
69
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000070 private static final int PGFAULT_INDEX = 9;
71 private static final int PGMAJFAULT_INDEX = 11;
Rafal Slawikbf67d072018-10-23 11:07:54 +010072 private static final int START_TIME_INDEX = 21;
Rafal Slawikc886af92018-10-01 16:06:39 +010073 private static final int RSS_IN_PAGES_INDEX = 23;
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000074
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080075 private MemoryStatUtil() {}
76
77 /**
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000078 * Reads memory stat for a process.
79 *
Rafal Slawikba944072018-09-13 11:34:24 +010080 * Reads from per-app memcg if available on device, else fallback to procfs.
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000081 * Returns null if no stats can be read.
82 */
83 @Nullable
Wale Ogunwale387b34c2018-10-25 19:59:40 -070084 public static MemoryStat readMemoryStatFromFilesystem(int uid, int pid) {
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000085 return hasMemcg() ? readMemoryStatFromMemcg(uid, pid) : readMemoryStatFromProcfs(pid);
86 }
87
88 /**
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080089 * Reads memory.stat of a process from memcg.
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000090 *
91 * Returns null if file is not found in memcg or if file has unrecognized contents.
Ng Zhi Anbbefdec2018-01-30 17:12:39 -080092 */
Rajeev Kumar22d92b72018-02-07 18:38:36 -080093 @Nullable
94 static MemoryStat readMemoryStatFromMemcg(int uid, int pid) {
Rafal Slawikaaf60892018-09-12 13:04:30 +010095 final String statPath = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid);
Rafal Slawikd03ae422018-11-20 11:27:45 +000096 return parseMemoryStatFromMemcg(readFileContents(statPath));
Rajeev Kumarbfcd9202018-03-02 22:42:13 +000097 }
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 Slawikc3762622018-11-07 12:07:26 +0000105 public static MemoryStat readMemoryStatFromProcfs(int pid) {
Rafal Slawikaaf60892018-09-12 13:04:30 +0100106 final String statPath = String.format(Locale.US, PROC_STAT_FILE_FMT, pid);
Rafal Slawikd03ae422018-11-20 11:27:45 +0000107 return parseMemoryStatFromProcfs(readFileContents(statPath));
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000108 }
109
Rafal Slawik08621582018-10-15 14:53:07 +0100110 /**
Rafal Slawik3bea8952018-11-15 12:39:33 +0000111 * 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 Slawik08621582018-10-15 14:53:07 +0100120 * 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 Slawikc3762622018-11-07 12:07:26 +0000125 public static String readCmdlineFromProcfs(int pid) {
Rafal Slawik9dc56ac2018-12-12 15:21:44 +0000126 final String path = String.format(Locale.US, PROC_CMDLINE_FILE_FMT, pid);
127 return parseCmdlineFromProcfs(readFileContents(path));
Rafal Slawik08621582018-10-15 14:53:07 +0100128 }
129
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000130 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 Anbbefdec2018-01-30 17:12:39 -0800134 return null;
135 }
136
137 try {
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000138 return FileUtils.readTextFile(file, 0 /* max */, null /* ellipsis */);
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800139 } 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 Slawikaaf60892018-09-12 13:04:30 +0100148 @VisibleForTesting
Rajeev Kumar22d92b72018-02-07 18:38:36 -0800149 @Nullable
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000150 static MemoryStat parseMemoryStatFromMemcg(String memoryStatContents) {
151 if (memoryStatContents == null || memoryStatContents.isEmpty()) {
152 return null;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800153 }
154
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000155 final MemoryStat memoryStat = new MemoryStat();
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800156 Matcher m;
157 m = PGFAULT.matcher(memoryStatContents);
Rafal Slawikc886af92018-10-01 16:06:39 +0100158 memoryStat.pgfault = m.find() ? Long.parseLong(m.group(1)) : 0;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800159 m = PGMAJFAULT.matcher(memoryStatContents);
Rafal Slawikc886af92018-10-01 16:06:39 +0100160 memoryStat.pgmajfault = m.find() ? Long.parseLong(m.group(1)) : 0;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800161 m = RSS_IN_BYTES.matcher(memoryStatContents);
Rafal Slawikc886af92018-10-01 16:06:39 +0100162 memoryStat.rssInBytes = m.find() ? Long.parseLong(m.group(1)) : 0;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800163 m = CACHE_IN_BYTES.matcher(memoryStatContents);
Rafal Slawikc886af92018-10-01 16:06:39 +0100164 memoryStat.cacheInBytes = m.find() ? Long.parseLong(m.group(1)) : 0;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800165 m = SWAP_IN_BYTES.matcher(memoryStatContents);
Rafal Slawikc886af92018-10-01 16:06:39 +0100166 memoryStat.swapInBytes = m.find() ? Long.parseLong(m.group(1)) : 0;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800167 return memoryStat;
168 }
169
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000170 /**
Rafal Slawikaaf60892018-09-12 13:04:30 +0100171 * Parses relevant statistics out from the contents of the /proc/pid/stat file in procfs.
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000172 */
Rafal Slawikaaf60892018-09-12 13:04:30 +0100173 @VisibleForTesting
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000174 @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 Slawikc886af92018-10-01 16:06:39 +0100185 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 Slawikbf67d072018-10-23 11:07:54 +0100190 memoryStat.startTimeNanos = Long.parseLong(splits[START_TIME_INDEX]) * JIFFY_NANOS;
Rafal Slawikc886af92018-10-01 16:06:39 +0100191 return memoryStat;
192 } catch (NumberFormatException e) {
193 Slog.e(TAG, "Failed to parse value", e);
194 return null;
195 }
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000196 }
197
198 /**
Rafal Slawikaaf60892018-09-12 13:04:30 +0100199 * 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 Slawikc886af92018-10-01 16:06:39 +0100209 return m.find() ? Long.parseLong(m.group(1)) * BYTES_IN_KILOBYTE : 0;
Rafal Slawikaaf60892018-09-12 13:04:30 +0100210 }
211
Rafal Slawik9dc56ac2018-12-12 15:21:44 +0000212
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 Slawikaaf60892018-09-12 13:04:30 +0100230 /**
Rafal Slawikba944072018-09-13 11:34:24 +0100231 * Returns whether per-app memcg is available on device.
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000232 */
233 static boolean hasMemcg() {
Rafal Slawikba944072018-09-13 11:34:24 +0100234 return DEVICE_HAS_PER_APP_MEMCG;
Rajeev Kumarbfcd9202018-03-02 22:42:13 +0000235 }
236
Wale Ogunwale387b34c2018-10-25 19:59:40 -0700237 public static final class MemoryStat {
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800238 /** Number of page faults */
Wale Ogunwale59507092018-10-29 09:00:30 -0700239 public long pgfault;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800240 /** Number of major page faults */
Wale Ogunwale59507092018-10-29 09:00:30 -0700241 public long pgmajfault;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800242 /** Number of bytes of anonymous and swap cache memory */
Wale Ogunwale59507092018-10-29 09:00:30 -0700243 public long rssInBytes;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800244 /** Number of bytes of page cache memory */
Wale Ogunwale59507092018-10-29 09:00:30 -0700245 public long cacheInBytes;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800246 /** Number of bytes of swap usage */
Wale Ogunwale59507092018-10-29 09:00:30 -0700247 public long swapInBytes;
Rafal Slawikbf67d072018-10-23 11:07:54 +0100248 /** Device time when the processes started. */
Wale Ogunwale59507092018-10-29 09:00:30 -0700249 public long startTimeNanos;
Ng Zhi Anbbefdec2018-01-30 17:12:39 -0800250 }
Ng Zhi Anf39efa12018-02-22 14:06:33 -0800251}