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          */
onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs)79         void onUidCpuPolicyTime(int uid, long[] cpuClusterTimeMs);
80     }
81 
KernelUidCpuClusterTimeReader()82     public KernelUidCpuClusterTimeReader() {
83         mProcReader = KernelCpuProcReader.getClusterTimeReaderInstance();
84     }
85 
86     @VisibleForTesting
KernelUidCpuClusterTimeReader(KernelCpuProcReader procReader)87     public KernelUidCpuClusterTimeReader(KernelCpuProcReader procReader) {
88         mProcReader = procReader;
89     }
90 
91     @Override
readDeltaImpl(@ullable Callback cb)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 
readAbsolute(Callback callback)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 
sumClusterTime(IntBuffer buffer, double[] clusterTime)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      */
readImpl(Consumer<IntBuffer> processUid)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.
readCoreInfo(IntBuffer buf, int numClusters)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 
removeUid(int uid)228     public void removeUid(int uid) {
229         mLastUidPolicyTimeMs.delete(uid);
230     }
231 
removeUidsInRange(int startUid, int endUid)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