Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit aaf60895 authored by Rafal Slawik's avatar Rafal Slawik
Browse files

Read RSS high watermark

The value is read from /proc/PID/status or memory.max_usage_in_bytes (for devices with per-app memcg enabled).

Reading the value takes about 2ms per process.
Full snapshot taken by statsd is around 300ms.

Results: https://docs.google.com/spreadsheets/d/1vG9ku8Uu8104CmKbO4cNeEKVeeByvHY--p0_dK1GAdA/edit?usp=sharing

Bug: 115477992
Test: atest FrameworksServicesTests
Change-Id: I87995cbd85085375ade685f29e986ba173f9693e
parent 4fee3984
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -2220,6 +2220,11 @@ message ProcessMemoryState {

    // SWAP
    optional int64 swap_in_bytes = 8;

    // RSS high watermark.
    // Peak RSS usage of the process. Value is read from the VmHWM field in /proc/PID/status or
    // from memory.max_usage_in_bytes under /dev/memcg if the device uses per-app memory cgroups.
    optional int64 rss_high_watermark_in_bytes = 9;
}

/*
+1 −1
Original line number Diff line number Diff line
@@ -169,7 +169,7 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
          new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
        // process_memory_state
        {android::util::PROCESS_MEMORY_STATE,
         {{4, 5, 6, 7, 8},
         {{4, 5, 6, 7, 8, 9},
          {2, 3},
          1 * NS_PER_SEC,
          new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
+5 −1
Original line number Diff line number Diff line
@@ -32,10 +32,11 @@ public final class ProcessMemoryState implements Parcelable {
    public final long rssInBytes;
    public final long cacheInBytes;
    public final long swapInBytes;
    public final long rssHighWatermarkInBytes;

    public ProcessMemoryState(int uid, String processName, int oomScore, long pgfault,
                              long pgmajfault, long rssInBytes, long cacheInBytes,
                              long swapInBytes) {
                              long swapInBytes, long rssHighWatermarkInBytes) {
        this.uid = uid;
        this.processName = processName;
        this.oomScore = oomScore;
@@ -44,6 +45,7 @@ public final class ProcessMemoryState implements Parcelable {
        this.rssInBytes = rssInBytes;
        this.cacheInBytes = cacheInBytes;
        this.swapInBytes = swapInBytes;
        this.rssHighWatermarkInBytes = rssHighWatermarkInBytes;
    }

    private ProcessMemoryState(Parcel in) {
@@ -55,6 +57,7 @@ public final class ProcessMemoryState implements Parcelable {
        rssInBytes = in.readLong();
        cacheInBytes = in.readLong();
        swapInBytes = in.readLong();
        rssHighWatermarkInBytes = in.readLong();
    }

    public static final Creator<ProcessMemoryState> CREATOR = new Creator<ProcessMemoryState>() {
@@ -84,5 +87,6 @@ public final class ProcessMemoryState implements Parcelable {
        parcel.writeLong(rssInBytes);
        parcel.writeLong(cacheInBytes);
        parcel.writeLong(swapInBytes);
        parcel.writeLong(rssHighWatermarkInBytes);
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -20892,7 +20892,8 @@ public class ActivityManagerService extends IActivityManager.Stub
                                    memoryStat.pgmajfault,
                                    memoryStat.rssInBytes,
                                    memoryStat.cacheInBytes,
                                    memoryStat.swapInBytes);
                                    memoryStat.swapInBytes,
                                    memoryStat.rssHighWatermarkInBytes);
                    processMemoryStates.add(processMemoryState);
                }
            }
+55 −8
Original line number Diff line number Diff line
@@ -37,18 +37,25 @@ import java.util.regex.Pattern;
 * Static utility methods related to {@link MemoryStat}.
 */
final class MemoryStatUtil {
    static final int BYTES_IN_KILOBYTE = 1024;

    private static final String TAG = TAG_WITH_CLASS_NAME ? "MemoryStatUtil" : TAG_AM;

    /** True if device has per-app memcg */
    private static final Boolean DEVICE_HAS_PER_APP_MEMCG =
    private static final boolean DEVICE_HAS_PER_APP_MEMCG =
            SystemProperties.getBoolean("ro.config.per_app_memcg", false);

    /** Path to check if device has memcg */
    private static final String MEMCG_TEST_PATH = "/dev/memcg/apps/memory.stat";
    /** Path to memory stat file for logging app start memory state */
    private static final String MEMORY_STAT_FILE_FMT = "/dev/memcg/apps/uid_%d/pid_%d/memory.stat";
    /** Path to memory max usage file for logging app memory state */
    private static final String MEMORY_MAX_USAGE_FILE_FMT =
            "/dev/memcg/apps/uid_%d/pid_%d/memory.max_usage_in_bytes";
    /** Path to procfs stat file for logging app start memory state */
    private static final String PROC_STAT_FILE_FMT = "/proc/%d/stat";
    /** Path to procfs status file for logging app memory state */
    private static final String PROC_STATUS_FILE_FMT = "/proc/%d/status";

