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     protected SparseArray<long[]> mData = new SparseArray<>();
72     private long mLastReadTime = 0;
73     protected final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
74     protected final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
75     protected final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
76 
startTrackingBpfTimes()77     public boolean startTrackingBpfTimes() {
78         return KernelCpuBpfTracking.startTracking();
79     }
80 
readBpfData()81     protected abstract boolean readBpfData();
82 
83     /**
84      * Returns an array of metadata used to inform the caller of 1) the size of array required by
85      * getNextUid and 2) how to interpret the raw data copied to that array.
86      */
getDataDimensions()87     public abstract long[] getDataDimensions();
88 
removeUidsInRange(int startUid, int endUid)89     public void removeUidsInRange(int startUid, int endUid) {
90         if (mErrors > ERROR_THRESHOLD) {
91             return;
92         }
93         if (endUid < startUid || startUid < 0) {
94             return;
95         }
96 
97         mWriteLock.lock();
98         int firstIndex = mData.indexOfKey(startUid);
99         if (firstIndex < 0) {
100             mData.put(startUid, null);
101             firstIndex = mData.indexOfKey(startUid);
102         }
103         int lastIndex = mData.indexOfKey(endUid);
104         if (lastIndex < 0) {
105             mData.put(endUid, null);
106             lastIndex = mData.indexOfKey(endUid);
107         }
108         mData.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
109         mWriteLock.unlock();
110     }
111 
open()112     public BpfMapIterator open() {
113         return open(false);
114     }
115 
open(boolean ignoreCache)116     public BpfMapIterator open(boolean ignoreCache) {
117         if (mErrors > ERROR_THRESHOLD) {
118             return null;
119         }
120         if (!startTrackingBpfTimes()) {
121             Slog.w(mTag, "Failed to start tracking");
122             mErrors++;
123             return null;
124         }
125         if (ignoreCache) {
126             mWriteLock.lock();
127         } else {
128             mReadLock.lock();
129             if (dataValid()) {
130                 return new BpfMapIterator();
131             }
132             mReadLock.unlock();
133             mWriteLock.lock();
134             if (dataValid()) {
135                 mReadLock.lock();
136                 mWriteLock.unlock();
137                 return new BpfMapIterator();
138             }
139         }
140         if (readBpfData()) {
141             mLastReadTime = SystemClock.elapsedRealtime();
142             mReadLock.lock();
143             mWriteLock.unlock();
144             return new BpfMapIterator();
145         }
146 
147         mWriteLock.unlock();
148         mErrors++;
149         Slog.w(mTag, "Failed to read bpf times");
150         return null;
151     }
152 
dataValid()153     private boolean dataValid() {
154         return mData.size() > 0 && (SystemClock.elapsedRealtime() - mLastReadTime < FRESHNESS_MS);
155     }
156 
157     public class BpfMapIterator implements AutoCloseable {
158         private int mPos;
159 
BpfMapIterator()160         public BpfMapIterator() {
161         };
162 
getNextUid(long[] buf)163         public boolean getNextUid(long[] buf) {
164             if (mPos >= mData.size()) {
165                 return false;
166             }
167             buf[0] = mData.keyAt(mPos);
168             System.arraycopy(mData.valueAt(mPos), 0, buf, 1, mData.valueAt(mPos).length);
169             mPos++;
170             return true;
171         }
172 
close()173         public void close() {
174             mReadLock.unlock();
175         }
176     }
177 
178     public static class KernelCpuUidFreqTimeBpfMapReader extends KernelCpuUidBpfMapReader {
179 
removeUidRange(int startUid, int endUid)180         private final native boolean removeUidRange(int startUid, int endUid);
181 
182         @Override
readBpfData()183         protected final native boolean readBpfData();
184 
185         @Override
getDataDimensions()186         public final long[] getDataDimensions() {
187             return KernelCpuBpfTracking.getFreqsInternal();
188         }
189 
190         @Override
removeUidsInRange(int startUid, int endUid)191         public void removeUidsInRange(int startUid, int endUid) {
192             mWriteLock.lock();
193             super.removeUidsInRange(startUid, endUid);
194             removeUidRange(startUid, endUid);
195             mWriteLock.unlock();
196         }
197     }
198 
199     public static class KernelCpuUidActiveTimeBpfMapReader extends KernelCpuUidBpfMapReader {
200 
201         @Override
readBpfData()202         protected final native boolean readBpfData();
203 
204         @Override
getDataDimensions()205         public final native long[] getDataDimensions();
206     }
207 
208     public static class KernelCpuUidClusterTimeBpfMapReader extends KernelCpuUidBpfMapReader {
209 
210         @Override
readBpfData()211         protected final native boolean readBpfData();
212 
213         @Override
getDataDimensions()214         public final native long[] getDataDimensions();
215     }
216 }
217