| /* |
| * Copyright (C) 2018 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 com.android.internal.os.KernelCpuProcStringReader.asLongs; |
| import static com.android.internal.util.Preconditions.checkNotNull; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.os.StrictMode; |
| import android.os.SystemClock; |
| import android.util.IntArray; |
| import android.util.Slog; |
| import android.util.SparseArray; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator; |
| |
| import java.io.BufferedReader; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.nio.CharBuffer; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| |
| /** |
| * Reads per-UID CPU time proc files. Concrete implementations are all nested inside. |
| * |
| * This class uses a throttler to reject any {@link #readDelta} or {@link #readAbsolute} call |
| * within {@link #mMinTimeBetweenRead}. The throttler can be enable / disabled via a param in |
| * the constructor. |
| * |
| * This class and its subclasses are NOT thread-safe and NOT designed to be accessed by more than |
| * one caller since each caller has its own view of delta. |
| * |
| * @param <T> The type of CPU time for the callback. |
| */ |
| public abstract class KernelCpuUidTimeReader<T> { |
| protected static final boolean DEBUG = false; |
| private static final long DEFAULT_MIN_TIME_BETWEEN_READ = 1000L; // In milliseconds |
| |
| final String mTag = this.getClass().getSimpleName(); |
| final SparseArray<T> mLastTimes = new SparseArray<>(); |
| final KernelCpuProcStringReader mReader; |
| final boolean mThrottle; |
| private long mMinTimeBetweenRead = DEFAULT_MIN_TIME_BETWEEN_READ; |
| private long mLastReadTimeMs = 0; |
| |
| /** |
| * Callback interface for processing each line of the proc file. |
| * |
| * @param <T> The type of CPU time for the callback function. |
| */ |
| public interface Callback<T> { |
| /** |
| * @param uid UID of the app |
| * @param time Time spent. The exact data structure depends on subclass implementation. |
| */ |
| void onUidCpuTime(int uid, T time); |
| } |
| |
| KernelCpuUidTimeReader(KernelCpuProcStringReader reader, boolean throttle) { |
| mReader = reader; |
| mThrottle = throttle; |
| } |
| |
| /** |
| * Reads the proc file, calling into the callback with a delta of time for each UID. |
| * |
| * @param cb The callback to invoke for each line of the proc file. If null,the data is |
| * consumed and subsequent calls to readDelta will provide a fresh delta. |
| */ |
| public void readDelta(@Nullable Callback<T> cb) { |
| if (!mThrottle) { |
| readDeltaImpl(cb); |
| return; |
| } |
| final long currTimeMs = SystemClock.elapsedRealtime(); |
| if (currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) { |
| if (DEBUG) { |
| Slog.d(mTag, "Throttle readDelta"); |
| } |
| return; |
| } |
| readDeltaImpl(cb); |
| mLastReadTimeMs = currTimeMs; |
| } |
| |
| /** |
| * Reads the proc file, calling into the callback with cumulative time for each UID. |
| * |
| * @param cb The callback to invoke for each line of the proc file. It cannot be null. |
| */ |
| public void readAbsolute(Callback<T> cb) { |
| if (!mThrottle) { |
| readAbsoluteImpl(cb); |
| return; |
| } |
| final long currTimeMs = SystemClock.elapsedRealtime(); |
| if (currTimeMs < mLastReadTimeMs + mMinTimeBetweenRead) { |
| if (DEBUG) { |
| Slog.d(mTag, "Throttle readAbsolute"); |
| } |
| return; |
| } |
| readAbsoluteImpl(cb); |
| mLastReadTimeMs = currTimeMs; |
| } |
| |
| abstract void readDeltaImpl(@Nullable Callback<T> cb); |
| |
| abstract void readAbsoluteImpl(Callback<T> callback); |
| |
| /** |
| * Removes the UID from internal accounting data. This method, overridden in |
| * {@link KernelCpuUidUserSysTimeReader}, also removes the UID from the kernel module. |
| * |
| * @param uid The UID to remove. |
| * @see KernelCpuUidUserSysTimeReader#removeUid(int) |
| */ |
| public void removeUid(int uid) { |
| mLastTimes.delete(uid); |
| } |
| |
| /** |
| * Removes UIDs in a given range from internal accounting data. This method, overridden in |
| * {@link KernelCpuUidUserSysTimeReader}, also removes the UIDs from the kernel module. |
| * |
| * @param startUid the first uid to remove. |
| * @param endUid the last uid to remove. |
| * @see KernelCpuUidUserSysTimeReader#removeUidsInRange(int, int) |
| */ |
| public void removeUidsInRange(int startUid, int endUid) { |
| if (endUid < startUid) { |
| Slog.e(mTag, "start UID " + startUid + " > end UID " + endUid); |
| return; |
| } |
| mLastTimes.put(startUid, null); |
| mLastTimes.put(endUid, null); |
| final int firstIndex = mLastTimes.indexOfKey(startUid); |
| final int lastIndex = mLastTimes.indexOfKey(endUid); |
| mLastTimes.removeAtRange(firstIndex, lastIndex - firstIndex + 1); |
| } |
| |
| /** |
| * Set the minimum time in milliseconds between reads. If throttle is not enabled, this method |
| * has no effect. |
| * |
| * @param minTimeBetweenRead The minimum time in milliseconds. |
| */ |
| public void setThrottle(long minTimeBetweenRead) { |
| if (mThrottle && minTimeBetweenRead >= 0) { |
| mMinTimeBetweenRead = minTimeBetweenRead; |
| } |
| } |
| |
| /** |
| * Reads /proc/uid_cputime/show_uid_stat which has the line format: |
| * |
| * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds |
| * |
| * This provides the time a UID's processes spent executing in user-space and kernel-space. |
| * The file contains a monotonically increasing count of time for a single boot. This class |
| * maintains the previous results of a call to {@link #readDelta} in order to provide a proper |
| * delta. |
| * |
| * The second parameter of the callback is a long[] with 2 elements, [user time in us, system |
| * time in us]. |
| */ |
| public static class KernelCpuUidUserSysTimeReader extends KernelCpuUidTimeReader<long[]> { |
| private static final String REMOVE_UID_PROC_FILE = "/proc/uid_cputime/remove_uid_range"; |
| |
| // [uid, user_time, system_time, (maybe) power_in_milli-amp-micro_seconds] |
| private final long[] mBuffer = new long[4]; |
| // A reusable array to hold [user_time, system_time] for the callback. |
| private final long[] mUsrSysTime = new long[2]; |
| |
| public KernelCpuUidUserSysTimeReader(boolean throttle) { |
| super(KernelCpuProcStringReader.getUserSysTimeReaderInstance(), throttle); |
| } |
| |
| @VisibleForTesting |
| public KernelCpuUidUserSysTimeReader(KernelCpuProcStringReader reader, boolean throttle) { |
| super(reader, throttle); |
| } |
| |
| @Override |
| void readDeltaImpl(@Nullable Callback<long[]> cb) { |
| try (ProcFileIterator iter = mReader.open(!mThrottle)) { |
| if (iter == null) { |
| return; |
| } |
| CharBuffer buf; |
| while ((buf = iter.nextLine()) != null) { |
| if (asLongs(buf, mBuffer) < 3) { |
| Slog.wtf(mTag, "Invalid line: " + buf.toString()); |
| continue; |
| } |
| final int uid = (int) mBuffer[0]; |
| long[] lastTimes = mLastTimes.get(uid); |
| if (lastTimes == null) { |
| lastTimes = new long[2]; |
| mLastTimes.put(uid, lastTimes); |
| } |
| final long currUsrTimeUs = mBuffer[1]; |
| final long currSysTimeUs = mBuffer[2]; |
| mUsrSysTime[0] = currUsrTimeUs - lastTimes[0]; |
| mUsrSysTime[1] = currSysTimeUs - lastTimes[1]; |
| |
| if (mUsrSysTime[0] < 0 || mUsrSysTime[1] < 0) { |
| Slog.e(mTag, "Negative user/sys time delta for UID=" + uid |
| + "\nPrev times: u=" + lastTimes[0] + " s=" + lastTimes[1] |
| + " Curr times: u=" + currUsrTimeUs + " s=" + currSysTimeUs); |
| } else if (mUsrSysTime[0] > 0 || mUsrSysTime[1] > 0) { |
| if (cb != null) { |
| cb.onUidCpuTime(uid, mUsrSysTime); |
| } |
| } |
| lastTimes[0] = currUsrTimeUs; |
| lastTimes[1] = currSysTimeUs; |
| } |
| } |
| } |
| |
| @Override |
| void readAbsoluteImpl(Callback<long[]> cb) { |
| try (ProcFileIterator iter = mReader.open(!mThrottle)) { |
| if (iter == null) { |
| return; |
| } |
| CharBuffer buf; |
| while ((buf = iter.nextLine()) != null) { |
| if (asLongs(buf, mBuffer) < 3) { |
| Slog.wtf(mTag, "Invalid line: " + buf.toString()); |
| continue; |
| } |
| mUsrSysTime[0] = mBuffer[1]; // User time in microseconds |
| mUsrSysTime[1] = mBuffer[2]; // System time in microseconds |
| cb.onUidCpuTime((int) mBuffer[0], mUsrSysTime); |
| } |
| } |
| } |
| |
| @Override |
| public void removeUid(int uid) { |
| super.removeUid(uid); |
| removeUidsFromKernelModule(uid, uid); |
| } |
| |
| @Override |
| public void removeUidsInRange(int startUid, int endUid) { |
| super.removeUidsInRange(startUid, endUid); |
| removeUidsFromKernelModule(startUid, endUid); |
| } |
| |
| /** |
| * Removes UIDs in a given range from the kernel module and internal accounting data. Only |
| * {@link BatteryStatsImpl} and its child processes should call this, as the change on |
| * Kernel is |
| * visible system wide. |
| * |
| * @param startUid the first uid to remove |
| * @param endUid the last uid to remove |
| */ |
| private void removeUidsFromKernelModule(int startUid, int endUid) { |
| Slog.d(mTag, "Removing uids " + startUid + "-" + endUid); |
| final int oldMask = StrictMode.allowThreadDiskWritesMask(); |
| try (FileWriter writer = new FileWriter(REMOVE_UID_PROC_FILE)) { |
| writer.write(startUid + "-" + endUid); |
| writer.flush(); |
| } catch (IOException e) { |
| Slog.e(mTag, "failed to remove uids " + startUid + " - " + endUid |
| + " from uid_cputime module", e); |
| } finally { |
| StrictMode.setThreadPolicyMask(oldMask); |
| } |
| } |
| } |
| |
| /** |
| * Reads /proc/uid_time_in_state which has the format: |
| * |
| * uid: [freq1] [freq2] [freq3] ... |
| * [uid1]: [time in freq1] [time in freq2] [time in freq3] ... |
| * [uid2]: [time in freq1] [time in freq2] [time in freq3] ... |
| * ... |
| * |
| * This provides the times a UID's processes spent executing at each different cpu frequency. |
| * The file contains a monotonically increasing count of time for a single boot. This class |
| * maintains the previous results of a call to {@link #readDelta} in order to provide a proper |
| * delta. |
| */ |
| public static class KernelCpuUidFreqTimeReader extends KernelCpuUidTimeReader<long[]> { |
| private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state"; |
| // We check the existence of proc file a few times (just in case it is not ready yet when we |
| // start reading) and if it is not available, we simply ignore further read requests. |
| private static final int MAX_ERROR_COUNT = 5; |
| |
| private final Path mProcFilePath; |
| private long[] mBuffer; |
| private long[] mCurTimes; |
| private long[] mDeltaTimes; |
| private long[] mCpuFreqs; |
| |
| private int mFreqCount = 0; |
| private int mErrors = 0; |
| private boolean mPerClusterTimesAvailable; |
| private boolean mAllUidTimesAvailable = true; |
| |
| public KernelCpuUidFreqTimeReader(boolean throttle) { |
| this(UID_TIMES_PROC_FILE, KernelCpuProcStringReader.getFreqTimeReaderInstance(), |
| throttle); |
| } |
| |
| @VisibleForTesting |
| public KernelCpuUidFreqTimeReader(String procFile, KernelCpuProcStringReader reader, |
| boolean throttle) { |
| super(reader, throttle); |
| mProcFilePath = Paths.get(procFile); |
| } |
| |
| /** |
| * @return Whether per-cluster times are available. |
| */ |
| public boolean perClusterTimesAvailable() { |
| return mPerClusterTimesAvailable; |
| } |
| |
| /** |
| * @return Whether all-UID times are available. |
| */ |
| public boolean allUidTimesAvailable() { |
| return mAllUidTimesAvailable; |
| } |
| |
| /** |
| * @return A map of all UIDs to their CPU time-in-state array in milliseconds. |
| */ |
| public SparseArray<long[]> getAllUidCpuFreqTimeMs() { |
| return mLastTimes; |
| } |
| |
| /** |
| * Reads a list of CPU frequencies from /proc/uid_time_in_state. Uses a given PowerProfile |
| * to determine if per-cluster times are available. |
| * |
| * @param powerProfile The PowerProfile to compare against. |
| * @return A long[] of CPU frequencies in Hz. |
| */ |
| public long[] readFreqs(@NonNull PowerProfile powerProfile) { |
| checkNotNull(powerProfile); |
| if (mCpuFreqs != null) { |
| // No need to read cpu freqs more than once. |
| return mCpuFreqs; |
| } |
| if (!mAllUidTimesAvailable) { |
| return null; |
| } |
| final int oldMask = StrictMode.allowThreadDiskReadsMask(); |
| try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) { |
| if (readFreqs(reader.readLine()) == null) { |
| return null; |
| } |
| } catch (IOException e) { |
| if (++mErrors >= MAX_ERROR_COUNT) { |
| mAllUidTimesAvailable = false; |
| } |
| Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e); |
| return null; |
| } finally { |
| StrictMode.setThreadPolicyMask(oldMask); |
| } |
| // Check if the freqs in the proc file correspond to per-cluster freqs. |
| final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs(); |
| final int numClusters = powerProfile.getNumCpuClusters(); |
| if (numClusterFreqs.size() == numClusters) { |
| mPerClusterTimesAvailable = true; |
| for (int i = 0; i < numClusters; ++i) { |
| if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) { |
| mPerClusterTimesAvailable = false; |
| break; |
| } |
| } |
| } else { |
| mPerClusterTimesAvailable = false; |
| } |
| Slog.i(mTag, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable); |
| return mCpuFreqs; |
| } |
| |
| private long[] readFreqs(String line) { |
| if (line == null) { |
| return null; |
| } |
| final String[] lineArray = line.split(" "); |
| if (lineArray.length <= 1) { |
| Slog.wtf(mTag, "Malformed freq line: " + line); |
| return null; |
| } |
| mFreqCount = lineArray.length - 1; |
| mCpuFreqs = new long[mFreqCount]; |
| mCurTimes = new long[mFreqCount]; |
| mDeltaTimes = new long[mFreqCount]; |
| mBuffer = new long[mFreqCount + 1]; |
| for (int i = 0; i < mFreqCount; ++i) { |
| mCpuFreqs[i] = Long.parseLong(lineArray[i + 1], 10); |
| } |
| return mCpuFreqs; |
| } |
| |
| @Override |
| void readDeltaImpl(@Nullable Callback<long[]> cb) { |
| try (ProcFileIterator iter = mReader.open(!mThrottle)) { |
| if (!checkPrecondition(iter)) { |
| return; |
| } |
| CharBuffer buf; |
| while ((buf = iter.nextLine()) != null) { |
| if (asLongs(buf, mBuffer) != mBuffer.length) { |
| Slog.wtf(mTag, "Invalid line: " + buf.toString()); |
| continue; |
| } |
| final int uid = (int) mBuffer[0]; |
| long[] lastTimes = mLastTimes.get(uid); |
| if (lastTimes == null) { |
| lastTimes = new long[mFreqCount]; |
| mLastTimes.put(uid, lastTimes); |
| } |
| copyToCurTimes(); |
| boolean notify = false; |
| boolean valid = true; |
| for (int i = 0; i < mFreqCount; i++) { |
| // Unit is 10ms. |
| mDeltaTimes[i] = mCurTimes[i] - lastTimes[i]; |
| if (mDeltaTimes[i] < 0) { |
| Slog.e(mTag, "Negative delta from freq time proc: " + mDeltaTimes[i]); |
| valid = false; |
| } |
| notify |= mDeltaTimes[i] > 0; |
| } |
| if (notify && valid) { |
| System.arraycopy(mCurTimes, 0, lastTimes, 0, mFreqCount); |
| if (cb != null) { |
| cb.onUidCpuTime(uid, mDeltaTimes); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| void readAbsoluteImpl(Callback<long[]> cb) { |
| try (ProcFileIterator iter = mReader.open(!mThrottle)) { |
| if (!checkPrecondition(iter)) { |
| return; |
| } |
| CharBuffer buf; |
| while ((buf = iter.nextLine()) != null) { |
| if (asLongs(buf, mBuffer) != mBuffer.length) { |
| Slog.wtf(mTag, "Invalid line: " + buf.toString()); |
| continue; |
| } |
| copyToCurTimes(); |
| cb.onUidCpuTime((int) mBuffer[0], mCurTimes); |
| } |
| } |
| } |
| |
| private void copyToCurTimes() { |
| for (int i = 0; i < mFreqCount; i++) { |
| mCurTimes[i] = mBuffer[i + 1] * 10; |
| } |
| } |
| |
| private boolean checkPrecondition(ProcFileIterator iter) { |
| if (iter == null || !iter.hasNextLine()) { |
| // Error logged in KernelCpuProcStringReader. |
| return false; |
| } |
| CharBuffer line = iter.nextLine(); |
| if (mCpuFreqs != null) { |
| return true; |
| } |
| return readFreqs(line.toString()) != null; |
| } |
| |
| /** |
| * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs |
| * read from the proc file. |
| * |
| * We need to assume that freqs in each cluster are strictly increasing. |
| * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means |
| * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52) |
| * |
| * @return an IntArray filled with no. of freqs in each cluster. |
| */ |
| private IntArray extractClusterInfoFromProcFileFreqs() { |
| final IntArray numClusterFreqs = new IntArray(); |
| int freqsFound = 0; |
| for (int i = 0; i < mFreqCount; ++i) { |
| freqsFound++; |
| if (i + 1 == mFreqCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) { |
| numClusterFreqs.add(freqsFound); |
| freqsFound = 0; |
| } |
| } |
| return numClusterFreqs; |
| } |
| } |
| |
| /** |
| * Reads /proc/uid_concurrent_active_time and reports CPU active time to BatteryStats to |
| * compute {@link PowerProfile#POWER_CPU_ACTIVE}. |
| * |
| * /proc/uid_concurrent_active_time has the following format: |
| * cpus: n |
| * uid0: time0a, time0b, ..., time0n, |
| * uid1: time1a, time1b, ..., time1n, |
| * uid2: time2a, time2b, ..., time2n, |
| * ... |
| * where n is the total number of cpus (num_possible_cpus) |
| * timeXn means the CPU time that a UID X spent running concurrently with n other processes. |
| * |
| * The file contains a monotonically increasing count of time for a single boot. This class |
| * maintains the previous results of a call to {@link #readDelta} in order to provide a |
| * proper delta. |
| */ |
| public static class KernelCpuUidActiveTimeReader extends KernelCpuUidTimeReader<Long> { |
| private int mCores = 0; |
| private long[] mBuffer; |
| |
| public KernelCpuUidActiveTimeReader(boolean throttle) { |
| super(KernelCpuProcStringReader.getActiveTimeReaderInstance(), throttle); |
| } |
| |
| @VisibleForTesting |
| public KernelCpuUidActiveTimeReader(KernelCpuProcStringReader reader, boolean throttle) { |
| super(reader, throttle); |
| } |
| |
| @Override |
| void readDeltaImpl(@Nullable Callback<Long> cb) { |
| try (ProcFileIterator iter = mReader.open(!mThrottle)) { |
| if (!checkPrecondition(iter)) { |
| return; |
| } |
| CharBuffer buf; |
| while ((buf = iter.nextLine()) != null) { |
| if (asLongs(buf, mBuffer) != mBuffer.length) { |
| Slog.wtf(mTag, "Invalid line: " + buf.toString()); |
| continue; |
| } |
| int uid = (int) mBuffer[0]; |
| long cpuActiveTime = sumActiveTime(mBuffer); |
| if (cpuActiveTime > 0) { |
| long delta = cpuActiveTime - mLastTimes.get(uid, 0L); |
| if (delta > 0) { |
| mLastTimes.put(uid, cpuActiveTime); |
| if (cb != null) { |
| cb.onUidCpuTime(uid, delta); |
| } |
| } else if (delta < 0) { |
| Slog.e(mTag, "Negative delta from active time proc: " + delta); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| void readAbsoluteImpl(Callback<Long> cb) { |
| try (ProcFileIterator iter = mReader.open(!mThrottle)) { |
| if (!checkPrecondition(iter)) { |
| return; |
| } |
| CharBuffer buf; |
| while ((buf = iter.nextLine()) != null) { |
| if (asLongs(buf, mBuffer) != mBuffer.length) { |
| Slog.wtf(mTag, "Invalid line: " + buf.toString()); |
| continue; |
| } |
| long cpuActiveTime = sumActiveTime(mBuffer); |
| if (cpuActiveTime > 0) { |
| cb.onUidCpuTime((int) mBuffer[0], cpuActiveTime); |
| } |
| } |
| } |
| } |
| |
| private static long sumActiveTime(long[] times) { |
| // UID is stored at times[0]. |
| double sum = 0; |
| for (int i = 1; i < times.length; i++) { |
| sum += (double) times[i] * 10 / i; // Unit is 10ms. |
| } |
| return (long) sum; |
| } |
| |
| private boolean checkPrecondition(ProcFileIterator iter) { |
| if (iter == null || !iter.hasNextLine()) { |
| // Error logged in KernelCpuProcStringReader. |
| return false; |
| } |
| CharBuffer line = iter.nextLine(); |
| if (mCores > 0) { |
| return true; |
| } |
| |
| String str = line.toString(); |
| if (!str.startsWith("cpus:")) { |
| Slog.wtf(mTag, "Malformed uid_concurrent_active_time line: " + line); |
| return false; |
| } |
| int cores = Integer.parseInt(str.substring(5).trim(), 10); |
| if (cores <= 0) { |
| Slog.wtf(mTag, "Malformed uid_concurrent_active_time line: " + line); |
| return false; |
| } |
| mCores = cores; |
| mBuffer = new long[mCores + 1]; // UID is stored at mBuffer[0]. |
| return true; |
| } |
| } |
| |
| |
| /** |
| * Reads /proc/uid_concurrent_policy_time and reports CPU cluster times to BatteryStats to |
| * compute cluster power. See {@link PowerProfile#getAveragePowerForCpuCluster(int)}. |
| * |
| * /proc/uid_concurrent_policy_time has the following format: |
| * policyX: x policyY: y policyZ: z... |
| * uid1, time1a, time1b, ..., time1n, |
| * uid2, time2a, time2b, ..., time2n, |
| * ... |
| * The first line lists all policies (i.e. clusters) followed by # cores in each policy. |
| * Each uid is followed by x time entries corresponding to the time it spent on clusterX |
| * running concurrently with 0, 1, 2, ..., x - 1 other processes, then followed by y, z, ... |
| * time entries. |
| * |
| * The file contains a monotonically increasing count of time for a single boot. This class |
| * maintains the previous results of a call to {@link #readDelta} in order to provide a |
| * proper delta. |
| */ |
| public static class KernelCpuUidClusterTimeReader extends KernelCpuUidTimeReader<long[]> { |
| private int mNumClusters; |
| private int mNumCores; |
| private int[] mCoresOnClusters; // # cores on each cluster. |
| private long[] mBuffer; // To store data returned from ProcFileIterator. |
| private long[] mCurTime; |
| private long[] mDeltaTime; |
| |
| public KernelCpuUidClusterTimeReader(boolean throttle) { |
| super(KernelCpuProcStringReader.getClusterTimeReaderInstance(), throttle); |
| } |
| |
| @VisibleForTesting |
| public KernelCpuUidClusterTimeReader(KernelCpuProcStringReader reader, boolean throttle) { |
| super(reader, throttle); |
| } |
| |
| @Override |
| void readDeltaImpl(@Nullable Callback<long[]> cb) { |
| try (ProcFileIterator iter = mReader.open(!mThrottle)) { |
| if (!checkPrecondition(iter)) { |
| return; |
| } |
| CharBuffer buf; |
| while ((buf = iter.nextLine()) != null) { |
| if (asLongs(buf, mBuffer) != mBuffer.length) { |
| Slog.wtf(mTag, "Invalid line: " + buf.toString()); |
| continue; |
| } |
| int uid = (int) mBuffer[0]; |
| long[] lastTimes = mLastTimes.get(uid); |
| if (lastTimes == null) { |
| lastTimes = new long[mNumClusters]; |
| mLastTimes.put(uid, lastTimes); |
| } |
| sumClusterTime(); |
| boolean valid = true; |
| boolean notify = false; |
| for (int i = 0; i < mNumClusters; i++) { |
| mDeltaTime[i] = mCurTime[i] - lastTimes[i]; |
| if (mDeltaTime[i] < 0) { |
| Slog.e(mTag, "Negative delta from cluster time proc: " + mDeltaTime[i]); |
| valid = false; |
| } |
| notify |= mDeltaTime[i] > 0; |
| } |
| if (notify && valid) { |
| System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters); |
| if (cb != null) { |
| cb.onUidCpuTime(uid, mDeltaTime); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| void readAbsoluteImpl(Callback<long[]> cb) { |
| try (ProcFileIterator iter = mReader.open(!mThrottle)) { |
| if (!checkPrecondition(iter)) { |
| return; |
| } |
| CharBuffer buf; |
| while ((buf = iter.nextLine()) != null) { |
| if (asLongs(buf, mBuffer) != mBuffer.length) { |
| Slog.wtf(mTag, "Invalid line: " + buf.toString()); |
| continue; |
| } |
| sumClusterTime(); |
| cb.onUidCpuTime((int) mBuffer[0], mCurTime); |
| } |
| } |
| } |
| |
| private void sumClusterTime() { |
| // UID is stored at mBuffer[0]. |
| int core = 1; |
| for (int i = 0; i < mNumClusters; i++) { |
| double sum = 0; |
| for (int j = 1; j <= mCoresOnClusters[i]; j++) { |
| sum += (double) mBuffer[core++] * 10 / j; // Unit is 10ms. |
| } |
| mCurTime[i] = (long) sum; |
| } |
| } |
| |
| private boolean checkPrecondition(ProcFileIterator iter) { |
| if (iter == null || !iter.hasNextLine()) { |
| // Error logged in KernelCpuProcStringReader. |
| return false; |
| } |
| CharBuffer line = iter.nextLine(); |
| if (mNumClusters > 0) { |
| return true; |
| } |
| // Parse # cores in clusters. |
| String[] lineArray = line.toString().split(" "); |
| if (lineArray.length % 2 != 0) { |
| Slog.wtf(mTag, "Malformed uid_concurrent_policy_time line: " + line); |
| return false; |
| } |
| int[] clusters = new int[lineArray.length / 2]; |
| int cores = 0; |
| for (int i = 0; i < clusters.length; i++) { |
| if (!lineArray[i * 2].startsWith("policy")) { |
| Slog.wtf(mTag, "Malformed uid_concurrent_policy_time line: " + line); |
| return false; |
| } |
| clusters[i] = Integer.parseInt(lineArray[i * 2 + 1], 10); |
| cores += clusters[i]; |
| } |
| mNumClusters = clusters.length; |
| mNumCores = cores; |
| mCoresOnClusters = clusters; |
| mBuffer = new long[cores + 1]; |
| mCurTime = new long[mNumClusters]; |
| mDeltaTime = new long[mNumClusters]; |
| return true; |
| } |
| } |
| |
| } |