• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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