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

Commit 8d247edc authored by Dmitri Plotnikov's avatar Dmitri Plotnikov
Browse files

Revert "Revert "Use eBPF-based time-in-state monitoring for groups of threads""

This reverts commit c9e90583.

This is the original CL, unchanged. The test that was the reason for
the rollback now passes, see http://go/forrest-run/L29200000799760670

The reason for the test failure was that Cuttlefish does not
define any CPU frequencies, which caused https://android.googlesource.com/platform/frameworks/native/+/refs/changes/65/1558465/2/libs/cputimeinstate/cputimeinstate.cpp#578
to fall into a near-infinite loop, thus locking up BatteryStats.

The no-frequencies issue was addressed in https://r.android.com/1558465.

Reason for revert: rolling forward since the test failure has been addressed

Bug: 169279846
Change-Id: I537561b4c86ef2aaf236b1807b844855fef38f8b
(cherry picked from commit 4a7792b3)
Merged-In: I537561b4c86ef2aaf236b1807b844855fef38f8b
parent 258ae119
Loading
Loading
Loading
Loading
+19 −95
Original line number Diff line number Diff line
@@ -1098,16 +1098,6 @@ public class BatteryStatsImpl extends BatteryStats {
    private long[] mCpuFreqs;
    /**
     * Times spent by the system server process grouped by cluster and CPU speed.
     */
    private LongSamplingCounterArray mSystemServerCpuTimesUs;
    /**
     * Times spent by the system server threads grouped by cluster and CPU speed.
     */
    private LongSamplingCounterArray mSystemServerThreadCpuTimesUs;
    /**
     * Times spent by the system server threads handling incoming binder requests.
     */
@@ -10856,6 +10846,14 @@ public class BatteryStatsImpl extends BatteryStats {
        }
    }
    /**
     * Starts tracking CPU time-in-state for threads of the system server process,
     * keeping a separate account of threads receiving incoming binder calls.
     */
    public void startTrackingSystemServerCpuTime() {
        mSystemServerCpuThreadReader.startTrackingThreadCpuTime();
    }
    public void setCallback(BatteryCallback cb) {
        mCallback = cb;
    }
@@ -11508,8 +11506,6 @@ public class BatteryStatsImpl extends BatteryStats {
        MeasuredEnergyStats.resetIfNotNull(mGlobalMeasuredEnergyStats);
        resetIfNotNull(mSystemServerCpuTimesUs, false, elapsedRealtimeUs);
        resetIfNotNull(mSystemServerThreadCpuTimesUs, false, elapsedRealtimeUs);
        resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
        mLastHistoryStepDetails = null;
@@ -12695,27 +12691,17 @@ public class BatteryStatsImpl extends BatteryStats {
            return;
        }
        if (mSystemServerCpuTimesUs == null) {
            mSystemServerCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
            mSystemServerThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
        if (mBinderThreadCpuTimesUs == null) {
            mBinderThreadCpuTimesUs = new LongSamplingCounterArray(mOnBatteryTimeBase);
        }
        mSystemServerCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.processCpuTimesUs);
        mSystemServerThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.threadCpuTimesUs);
        mBinderThreadCpuTimesUs.addCountLocked(systemServiceCpuThreadTimes.binderThreadCpuTimesUs);
        if (DEBUG_BINDER_STATS) {
            Slog.d(TAG, "System server threads per CPU cluster (binder threads/total threads/%)");
            long totalCpuTimeMs = 0;
            long totalThreadTimeMs = 0;
            Slog.d(TAG, "System server threads per CPU cluster (incoming binder threads)");
            long binderThreadTimeMs = 0;
            int cpuIndex = 0;
            final long[] systemServerCpuTimesUs =
                    mSystemServerCpuTimesUs.getCountsLocked(0);
            final long[] systemServerThreadCpuTimesUs =
                    mSystemServerThreadCpuTimesUs.getCountsLocked(0);
            final long[] binderThreadCpuTimesUs =
                    mBinderThreadCpuTimesUs.getCountsLocked(0);
            final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked(
                    BatteryStats.STATS_SINCE_CHARGED);
            int index = 0;
            int numCpuClusters = mPowerProfile.getNumCpuClusters();
            for (int cluster = 0; cluster < numCpuClusters; cluster++) {
@@ -12726,28 +12712,15 @@ public class BatteryStatsImpl extends BatteryStats {
                    if (speed != 0) {
                        sb.append(", ");
                    }
                    long totalCountMs = systemServerThreadCpuTimesUs[index] / 1000;
                    long binderCountMs = binderThreadCpuTimesUs[index] / 1000;
                    sb.append(String.format("%d/%d(%.1f%%)",
                            binderCountMs,
                            totalCountMs,
                            totalCountMs != 0 ? (double) binderCountMs * 100 / totalCountMs : 0));
                    sb.append(TextUtils.formatSimple("%10d", binderCountMs));
                    totalCpuTimeMs += systemServerCpuTimesUs[index] / 1000;
                    totalThreadTimeMs += totalCountMs;
                    binderThreadTimeMs += binderCountMs;
                    index++;
                }
                cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster);
                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, String.format("Total Binder thread time (ms): %d (%.1f%%)",
                    binderThreadTimeMs,
                    binderThreadTimeMs != 0
                            ? (double) binderThreadTimeMs * 100 / totalThreadTimeMs : 0));
        }
    }
