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

Commit 9187f3fa authored by Lakshman Annadorai's avatar Lakshman Annadorai
Browse files

Read latest CPU usage stats from procfs.

Unit tests are added in one of the following changes.

Test: m
Bug: 217422127
Bug: 242722241
Change-Id: Ie3bac338c1ba5f3fe56e0c7925a21a94fb8bb8c1
(cherry picked from commit 8023f7ccc1f3768ac5c6c5ec1790e78c877f31a3)
parent eb98a357
Loading
Loading
Loading
Loading
+227 −0
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@
package com.android.server.cpu;

import android.annotation.IntDef;
import android.annotation.Nullable;
import android.system.Os;
import android.system.OsConstants;
import android.util.SparseArray;
import android.util.SparseIntArray;

@@ -30,6 +33,9 @@ import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Reader to read CPU information from proc and sys fs files exposed by the Kernel. */
public final class CpuInfoReader {
@@ -47,6 +53,14 @@ public final class CpuInfoReader {
    private static final String CPUSET_BACKGROUND_DIR = "background";
    private static final String CPUS_FILE = "cpus";
    private static final String PROC_STAT_FILE_PATH = "/proc/stat";
    private static final Pattern PROC_STAT_PATTERN =
            Pattern.compile("cpu(?<core>[0-9]+)\\s(?<userClockTicks>[0-9]+)\\s"
                    + "(?<niceClockTicks>[0-9]+)\\s(?<sysClockTicks>[0-9]+)\\s"
                    + "(?<idleClockTicks>[0-9]+)\\s(?<iowaitClockTicks>[0-9]+)\\s"
                    + "(?<irqClockTicks>[0-9]+)\\s(?<softirqClockTicks>[0-9]+)\\s"
                    + "(?<stealClockTicks>[0-9]+)\\s(?<guestClockTicks>[0-9]+)\\s"
                    + "(?<guestNiceClockTicks>[0-9]+)");
    private static final long MILLIS_PER_JIFFY = 1000L / Os.sysconf(OsConstants._SC_CLK_TCK);

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = {"FLAG_CPUSET_CATEGORY_"}, flag = true, value = {
@@ -62,6 +76,8 @@ public final class CpuInfoReader {
    private final SparseArray<Long> mMaxCpuFrequenciesByCpus = new SparseArray<>();

    private File[] mCpuFreqPolicyDirs;
    private SparseArray<CpuUsageStats> mCumulativeCpuUsageStats = new SparseArray<>();
    private boolean mIsEnabled;

    public CpuInfoReader() {
        this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH));
@@ -99,9 +115,24 @@ public final class CpuInfoReader {
                    mCpuFreqDir.getAbsolutePath());
            return false;
        }
        mIsEnabled = true;
        return true;
    }

    /** Reads CPU information from proc and sys fs files exposed by the Kernel. */
    public List<CpuInfo> readCpuInfos() {
        if (!mIsEnabled) {
            return Collections.emptyList();
        }
        SparseArray<CpuUsageStats> latestCpuUsageStats = readLatestCpuUsageStats();
        if (latestCpuUsageStats == null) {
            Slogf.e(TAG, "Failed to read latest CPU usage stats");
            return Collections.emptyList();
        }
        // TODO(b/217422127): Read current CPU frequencies and populate the CpuInfo.
        return Collections.emptyList();
    }

