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