WifiScoreReport.java revision 279abf6c5af5d42f6deb8e8738c7f030521976ef
1/* 2 * Copyright (C) 2016 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 17package com.android.server.wifi; 18 19import android.content.Context; 20import android.net.NetworkAgent; 21import android.net.wifi.WifiConfiguration; 22import android.net.wifi.WifiInfo; 23import android.util.Log; 24 25import com.android.internal.R; 26 27import java.io.FileDescriptor; 28import java.io.PrintWriter; 29import java.text.SimpleDateFormat; 30import java.util.Date; 31import java.util.LinkedList; 32import java.util.Locale; 33 34/** 35 * Class used to calculate scores for connected wifi networks and report it to the associated 36 * network agent. 37*/ 38public class WifiScoreReport { 39 private static final String TAG = "WifiScoreReport"; 40 41 private static final int STARTING_SCORE = 56; 42 43 private static final int SCAN_CACHE_VISIBILITY_MS = 12000; 44 private static final int HOME_VISIBLE_NETWORK_MAX_COUNT = 6; 45 private static final int SCAN_CACHE_COUNT_PENALTY = 2; 46 private static final int AGGRESSIVE_HANDOVER_PENALTY = 6; 47 private static final int MAX_SUCCESS_RATE_OF_STUCK_LINK = 3; // proportional to packets per sec 48 private static final int MAX_STUCK_LINK_COUNT = 5; 49 private static final int MAX_BAD_RSSI_COUNT = 7; 50 private static final int BAD_RSSI_COUNT_PENALTY = 2; 51 private static final int MAX_LOW_RSSI_COUNT = 1; 52 private static final double MIN_TX_FAILURE_RATE_FOR_WORKING_LINK = 0.3; 53 private static final int MIN_SUSTAINED_LINK_STUCK_COUNT = 1; 54 private static final int LINK_STUCK_PENALTY = 2; 55 private static final int BAD_LINKSPEED_PENALTY = 4; 56 private static final int GOOD_LINKSPEED_BONUS = 4; 57 private static final int DUMPSYS_ENTRY_COUNT_LIMIT = 14400; // 12 hours on 3 second poll 58 59 // Device configs. The values are examples. 60 private final int mThresholdMinimumRssi5; // -82 61 private final int mThresholdQualifiedRssi5; // -70 62 private final int mThresholdSaturatedRssi5; // -57 63 private final int mThresholdMinimumRssi24; // -85 64 private final int mThresholdQualifiedRssi24; // -73 65 private final int mThresholdSaturatedRssi24; // -60 66 private final int mBadLinkSpeed24; // 6 Mbps 67 private final int mBadLinkSpeed5; // 12 Mbps 68 private final int mGoodLinkSpeed24; // 24 Mbps 69 private final int mGoodLinkSpeed5; // 36 Mbps 70 71 private final WifiConfigManager mWifiConfigManager; 72 private boolean mVerboseLoggingEnabled = false; 73 74 // Cache of the last score report. 75 private String mReport; 76 private boolean mReportValid = false; 77 78 // State set by updateScoringState 79 private boolean mMultiBandScanResults; 80 private boolean mIsHomeNetwork; 81 82 private final Clock mClock; 83 private int mSessionNumber = 0; 84 85 WifiScoreReport(Context context, WifiConfigManager wifiConfigManager, Clock clock) { 86 // Fetch all the device configs. 87 mThresholdMinimumRssi5 = context.getResources().getInteger( 88 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz); 89 mThresholdQualifiedRssi5 = context.getResources().getInteger( 90 R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_5GHz); 91 mThresholdSaturatedRssi5 = context.getResources().getInteger( 92 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_5GHz); 93 mThresholdMinimumRssi24 = context.getResources().getInteger( 94 R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz); 95 mThresholdQualifiedRssi24 = context.getResources().getInteger( 96 R.integer.config_wifi_framework_wifi_score_low_rssi_threshold_24GHz); 97 mThresholdSaturatedRssi24 = context.getResources().getInteger( 98 R.integer.config_wifi_framework_wifi_score_good_rssi_threshold_24GHz); 99 mBadLinkSpeed24 = context.getResources().getInteger( 100 R.integer.config_wifi_framework_wifi_score_bad_link_speed_24); 101 mBadLinkSpeed5 = context.getResources().getInteger( 102 R.integer.config_wifi_framework_wifi_score_bad_link_speed_5); 103 mGoodLinkSpeed24 = context.getResources().getInteger( 104 R.integer.config_wifi_framework_wifi_score_good_link_speed_24); 105 mGoodLinkSpeed5 = context.getResources().getInteger( 106 R.integer.config_wifi_framework_wifi_score_good_link_speed_5); 107 108 mWifiConfigManager = wifiConfigManager; 109 mClock = clock; 110 } 111 112 /** 113 * Method returning the String representation of the last score report. 114 * 115 * @return String score report 116 */ 117 public String getLastReport() { 118 return mReport; 119 } 120 121 /** 122 * Reset the last calculated score. 123 */ 124 public void reset() { 125 mReport = ""; 126 mReportValid = false; 127 mSessionNumber++; 128 if (mVerboseLoggingEnabled) Log.d(TAG, "reset"); 129 } 130 131 /** 132 * Checks if the last report data is valid or not. This will be cleared when {@link #reset()} is 133 * invoked. 134 * 135 * @return true if valid, false otherwise. 136 */ 137 public boolean isLastReportValid() { 138 return mReportValid; 139 } 140 141 /** 142 * Enable/Disable verbose logging in score report generation. 143 */ 144 public void enableVerboseLogging(boolean enable) { 145 mVerboseLoggingEnabled = enable; 146 } 147 148 /** 149 * Calculate wifi network score based on updated link layer stats and send the score to 150 * the provided network agent. 151 * 152 * If the score has changed from the previous value, update the WifiNetworkAgent. 153 * 154 * Called periodically (POLL_RSSI_INTERVAL_MSECS) about every 3 seconds. 155 * 156 * @param wifiInfo WifiInfo instance pointing to the currently connected network. 157 * @param networkAgent NetworkAgent to be notified of new score. 158 * @param aggressiveHandover int current aggressiveHandover setting. 159 * @param wifiMetrics for reporting our scores. 160 */ 161 public void calculateAndReportScore(WifiInfo wifiInfo, NetworkAgent networkAgent, 162 int aggressiveHandover, WifiMetrics wifiMetrics) { 163 int score; 164 165 logLinkMetrics(wifiInfo); 166 if (aggressiveHandover == 0) { 167 // Use the old method 168 updateScoringState(wifiInfo, aggressiveHandover); 169 score = calculateScore(wifiInfo, aggressiveHandover); 170 } else { 171 score = calculateAlternativeScore(wifiInfo); 172 } 173 174 //sanitize boundaries 175 if (score > NetworkAgent.WIFI_BASE_SCORE) { 176 score = NetworkAgent.WIFI_BASE_SCORE; 177 } 178 if (score < 0) { 179 score = 0; 180 } 181 182 //report score 183 if (score != wifiInfo.score) { 184 if (mVerboseLoggingEnabled) { 185 Log.d(TAG, " report new wifi score " + score); 186 } 187 wifiInfo.score = score; 188 if (networkAgent != null) { 189 networkAgent.sendNetworkScore(score); 190 } 191 } 192 193 mReport = String.format(Locale.US, " score=%d", score); 194 mReportValid = true; 195 wifiMetrics.incrementWifiScoreCount(score); 196 } 197 198 /** 199 * Updates the state. 200 */ 201 private void updateScoringState(WifiInfo wifiInfo, int aggressiveHandover) { 202 mMultiBandScanResults = multiBandScanResults(wifiInfo); 203 mIsHomeNetwork = isHomeNetwork(wifiInfo); 204 205 int rssiThreshBad = mThresholdMinimumRssi24; 206 int rssiThreshLow = mThresholdQualifiedRssi24; 207 208 if (wifiInfo.is5GHz() && !mMultiBandScanResults) { 209 rssiThreshBad = mThresholdMinimumRssi5; 210 rssiThreshLow = mThresholdQualifiedRssi5; 211 } 212 213 int rssi = wifiInfo.getRssi(); 214 if (aggressiveHandover != 0) { 215 rssi -= AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover; 216 } 217 if (mIsHomeNetwork) { 218 rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST; 219 } 220 221 if ((wifiInfo.txBadRate >= 1) 222 && (wifiInfo.txSuccessRate < MAX_SUCCESS_RATE_OF_STUCK_LINK) 223 && rssi < rssiThreshLow) { 224 // Link is stuck 225 if (wifiInfo.linkStuckCount < MAX_STUCK_LINK_COUNT) { 226 wifiInfo.linkStuckCount += 1; 227 } 228 } else if (wifiInfo.txBadRate < MIN_TX_FAILURE_RATE_FOR_WORKING_LINK) { 229 if (wifiInfo.linkStuckCount > 0) { 230 wifiInfo.linkStuckCount -= 1; 231 } 232 } 233 234 if (rssi < rssiThreshBad) { 235 if (wifiInfo.badRssiCount < MAX_BAD_RSSI_COUNT) { 236 wifiInfo.badRssiCount += 1; 237 } 238 } else if (rssi < rssiThreshLow) { 239 wifiInfo.lowRssiCount = MAX_LOW_RSSI_COUNT; // Dont increment the lowRssi count above 1 240 if (wifiInfo.badRssiCount > 0) { 241 // Decrement bad Rssi count 242 wifiInfo.badRssiCount -= 1; 243 } 244 } else { 245 wifiInfo.badRssiCount = 0; 246 wifiInfo.lowRssiCount = 0; 247 } 248 249 } 250 251 /** 252 * Calculates the score, without all the cruft. 253 */ 254 private int calculateScore(WifiInfo wifiInfo, int aggressiveHandover) { 255 int score = STARTING_SCORE; 256 257 int rssiThreshSaturated = mThresholdSaturatedRssi24; 258 int linkspeedThreshBad = mBadLinkSpeed24; 259 int linkspeedThreshGood = mGoodLinkSpeed24; 260 261 if (wifiInfo.is5GHz()) { 262 if (!mMultiBandScanResults) { 263 rssiThreshSaturated = mThresholdSaturatedRssi5; 264 } 265 linkspeedThreshBad = mBadLinkSpeed5; 266 linkspeedThreshGood = mGoodLinkSpeed5; 267 } 268 269 int rssi = wifiInfo.getRssi(); 270 if (aggressiveHandover != 0) { 271 rssi -= AGGRESSIVE_HANDOVER_PENALTY * aggressiveHandover; 272 } 273 if (mIsHomeNetwork) { 274 rssi += WifiConfiguration.HOME_NETWORK_RSSI_BOOST; 275 } 276 277 int linkSpeed = wifiInfo.getLinkSpeed(); 278 279 if (wifiInfo.linkStuckCount > MIN_SUSTAINED_LINK_STUCK_COUNT) { 280 // Once link gets stuck for more than 3 seconds, start reducing the score 281 score = score - LINK_STUCK_PENALTY * (wifiInfo.linkStuckCount - 1); 282 } 283 284 if (linkSpeed < linkspeedThreshBad) { 285 score -= BAD_LINKSPEED_PENALTY; 286 } else if ((linkSpeed >= linkspeedThreshGood) && (wifiInfo.txSuccessRate > 5)) { 287 score += GOOD_LINKSPEED_BONUS; // So as bad rssi alone doesn't kill us 288 } 289 290 score -= wifiInfo.badRssiCount * BAD_RSSI_COUNT_PENALTY + wifiInfo.lowRssiCount; 291 292 if (rssi >= rssiThreshSaturated) score += 5; 293 294 if (score > NetworkAgent.WIFI_BASE_SCORE) score = NetworkAgent.WIFI_BASE_SCORE; 295 if (score < 0) score = 0; 296 297 return score; 298 } 299 300 /** 301 * Determines if we can see both 2.4GHz and 5GHz for current config 302 */ 303 private boolean multiBandScanResults(WifiInfo wifiInfo) { 304 WifiConfiguration currentConfiguration = 305 mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId()); 306 if (currentConfiguration == null) return false; 307 ScanDetailCache scanDetailCache = 308 mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId()); 309 if (scanDetailCache == null) return false; 310 // Nasty that we change state here... 311 currentConfiguration.setVisibility(scanDetailCache.getVisibility(SCAN_CACHE_VISIBILITY_MS)); 312 if (currentConfiguration.visibility == null) return false; 313 if (currentConfiguration.visibility.rssi24 == WifiConfiguration.INVALID_RSSI) return false; 314 if (currentConfiguration.visibility.rssi5 == WifiConfiguration.INVALID_RSSI) return false; 315 // N.B. this does not do exactly what is claimed! 316 if (currentConfiguration.visibility.rssi24 317 >= currentConfiguration.visibility.rssi5 - SCAN_CACHE_COUNT_PENALTY) { 318 return true; 319 } 320 return false; 321 } 322 323 /** 324 * Decides whether the current network is a "home" network 325 */ 326 private boolean isHomeNetwork(WifiInfo wifiInfo) { 327 WifiConfiguration currentConfiguration = 328 mWifiConfigManager.getConfiguredNetwork(wifiInfo.getNetworkId()); 329 if (currentConfiguration == null) return false; 330 // This seems like it will only return true for really old routers! 331 if (currentConfiguration.allowedKeyManagement.cardinality() != 1) return false; 332 if (!currentConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) { 333 return false; 334 } 335 ScanDetailCache scanDetailCache = 336 mWifiConfigManager.getScanDetailCacheForNetwork(wifiInfo.getNetworkId()); 337 if (scanDetailCache == null) return false; 338 if (scanDetailCache.size() <= HOME_VISIBLE_NETWORK_MAX_COUNT) { 339 return true; 340 } 341 return false; 342 } 343 344 /** 345 * Experimental scorer, used when aggressive handover preference is set 346 */ 347 private int calculateAlternativeScore(WifiInfo wifiInfo) { 348 double rssi = wifiInfo.getRssi(); 349 double badRssi = wifiInfo.is5GHz() ? mThresholdQualifiedRssi5 : mThresholdQualifiedRssi24; 350 351 double baseScore = 50.0; // The score to beat to be chosen over mobile data 352 double score = (rssi - badRssi) + baseScore; 353 return (int) Math.round(score); 354 } 355 356 /** 357 * Data for dumpsys 358 * 359 * These are stored as csv formatted lines 360 */ 361 private LinkedList<String> mLinkMetricsHistory = new LinkedList<String>(); 362 363 /** 364 * Data logging for dumpsys 365 */ 366 private void logLinkMetrics(WifiInfo wifiInfo) { 367 long now = mClock.getWallClockMillis(); 368 double rssi = wifiInfo.getRssi(); 369 int freq = wifiInfo.getFrequency(); 370 int linkSpeed = wifiInfo.getLinkSpeed(); 371 double txSuccessRate = wifiInfo.txSuccessRate; 372 double txRetriesRate = wifiInfo.txRetriesRate; 373 double txBadRate = wifiInfo.txBadRate; 374 double rxSuccessRate = wifiInfo.rxSuccessRate; 375 try { 376 String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now)); 377 String s = String.format(Locale.US, // Use US to avoid comma/decimal confusion 378 "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f", 379 timestamp, mSessionNumber, rssi, freq, linkSpeed, 380 txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate); 381 mLinkMetricsHistory.add(s); 382 } catch (Exception e) { 383 Log.e(TAG, "format problem", e); 384 } 385 while (mLinkMetricsHistory.size() > DUMPSYS_ENTRY_COUNT_LIMIT) { 386 mLinkMetricsHistory.removeFirst(); 387 } 388 } 389 390 /** 391 * Tag to be used in dumpsys request 392 */ 393 public static final String DUMP_ARG = "WifiScoreReport"; 394 395 /** 396 * Dump logged signal strength and traffic measurements. 397 * @param fd unused 398 * @param pw PrintWriter for writing dump to 399 * @param args unused 400 */ 401 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 402 pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx"); 403 for (String line : mLinkMetricsHistory) { 404 pw.println(line); 405 } 406 } 407} 408