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