Home | History | Annotate | Download | only in os
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.internal.os;
     18 
     19 import static com.android.internal.util.Preconditions.checkNotNull;
     20 
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.os.StrictMode;
     24 import android.util.IntArray;
     25 import android.util.Slog;
     26 import android.util.SparseArray;
     27 
     28 import com.android.internal.annotations.VisibleForTesting;
     29 
     30 import java.io.BufferedReader;
     31 import java.io.FileReader;
     32 import java.io.IOException;
     33 import java.nio.ByteBuffer;
     34 import java.nio.IntBuffer;
     35 import java.util.function.Consumer;
     36 
     37 /**
     38  * Reads /proc/uid_time_in_state which has the format:
     39  *
     40  * uid: [freq1] [freq2] [freq3] ...
     41  * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
     42  * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
     43  * ...
     44  *
     45  * Binary variation reads /proc/uid_cpupower/time_in_state in the following format:
     46  * [n, uid0, time0a, time0b, ..., time0n,
     47  * uid1, time1a, time1b, ..., time1n,
     48  * uid2, time2a, time2b, ..., time2n, etc.]
     49  * where n is the total number of frequencies.
     50  *
     51  * This provides the times a UID's processes spent executing at each different cpu frequency.
     52  * The file contains a monotonically increasing count of time for a single boot. This class
     53  * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
     54  * delta.
     55  *
     56  * This class uses a throttler to reject any {@link #readDelta} call within
     57  * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
     58  * which has a shorter throttle interval and returns cached result from last read when the request
     59  * is throttled.
     60  *
     61  * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
     62  * caller has its own view of delta.
     63  */
     64 public class KernelUidCpuFreqTimeReader extends
     65         KernelUidCpuTimeReaderBase<KernelUidCpuFreqTimeReader.Callback> {
     66     private static final String TAG = KernelUidCpuFreqTimeReader.class.getSimpleName();
     67     static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
     68 
     69     public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
     70         void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
     71     }
     72 
     73     private long[] mCpuFreqs;
     74     private long[] mCurTimes; // Reuse to prevent GC.
     75     private long[] mDeltaTimes; // Reuse to prevent GC.
     76     private int mCpuFreqsCount;
     77     private final KernelCpuProcReader mProcReader;
     78 
     79     private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
     80 
     81     // We check the existence of proc file a few times (just in case it is not ready yet when we
     82     // start reading) and if it is not available, we simply ignore further read requests.
     83     private static final int TOTAL_READ_ERROR_COUNT = 5;
     84     private int mReadErrorCounter;
     85     private boolean mPerClusterTimesAvailable;
     86     private boolean mAllUidTimesAvailable = true;
     87 
     88     public KernelUidCpuFreqTimeReader() {
     89         mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance();
     90     }
     91 
     92     @VisibleForTesting
     93     public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) {
     94         mProcReader = procReader;
     95     }
     96 
     97     public boolean perClusterTimesAvailable() {
     98         return mPerClusterTimesAvailable;
     99     }
    100 
    101     public boolean allUidTimesAvailable() {
    102         return mAllUidTimesAvailable;
    103     }
    104 
    105     public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
    106         return mLastUidCpuFreqTimeMs;
    107     }
    108 
    109     public long[] readFreqs(@NonNull PowerProfile powerProfile) {
    110         checkNotNull(powerProfile);
    111         if (mCpuFreqs != null) {
    112             // No need to read cpu freqs more than once.
    113             return mCpuFreqs;
    114         }
    115         if (!mAllUidTimesAvailable) {
    116             return null;
    117         }
    118         final int oldMask = StrictMode.allowThreadDiskReadsMask();
    119         try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
    120             return readFreqs(reader, powerProfile);
    121         } catch (IOException e) {
    122             if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
    123                 mAllUidTimesAvailable = false;
    124             }
    125             Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
    126             return null;
    127         } finally {
    128             StrictMode.setThreadPolicyMask(oldMask);
    129         }
    130     }
    131 
    132     @VisibleForTesting
    133     public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
    134             throws IOException {
    135         final String line = reader.readLine();
    136         if (line == null) {
    137             return null;
    138         }
    139         final String[] freqStr = line.split(" ");
    140         // First item would be "uid: " which needs to be ignored.
    141         mCpuFreqsCount = freqStr.length - 1;
    142         mCpuFreqs = new long[mCpuFreqsCount];
    143         mCurTimes = new long[mCpuFreqsCount];
    144         mDeltaTimes = new long[mCpuFreqsCount];
    145         for (int i = 0; i < mCpuFreqsCount; ++i) {
    146             mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
    147         }
    148 
    149         // Check if the freqs in the proc file correspond to per-cluster freqs.
    150         final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
    151         final int numClusters = powerProfile.getNumCpuClusters();
    152         if (numClusterFreqs.size() == numClusters) {
    153             mPerClusterTimesAvailable = true;
    154             for (int i = 0; i < numClusters; ++i) {
    155                 if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
    156                     mPerClusterTimesAvailable = false;
    157                     break;
    158                 }
    159             }
    160         } else {
    161             mPerClusterTimesAvailable = false;
    162         }
    163         Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
    164         return mCpuFreqs;
    165     }
    166 
    167     @Override
    168     @VisibleForTesting
    169     public void readDeltaImpl(@Nullable Callback callback) {
    170         if (mCpuFreqs == null) {
    171             return;
    172         }
    173         readImpl((buf) -> {
    174             int uid = buf.get();
    175             long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid);
    176             if (lastTimes == null) {
    177                 lastTimes = new long[mCpuFreqsCount];
    178                 mLastUidCpuFreqTimeMs.put(uid, lastTimes);
    179             }
    180             if (!getFreqTimeForUid(buf, mCurTimes)) {
    181                 return;
    182             }
    183             boolean notify = false;
    184             boolean valid = true;
    185             for (int i = 0; i < mCpuFreqsCount; i++) {
    186                 mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
    187                 if (mDeltaTimes[i] < 0) {
    188                     Slog.e(TAG, "Negative delta from freq time proc: " + mDeltaTimes[i]);
    189                     valid = false;
    190                 }
    191                 notify |= mDeltaTimes[i] > 0;
    192             }
    193             if (notify && valid) {
    194                 System.arraycopy(mCurTimes, 0, lastTimes, 0, mCpuFreqsCount);
    195                 if (callback != null) {
    196                     callback.onUidCpuFreqTime(uid, mDeltaTimes);
    197                 }
    198             }
    199         });
    200     }
    201 
    202     public void readAbsolute(Callback callback) {
    203         readImpl((buf) -> {
    204             int uid = buf.get();
    205             if (getFreqTimeForUid(buf, mCurTimes)) {
    206                 callback.onUidCpuFreqTime(uid, mCurTimes);
    207             }
    208         });
    209     }
    210 
    211     private boolean getFreqTimeForUid(IntBuffer buffer, long[] freqTime) {
    212         boolean valid = true;
    213         for (int i = 0; i < mCpuFreqsCount; i++) {
    214             freqTime[i] = (long) buffer.get() * 10; // Unit is 10ms.
    215             if (freqTime[i] < 0) {
    216                 Slog.e(TAG, "Negative time from freq time proc: " + freqTime[i]);
    217                 valid = false;
    218             }
    219         }
    220         return valid;
    221     }
    222 
    223     /**
    224      * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
    225      * seen results while processing the buffer, while readAbsolute returns the absolute value read
    226      * from the buffer without storing. So readImpl contains the common logic of the two, leaving
    227      * the difference to a processUid function.
    228      *
    229      * @param processUid the callback function to process the uid entry in the buffer.
    230      */
    231     private void readImpl(Consumer<IntBuffer> processUid) {
    232         synchronized (mProcReader) {
    233             ByteBuffer bytes = mProcReader.readBytes();
    234             if (bytes == null || bytes.remaining() <= 4) {
    235                 // Error already logged in mProcReader.
    236                 return;
    237             }
    238             if ((bytes.remaining() & 3) != 0) {
    239                 Slog.wtf(TAG, "Cannot parse freq time proc bytes to int: " + bytes.remaining());
    240                 return;
    241             }
    242             IntBuffer buf = bytes.asIntBuffer();
    243             final int freqs = buf.get();
    244             if (freqs != mCpuFreqsCount) {
    245                 Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs);
    246                 return;
    247             }
    248             if (buf.remaining() % (freqs + 1) != 0) {
    249                 Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1));
    250                 return;
    251             }
    252             int numUids = buf.remaining() / (freqs + 1);
    253             for (int i = 0; i < numUids; i++) {
    254                 processUid.accept(buf);
    255             }
    256             if (DEBUG) {
    257                 Slog.d(TAG, "Read uids: #" + numUids);
    258             }
    259         }
    260     }
    261 
    262     public void removeUid(int uid) {
    263         mLastUidCpuFreqTimeMs.delete(uid);
    264     }
    265 
    266     public void removeUidsInRange(int startUid, int endUid) {
    267         mLastUidCpuFreqTimeMs.put(startUid, null);
    268         mLastUidCpuFreqTimeMs.put(endUid, null);
    269         final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
    270         final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
    271         mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
    272     }
    273 
    274     /**
    275      * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
    276      * read from the proc file.
    277      *
    278      * We need to assume that freqs in each cluster are strictly increasing.
    279      * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
    280      * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
    281      *
    282      * @return an IntArray filled with no. of freqs in each cluster.
    283      */
    284     private IntArray extractClusterInfoFromProcFileFreqs() {
    285         final IntArray numClusterFreqs = new IntArray();
    286         int freqsFound = 0;
    287         for (int i = 0; i < mCpuFreqsCount; ++i) {
    288             freqsFound++;
    289             if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
    290                 numClusterFreqs.add(freqsFound);
    291                 freqsFound = 0;
    292             }
    293         }
    294         return numClusterFreqs;
    295     }
    296 }
    297