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