Home | History | Annotate | Download | only in am
      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 
     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
    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
    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
    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      */
    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      */
    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      */
    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      */
    163     public static List<IonAllocations> readProcessSystemIonHeapSizesFromDebugfs() {
    164         return parseProcessIonHeapSizesFromDebugfs(readFileContents(DEBUG_SYSTEM_ION_HEAP_FILE));
    165     }
    166 
    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
    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
    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
    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
    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
    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
    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      */
    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      */
    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
    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
    378         public int hashCode() {
    379             return Objects.hash(pid, totalSizeInBytes, count, maxSizeInBytes);
    380         }
    381 
    382         @Override
    383         public String toString() {
    384             return "IonAllocations{"
    385                     + "pid=" + pid
    386                     + ", totalSizeInBytes=" + totalSizeInBytes
    387                     + ", count=" + count
    388                     + ", maxSizeInBytes=" + maxSizeInBytes
    389                     + '}';
    390         }
    391     }
    392 }
    393