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

Commit 0f3fbe3e authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Optimize getting time_in_state for threads of system server"

parents 17e3ffc7 2237efed
Loading
Loading
Loading
Loading
+8 −4
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;


import android.annotation.IntDef;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager;
import android.app.job.JobParameters;
import android.app.job.JobParameters;
import android.compat.annotation.UnsupportedAppUsage;
import android.compat.annotation.UnsupportedAppUsage;
@@ -2889,14 +2890,17 @@ public abstract class BatteryStats implements Parcelable {


    /**
    /**
     * Returns the approximate CPU time (in microseconds) spent by the system server handling
     * Returns the approximate CPU time (in microseconds) spent by the system server handling
     * incoming service calls from apps.
     * incoming service calls from apps.  The result is returned as an array of longs,
     * organized as a sequence like this:
     * <pre>
     *     cluster1-speeed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...
     * </pre>
     *
     *
     * @param cluster the index of the CPU cluster.
     * @param step the index of the CPU speed. This is not the actual speed of the CPU.
     * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
     * @see com.android.internal.os.PowerProfile#getNumCpuClusters()
     * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
     * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int)
     */
     */
    public abstract long getSystemServiceTimeAtCpuSpeed(int cluster, int step);
    @Nullable
    public abstract long[] getSystemServiceTimeAtCpuSpeeds();


    /**
    /**
     * Returns the total, last, or current battery uptime in microseconds.
     * Returns the total, last, or current battery uptime in microseconds.
+99 −84
Original line number Original line Diff line number Diff line
@@ -145,7 +145,7 @@ public class BatteryStatsImpl extends BatteryStats {
    private static final boolean DEBUG = false;
    private static final boolean DEBUG = false;
    public static final boolean DEBUG_ENERGY = false;
    public static final boolean DEBUG_ENERGY = false;
    private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
    private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
    private static final boolean DEBUG_BINDER_STATS = true;
    private static final boolean DEBUG_BINDER_STATS = false;
    private static final boolean DEBUG_MEMORY = false;
    private static final boolean DEBUG_MEMORY = false;
    private static final boolean DEBUG_HISTORY = false;
    private static final boolean DEBUG_HISTORY = false;
    private static final boolean USE_OLD_HISTORY = false;   // for debugging.
    private static final boolean USE_OLD_HISTORY = false;   // for debugging.
@@ -156,7 +156,7 @@ public class BatteryStatsImpl extends BatteryStats {
    private static final int MAGIC = 0xBA757475; // 'BATSTATS'
    private static final int MAGIC = 0xBA757475; // 'BATSTATS'
    // Current on-disk Parcel version
    // Current on-disk Parcel version
    static final int VERSION = 188 + (USE_OLD_HISTORY ? 1000 : 0);
    static final int VERSION = 189 + (USE_OLD_HISTORY ? 1000 : 0);
    // The maximum number of names wakelocks we will keep track of
    // The maximum number of names wakelocks we will keep track of
    // per uid; once the limit is reached, we batch the remaining wakelocks
    // per uid; once the limit is reached, we batch the remaining wakelocks
@@ -221,7 +221,8 @@ public class BatteryStatsImpl extends BatteryStats {
    @VisibleForTesting
    @VisibleForTesting
    protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
    protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
    @VisibleForTesting
    @VisibleForTesting
    protected SystemServerCpuThreadReader mSystemServerCpuThreadReader;
    protected SystemServerCpuThreadReader mSystemServerCpuThreadReader =
            SystemServerCpuThreadReader.create();
    private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
    private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
            = new KernelMemoryBandwidthStats();
            = new KernelMemoryBandwidthStats();
@@ -1013,15 +1014,22 @@ public class BatteryStatsImpl extends BatteryStats {
    private long[] mCpuFreqs;
    private long[] mCpuFreqs;
    /**
     * Times spent by the system server process grouped by cluster and CPU speed.
     */
    private LongSamplingCounterArray mSystemServerCpuTimesUs;
    private long[] mTmpSystemServerCpuTimesUs;
    /**
    /**
     * Times spent by the system server threads grouped by cluster and CPU speed.
     * Times spent by the system server threads grouped by cluster and CPU speed.
     */
     */
    private LongSamplingCounter[][] mSystemServerThreadCpuTimesUs;
    private LongSamplingCounterArray mSystemServerThreadCpuTimesUs;
    /**
    /**
     * Times spent by the system server threads handling incoming binder requests.
     * Times spent by the system server threads handling incoming binder requests.
     */
     */
    private LongSamplingCounter[][] mBinderThreadCpuTimesUs;
    private LongSamplingCounterArray mBinderThreadCpuTimesUs;
    @VisibleForTesting
    @VisibleForTesting
    protected PowerProfile mPowerProfile;
    protected PowerProfile mPowerProfile;
@@ -10610,8 +10618,6 @@ public class BatteryStatsImpl extends BatteryStats {
            firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
            firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i);
        }
        }
        mSystemServerCpuThreadReader = SystemServerCpuThreadReader.create();
        if (mEstimatedBatteryCapacity == -1) {
        if (mEstimatedBatteryCapacity == -1) {
            // Initialize the estimated battery capacity to a known preset one.
            // Initialize the estimated battery capacity to a known preset one.
            mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
            mEstimatedBatteryCapacity = (int) mPowerProfile.getBatteryCapacity();
@@ -11291,6 +11297,7 @@ public class BatteryStatsImpl extends BatteryStats {
        mTmpRailStats.reset();
        mTmpRailStats.reset();
        resetIfNotNull(mSystemServerCpuTimesUs, false, elapsedRealtimeUs);
        resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs);
        resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs);
        resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
        resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
@@ -12421,38 +12428,58 @@ public class BatteryStatsImpl extends BatteryStats {
        SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
        SystemServerCpuThreadReader.SystemServiceCpuThreadTimes systemServiceCpuThreadTimes =
                    mSystemServerCpuThreadReader.readDelta();
                    mSystemServerCpuThreadReader.readDelta();
        if (systemServiceCpuThreadTimes == null) {
            return;
        }
        int index = 0;
        int numCpuClusters = mPowerProfile.getNumCpuClusters();
        int numCpuClusters = mPowerProfile.getNumCpuClusters();
        if (mSystemServerThreadCpuTimesUs == null) {
        if (mSystemServerCpuTimesUs == null) {
            mSystemServerThreadCpuTimesUs = new LongSamplingCounter[numCpuClusters][];
            mSystemServerCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
            mBinderThreadCpuTimesUs = new LongSamplingCounter[numCpuClusters][];
            mSystemServerThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
        }
            mBinderThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
        for (int cluster = 0; cluster < numCpuClusters; cluster++) {
            int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster);
            if (mSystemServerThreadCpuTimesUs[cluster] == null) {
                mSystemServerThreadCpuTimesUs[cluster] = new LongSamplingCounter[numSpeeds];
                mBinderThreadCpuTimesUs[cluster] = new LongSamplingCounter[numSpeeds];
                for (int speed = 0; speed < numSpeeds; speed++) {
                    mSystemServerThreadCpuTimesUs[cluster][speed] =
                            new LongSamplingCounter(mOnBatteryTimeBase);
                    mBinderThreadCpuTimesUs[cluster][speed] =
                            new LongSamplingCounter(mOnBatteryTimeBase);
        }
        }
        mSystemServerThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.threadCpuTimesUs);
        mBinderThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
        long totalCpuTimeAllThreads = 0;
        for (int index = systemServiceCpuThreadTimes.threadCpuTimesUs.length - 1; index >= 0;
                index--) {
            totalCpuTimeAllThreads += systemServiceCpuThreadTimes.threadCpuTimesUs[index];
        }
        }
            for (int speed = 0; speed < numSpeeds; speed++) {
                mSystemServerThreadCpuTimesUs[cluster][speed].addCountLocked(
        // Estimate per cluster per frequency CPU time for the entire process
                        systemServiceCpuThreadTimes.threadCpuTimesUs[index]);
        // by distributing the total process CPU time proportionately to how much
                mBinderThreadCpuTimesUs[cluster][speed].addCountLocked(
        // CPU time its threads took on those clusters/frequencies.  This algorithm
                        systemServiceCpuThreadTimes.binderThreadCpuTimesUs[index]);
        // works more accurately when when we have equally distributed concurrency.
                index++;
        // TODO(b/169279846): obtain actual process CPU times from the kernel
        long processCpuTime = systemServiceCpuThreadTimes.processCpuTimeUs;
        if (mTmpSystemServerCpuTimesUs == null) {
            mTmpSystemServerCpuTimesUs =
                    new long[systemServiceCpuThreadTimes.threadCpuTimesUs.length];
        }
        }
        for (int index = systemServiceCpuThreadTimes.threadCpuTimesUs.length - 1; index >= 0;
                index--) {
            mTmpSystemServerCpuTimesUs[index] =
                    processCpuTime * systemServiceCpuThreadTimes.threadCpuTimesUs[index]
                            / totalCpuTimeAllThreads;
        }
        }
        mSystemServerCpuTimesUs.addCountLocked(mTmpSystemServerCpuTimesUs);
        if (DEBUG_BINDER_STATS) {
        if (DEBUG_BINDER_STATS) {
            Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)");
            Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)");
            long binderThreadTimeMs = 0;
            long totalCpuTimeMs = 0;
            long totalThreadTimeMs = 0;
            long totalThreadTimeMs = 0;
            long binderThreadTimeMs = 0;
            int cpuIndex = 0;
            int cpuIndex = 0;
            final long[] systemServerCpuTimesUs =
                    mSystemServerCpuTimesUs.getCountsLocked(0);
            final long[] systemServerThreadCpuTimesUs =
                    mSystemServerThreadCpuTimesUs.getCountsLocked(0);
            final long[] binderThreadCpuTimesUs =
                    mBinderThreadCpuTimesUs.getCountsLocked(0);
            int index = 0;
            for (int cluster = 0; cluster < numCpuClusters; cluster++) {
            for (int cluster = 0; cluster < numCpuClusters; cluster++) {
                StringBuilder sb = new StringBuilder();
                StringBuilder sb = new StringBuilder();
                sb.append("cpu").append(cpuIndex).append(": [");
                sb.append("cpu").append(cpuIndex).append(": [");
@@ -12461,15 +12488,14 @@ public class BatteryStatsImpl extends BatteryStats {
                    if (speed != 0) {
                    if (speed != 0) {
                        sb.append(", ");
                        sb.append(", ");
                    }
                    }
                    long totalCountMs =
                    long totalCountMs = systemServerThreadCpuTimesUs[index] / 1000;
                            mSystemServerThreadCpuTimesUs[cluster][speed].getCountLocked(0) / 1000;
                    long binderCountMs = binderThreadCpuTimesUs[index] / 1000;
                    long binderCountMs = mBinderThreadCpuTimesUs[cluster][speed].getCountLocked(0)
                            / 1000;
                    sb.append(String.format("%d/%d(%.1f%%)",
                    sb.append(String.format("%d/%d(%.1f%%)",
                            binderCountMs,
                            binderCountMs,
                            totalCountMs,
                            totalCountMs,
                            totalCountMs != 0 ? (double) binderCountMs * 100 / totalCountMs : 0));
                            totalCountMs != 0 ? (double) binderCountMs * 100 / totalCountMs : 0));
                    totalCpuTimeMs += systemServerCpuTimesUs[index] / 1000;
                    totalThreadTimeMs += totalCountMs;
                    totalThreadTimeMs += totalCountMs;
                    binderThreadTimeMs += binderCountMs;
                    binderThreadTimeMs += binderCountMs;
                    index++;
                    index++;
@@ -12477,6 +12503,8 @@ public class BatteryStatsImpl extends BatteryStats {
                cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster);
                cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster);
                Slog.d(TAG, sb.toString());
                Slog.d(TAG, sb.toString());
            }
            }
            Slog.d(TAG, "Total system server CPU time (ms): " + totalCpuTimeMs);
            Slog.d(TAG, "Total system server thread time (ms): " + totalThreadTimeMs);
            Slog.d(TAG, "Total system server thread time (ms): " + totalThreadTimeMs);
            Slog.d(TAG, String.format("Total Binder thread time (ms): %d (%.1f%%)",
            Slog.d(TAG, String.format("Total Binder thread time (ms): %d (%.1f%%)",
                    binderThreadTimeMs,
                    binderThreadTimeMs,
@@ -13715,7 +13743,7 @@ public class BatteryStatsImpl extends BatteryStats {
    @Override
    @Override
    public long getSystemServiceTimeAtCpuSpeed(int cluster, int step) {
    public long[] getSystemServiceTimeAtCpuSpeeds() {
        // Estimates the time spent by the system server handling incoming binder requests.
        // Estimates the time spent by the system server handling incoming binder requests.
        //
        //
        // The data that we can get from the kernel is this:
        // The data that we can get from the kernel is this:
@@ -13731,7 +13759,7 @@ public class BatteryStatsImpl extends BatteryStats {
        // - These 10 threads spent 1000 ms of CPU time in aggregate
        // - These 10 threads spent 1000 ms of CPU time in aggregate
        // - Of the 10 threads 4 were execute exclusively incoming binder calls.
        // - Of the 10 threads 4 were execute exclusively incoming binder calls.
        // - These 4 "binder" threads consumed 600 ms of CPU time in aggregate
        // - These 4 "binder" threads consumed 600 ms of CPU time in aggregate
        // - The real time spent by the system server UID doing all of this is, say, 200 ms.
        // - The real time spent by the system server process doing all of this is, say, 200 ms.
        //
        //
        // We will assume that power consumption is proportional to the time spent by the CPU
        // We will assume that power consumption is proportional to the time spent by the CPU
        // across all threads.  This is a crude assumption, but we don't have more detailed data.
        // across all threads.  This is a crude assumption, but we don't have more detailed data.
@@ -13745,41 +13773,29 @@ public class BatteryStatsImpl extends BatteryStats {
        // of the total power consumed by incoming binder calls for the given cluster/speed
        // of the total power consumed by incoming binder calls for the given cluster/speed
        // combination.
        // combination.
        if (mSystemServerThreadCpuTimesUs == null) {
        if (mSystemServerCpuTimesUs == null) {
            return 0;
            return null;
        }
        if (cluster < 0 || cluster >= mSystemServerThreadCpuTimesUs.length) {
            return 0;
        }
        final LongSamplingCounter[] threadTimesForCluster = mSystemServerThreadCpuTimesUs[cluster];
        if (step < 0 || step >= threadTimesForCluster.length) {
            return 0;
        }
        Uid systemUid = mUidStats.get(Process.SYSTEM_UID);
        if (systemUid == null) {
            return 0;
        }
        }
        final long uidTimeAtCpuSpeedUs = systemUid.getTimeAtCpuSpeed(cluster, step,
        final long[] systemServerCpuTimesUs = mSystemServerCpuTimesUs.getCountsLocked(
                BatteryStats.STATS_SINCE_CHARGED);
        final long [] systemServerThreadCpuTimesUs = mSystemServerThreadCpuTimesUs.getCountsLocked(
                BatteryStats.STATS_SINCE_CHARGED);
        final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked(
                BatteryStats.STATS_SINCE_CHARGED);
                BatteryStats.STATS_SINCE_CHARGED);
        if (uidTimeAtCpuSpeedUs == 0) {
            return 0;
        }
        final long uidThreadTimeUs =
        final int size = systemServerCpuTimesUs.length;
                threadTimesForCluster[step].getCountLocked(BatteryStats.STATS_SINCE_CHARGED);
        final long[] results = new long[size];
        if (uidThreadTimeUs == 0) {
        for (int i = 0; i < size; i++) {
            return 0;
            if (systemServerThreadCpuTimesUs[i] == 0) {
                continue;
            }
            }
        final long binderThreadTimeUs = mBinderThreadCpuTimesUs[cluster][step].getCountLocked(
            results[i] = systemServerCpuTimesUs[i] * binderThreadCpuTimesUs[i]
                BatteryStats.STATS_SINCE_CHARGED);
                    / systemServerThreadCpuTimesUs[i];
        return uidTimeAtCpuSpeedUs * binderThreadTimeUs / uidThreadTimeUs;
        }
        return results;
    }
    }
    /**
    /**
@@ -14181,18 +14197,14 @@ public class BatteryStatsImpl extends BatteryStats {
        updateSystemServiceCallStats();
        updateSystemServiceCallStats();
        if (mSystemServerThreadCpuTimesUs != null) {
        if (mSystemServerThreadCpuTimesUs != null) {
            pw.println("Per UID System server binder time in ms:");
            pw.println("Per UID System server binder time in ms:");
            long[] systemServiceTimeAtCpuSpeeds = getSystemServiceTimeAtCpuSpeeds();
            for (int i = 0; i < size; i++) {
            for (int i = 0; i < size; i++) {
                int u = mUidStats.keyAt(i);
                int u = mUidStats.keyAt(i);
                Uid uid = mUidStats.get(u);
                Uid uid = mUidStats.get(u);
                double proportionalSystemServiceUsage = uid.getProportionalSystemServiceUsage();
                double proportionalSystemServiceUsage = uid.getProportionalSystemServiceUsage();
                long timeUs = 0;
                long timeUs = 0;
                for (int cluster = 0; cluster < mSystemServerThreadCpuTimesUs.length; cluster++) {
                for (int j = systemServiceTimeAtCpuSpeeds.length - 1; j >= 0; j--) {
                    int numSpeeds = mSystemServerThreadCpuTimesUs[cluster].length;
                    timeUs += systemServiceTimeAtCpuSpeeds[j] * proportionalSystemServiceUsage;
                    for (int speed = 0; speed < numSpeeds; speed++) {
                        timeUs += getSystemServiceTimeAtCpuSpeed(cluster, speed)
                                * proportionalSystemServiceUsage;
                    }
                }
                }
                pw.print("  ");
                pw.print("  ");
@@ -15728,8 +15740,10 @@ public class BatteryStatsImpl extends BatteryStats {
            mUidStats.append(uid, u);
            mUidStats.append(uid, u);
        }
        }
        mSystemServerThreadCpuTimesUs = readCpuSpeedCountersFromParcel(in);
        mSystemServerCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
        mBinderThreadCpuTimesUs = readCpuSpeedCountersFromParcel(in);
        mSystemServerThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in,
                mOnBatteryTimeBase);
        mBinderThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
    }
    }
    public void writeToParcel(Parcel out, int flags) {
    public void writeToParcel(Parcel out, int flags) {
@@ -15930,8 +15944,9 @@ public class BatteryStatsImpl extends BatteryStats {
        } else {
        } else {
            out.writeInt(0);
            out.writeInt(0);
        }
        }
        writeCpuSpeedCountersToParcel(out, mSystemServerThreadCpuTimesUs);
        LongSamplingCounterArray.writeToParcel(out, mSystemServerCpuTimesUs);
        writeCpuSpeedCountersToParcel(out, mBinderThreadCpuTimesUs);
        LongSamplingCounterArray.writeToParcel(out, mSystemServerThreadCpuTimesUs);
        LongSamplingCounterArray.writeToParcel(out, mBinderThreadCpuTimesUs);
    }
    }
    private void writeCpuSpeedCountersToParcel(Parcel out, LongSamplingCounter[][] counters) {
    private void writeCpuSpeedCountersToParcel(Parcel out, LongSamplingCounter[][] counters) {
+259 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.os;

import static android.os.Process.PROC_OUT_LONG;
import static android.os.Process.PROC_SPACE_TERM;

import android.annotation.Nullable;
import android.os.Process;
import android.system.Os;
import android.system.OsConstants;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;

import java.io.IOException;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

/**
 * Iterates over all threads owned by a given process, and return the CPU usage for
 * each thread. The CPU usage statistics contain the amount of time spent in a frequency band. CPU
 * usage is collected using {@link ProcTimeInStateReader}.
 */
public class KernelSingleProcessCpuThreadReader {

    private static final String TAG = "KernelSingleProcCpuThreadRdr";

    private static final boolean DEBUG = false;

    /**
     * The name of the file to read CPU statistics from, must be found in {@code
     * /proc/$PID/task/$TID}
     */
    private static final String CPU_STATISTICS_FILENAME = "time_in_state";

    private static final String PROC_STAT_FILENAME = "stat";

    /** Directory under /proc/$PID containing CPU stats files for threads */
    public static final String THREAD_CPU_STATS_DIRECTORY = "task";

    /** Default mount location of the {@code proc} filesystem */
    private static final Path DEFAULT_PROC_PATH = Paths.get("/proc");

    /** The initial {@code time_in_state} file for {@link ProcTimeInStateReader} */
    private static final Path INITIAL_TIME_IN_STATE_PATH = Paths.get("self/time_in_state");

    /** See https://man7.org/linux/man-pages/man5/proc.5.html */
    private static final int[] PROCESS_FULL_STATS_FORMAT = new int[]{
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM,
            PROC_SPACE_TERM | PROC_OUT_LONG,                  // 14: utime
            PROC_SPACE_TERM | PROC_OUT_LONG,                  // 15: stime
            // Ignore remaining fields
    };

    private final long[] mProcessFullStatsData = new long[2];

    private static final int PROCESS_FULL_STAT_UTIME = 0;
    private static final int PROCESS_FULL_STAT_STIME = 1;

    /** Used to read and parse {@code time_in_state} files */
    private final ProcTimeInStateReader mProcTimeInStateReader;

    private final int mPid;

    /** Where the proc filesystem is mounted */
    private final Path mProcPath;

    // How long a CPU jiffy is in milliseconds.
    private final long mJiffyMillis;

    // Path: /proc/<pid>/stat
    private final String mProcessStatFilePath;

    // Path: /proc/<pid>/task
    private final Path mThreadsDirectoryPath;

    /**
     * Count of frequencies read from the {@code time_in_state} file. Read from {@link
     * #mProcTimeInStateReader#getCpuFrequenciesKhz()}.
     */
    private int mFrequencyCount;

    /**
     * Create with a path where `proc` is mounted. Used primarily for testing
     *
     * @param pid      PID of the process whose threads are to be read.
     * @param procPath where `proc` is mounted (to find, see {@code mount | grep ^proc})
     */
    @VisibleForTesting
    public KernelSingleProcessCpuThreadReader(
            int pid,
            Path procPath) throws IOException {
        mPid = pid;
        mProcPath = procPath;
        mProcTimeInStateReader = new ProcTimeInStateReader(
                mProcPath.resolve(INITIAL_TIME_IN_STATE_PATH));
        long jiffyHz = Os.sysconf(OsConstants._SC_CLK_TCK);
        mJiffyMillis = 1000 / jiffyHz;
        mProcessStatFilePath =
                mProcPath.resolve(String.valueOf(mPid)).resolve(PROC_STAT_FILENAME).toString();
        mThreadsDirectoryPath =
                mProcPath.resolve(String.valueOf(mPid)).resolve(THREAD_CPU_STATS_DIRECTORY);
    }

    /**
     * Create the reader and handle exceptions during creation
     *
     * @return the reader, null if an exception was thrown during creation
     */
    @Nullable
    public static KernelSingleProcessCpuThreadReader create(int pid) {
        try {
            return new KernelSingleProcessCpuThreadReader(pid, DEFAULT_PROC_PATH);
        } catch (IOException e) {
            Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e);
            return null;
        }
    }

    /**
     * Get the CPU frequencies that correspond to the times reported in {@link
     * ThreadCpuUsage#usageTimesMillis}
     */
    public int getCpuFrequencyCount() {
        if (mFrequencyCount == 0) {
            mFrequencyCount = mProcTimeInStateReader.getFrequenciesKhz().length;
        }
        return mFrequencyCount;
    }

    /**
     * Get the total and per-thread CPU usage of the process with the PID specified in the
     * constructor.
     */
    @Nullable
    public ProcessCpuUsage getProcessCpuUsage() {
        if (DEBUG) {
            Slog.d(TAG, "Reading CPU thread usages with directory " + mProcPath + " process ID "
                    + mPid);
        }

        if (!Process.readProcFile(mProcessStatFilePath, PROCESS_FULL_STATS_FORMAT, null,
                mProcessFullStatsData, null)) {
            Slog.e(TAG, "Failed to read process stat file " + mProcessStatFilePath);
            return null;
        }

        long utime = mProcessFullStatsData[PROCESS_FULL_STAT_UTIME];
        long stime = mProcessFullStatsData[PROCESS_FULL_STAT_STIME];

        long processCpuTimeMillis = (utime + stime) * mJiffyMillis;

        final ArrayList<ThreadCpuUsage> threadCpuUsages = new ArrayList<>();
        try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(mThreadsDirectoryPath)) {
            for (Path threadDirectory : threadPaths) {
                ThreadCpuUsage threadCpuUsage = getThreadCpuUsage(threadDirectory);
                if (threadCpuUsage == null) {
                    continue;
                }
                threadCpuUsages.add(threadCpuUsage);
            }
        } catch (IOException | DirectoryIteratorException e) {
            // Expected when a process finishes
            return null;
        }

        // If we found no threads, then the process has exited while we were reading from it
        if (threadCpuUsages.isEmpty()) {
            return null;
        }
        if (DEBUG) {
            Slog.d(TAG, "Read CPU usage of " + threadCpuUsages.size() + " threads");
        }
        return new ProcessCpuUsage(processCpuTimeMillis, threadCpuUsages);
    }

    /**
     * Get a thread's CPU usage
     *
     * @param threadDirectory the {@code /proc} directory of the thread
     * @return thread CPU usage. Null if the thread exited and its {@code proc} directory was
     * removed while collecting information
     */
    @Nullable
    private ThreadCpuUsage getThreadCpuUsage(Path threadDirectory) {
        // Get the thread ID from the directory name
        final int threadId;
        try {
            final String directoryName = threadDirectory.getFileName().toString();
            threadId = Integer.parseInt(directoryName);
        } catch (NumberFormatException e) {
            Slog.w(TAG, "Failed to parse thread ID when iterating over /proc/*/task", e);
            return null;
        }

        // Get the CPU statistics from the directory
        final Path threadCpuStatPath = threadDirectory.resolve(CPU_STATISTICS_FILENAME);
        final long[] cpuUsages = mProcTimeInStateReader.getUsageTimesMillis(threadCpuStatPath);
        if (cpuUsages == null) {
            return null;
        }

        return new ThreadCpuUsage(threadId, cpuUsages);
    }

    /** CPU usage of a process and all of its threads */
    public static class ProcessCpuUsage {
        public final long cpuTimeMillis;
        public final List<ThreadCpuUsage> threadCpuUsages;

        ProcessCpuUsage(long cpuTimeMillis, List<ThreadCpuUsage> threadCpuUsages) {
            this.cpuTimeMillis = cpuTimeMillis;
            this.threadCpuUsages = threadCpuUsages;
        }
    }

    /** CPU usage of a thread */
    public static class ThreadCpuUsage {
        public final int threadId;
        public final long[] usageTimesMillis;

        ThreadCpuUsage(int threadId, long[] usageTimesMillis) {
            this.threadId = threadId;
            this.usageTimesMillis = usageTimesMillis;
        }
    }
}
+45 −47

File changed.

Preview size limit exceeded, changes collapsed.

+24 −18

File changed.

Preview size limit exceeded, changes collapsed.

Loading