Home | History | Annotate | Download | only in os
      1 /*
      2  * Copyright (C) 2018 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.os.StrictMode;
     20 import android.os.SystemClock;
     21 import android.util.Slog;
     22 
     23 import com.android.internal.annotations.VisibleForTesting;
     24 
     25 import java.io.FileNotFoundException;
     26 import java.io.IOException;
     27 import java.nio.ByteBuffer;
     28 import java.nio.ByteOrder;
     29 import java.nio.channels.FileChannel;
     30 import java.nio.file.NoSuchFileException;
     31 import java.nio.file.Path;
     32 import java.nio.file.Paths;
     33 import java.nio.file.StandardOpenOption;
     34 
     35 /**
     36  * Reads cpu time proc files with throttling (adjustable interval).
     37  *
     38  * KernelCpuProcReader is implemented as singletons for built-in kernel proc files. Get___Instance()
     39  * method will return corresponding reader instance. In order to prevent frequent GC,
     40  * KernelCpuProcReader reuses a {@link ByteBuffer} to store data read from proc files.
     41  *
     42  * A KernelCpuProcReader instance keeps an error counter. When the number of read errors within that
     43  * instance accumulates to 5, this instance will reject all further read requests.
     44  *
     45  * Each KernelCpuProcReader instance also has a throttler. Throttle interval can be adjusted via
     46  * {@link #setThrottleInterval(long)} method. Default throttle interval is 3000ms. If current
     47  * timestamp based on {@link SystemClock#elapsedRealtime()} is less than throttle interval from
     48  * the last read timestamp, {@link #readBytes()} will return previous result.
     49  *
     50  * A KernelCpuProcReader instance is thread-unsafe. Caller needs to hold a lock on this object while
     51  * accessing its instance methods or digesting the return values.
     52  */
     53 public class KernelCpuProcReader {
     54     private static final String TAG = "KernelCpuProcReader";
     55     private static final int ERROR_THRESHOLD = 5;
     56     // Throttle interval in milliseconds
     57     private static final long DEFAULT_THROTTLE_INTERVAL = 3000L;
     58     private static final int INITIAL_BUFFER_SIZE = 8 * 1024;
     59     private static final int MAX_BUFFER_SIZE = 1024 * 1024;
     60     private static final String PROC_UID_FREQ_TIME = "/proc/uid_cpupower/time_in_state";
     61     private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_cpupower/concurrent_active_time";
     62     private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_cpupower/concurrent_policy_time";
     63 
     64     private static final KernelCpuProcReader mFreqTimeReader = new KernelCpuProcReader(
     65             PROC_UID_FREQ_TIME);
     66     private static final KernelCpuProcReader mActiveTimeReader = new KernelCpuProcReader(
     67             PROC_UID_ACTIVE_TIME);
     68     private static final KernelCpuProcReader mClusterTimeReader = new KernelCpuProcReader(
     69             PROC_UID_CLUSTER_TIME);
     70 
     71     public static KernelCpuProcReader getFreqTimeReaderInstance() {
     72         return mFreqTimeReader;
     73     }
     74 
     75     public static KernelCpuProcReader getActiveTimeReaderInstance() {
     76         return mActiveTimeReader;
     77     }
     78 
     79     public static KernelCpuProcReader getClusterTimeReaderInstance() {
     80         return mClusterTimeReader;
     81     }
     82 
     83     private int mErrors;
     84     private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
     85     private long mLastReadTime = Long.MIN_VALUE;
     86     private final Path mProc;
     87     private ByteBuffer mBuffer;
     88 
     89     @VisibleForTesting
     90     public KernelCpuProcReader(String procFile) {
     91         mProc = Paths.get(procFile);
     92         mBuffer = ByteBuffer.allocateDirect(INITIAL_BUFFER_SIZE);
     93         mBuffer.clear();
     94     }
     95 
     96     /**
     97      * Reads all bytes from the corresponding proc file.
     98      *
     99      * If elapsed time since last call to this method is less than the throttle interval, it will
    100      * return previous result. When IOException accumulates to 5, it will always return null. This
    101      * method is thread-unsafe, so is the return value. Caller needs to hold a lock on this
    102      * object while calling this method and digesting its return value.
    103      *
    104      * @return a {@link ByteBuffer} containing all bytes from the proc file.
    105      */
    106     public ByteBuffer readBytes() {
    107         if (mErrors >= ERROR_THRESHOLD) {
    108             return null;
    109         }
    110         if (SystemClock.elapsedRealtime() < mLastReadTime + mThrottleInterval) {
    111             if (mBuffer.limit() > 0 && mBuffer.limit() < mBuffer.capacity()) {
    112                 // mBuffer has data.
    113                 return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
    114             }
    115             return null;
    116         }
    117         mLastReadTime = SystemClock.elapsedRealtime();
    118         mBuffer.clear();
    119         final int oldMask = StrictMode.allowThreadDiskReadsMask();
    120         try (FileChannel fc = FileChannel.open(mProc, StandardOpenOption.READ)) {
    121             while (fc.read(mBuffer) == mBuffer.capacity()) {
    122                 if (!resize()) {
    123                     mErrors++;
    124                     Slog.e(TAG, "Proc file is too large: " + mProc);
    125                     return null;
    126                 }
    127                 fc.position(0);
    128             }
    129         } catch (NoSuchFileException | FileNotFoundException e) {
    130             // Happens when the kernel does not provide this file. Not a big issue. Just log it.
    131             mErrors++;
    132             Slog.w(TAG, "File not exist: " + mProc);
    133             return null;
    134         } catch (IOException e) {
    135             mErrors++;
    136             Slog.e(TAG, "Error reading: " + mProc, e);
    137             return null;
    138         } finally {
    139             StrictMode.setThreadPolicyMask(oldMask);
    140         }
    141         mBuffer.flip();
    142         return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
    143     }
    144 
    145     /**
    146      * Sets the throttle interval. Set to 0 will disable throttling. Thread-unsafe, holding a lock
    147      * on this object is recommended.
    148      *
    149      * @param throttleInterval throttle interval in milliseconds
    150      */
    151     public void setThrottleInterval(long throttleInterval) {
    152         if (throttleInterval >= 0) {
    153             mThrottleInterval = throttleInterval;
    154         }
    155     }
    156 
    157     private boolean resize() {
    158         if (mBuffer.capacity() >= MAX_BUFFER_SIZE) {
    159             return false;
    160         }
    161         int newSize = Math.min(mBuffer.capacity() << 1, MAX_BUFFER_SIZE);
    162         // Slog.i(TAG, "Resize buffer " + mBuffer.capacity() + " => " + newSize);
    163         mBuffer = ByteBuffer.allocateDirect(newSize);
    164         return true;
    165     }
    166 }
    167