• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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