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
17package com.android.internal.os;
18
19import android.os.StrictMode;
20import android.os.SystemClock;
21import android.util.Slog;
22
23import com.android.internal.annotations.VisibleForTesting;
24
25import java.io.FileNotFoundException;
26import java.io.IOException;
27import java.nio.ByteBuffer;
28import java.nio.ByteOrder;
29import java.nio.channels.FileChannel;
30import java.nio.file.NoSuchFileException;
31import java.nio.file.Path;
32import java.nio.file.Paths;
33import 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 */
53public 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