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 package com.android.internal.os;
     17 
     18 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
     19 import static com.android.internal.os.KernelUidCpuFreqTimeReader.UID_TIMES_PROC_FILE;
     20 
     21 import android.annotation.NonNull;
     22 import android.util.Slog;
     23 import android.util.SparseArray;
     24 
     25 import com.android.internal.annotations.GuardedBy;
     26 import com.android.internal.annotations.VisibleForTesting;
     27 
     28 import java.io.IOException;
     29 import java.nio.ByteBuffer;
     30 import java.nio.ByteOrder;
     31 import java.nio.file.Files;
     32 import java.nio.file.Paths;
     33 import java.util.Arrays;
     34 
     35 @VisibleForTesting(visibility = PACKAGE)
     36 public class KernelSingleUidTimeReader {
     37     private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
     38     private final boolean DBG = false;
     39 
     40     private final String PROC_FILE_DIR = "/proc/uid/";
     41     private final String PROC_FILE_NAME = "/time_in_state";
     42 
     43     @VisibleForTesting
     44     public static final int TOTAL_READ_ERROR_COUNT = 5;
     45 
     46     @GuardedBy("this")
     47     private final int mCpuFreqsCount;
     48 
     49     @GuardedBy("this")
     50     private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
     51 
     52     @GuardedBy("this")
     53     private int mReadErrorCounter;
     54     @GuardedBy("this")
     55     private boolean mSingleUidCpuTimesAvailable = true;
     56     @GuardedBy("this")
     57     private boolean mHasStaleData;
     58     // We use the freq count obtained from /proc/uid_time_in_state to decide how many longs
     59     // to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is
     60     // correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will
     61     // indicate whether we checked for validity or not.
     62     @GuardedBy("this")
     63     private boolean mCpuFreqsCountVerified;
     64 
     65     private final Injector mInjector;
     66 
     67     KernelSingleUidTimeReader(int cpuFreqsCount) {
     68         this(cpuFreqsCount, new Injector());
     69     }
     70 
     71     public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
     72         mInjector = injector;
     73         mCpuFreqsCount = cpuFreqsCount;
     74         if (mCpuFreqsCount == 0) {
     75             mSingleUidCpuTimesAvailable = false;
     76         }
     77     }
     78 
     79     public boolean singleUidCpuTimesAvailable() {
     80         return mSingleUidCpuTimesAvailable;
     81     }
     82 
     83     public long[] readDeltaMs(int uid) {
     84         synchronized (this) {
     85             if (!mSingleUidCpuTimesAvailable) {
     86                 return null;
     87             }
     88             // Read total cpu times from the proc file.
     89             final String procFile = new StringBuilder(PROC_FILE_DIR)
     90                     .append(uid)
     91                     .append(PROC_FILE_NAME).toString();
     92             final long[] cpuTimesMs;
     93             try {
     94                 final byte[] data = mInjector.readData(procFile);
     95                 if (!mCpuFreqsCountVerified) {
     96                     verifyCpuFreqsCount(data.length, procFile);
     97                 }
     98                 final ByteBuffer buffer = ByteBuffer.wrap(data);
     99                 buffer.order(ByteOrder.nativeOrder());
    100                 cpuTimesMs = readCpuTimesFromByteBuffer(buffer);
    101             } catch (Exception e) {
    102                 if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
    103                     mSingleUidCpuTimesAvailable = false;
    104                 }
    105                 if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
    106                 return null;
    107             }
    108 
    109             return computeDelta(uid, cpuTimesMs);
    110         }
    111     }
    112 
    113     private void verifyCpuFreqsCount(int numBytes, String procFile) {
    114         final int actualCount = (numBytes / Long.BYTES);
    115         if (mCpuFreqsCount != actualCount) {
    116             mSingleUidCpuTimesAvailable = false;
    117             throw new IllegalStateException("Freq count didn't match,"
    118                     + "count from " + UID_TIMES_PROC_FILE + "=" + mCpuFreqsCount + ", but"
    119                     + "count from " + procFile + "=" + actualCount);
    120         }
    121         mCpuFreqsCountVerified = true;
    122     }
    123 
    124     private long[] readCpuTimesFromByteBuffer(ByteBuffer buffer) {
    125         final long[] cpuTimesMs;
    126         cpuTimesMs = new long[mCpuFreqsCount];
    127         for (int i = 0; i < mCpuFreqsCount; ++i) {
    128             // Times read will be in units of 10ms
    129             cpuTimesMs[i] = buffer.getLong() * 10;
    130         }
    131         return cpuTimesMs;
    132     }
    133 
    134     /**
    135      * Compute and return cpu times delta of an uid using previously read cpu times and
    136      * {@param latestCpuTimesMs}.
    137      *
    138      * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
    139      */
    140     public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
    141         synchronized (this) {
    142             if (!mSingleUidCpuTimesAvailable) {
    143                 return null;
    144             }
    145             // Subtract the last read cpu times to get deltas.
    146             final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
    147             final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
    148             if (deltaTimesMs == null) {
    149                 if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
    150                         + "; last=" + Arrays.toString(lastCpuTimesMs)
    151                         + "; latest=" + Arrays.toString(latestCpuTimesMs));
    152                 return null;
    153             }
    154             // If all elements are zero, return null to avoid unnecessary work on the caller side.
    155             boolean hasNonZero = false;
    156             for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
    157                 if (deltaTimesMs[i] > 0) {
    158                     hasNonZero = true;
    159                     break;
    160                 }
    161             }
    162             if (hasNonZero) {
    163                 mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
    164                 return deltaTimesMs;
    165             } else {
    166                 return null;
    167             }
    168         }
    169     }
    170 
    171     /**
    172      * Returns null if the latest cpu times are not valid**, otherwise delta of
    173      * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
    174      *
    175      * **latest cpu times are considered valid if all the cpu times are +ve and
    176      * greater than or equal to previously read cpu times.
    177      */
    178     @GuardedBy("this")
    179     @VisibleForTesting(visibility = PACKAGE)
    180     public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
    181         for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
    182             if (latestCpuTimesMs[i] < 0) {
    183                 return null;
    184             }
    185         }
    186         if (lastCpuTimesMs == null) {
    187             return latestCpuTimesMs;
    188         }
    189         final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
    190         for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
    191             deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
    192             if (deltaTimesMs[i] < 0) {
    193                 return null;
    194             }
    195         }
    196         return deltaTimesMs;
    197     }
    198 
    199     public void markDataAsStale(boolean hasStaleData) {
    200         synchronized (this) {
    201             mHasStaleData = hasStaleData;
    202         }
    203     }
    204 
    205     public boolean hasStaleData() {
    206         synchronized (this) {
    207             return mHasStaleData;
    208         }
    209     }
    210 
    211     public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) {
    212         synchronized (this) {
    213             mLastUidCpuTimeMs.clear();
    214             for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) {
    215                 final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i);
    216                 if (cpuTimesMs != null) {
    217                     mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone());
    218                 }
    219             }
    220         }
    221     }
    222 
    223     public void removeUid(int uid) {
    224         synchronized (this) {
    225             mLastUidCpuTimeMs.delete(uid);
    226         }
    227     }
    228 
    229     public void removeUidsInRange(int startUid, int endUid) {
    230         if (endUid < startUid) {
    231             return;
    232         }
    233         synchronized (this) {
    234             mLastUidCpuTimeMs.put(startUid, null);
    235             mLastUidCpuTimeMs.put(endUid, null);
    236             final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
    237             final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
    238             mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
    239         }
    240     }
    241 
    242     @VisibleForTesting
    243     public static class Injector {
    244         public byte[] readData(String procFile) throws IOException {
    245             return Files.readAllBytes(Paths.get(procFile));
    246         }
    247     }
    248 
    249     @VisibleForTesting
    250     public SparseArray<long[]> getLastUidCpuTimeMs() {
    251         return mLastUidCpuTimeMs;
    252     }
    253 
    254     @VisibleForTesting
    255     public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
    256         mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
    257     }
    258 }