    private void readCpusetCategories() {
        File[] cpusetDirs = mCpusetDir.listFiles(File::isDirectory);
        if (cpusetDirs == null) {
@@ -223,4 +254,200 @@ public final class CpuInfoReader {
        }
        return Collections.emptyList();
    }

    @Nullable
    private SparseArray<CpuUsageStats> readLatestCpuUsageStats() {
        SparseArray<CpuUsageStats> cumulativeCpuUsageStats = readCumulativeCpuUsageStats();
        if (cumulativeCpuUsageStats.size() == 0) {
            Slogf.e(TAG, "Failed to read cumulative CPU usage stats");
            return null;
        }
        SparseArray<CpuUsageStats> deltaCpuUsageStats = new SparseArray();
        for (int i = 0; i < cumulativeCpuUsageStats.size(); i++) {
            int cpu = cumulativeCpuUsageStats.keyAt(i);
            CpuUsageStats newStats = cumulativeCpuUsageStats.valueAt(i);
            CpuUsageStats oldStats = mCumulativeCpuUsageStats.get(cpu);
            deltaCpuUsageStats.append(cpu, oldStats == null ? newStats : newStats.delta(oldStats));
        }
        mCumulativeCpuUsageStats = cumulativeCpuUsageStats;
        return deltaCpuUsageStats;
    }

    private SparseArray<CpuUsageStats> readCumulativeCpuUsageStats() {
        SparseArray<CpuUsageStats> cpuUsageStats = new SparseArray<>();
        try {
            List<String> lines = Files.readAllLines(mProcStatFile.toPath());
            for (int i = 0; i < lines.size(); i++) {
                Matcher m = PROC_STAT_PATTERN.matcher(lines.get(i).trim());
                if (!m.find()) {
                    continue;
                }
                cpuUsageStats.append(Integer.parseInt(Objects.requireNonNull(m.group("core"))),
                        new CpuUsageStats(jiffyStrToMillis(m.group("userClockTicks")),
                                jiffyStrToMillis(m.group("niceClockTicks")),
                                jiffyStrToMillis(m.group("sysClockTicks")),
                                jiffyStrToMillis(m.group("idleClockTicks")),
                                jiffyStrToMillis(m.group("iowaitClockTicks")),
                                jiffyStrToMillis(m.group("irqClockTicks")),
                                jiffyStrToMillis(m.group("softirqClockTicks")),
                                jiffyStrToMillis(m.group("stealClockTicks")),
                                jiffyStrToMillis(m.group("guestClockTicks")),
                                jiffyStrToMillis(m.group("guestNiceClockTicks"))));
            }
        } catch (Exception e) {
            Slogf.e(TAG, e, "Failed to read cpu usage stats from %s",
                    mProcStatFile.getAbsolutePath());
        }
        return cpuUsageStats;
    }

    private static long jiffyStrToMillis(String jiffyStr) {
        return Long.parseLong(Objects.requireNonNull(jiffyStr)) * MILLIS_PER_JIFFY;
    }

    /** Contains information for each CPU core on the system. */
    public static final class CpuInfo {
        public final int cpuCore;
        public final @CpusetCategory int cpusetCategories;
        public final long curCpuFreqKHz;
        public final long maxCpuFreqKHz;
        public final CpuUsageStats latestCpuUsageStats;

        CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, long curCpuFreqKHz,
                long maxCpuFreqKHz, CpuUsageStats latestCpuUsageStats) {
            this.cpuCore = cpuCore;
            this.cpusetCategories = cpusetCategories;
            this.curCpuFreqKHz = curCpuFreqKHz;
            this.maxCpuFreqKHz = maxCpuFreqKHz;
            this.latestCpuUsageStats = latestCpuUsageStats;
        }

        @Override
        public String toString() {
            return new StringBuilder("CpuInfo{ cpuCore = ").append(cpuCore)
                    .append(", cpusetCategories = ").append(cpusetCategories)
                    .append(", curCpuFreqKHz = ").append(curCpuFreqKHz)
                    .append(", maxCpuFreqKHz = ").append(maxCpuFreqKHz)
                    .append(", latestCpuUsageStats = ").append(latestCpuUsageStats)
                    .append(" }").toString();
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof CpuInfo)) {
                return false;
            }
            CpuInfo other = (CpuInfo) obj;
            return cpuCore == other.cpuCore && cpusetCategories == other.cpusetCategories
                    && curCpuFreqKHz == other.curCpuFreqKHz
                    && maxCpuFreqKHz == other.maxCpuFreqKHz
                    && latestCpuUsageStats.equals(other.latestCpuUsageStats);
        }

        @Override
        public int hashCode() {
            return Objects.hash(cpuCore, cpusetCategories, curCpuFreqKHz, maxCpuFreqKHz,
                    latestCpuUsageStats);
        }
    }

    /** CPU time spent in different modes. */
    public static final class CpuUsageStats {
        public final long userTimeMillis;
        public final long niceTimeMillis;
        public final long systemTimeMillis;
        public final long idleTimeMillis;
        public final long iowaitTimeMillis;
        public final long irqTimeMillis;
        public final long softirqTimeMillis;
        public final long stealTimeMillis;
        public final long guestTimeMillis;
        public final long guestNiceTimeMillis;

        public CpuUsageStats(long userTimeMillis, long niceTimeMillis, long systemTimeMillis,
                long idleTimeMillis, long iowaitTimeMillis, long irqTimeMillis,
                long softirqTimeMillis, long stealTimeMillis, long guestTimeMillis,
                long guestNiceTimeMillis) {
            this.userTimeMillis = userTimeMillis;
            this.niceTimeMillis = niceTimeMillis;
            this.systemTimeMillis = systemTimeMillis;
            this.idleTimeMillis = idleTimeMillis;
            this.iowaitTimeMillis = iowaitTimeMillis;
            this.irqTimeMillis = irqTimeMillis;
            this.softirqTimeMillis = softirqTimeMillis;
            this.stealTimeMillis = stealTimeMillis;
            this.guestTimeMillis = guestTimeMillis;
            this.guestNiceTimeMillis = guestNiceTimeMillis;
        }

        public long getTotalTime() {
            return userTimeMillis + niceTimeMillis + systemTimeMillis + idleTimeMillis
                    + iowaitTimeMillis + irqTimeMillis + softirqTimeMillis + stealTimeMillis
                    + guestTimeMillis + guestNiceTimeMillis;
        }

        @Override
        public String toString() {
            return new StringBuilder("CpuUsageStats{ userTimeMillis = ")
                    .append(userTimeMillis)
                    .append(", niceTimeMillis = ").append(niceTimeMillis)
                    .append(", systemTimeMillis = ").append(systemTimeMillis)
                    .append(", idleTimeMillis = ").append(idleTimeMillis)
                    .append(", iowaitTimeMillis = ").append(iowaitTimeMillis)
                    .append(", irqTimeMillis = ").append(irqTimeMillis)
                    .append(", softirqTimeMillis = ").append(softirqTimeMillis)
                    .append(", stealTimeMillis = ").append(stealTimeMillis)
                    .append(", guestTimeMillis = ").append(guestTimeMillis)
                    .append(", guestNiceTimeMillis = ").append(guestNiceTimeMillis)
                    .append(" }").toString();
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof CpuUsageStats)) {
                return false;
            }
            CpuUsageStats other = (CpuUsageStats) obj;
            return userTimeMillis == other.userTimeMillis && niceTimeMillis == other.niceTimeMillis
                    && systemTimeMillis == other.systemTimeMillis
                    && idleTimeMillis == other.idleTimeMillis
                    && iowaitTimeMillis == other.iowaitTimeMillis
                    && irqTimeMillis == other.irqTimeMillis
                    && softirqTimeMillis == other.softirqTimeMillis
                    && stealTimeMillis == other.stealTimeMillis
                    && guestTimeMillis == other.guestTimeMillis
                    && guestNiceTimeMillis == other.guestNiceTimeMillis;
        }

        @Override
        public int hashCode() {
            return Objects.hash(userTimeMillis, niceTimeMillis, systemTimeMillis, idleTimeMillis,
                    iowaitTimeMillis, irqTimeMillis, softirqTimeMillis, stealTimeMillis,
                    guestTimeMillis,
                    guestNiceTimeMillis);
        }

        CpuUsageStats delta(CpuUsageStats rhs) {
            return new CpuUsageStats(diff(userTimeMillis, rhs.userTimeMillis),
                    diff(niceTimeMillis, rhs.niceTimeMillis),
                    diff(systemTimeMillis, rhs.systemTimeMillis),
                    diff(idleTimeMillis, rhs.idleTimeMillis),
                    diff(iowaitTimeMillis, rhs.iowaitTimeMillis),
                    diff(irqTimeMillis, rhs.irqTimeMillis),
                    diff(softirqTimeMillis, rhs.softirqTimeMillis),
                    diff(stealTimeMillis, rhs.stealTimeMillis),
                    diff(guestTimeMillis, rhs.guestTimeMillis),
                    diff(guestNiceTimeMillis, rhs.guestNiceTimeMillis));
        }

        private static long diff(long lhs, long rhs) {
            return lhs > rhs ? lhs - rhs : 0;
        }
    }
}