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