1 /*
2  * Copyright (C) 2020 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.SystemClock;
20 import android.util.Slog;
21 import android.util.SparseArray;
22 
23 import java.util.concurrent.locks.ReentrantReadWriteLock;
24 
25 /**
26  * Reads cpu time bpf maps.
27  *
28  * It is implemented as singletons for each separate set of per-UID times. Get___Instance() method
29  * returns the corresponding reader instance. In order to prevent frequent GC, it reuses the same
30  * SparseArray to store data read from BPF maps.
31  *
32  * A KernelCpuUidBpfMapReader instance keeps an error counter. When the number of read errors within
33  * that instance accumulates to 5, this instance will reject all further read requests.
34  *
35  * Data fetched within last 500ms is considered fresh, since the reading lifecycle can take up to
36  * 25ms. KernelCpuUidBpfMapReader always tries to use cache if it is fresh and valid, but it can
37  * be disabled through a parameter.
38  *
39  * A KernelCpuUidBpfMapReader instance is thread-safe. It acquires a write lock when reading the bpf
40  * map, releases it right after, then acquires a read lock before returning a BpfMapIterator. Caller
41  * is responsible for closing BpfMapIterator (also auto-closable) after reading, otherwise deadlock
42  * will occur.
43  */
44 public abstract class KernelCpuUidBpfMapReader {
45     private static final int ERROR_THRESHOLD = 5;
46     private static final long FRESHNESS_MS = 500L;
47 
48     private static final KernelCpuUidBpfMapReader FREQ_TIME_READER =
49         new KernelCpuUidFreqTimeBpfMapReader();
50 
51     private static final KernelCpuUidBpfMapReader ACTIVE_TIME_READER =
52         new KernelCpuUidActiveTimeBpfMapReader();
53 
54     private static final KernelCpuUidBpfMapReader CLUSTER_TIME_READER =
55         new KernelCpuUidClusterTimeBpfMapReader();
56 
getFreqTimeReaderInstance()57     static KernelCpuUidBpfMapReader getFreqTimeReaderInstance() {
58         return FREQ_TIME_READER;
59     }
60 
getActiveTimeReaderInstance()61     static KernelCpuUidBpfMapReader getActiveTimeReaderInstance() {
62         return ACTIVE_TIME_READER;
63     }
64 
getClusterTimeReaderInstance()65     static KernelCpuUidBpfMapReader getClusterTimeReaderInstance() {
66         return CLUSTER_TIME_READER;
67     }
68 
69     final String mTag = this.getClass().getSimpleName();
70     private int mErrors = 0;
71     private boolean mTracking = false;
72     protected SparseArray<long[]> mData = new SparseArray<>();
73     private long mLastReadTime = 0;
74     protected final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
75     protected final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
76     protected final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
77 
startTrackingBpfTimes()78     public native boolean startTrackingBpfTimes();
79 
readBpfData()80     protected abstract boolean readBpfData();
81 
82     /**
83      * Returns an array of metadata used to inform the caller of 1) the size of array required by
84      * getNextUid and 2) how to interpret the raw data copied to that array.
85      */
getDataDimensions()86     public abstract long[] getDataDimensions();
87 
removeUidsInRange(int startUid, int endUid)88     public void removeUidsInRange(int startUid, int endUid) {
89         if (mErrors > ERROR_THRESHOLD) {
90             return;
91         }
92         if (endUid < startUid || startUid < 0) {
93             return;
94         }
95 
96         mWriteLock.lock();
97         int firstIndex = mData.indexOfKey(startUid);
98         if (firstIndex < 0) {
99             mData.put(startUid, null);
100             firstIndex = mData.indexOfKey(startUid);
101         }
102         int lastIndex = mData.indexOfKey(endUid);
103         if (lastIndex < 0) {
104             mData.put(endUid, null);
105             lastIndex = mData.indexOfKey(endUid);
106         }
107         mData.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
108         mWriteLock.unlock();
109     }
110 
open()111     public BpfMapIterator open() {
112         return open(false);
113     }
114 
open(boolean ignoreCache)115     public BpfMapIterator open(boolean ignoreCache) {
116         if (mErrors > ERROR_THRESHOLD) {
117             return null;
118         }
119         if (!mTracking && !startTrackingBpfTimes()) {
120             Slog.w(mTag, "Failed to start tracking");
121             mErrors++;
122             return null;
123         }
124         if (ignoreCache) {
125             mWriteLock.lock();
126         } else {
127             mReadLock.lock();
128             if (dataValid()) {
129                 return new BpfMapIterator();
130             }
131             mReadLock.unlock();
132             mWriteLock.lock();
133             if (dataValid()) {
134                 mReadLock.lock();
135                 mWriteLock.unlock();
136                 return new BpfMapIterator();
137             }
138         }
139         if (readBpfData()) {
140             mLastReadTime = SystemClock.elapsedRealtime();
141             mReadLock.lock();
142             mWriteLock.unlock();
143             return new BpfMapIterator();
144         }
145 
146         mWriteLock.unlock();
147         mErrors++;
148         Slog.w(mTag, "Failed to read bpf times");
149         return null;
150     }
151 
dataValid()152     private boolean dataValid() {
153         return mData.size() > 0 && (SystemClock.elapsedRealtime() - mLastReadTime < FRESHNESS_MS);
154     }
155 
156     public class BpfMapIterator implements AutoCloseable {
157         private int mPos;
158 
BpfMapIterator()159         public BpfMapIterator() {
160         };
161 
getNextUid(long[] buf)162         public boolean getNextUid(long[] buf) {
163             if (mPos >= mData.size()) {
164                 return false;
165             }
166             buf[0] = mData.keyAt(mPos);
167             System.arraycopy(mData.valueAt(mPos), 0, buf, 1, mData.valueAt(mPos).length);
168             mPos++;
169             return true;
170         }
171 
close()172         public void close() {
173             mReadLock.unlock();
174         }
175     }
176 
177     public static class KernelCpuUidFreqTimeBpfMapReader extends KernelCpuUidBpfMapReader {
178 
removeUidRange(int startUid, int endUid)179         private final native boolean removeUidRange(int startUid, int endUid);
180 
181         @Override
readBpfData()182         protected final native boolean readBpfData();
183 
184         @Override
getDataDimensions()185         public final native long[] getDataDimensions();
186 
187         @Override
removeUidsInRange(int startUid, int endUid)188         public void removeUidsInRange(int startUid, int endUid) {
189             mWriteLock.lock();
190             super.removeUidsInRange(startUid, endUid);
191             removeUidRange(startUid, endUid);
192             mWriteLock.unlock();
193         }
194     }
195 
196     public static class KernelCpuUidActiveTimeBpfMapReader extends KernelCpuUidBpfMapReader {
197 
198         @Override
readBpfData()199         protected final native boolean readBpfData();
200 
201         @Override
getDataDimensions()202         public final native long[] getDataDimensions();
203     }
204 
205     public static class KernelCpuUidClusterTimeBpfMapReader extends KernelCpuUidBpfMapReader {
206 
207         @Override
readBpfData()208         protected final native boolean readBpfData();
209 
210         @Override
getDataDimensions()211         public final native long[] getDataDimensions();
212     }
213 }
214