1 /* 2 * Copyright 2019 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.server.wifi; 18 19 import static android.net.wifi.WifiManager.DEVICE_MOBILITY_STATE_STATIONARY; 20 import static android.net.wifi.WifiManager.DEVICE_MOBILITY_STATE_UNKNOWN; 21 22 import android.content.Context; 23 import android.net.wifi.ScanResult; 24 import android.net.wifi.WifiManager.DeviceMobilityState; 25 import android.util.Log; 26 import android.util.SparseArray; 27 import android.util.SparseIntArray; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.server.wifi.WifiLinkLayerStats.ChannelStats; 31 import com.android.server.wifi.util.InformationElementUtil.BssLoad; 32 import com.android.wifi.resources.R; 33 34 import java.util.ArrayDeque; 35 import java.util.Iterator; 36 37 /** 38 * This class collects channel stats over a Wifi Interface 39 * and calculates channel utilization using the latest and cached channel stats. 40 * Cache saves previous readings of channel stats in a FIFO. 41 * The cache is updated when a new stats arrives and it has been a long while since the last update. 42 * To get more statistically sound channel utilization, for these devices which support 43 * mobility state report, the cache update is stopped when the device stays in the stationary state. 44 * TODO(b/159052883): This may need to be reworked for STA + STA. 45 */ 46 public class WifiChannelUtilization { 47 private static final String TAG = "WifiChannelUtilization"; 48 private static boolean sVerboseLoggingEnabled = false; 49 public static final int UNKNOWN_FREQ = -1; 50 // Invalidate the utilization value if it is larger than the following value. 51 // This is to detect and mitigate the incorrect HW reports of ccaBusy/OnTime. 52 // It is reasonable to assume that utilization ratio in the real life is never beyond this value 53 // given by all the inter-frame-spacings (IFS) 54 static final int UTILIZATION_RATIO_MAX = BssLoad.MAX_CHANNEL_UTILIZATION * 94 / 100; 55 // Minimum time interval in ms between two cache updates. 56 @VisibleForTesting 57 static final int DEFAULT_CACHE_UPDATE_INTERVAL_MIN_MS = 10 * 60 * 1000; 58 // To get valid channel utilization, the time difference between the reference chanStat's 59 // radioOnTime and current chanStat's radioOntime should be no less than the following value 60 @VisibleForTesting 61 static final int RADIO_ON_TIME_DIFF_MIN_MS = 250; 62 // The number of chanStatsMap readings saved in cache 63 // where each reading corresponds to one link layer stats update. 64 @VisibleForTesting 65 static final int CHANNEL_STATS_CACHE_SIZE = 5; 66 private final Clock mClock; 67 private final Context mContext; 68 private @DeviceMobilityState int mDeviceMobilityState = DEVICE_MOBILITY_STATE_UNKNOWN; 69 private int mCacheUpdateIntervalMinMs = DEFAULT_CACHE_UPDATE_INTERVAL_MIN_MS; 70 71 // Map frequency (key) to utilization ratio (value) with the valid range of 72 // [BssLoad.MIN_CHANNEL_UTILIZATION, BssLoad.MAX_CHANNEL_UTILIZATION], 73 // where MIN_CHANNEL_UTILIZATION corresponds to ratio 0% 74 // and MAX_CHANNEL_UTILIZATION corresponds to ratio 100% 75 private SparseIntArray mChannelUtilizationMap = new SparseIntArray(); 76 private ArrayDeque<SparseArray<ChannelStats>> mChannelStatsMapCache = new ArrayDeque<>(); 77 private long mLastChannelStatsMapTimeStamp; 78 private int mLastChannelStatsMapMobilityState; 79 WifiChannelUtilization(Clock clock, Context context)80 WifiChannelUtilization(Clock clock, Context context) { 81 mContext = context; 82 mClock = clock; 83 } 84 85 /** 86 * Enable/Disable verbose logging. 87 * @param verbose true to enable and false to disable. 88 */ enableVerboseLogging(boolean verbose)89 public void enableVerboseLogging(boolean verbose) { 90 sVerboseLoggingEnabled = verbose; 91 } 92 93 /** 94 * (Re)initialize internal variables and status 95 * @param wifiLinkLayerStats The latest wifi link layer stats 96 */ init(WifiLinkLayerStats wifiLinkLayerStats)97 public void init(WifiLinkLayerStats wifiLinkLayerStats) { 98 mChannelUtilizationMap.clear(); 99 mChannelStatsMapCache.clear(); 100 mDeviceMobilityState = DEVICE_MOBILITY_STATE_UNKNOWN; 101 mLastChannelStatsMapMobilityState = DEVICE_MOBILITY_STATE_UNKNOWN; 102 for (int i = 0; i < (CHANNEL_STATS_CACHE_SIZE - 1); ++i) { 103 mChannelStatsMapCache.addFirst(new SparseArray<>()); 104 } 105 if (wifiLinkLayerStats != null) { 106 mChannelStatsMapCache.addFirst(wifiLinkLayerStats.channelStatsMap); 107 } else { 108 mChannelStatsMapCache.addFirst(new SparseArray<>()); 109 } 110 mLastChannelStatsMapTimeStamp = mClock.getElapsedSinceBootMillis(); 111 if (sVerboseLoggingEnabled) { 112 Log.d(TAG, "initializing"); 113 } 114 } 115 116 /** 117 * Set channel stats cache update minimum interval 118 */ setCacheUpdateIntervalMs(int cacheUpdateIntervalMinMs)119 public void setCacheUpdateIntervalMs(int cacheUpdateIntervalMinMs) { 120 mCacheUpdateIntervalMinMs = cacheUpdateIntervalMinMs; 121 } 122 123 /** 124 * Get channel utilization ratio for a given frequency 125 * @param frequency The center frequency of 20MHz WLAN channel 126 * @return Utilization ratio value if it is available; BssLoad.INVALID otherwise 127 */ getUtilizationRatio(int frequency)128 public int getUtilizationRatio(int frequency) { 129 if (mContext.getResources().getBoolean( 130 R.bool.config_wifiChannelUtilizationOverrideEnabled)) { 131 if (ScanResult.is24GHz(frequency)) { 132 return mContext.getResources().getInteger( 133 R.integer.config_wifiChannelUtilizationOverride2g); 134 } 135 if (ScanResult.is5GHz(frequency)) { 136 return mContext.getResources().getInteger( 137 R.integer.config_wifiChannelUtilizationOverride5g); 138 } 139 return mContext.getResources().getInteger( 140 R.integer.config_wifiChannelUtilizationOverride6g); 141 } 142 return mChannelUtilizationMap.get(frequency, BssLoad.INVALID); 143 } 144 145 /** 146 * Update device mobility state 147 * @param newState the new device mobility state 148 */ setDeviceMobilityState(@eviceMobilityState int newState)149 public void setDeviceMobilityState(@DeviceMobilityState int newState) { 150 mDeviceMobilityState = newState; 151 if (sVerboseLoggingEnabled) { 152 Log.d(TAG, " update device mobility state to " + newState); 153 } 154 } 155 156 /** 157 * Set channel utilization ratio for a given frequency 158 * @param frequency The center frequency of 20MHz channel 159 * @param utilizationRatio The utilization ratio of 20MHz channel 160 */ setUtilizationRatio(int frequency, int utilizationRatio)161 public void setUtilizationRatio(int frequency, int utilizationRatio) { 162 mChannelUtilizationMap.put(frequency, utilizationRatio); 163 } 164 165 /** 166 * Update channel utilization with the latest link layer stats and the cached channel stats 167 * and then update channel stats cache 168 * If the given frequency is UNKNOWN_FREQ, calculate channel utilization of all frequencies 169 * Otherwise, calculate the channel utilization of the given frequency 170 * @param wifiLinkLayerStats The latest wifi link layer stats 171 * @param frequency Current frequency of network. 172 */ refreshChannelStatsAndChannelUtilization(WifiLinkLayerStats wifiLinkLayerStats, int frequency)173 public void refreshChannelStatsAndChannelUtilization(WifiLinkLayerStats wifiLinkLayerStats, 174 int frequency) { 175 if (mContext.getResources().getBoolean( 176 R.bool.config_wifiChannelUtilizationOverrideEnabled)) { 177 return; 178 } 179 180 if (wifiLinkLayerStats == null) { 181 return; 182 } 183 SparseArray<ChannelStats> channelStatsMap = wifiLinkLayerStats.channelStatsMap; 184 if (channelStatsMap == null) { 185 return; 186 } 187 if (frequency != UNKNOWN_FREQ) { 188 ChannelStats channelStats = channelStatsMap.get(frequency, null); 189 if (channelStats != null) calculateChannelUtilization(channelStats); 190 } else { 191 for (int i = 0; i < channelStatsMap.size(); i++) { 192 ChannelStats channelStats = channelStatsMap.valueAt(i); 193 calculateChannelUtilization(channelStats); 194 } 195 } 196 updateChannelStatsCache(channelStatsMap, frequency); 197 } 198 calculateChannelUtilization(ChannelStats channelStats)199 private void calculateChannelUtilization(ChannelStats channelStats) { 200 int freq = channelStats.frequency; 201 int ccaBusyTimeMs = channelStats.ccaBusyTimeMs; 202 int radioOnTimeMs = channelStats.radioOnTimeMs; 203 204 ChannelStats channelStatsRef = findChanStatsReference(freq, radioOnTimeMs); 205 int busyTimeDiff = ccaBusyTimeMs - channelStatsRef.ccaBusyTimeMs; 206 int radioOnTimeDiff = radioOnTimeMs - channelStatsRef.radioOnTimeMs; 207 int utilizationRatio = BssLoad.INVALID; 208 if (radioOnTimeDiff >= RADIO_ON_TIME_DIFF_MIN_MS && busyTimeDiff >= 0) { 209 utilizationRatio = calculateUtilizationRatio(radioOnTimeDiff, busyTimeDiff); 210 } 211 mChannelUtilizationMap.put(freq, utilizationRatio); 212 213 if (sVerboseLoggingEnabled) { 214 int utilizationRatioT0 = calculateUtilizationRatio(radioOnTimeMs, ccaBusyTimeMs); 215 StringBuilder sb = new StringBuilder(); 216 Log.d(TAG, sb.append(" freq: ").append(freq) 217 .append(" onTime: ").append(radioOnTimeMs) 218 .append(" busyTime: ").append(ccaBusyTimeMs) 219 .append(" onTimeDiff: ").append(radioOnTimeDiff) 220 .append(" busyTimeDiff: ").append(busyTimeDiff) 221 .append(" utilization: ").append(utilizationRatio) 222 .append(" utilization t0: ").append(utilizationRatioT0) 223 .toString()); 224 } 225 } 226 /** 227 * Find a proper channelStats reference from channelStatsMap cache. 228 * The search continues until it finds a channelStat at the given frequency with radioOnTime 229 * sufficiently smaller than current radioOnTime, or there is no channelStats for the given 230 * frequency or it reaches the end of cache. 231 * @param freq Frequency of current channel 232 * @param radioOnTimeMs The latest radioOnTime of current channel 233 * @return the found channelStat reference if search succeeds, 234 * or a placeholder channelStats with time zero if channelStats is not found 235 * for the given frequency, 236 * or a placeholder channelStats with the latest radioOnTimeMs if it reaches 237 * the end of cache. 238 */ findChanStatsReference(int freq, int radioOnTimeMs)239 private ChannelStats findChanStatsReference(int freq, int radioOnTimeMs) { 240 // A placeholder channelStats with the latest radioOnTimeMs. 241 ChannelStats channelStatsCurrRadioOnTime = new ChannelStats(); 242 channelStatsCurrRadioOnTime.radioOnTimeMs = radioOnTimeMs; 243 Iterator iterator = mChannelStatsMapCache.iterator(); 244 while (iterator.hasNext()) { 245 SparseArray<ChannelStats> channelStatsMap = (SparseArray<ChannelStats>) iterator.next(); 246 // If the freq can't be found in current channelStatsMap, stop search because it won't 247 // appear in older ones either due to the fact that channelStatsMap are accumulated 248 // in HW and thus a recent reading should have channels no less than old readings. 249 // Return a placeholder channelStats with zero radioOnTimeMs 250 if (channelStatsMap == null || channelStatsMap.get(freq) == null) { 251 return new ChannelStats(); 252 } 253 ChannelStats channelStats = channelStatsMap.get(freq); 254 int radioOnTimeDiff = radioOnTimeMs - channelStats.radioOnTimeMs; 255 if (radioOnTimeDiff >= RADIO_ON_TIME_DIFF_MIN_MS) { 256 return channelStats; 257 } 258 } 259 return channelStatsCurrRadioOnTime; 260 } 261 calculateUtilizationRatio(int radioOnTimeDiff, int busyTimeDiff)262 private int calculateUtilizationRatio(int radioOnTimeDiff, int busyTimeDiff) { 263 if (radioOnTimeDiff > 0) { 264 int utilizationRatio = busyTimeDiff * BssLoad.MAX_CHANNEL_UTILIZATION / radioOnTimeDiff; 265 return (utilizationRatio > UTILIZATION_RATIO_MAX) ? BssLoad.INVALID : utilizationRatio; 266 } else { 267 return BssLoad.INVALID; 268 } 269 } 270 updateChannelStatsCache(SparseArray<ChannelStats> channelStatsMap, int freq)271 private void updateChannelStatsCache(SparseArray<ChannelStats> channelStatsMap, int freq) { 272 // Update cache if it hits one of following conditions 273 // 1) it has been a long while since the last update and device doesn't remain stationary 274 // 2) cache is empty 275 boolean remainStationary = 276 mLastChannelStatsMapMobilityState == DEVICE_MOBILITY_STATE_STATIONARY 277 && mDeviceMobilityState == DEVICE_MOBILITY_STATE_STATIONARY; 278 long currTimeStamp = mClock.getElapsedSinceBootMillis(); 279 boolean isLongTimeSinceLastUpdate = 280 (currTimeStamp - mLastChannelStatsMapTimeStamp) >= mCacheUpdateIntervalMinMs; 281 if ((isLongTimeSinceLastUpdate && !remainStationary) || isChannelStatsMapCacheEmpty(freq)) { 282 mChannelStatsMapCache.addFirst(channelStatsMap); 283 mChannelStatsMapCache.removeLast(); 284 mLastChannelStatsMapTimeStamp = currTimeStamp; 285 mLastChannelStatsMapMobilityState = mDeviceMobilityState; 286 } 287 } 288 isChannelStatsMapCacheEmpty(int freq)289 private boolean isChannelStatsMapCacheEmpty(int freq) { 290 SparseArray<ChannelStats> channelStatsMap = mChannelStatsMapCache.peekFirst(); 291 if (channelStatsMap == null || channelStatsMap.size() == 0) return true; 292 if (freq != UNKNOWN_FREQ && channelStatsMap.get(freq) == null) return true; 293 return false; 294 } 295 } 296