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 android.annotation.Nullable;
     20 import android.util.Slog;
     21 import android.util.SparseArray;
     22 
     23 import com.android.internal.annotations.VisibleForTesting;
     24 
     25 import java.nio.ByteBuffer;
     26 import java.nio.IntBuffer;
     27 import java.util.function.Consumer;
     28 
     29 /**
     30  * Reads binary proc file /proc/uid_cpupower/concurrent_policy_time and reports CPU cluster times
     31  * to BatteryStats to compute cluster power. See
     32  * {@link PowerProfile#getAveragePowerForCpuCluster(int)}.
     33  *
     34  * concurrent_policy_time is an array of u32's in the following format:
     35  * [n, x0, ..., xn, uid0, time0a, time0b, ..., time0n,
     36  * uid1, time1a, time1b, ..., time1n,
     37  * uid2, time2a, time2b, ..., time2n, etc.]
     38  * where n is the number of policies
     39  * xi is the number cpus on a particular policy
     40  * Each uidX is followed by x0 time entries corresponding to the time UID X spent on cluster0
     41  * running concurrently with 0, 1, 2, ..., x0 - 1 other processes, then followed by x1, ..., xn
     42  * time entries.
     43  *
     44  * The file contains a monotonically increasing count of time for a single boot. This class
     45  * maintains the previous results of a call to {@link #readDelta} in order to provide a
     46  * proper delta.
     47  *
     48  * This class uses a throttler to reject any {@link #readDelta} call within
     49  * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
     50  * which has a shorter throttle interval and returns cached result from last read when the request
     51  * is throttled.
     52  *
     53  * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
     54  * caller has its own view of delta.
     55  */
     56 public class KernelUidCpuClusterTimeReader extends
     57         KernelUidCpuTimeReaderBase<KernelUidCpuClusterTimeReader.Callback> {
     58     private static final String TAG = KernelUidCpuClusterTimeReader.class.getSimpleName();
     59 
     60     private final KernelCpuProcReader mProcReader;
     61     private SparseArray<double[]> mLastUidPolicyTimeMs = new SparseArray<>();
     62 
     63     private int mNumClusters = -1;
     64     private int mNumCores;
     65     private int[] mNumCoresOnCluster;
     66 
     67     private double[] mCurTime; // Reuse to avoid GC.
     68     private long[] mDeltaTime; // Reuse to avoid GC.
     69     private long[] mCurTimeRounded; // Reuse to avoid GC.
     70 
     71     public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
     72         /**
     73          * Notifies when new data is available.
     74          *
     75          * @param uid              uid int
     76          * @param cpuClusterTimeMs an array of times spent by this uid on corresponding clusters.
     77          *                         The array index is the cluster index.
     78          */
     79         void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs);
     80     }
     81 
     82     public KernelUidCpuClusterTimeReader() {
     83         mProcReader = KernelCpuProcReader.getClusterTimeReaderInstance();
     84     }
     85 
     86     @VisibleForTesting
     87     public KernelUidCpuClusterTimeReader(KernelCpuProcReader procReader) {
     88         mProcReader = procReader;
     89     }
     90 
     91     @Override
     92     protected void readDeltaImpl(@Nullable Callback cb) {
     93         readImpl((buf) -> {
     94             int uid = buf.get();
     95             double[] lastTimes = mLastUidPolicyTimeMs.get(uid);
     96             if (lastTimes == null) {
     97                 lastTimes = new double[mNumClusters];
     98                 mLastUidPolicyTimeMs.put(uid, lastTimes);
     99             }
    100             if (!sumClusterTime(buf, mCurTime)) {
    101                 return;
    102             }
    103             boolean valid = true;
    104             boolean notify = false;
    105             for (int i = 0; i < mNumClusters; i++) {
    106                 mDeltaTime[i] = (long) (mCurTime[i] - lastTimes[i]);
    107                 if (mDeltaTime[i] < 0) {
    108                     Slog.e(TAG, "Negative delta from cluster time proc: " + mDeltaTime[i]);
    109                     valid = false;
    110                 }
    111                 notify |= mDeltaTime[i] > 0;
    112             }
    113             if (notify && valid) {
    114                 System.arraycopy(mCurTime, 0, lastTimes, 0, mNumClusters);
    115                 if (cb != null) {
    116                     cb.onUidCpuPolicyTime(uid, mDeltaTime);
    117                 }
    118             }
    119         });
    120     }
    121 
    122     public void readAbsolute(Callback callback) {
    123         readImpl((buf) -> {
    124             int uid = buf.get();
    125             if (sumClusterTime(buf, mCurTime)) {
    126                 for (int i = 0; i < mNumClusters; i++) {
    127                     mCurTimeRounded[i] = (long) mCurTime[i];
    128                 }
    129                 callback.onUidCpuPolicyTime(uid, mCurTimeRounded);
    130             }
    131         });
    132     }
    133 
    134     private boolean sumClusterTime(IntBuffer buffer, double[] clusterTime) {
    135         boolean valid = true;
    136         for (int i = 0; i < mNumClusters; i++) {
    137             clusterTime[i] = 0;
    138             for (int j = 1; j <= mNumCoresOnCluster[i]; j++) {
    139                 int time = buffer.get();
    140                 if (time < 0) {
    141                     Slog.e(TAG, "Negative time from cluster time proc: " + time);
    142                     valid = false;
    143                 }
    144                 clusterTime[i] += (double) time * 10 / j; // Unit is 10ms.
    145             }
    146         }
    147         return valid;
    148     }
    149 
    150     /**
    151      * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
    152      * seen results while processing the buffer, while readAbsolute returns the absolute value read
    153      * from the buffer without storing. So readImpl contains the common logic of the two, leaving
    154      * the difference to a processUid function.
    155      *
    156      * @param processUid the callback function to process the uid entry in the buffer.
    157      */
    158     private void readImpl(Consumer<IntBuffer> processUid) {
    159         synchronized (mProcReader) {
    160             ByteBuffer bytes = mProcReader.readBytes();
    161             if (bytes == null || bytes.remaining() <= 4) {
    162                 // Error already logged in mProcReader.
    163                 return;
    164             }
    165             if ((bytes.remaining() & 3) != 0) {
    166                 Slog.wtf(TAG,
    167                         "Cannot parse cluster time proc bytes to int: " + bytes.remaining());
    168                 return;
    169             }
    170             IntBuffer buf = bytes.asIntBuffer();
    171             final int numClusters = buf.get();
    172             if (numClusters <= 0) {
    173                 Slog.wtf(TAG, "Cluster time format error: " + numClusters);
    174                 return;
    175             }
    176             if (mNumClusters == -1) {
    177                 mNumClusters = numClusters;
    178             }
    179             if (buf.remaining() < numClusters) {
    180                 Slog.wtf(TAG, "Too few data left in the buffer: " + buf.remaining());
    181                 return;
    182             }
    183             if (mNumCores <= 0) {
    184                 if (!readCoreInfo(buf, numClusters)) {
    185                     return;
    186                 }
    187             } else {
    188                 buf.position(buf.position() + numClusters);
    189             }
    190 
    191             if (buf.remaining() % (mNumCores + 1) != 0) {
    192                 Slog.wtf(TAG,
    193                         "Cluster time format error: " + buf.remaining() + " / " + (mNumCores
    194                                 + 1));
    195                 return;
    196             }
    197             int numUids = buf.remaining() / (mNumCores + 1);
    198 
    199             for (int i = 0; i < numUids; i++) {
    200                 processUid.accept(buf);
    201             }
    202             if (DEBUG) {
    203                 Slog.d(TAG, "Read uids: " + numUids);
    204             }
    205         }
    206     }
    207 
    208     // Returns if it has read valid info.
    209     private boolean readCoreInfo(IntBuffer buf, int numClusters) {
    210         int numCores = 0;
    211         int[] numCoresOnCluster = new int[numClusters];
    212         for (int i = 0; i < numClusters; i++) {
    213             numCoresOnCluster[i] = buf.get();
    214             numCores += numCoresOnCluster[i];
    215         }
    216         if (numCores <= 0) {
    217             Slog.e(TAG, "Invalid # cores from cluster time proc file: " + numCores);
    218             return false;
    219         }
    220         mNumCores = numCores;
    221         mNumCoresOnCluster = numCoresOnCluster;
    222         mCurTime = new double[numClusters];
    223         mDeltaTime = new long[numClusters];
    224         mCurTimeRounded = new long[numClusters];
    225         return true;
    226     }
    227 
    228     public void removeUid(int uid) {
    229         mLastUidPolicyTimeMs.delete(uid);
    230     }
    231 
    232     public void removeUidsInRange(int startUid, int endUid) {
    233         mLastUidPolicyTimeMs.put(startUid, null);
    234         mLastUidPolicyTimeMs.put(endUid, null);
    235         final int firstIndex = mLastUidPolicyTimeMs.indexOfKey(startUid);
    236         final int lastIndex = mLastUidPolicyTimeMs.indexOfKey(endUid);
    237         mLastUidPolicyTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
    238     }
    239 }
    240