    private static final Pattern PGFAULT = Pattern.compile("total_pgfault (\\d+)");
    private static final Pattern PGMAJFAULT = Pattern.compile("total_pgmajfault (\\d+)");
@@ -56,6 +63,9 @@ final class MemoryStatUtil {
    private static final Pattern CACHE_IN_BYTES = Pattern.compile("total_cache (\\d+)");
    private static final Pattern SWAP_IN_BYTES = Pattern.compile("total_swap (\\d+)");

    private static final Pattern RSS_HIGH_WATERMARK_IN_BYTES =
            Pattern.compile("VmHWM:\\s*(\\d+)\\s*kB");

    private static final int PGFAULT_INDEX = 9;
    private static final int PGMAJFAULT_INDEX = 11;
    private static final int RSS_IN_BYTES_INDEX = 23;
@@ -80,8 +90,15 @@ final class MemoryStatUtil {
     */
    @Nullable
    static MemoryStat readMemoryStatFromMemcg(int uid, int pid) {
        final String path = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid);
        return parseMemoryStatFromMemcg(readFileContents(path));
        final String statPath = String.format(Locale.US, MEMORY_STAT_FILE_FMT, uid, pid);
        MemoryStat stat = parseMemoryStatFromMemcg(readFileContents(statPath));
        if (stat == null) {
            return null;
        }
        String maxUsagePath = String.format(Locale.US, MEMORY_MAX_USAGE_FILE_FMT, uid, pid);
        stat.rssHighWatermarkInBytes = parseMemoryMaxUsageFromMemCg(
                readFileContents(maxUsagePath));
        return stat;
    }

    /**
@@ -91,8 +108,14 @@ final class MemoryStatUtil {
     */
    @Nullable
    static MemoryStat readMemoryStatFromProcfs(int pid) {
        final String path = String.format(Locale.US, PROC_STAT_FILE_FMT, pid);
        return parseMemoryStatFromProcfs(readFileContents(path));
        final String statPath = String.format(Locale.US, PROC_STAT_FILE_FMT, pid);
        MemoryStat stat = parseMemoryStatFromProcfs(readFileContents(statPath));
        if (stat == null) {
            return null;
        }
        final String statusPath = String.format(Locale.US, PROC_STATUS_FILE_FMT, pid);
        stat.rssHighWatermarkInBytes = parseVmHWMFromProcfs(readFileContents(statusPath));
        return stat;
    }

    private static String readFileContents(String path) {
@@ -113,7 +136,7 @@ final class MemoryStatUtil {
    /**
     * Parses relevant statistics out from the contents of a memory.stat file in memcg.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @VisibleForTesting
    @Nullable
    static MemoryStat parseMemoryStatFromMemcg(String memoryStatContents) {
        if (memoryStatContents == null || memoryStatContents.isEmpty()) {
@@ -135,10 +158,18 @@ final class MemoryStatUtil {
        return memoryStat;
    }

    @VisibleForTesting
    static long parseMemoryMaxUsageFromMemCg(String memoryMaxUsageContents) {
        if (memoryMaxUsageContents == null || memoryMaxUsageContents.isEmpty()) {
            return 0;
        }
        return Long.valueOf(memoryMaxUsageContents);
    }

    /**
     * Parses relevant statistics out from the contents of a /proc/pid/stat file in procfs.
     * Parses relevant statistics out from the contents of the /proc/pid/stat file in procfs.
     */
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
    @VisibleForTesting
    @Nullable
    static MemoryStat parseMemoryStatFromProcfs(String procStatContents) {
        if (procStatContents == null || procStatContents.isEmpty()) {
@@ -157,6 +188,20 @@ final class MemoryStatUtil {
        return memoryStat;
    }

    /**
     * Parses RSS high watermark out from the contents of the /proc/pid/status file in procfs. The
     * returned value is in bytes.
     */
    @VisibleForTesting
    static long parseVmHWMFromProcfs(String procStatusContents) {
        if (procStatusContents == null || procStatusContents.isEmpty()) {
            return 0;
        }
        Matcher m = RSS_HIGH_WATERMARK_IN_BYTES.matcher(procStatusContents);
        // Convert value read from /proc/pid/status from kilobytes to bytes.
        return m.find() ? Long.valueOf(m.group(1)) * BYTES_IN_KILOBYTE : 0;
    }

    /**
     * Returns whether per-app memcg is available on device.
     */
@@ -175,5 +220,7 @@ final class MemoryStatUtil {
        long cacheInBytes;
        /** Number of bytes of swap usage */
        long swapInBytes;
        /** Number of bytes of peak anonymous and swap cache memory */
        long rssHighWatermarkInBytes;
    }
}
Loading