1 /*
2  * Copyright 2018 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 android.content.Context;
20 import android.net.wifi.WifiInfo;
21 import android.os.Handler;
22 import android.os.Looper;
23 import android.provider.Settings;
24 
25 import com.android.server.wifi.nano.WifiMetricsProto.WifiIsUnusableEvent;
26 
27 /**
28  * Looks for Wifi data stalls
29  */
30 public class WifiDataStall {
31 
32     // Default minimum number of txBadDelta to trigger data stall
33     public static final int MIN_TX_BAD_DEFAULT = 1;
34     // Default minimum number of txSuccessDelta to trigger data stall
35     // when rxSuccessDelta is 0
36     public static final int MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT = 50;
37     // Maximum time gap between two WifiLinkLayerStats to trigger a data stall
38     public static final long MAX_MS_DELTA_FOR_DATA_STALL = 60 * 1000; // 1 minute
39     // Maximum time that a data stall start time stays valid.
40     public static final long VALIDITY_PERIOD_OF_DATA_STALL_START_MS = 30 * 1000; // 0.5 minutes
41     // Default Tx packet error rate when there is no Tx attempt
42     public static final int DEFAULT_TX_PACKET_ERROR_RATE = 20;
43     // Default CCA level when CCA stats are not available
44     public static final int DEFAULT_CCA_LEVEL = 0;
45 
46     private final Context mContext;
47     private final DeviceConfigFacade mDeviceConfigFacade;
48     private final FrameworkFacade mFacade;
49     private final WifiMetrics mWifiMetrics;
50 
51     private Handler mHandler;
52     private int mMinTxBad;
53     private int mMinTxSuccessWithoutRx;
54     private int mDataStallDurationMs;
55     private int mDataStallTxTputThrMbps;
56     private int mDataStallRxTputThrMbps;
57     private int mDataStallTxPerThr;
58     private int mDataStallCcaLevelThr;
59     private int mLastFrequency = -1;
60     private String mLastBssid;
61     private long mLastTotalRadioOnFreqTimeMs = -1;
62     private long mLastTotalCcaBusyFreqTimeMs = -1;
63     private long mDataStallStartTimeMs = -1;
64     private Clock mClock;
65     private boolean mDataStallTx = false;
66     private boolean mDataStallRx = false;
67 
WifiDataStall(Context context, FrameworkFacade facade, WifiMetrics wifiMetrics, DeviceConfigFacade deviceConfigFacade, Looper clientModeImplLooper, Clock clock)68     public WifiDataStall(Context context, FrameworkFacade facade, WifiMetrics wifiMetrics,
69             DeviceConfigFacade deviceConfigFacade, Looper clientModeImplLooper, Clock clock) {
70         mContext = context;
71         mDeviceConfigFacade = deviceConfigFacade;
72         mFacade = facade;
73         mHandler = new Handler(clientModeImplLooper);
74         mWifiMetrics = wifiMetrics;
75         mClock = clock;
76         loadSettings();
77 
78         mDeviceConfigFacade.addOnPropertiesChangedListener(
79                 command -> mHandler.post(command),
80                 properties -> {
81                     updateUsabilityDataCollectionFlags();
82                 });
83     }
84 
85     /**
86      * Load setting values related to wifi data stall.
87      */
loadSettings()88     public void loadSettings() {
89         mMinTxBad = mFacade.getIntegerSetting(
90                 mContext, Settings.Global.WIFI_DATA_STALL_MIN_TX_BAD, MIN_TX_BAD_DEFAULT);
91         mMinTxSuccessWithoutRx = mFacade.getIntegerSetting(
92                 mContext, Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX,
93                 MIN_TX_SUCCESS_WITHOUT_RX_DEFAULT);
94         mWifiMetrics.setWifiDataStallMinTxBad(mMinTxBad);
95         mWifiMetrics.setWifiDataStallMinRxWithoutTx(mMinTxSuccessWithoutRx);
96         updateUsabilityDataCollectionFlags();
97     }
98 
99     /**
100      * Checks for data stall by looking at tx/rx packet counts
101      * @param oldStats second most recent WifiLinkLayerStats
102      * @param newStats most recent WifiLinkLayerStats
103      * @param wifiInfo WifiInfo for current connection
104      * @return trigger type of WifiIsUnusableEvent
105      */
checkForDataStall(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, WifiInfo wifiInfo)106     public int checkForDataStall(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats,
107             WifiInfo wifiInfo) {
108         if (oldStats == null || newStats == null) {
109             mWifiMetrics.resetWifiIsUnusableLinkLayerStats();
110             return WifiIsUnusableEvent.TYPE_UNKNOWN;
111         }
112 
113         long txSuccessDelta = (newStats.txmpdu_be + newStats.txmpdu_bk
114                 + newStats.txmpdu_vi + newStats.txmpdu_vo)
115                 - (oldStats.txmpdu_be + oldStats.txmpdu_bk
116                 + oldStats.txmpdu_vi + oldStats.txmpdu_vo);
117         long txRetriesDelta = (newStats.retries_be + newStats.retries_bk
118                 + newStats.retries_vi + newStats.retries_vo)
119                 - (oldStats.retries_be + oldStats.retries_bk
120                 + oldStats.retries_vi + oldStats.retries_vo);
121         long txBadDelta = (newStats.lostmpdu_be + newStats.lostmpdu_bk
122                 + newStats.lostmpdu_vi + newStats.lostmpdu_vo)
123                 - (oldStats.lostmpdu_be + oldStats.lostmpdu_bk
124                 + oldStats.lostmpdu_vi + oldStats.lostmpdu_vo);
125         long rxSuccessDelta = (newStats.rxmpdu_be + newStats.rxmpdu_bk
126                 + newStats.rxmpdu_vi + newStats.rxmpdu_vo)
127                 - (oldStats.rxmpdu_be + oldStats.rxmpdu_bk
128                 + oldStats.rxmpdu_vi + oldStats.rxmpdu_vo);
129         long timeMsDelta = newStats.timeStampInMs - oldStats.timeStampInMs;
130 
131         if (timeMsDelta < 0
132                 || txSuccessDelta < 0
133                 || txRetriesDelta < 0
134                 || txBadDelta < 0
135                 || rxSuccessDelta < 0) {
136             // There was a reset in WifiLinkLayerStats
137             mWifiMetrics.resetWifiIsUnusableLinkLayerStats();
138             return WifiIsUnusableEvent.TYPE_UNKNOWN;
139         }
140 
141         mWifiMetrics.updateWifiIsUnusableLinkLayerStats(txSuccessDelta, txRetriesDelta,
142                 txBadDelta, rxSuccessDelta, timeMsDelta);
143 
144         if (timeMsDelta < MAX_MS_DELTA_FOR_DATA_STALL) {
145             int txLinkSpeed = wifiInfo.getLinkSpeed();
146             int rxLinkSpeed = wifiInfo.getRxLinkSpeedMbps();
147             boolean isSameBssidAndFreq = mLastBssid == null || mLastFrequency == -1
148                     || (mLastBssid.equals(wifiInfo.getBSSID())
149                     && mLastFrequency == wifiInfo.getFrequency());
150             mLastFrequency = wifiInfo.getFrequency();
151             mLastBssid = wifiInfo.getBSSID();
152 
153             int ccaLevel = updateCcaLevel(newStats, wifiInfo, isSameBssidAndFreq);
154             int txPer = updateTxPer(txSuccessDelta, txRetriesDelta, isSameBssidAndFreq);
155 
156             boolean isTxTputLow = false;
157             boolean isRxTputLow = false;
158             if (txLinkSpeed > 0) {
159                 int txTput = txLinkSpeed * (100 - txPer) * (100 - ccaLevel);
160                 isTxTputLow = txTput < mDataStallTxTputThrMbps * 100 * 100;
161             }
162             if (rxLinkSpeed > 0) {
163                 int rxTput = rxLinkSpeed * (100 - ccaLevel);
164                 isRxTputLow = rxTput < mDataStallRxTputThrMbps * 100;
165             }
166 
167             boolean dataStallTx = isTxTputLow || ccaLevel >= mDataStallCcaLevelThr
168                     || txPer >= mDataStallTxPerThr;
169             boolean dataStallRx = isRxTputLow || ccaLevel >= mDataStallCcaLevelThr;
170 
171             // Data stall event is triggered if there are consecutive Tx and/or Rx data stalls
172             // Reset mDataStallStartTimeMs to -1 if currently there is no Tx or Rx data stall
173             if (dataStallTx || dataStallRx) {
174                 mDataStallTx = mDataStallTx || dataStallTx;
175                 mDataStallRx = mDataStallRx || dataStallRx;
176                 if (mDataStallStartTimeMs == -1) {
177                     mDataStallStartTimeMs = mClock.getElapsedSinceBootMillis();
178                     if (mDataStallDurationMs == 0) {
179                         mDataStallStartTimeMs = -1;
180                         int result = calculateUsabilityEventType(mDataStallTx, mDataStallRx);
181                         mDataStallRx = false;
182                         mDataStallTx = false;
183                         return result;
184                     }
185                 } else {
186                     long elapsedTime =  mClock.getElapsedSinceBootMillis() - mDataStallStartTimeMs;
187                     if (elapsedTime >= mDataStallDurationMs) {
188                         mDataStallStartTimeMs = -1;
189                         if (elapsedTime <= VALIDITY_PERIOD_OF_DATA_STALL_START_MS) {
190                             int result = calculateUsabilityEventType(mDataStallTx, mDataStallRx);
191                             mDataStallRx = false;
192                             mDataStallTx = false;
193                             return result;
194                         } else {
195                             mDataStallTx = false;
196                             mDataStallRx = false;
197                         }
198                     } else {
199                         // No need to do anything.
200                     }
201                 }
202             } else {
203                 mDataStallStartTimeMs = -1;
204                 mDataStallTx = false;
205                 mDataStallRx = false;
206             }
207         }
208 
209         return WifiIsUnusableEvent.TYPE_UNKNOWN;
210     }
211 
updateCcaLevel(WifiLinkLayerStats newStats, WifiInfo wifiInfo, boolean isSameBssidAndFreq)212     private int updateCcaLevel(WifiLinkLayerStats newStats, WifiInfo wifiInfo,
213             boolean isSameBssidAndFreq) {
214         WifiLinkLayerStats.ChannelStats statsMap = newStats.channelStatsMap.get(mLastFrequency);
215         if (statsMap == null || !isSameBssidAndFreq) {
216             return DEFAULT_CCA_LEVEL;
217         }
218         int radioOnTimeDelta = (int) (statsMap.radioOnTimeMs - mLastTotalRadioOnFreqTimeMs);
219         int ccaBusyTimeDelta = (int) (statsMap.ccaBusyTimeMs - mLastTotalCcaBusyFreqTimeMs);
220         mLastTotalRadioOnFreqTimeMs = statsMap.radioOnTimeMs;
221         mLastTotalCcaBusyFreqTimeMs = statsMap.ccaBusyTimeMs;
222 
223         boolean isCcaValid = (radioOnTimeDelta > 0) && (ccaBusyTimeDelta >= 0)
224                 && (ccaBusyTimeDelta <= radioOnTimeDelta);
225         // Update CCA level only if CCA stats are valid.
226         if (!isCcaValid) {
227             return DEFAULT_CCA_LEVEL;
228         }
229         return (int) (ccaBusyTimeDelta * 100 / radioOnTimeDelta);
230     }
231 
updateTxPer(long txSuccessDelta, long txRetriesDelta, boolean isSameBssidAndFreq)232     private int updateTxPer(long txSuccessDelta, long txRetriesDelta, boolean isSameBssidAndFreq) {
233         if (!isSameBssidAndFreq) {
234             return DEFAULT_TX_PACKET_ERROR_RATE;
235         }
236         long txAttempts = txSuccessDelta + txRetriesDelta;
237         if (txAttempts <= 0) {
238             return DEFAULT_TX_PACKET_ERROR_RATE;
239         }
240         return (int) (txRetriesDelta * 100 / txAttempts);
241     }
242 
calculateUsabilityEventType(boolean dataStallTx, boolean dataStallRx)243     private int calculateUsabilityEventType(boolean dataStallTx, boolean dataStallRx) {
244         int result = WifiIsUnusableEvent.TYPE_UNKNOWN;
245         if (dataStallTx && dataStallRx) {
246             result = WifiIsUnusableEvent.TYPE_DATA_STALL_BOTH;
247         } else if (dataStallTx) {
248             result = WifiIsUnusableEvent.TYPE_DATA_STALL_BAD_TX;
249         } else if (dataStallRx) {
250             result = WifiIsUnusableEvent.TYPE_DATA_STALL_TX_WITHOUT_RX;
251         }
252         mWifiMetrics.logWifiIsUnusableEvent(result);
253         return result;
254     }
255 
updateUsabilityDataCollectionFlags()256     private void updateUsabilityDataCollectionFlags() {
257         mDataStallDurationMs = mDeviceConfigFacade.getDataStallDurationMs();
258         mDataStallTxTputThrMbps = mDeviceConfigFacade.getDataStallTxTputThrMbps();
259         mDataStallRxTputThrMbps = mDeviceConfigFacade.getDataStallRxTputThrMbps();
260         mDataStallTxPerThr = mDeviceConfigFacade.getDataStallTxPerThr();
261         mDataStallCcaLevelThr = mDeviceConfigFacade.getDataStallCcaLevelThr();
262 
263         mWifiMetrics.setDataStallDurationMs(mDataStallDurationMs);
264         mWifiMetrics.setDataStallTxTputThrMbps(mDataStallTxTputThrMbps);
265         mWifiMetrics.setDataStallRxTputThrMbps(mDataStallRxTputThrMbps);
266         mWifiMetrics.setDataStallTxPerThr(mDataStallTxPerThr);
267         mWifiMetrics.setDataStallCcaLevelThr(mDataStallCcaLevelThr);
268     }
269 }
270