@@ -14022,60 +13995,16 @@ public class BatteryStatsImpl extends BatteryStats {
    }
    /**
     * Estimates the time spent by the system server handling incoming binder requests.
     */
    @Override
    public long[] getSystemServiceTimeAtCpuSpeeds() {
        // Estimates the time spent by the system server handling incoming binder requests.
        //
        // The data that we can get from the kernel is this:
        //   - CPU duration for a (thread - cluster - CPU speed) combination
        //   - CPU duration for a (UID - cluster - CPU speed) combination
        //
        // The configuration we have in the Power Profile is this:
        //   - Average CPU power for a (cluster - CPU speed) combination.
        //
        // The model used by BatteryStats can be illustrated with this example:
        //
        // - Let's say the system server has 10 threads.
        // - These 10 threads spent 1000 ms of CPU time in aggregate
        // - Of the 10 threads 4 were execute exclusively incoming binder calls.
        // - These 4 "binder" threads consumed 600 ms of CPU time in aggregate
        // - 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
        // across all threads.  This is a crude assumption, but we don't have more detailed data.
        // Thus,
        //   binderRealTime = realTime * aggregateBinderThreadTime / aggregateAllThreadTime
        //
        // In our example,
        //   binderRealTime = 200 * 600 / 1000 = 120ms
        //
        // We can then multiply this estimated time by the average power to obtain an estimate
        // of the total power consumed by incoming binder calls for the given cluster/speed
        // combination.
        if (mSystemServerCpuTimesUs == null) {
        if (mBinderThreadCpuTimesUs == null) {
            return null;
        }
        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);
        final int size = systemServerCpuTimesUs.length;
        final long[] results = new long[size];
        for (int i = 0; i < size; i++) {
            if (systemServerThreadCpuTimesUs[i] == 0) {
                continue;
            }
            results[i] = systemServerCpuTimesUs[i] * binderThreadCpuTimesUs[i]
                    / systemServerThreadCpuTimesUs[i];
        }
        return results;
        return mBinderThreadCpuTimesUs.getCountsLocked(BatteryStats.STATS_SINCE_CHARGED);
    }
    /**
@@ -14506,7 +14435,7 @@ public class BatteryStatsImpl extends BatteryStats {
        }
        updateSystemServiceCallStats();
        if (mSystemServerThreadCpuTimesUs != null) {
        if (mBinderThreadCpuTimesUs != null) {
            pw.println("Per UID System server binder time in ms:");
            long[] systemServiceTimeAtCpuSpeeds = getSystemServiceTimeAtCpuSpeeds();
            for (int i = 0; i < size; i++) {
@@ -16097,9 +16026,6 @@ public class BatteryStatsImpl extends BatteryStats {
            mUidStats.append(uid, u);
        }
        mSystemServerCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
        mSystemServerThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in,
                mOnBatteryTimeBase);
        mBinderThreadCpuTimesUs = LongSamplingCounterArray.readFromParcel(in, mOnBatteryTimeBase);
    }
@@ -16308,8 +16234,6 @@ public class BatteryStatsImpl extends BatteryStats {
        } else {
            out.writeInt(0);
        }
        LongSamplingCounterArray.writeToParcel(out, mSystemServerCpuTimesUs);
        LongSamplingCounterArray.writeToParcel(out, mSystemServerThreadCpuTimesUs);
        LongSamplingCounterArray.writeToParcel(out, mBinderThreadCpuTimesUs);
    }
+103 −190
Original line number Diff line number Diff line
@@ -16,23 +16,12 @@

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.Arrays;

/**
@@ -45,93 +34,65 @@ public class KernelSingleProcessCpuThreadReader {
    private static final String TAG = "KernelSingleProcCpuThreadRdr";

    private static final boolean DEBUG = false;
    private static final boolean NATIVE_ENABLED = true;

    private final int mPid;

    private final CpuTimeInStateReader mCpuTimeInStateReader;

    private int[] mSelectedThreadNativeTids = new int[0];  // Sorted

    /**
     * The name of the file to read CPU statistics from, must be found in {@code
     * /proc/$PID/task/$TID}
     * Count of frequencies read from the {@code time_in_state} file.
     */
    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;
    private int mFrequencyCount;

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

    // How long a CPU jiffy is in milliseconds.
    private final long mJiffyMillis;
    /**
     * A CPU time-in-state provider for testing.  Imitates the behavior of the corresponding
     * methods in frameworks/native/libs/cputimeinstate/cputimeinstate.c
     */
    @VisibleForTesting
    public interface CpuTimeInStateReader {
        /**
         * Returns the overall number of cluster-frequency combinations.
         */
        int getCpuFrequencyCount();

    // Path: /proc/<pid>/stat
    private final String mProcessStatFilePath;
        /**
         * Returns true to indicate success.
         *
         * Called from native.
         */
        boolean startTrackingProcessCpuTimes(int tgid);

    // Path: /proc/<pid>/task
    private final Path mThreadsDirectoryPath;
        /**
         * Returns true to indicate success.
         *
         * Called from native.
         */
        boolean startAggregatingTaskCpuTimes(int pid, int aggregationKey);

        /**
     * Count of frequencies read from the {@code time_in_state} file. Read from {@link
     * #mProcTimeInStateReader#getCpuFrequenciesKhz()}.
         * Must return an array of strings formatted like this:
         * "aggKey:t0_0 t0_1...:t1_0 t1_1..."
         * Times should be provided in nanoseconds.
         *
         * Called from native.
         */
    private int mFrequencyCount;
        String[] getAggregatedTaskCpuFreqTimes(int pid);
    }

    /**
     * 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 {
    public KernelSingleProcessCpuThreadReader(int pid,
            @Nullable CpuTimeInStateReader cpuTimeInStateReader) 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);
        mCpuTimeInStateReader = cpuTimeInStateReader;
    }

    /**
@@ -142,7 +103,7 @@ public class KernelSingleProcessCpuThreadReader {
    @Nullable
    public static KernelSingleProcessCpuThreadReader create(int pid) {
        try {
            return new KernelSingleProcessCpuThreadReader(pid, DEFAULT_PROC_PATH);
            return new KernelSingleProcessCpuThreadReader(pid, null);
        } catch (IOException e) {
            Slog.e(TAG, "Failed to initialize KernelSingleProcessCpuThreadReader", e);
            return null;
@@ -150,146 +111,98 @@ public class KernelSingleProcessCpuThreadReader {
    }

    /**
     * Get the CPU frequencies that correspond to the times reported in {@link
     * ProcessCpuUsage#processCpuTimesMillis} etc.
     * Starts tracking aggregated CPU time-in-state of all threads of the process with the PID
     * supplied in the constructor.
     */
    public int getCpuFrequencyCount() {
        if (mFrequencyCount == 0) {
            mFrequencyCount = mProcTimeInStateReader.getFrequenciesKhz().length;
    public void startTrackingThreadCpuTimes() {
        if (!mIsTracking) {
            if (!startTrackingProcessCpuTimes(mPid, mCpuTimeInStateReader)) {
                Slog.e(TAG, "Failed to start tracking process CPU times for " + mPid);
            }
            if (mSelectedThreadNativeTids.length > 0) {
                if (!startAggregatingThreadCpuTimes(mSelectedThreadNativeTids,
                        mCpuTimeInStateReader)) {
                    Slog.e(TAG, "Failed to start tracking aggregated thread CPU times for "
                            + Arrays.toString(mSelectedThreadNativeTids));
                }
            }
            mIsTracking = true;
        }
        return mFrequencyCount;
    }

    /**
     * Get the total and per-thread CPU usage of the process with the PID specified in the
     * constructor.
     *
     * @param selectedThreadIds a SORTED array of native Thread IDs whose CPU times should
     * @param nativeTids an array of native Thread IDs whose CPU times should
     *                   be aggregated as a group.  This is expected to be a subset
     *                   of all thread IDs owned by the process.
     */
    @Nullable
    public ProcessCpuUsage getProcessCpuUsage(int[] selectedThreadIds) {
        if (DEBUG) {
            Slog.d(TAG, "Reading CPU thread usages with directory " + mProcPath + " process ID "
                    + mPid);
    public void setSelectedThreadIds(int[] nativeTids) {
        mSelectedThreadNativeTids = nativeTids.clone();
        if (mIsTracking) {
            startAggregatingThreadCpuTimes(mSelectedThreadNativeTids, mCpuTimeInStateReader);
        }

        int cpuFrequencyCount = getCpuFrequencyCount();
        ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(cpuFrequencyCount);

        if (NATIVE_ENABLED) {
            boolean result = readProcessCpuUsage(mProcPath.toString(), mPid,
                    selectedThreadIds, processCpuUsage.processCpuTimesMillis,
                    processCpuUsage.threadCpuTimesMillis,
                    processCpuUsage.selectedThreadCpuTimesMillis);
            if (!result) {
                return null;
            }
            return processCpuUsage;
    }

        if (!isSorted(selectedThreadIds)) {
            throw new IllegalArgumentException("selectedThreadIds is not sorted: "
                    + Arrays.toString(selectedThreadIds));
    /**
     * Get the CPU frequencies that correspond to the times reported in {@link ProcessCpuUsage}.
     */
    public int getCpuFrequencyCount() {
        if (mFrequencyCount == 0) {
            mFrequencyCount = getCpuFrequencyCount(mCpuTimeInStateReader);
        }

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

        long utime = mProcessFullStatsData[PROCESS_FULL_STAT_UTIME];
        long stime = mProcessFullStatsData[PROCESS_FULL_STAT_STIME];
    /**
     * Get the total CPU usage of the process with the PID specified in the
     * constructor. The CPU usage time is aggregated across all threads and may
     * exceed the time the entire process has been running.
     */
    @Nullable
    public ProcessCpuUsage getProcessCpuUsage() {
        if (DEBUG) {
            Slog.d(TAG, "Reading CPU thread usages for PID " + mPid);
        }

        long processCpuTimeMillis = (utime + stime) * mJiffyMillis;
        ProcessCpuUsage processCpuUsage = new ProcessCpuUsage(getCpuFrequencyCount());

        try (DirectoryStream<Path> threadPaths = Files.newDirectoryStream(mThreadsDirectoryPath)) {
            for (Path threadDirectory : threadPaths) {
                readThreadCpuUsage(processCpuUsage, selectedThreadIds, threadDirectory);
            }
        } catch (IOException | DirectoryIteratorException e) {
            // Expected when a process finishes
        boolean result = readProcessCpuUsage(mPid,
                processCpuUsage.threadCpuTimesMillis,
                processCpuUsage.selectedThreadCpuTimesMillis,
                mCpuTimeInStateReader);
        if (!result) {
            return null;
        }

        // Estimate per cluster per frequency CPU time for the entire process
        // by distributing the total process CPU time proportionately to how much
        // CPU time its threads took on those clusters/frequencies.  This algorithm
        // works more accurately when when we have equally distributed concurrency.
        // TODO(b/169279846): obtain actual process CPU times from the kernel
        long totalCpuTimeAllThreads = 0;
        for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
            totalCpuTimeAllThreads += processCpuUsage.threadCpuTimesMillis[i];
        }

        for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
            processCpuUsage.processCpuTimesMillis[i] =
                    processCpuTimeMillis * processCpuUsage.threadCpuTimesMillis[i]
                            / totalCpuTimeAllThreads;
        if (DEBUG) {
            Slog.d(TAG, "threadCpuTimesMillis = "
                    + Arrays.toString(processCpuUsage.threadCpuTimesMillis));
            Slog.d(TAG, "selectedThreadCpuTimesMillis = "
                    + Arrays.toString(processCpuUsage.selectedThreadCpuTimesMillis));
        }

        return processCpuUsage;
    }

    /**
     * Reads a thread's CPU usage and aggregates the per-cluster per-frequency CPU times.
     *
     * @param threadDirectory the {@code /proc} directory of the thread
     */
    private void readThreadCpuUsage(ProcessCpuUsage processCpuUsage, int[] selectedThreadIds,
            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;
        }

        // 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;
        }

        final int cpuFrequencyCount = getCpuFrequencyCount();
        final boolean isSelectedThread = Arrays.binarySearch(selectedThreadIds, threadId) >= 0;
        for (int i = cpuFrequencyCount - 1; i >= 0; i--) {
            processCpuUsage.threadCpuTimesMillis[i] += cpuUsages[i];
            if (isSelectedThread) {
                processCpuUsage.selectedThreadCpuTimesMillis[i] += cpuUsages[i];
            }
        }
    }

    /** CPU usage of a process, all of its threads and a selected subset of its threads */
    public static class ProcessCpuUsage {
        public long[] processCpuTimesMillis;
        public long[] threadCpuTimesMillis;
        public long[] selectedThreadCpuTimesMillis;

        public ProcessCpuUsage(int cpuFrequencyCount) {
            processCpuTimesMillis = new long[cpuFrequencyCount];
            threadCpuTimesMillis = new long[cpuFrequencyCount];
            selectedThreadCpuTimesMillis = new long[cpuFrequencyCount];
        }
    }

    private static boolean isSorted(int[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            if (array[i] > array[i + 1]) {
                return false;
            }
        }
        return true;
    }
    private native int getCpuFrequencyCount(CpuTimeInStateReader reader);

    private native boolean startTrackingProcessCpuTimes(int pid, CpuTimeInStateReader reader);

    private native boolean startAggregatingThreadCpuTimes(int[] selectedThreadIds,
            CpuTimeInStateReader reader);

    private native boolean readProcessCpuUsage(String procPath, int pid, int[] selectedThreadIds,
            long[] processCpuTimesMillis, long[] threadCpuTimesMillis,
            long[] selectedThreadCpuTimesMillis);
    private native boolean readProcessCpuUsage(int pid,
            long[] threadCpuTimesMillis,
            long[] selectedThreadCpuTimesMillis,
            CpuTimeInStateReader reader);
}
+14 −18

File changed.

Preview size limit exceeded, changes collapsed.

+242 −178

File changed.

Preview size limit exceeded, changes collapsed.

+48 −83

File changed.

Preview size limit exceeded, changes collapsed.

Loading