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 static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS; 20 import static android.net.wifi.WifiInfo.INVALID_RSSI; 21 import static android.net.wifi.WifiInfo.LINK_SPEED_UNKNOWN; 22 23 import static com.android.server.wifi.WifiHealthMonitor.HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS; 24 import static com.android.server.wifi.WifiHealthMonitor.HEALTH_MONITOR_MIN_TX_PACKET_PER_SEC; 25 import static com.android.server.wifi.WifiHealthMonitor.REASON_ASSOC_REJECTION; 26 import static com.android.server.wifi.WifiHealthMonitor.REASON_ASSOC_TIMEOUT; 27 import static com.android.server.wifi.WifiHealthMonitor.REASON_AUTH_FAILURE; 28 import static com.android.server.wifi.WifiHealthMonitor.REASON_CONNECTION_FAILURE; 29 import static com.android.server.wifi.WifiHealthMonitor.REASON_CONNECTION_FAILURE_DISCONNECTION; 30 import static com.android.server.wifi.WifiHealthMonitor.REASON_DISCONNECTION_NONLOCAL; 31 import static com.android.server.wifi.WifiHealthMonitor.REASON_NO_FAILURE; 32 import static com.android.server.wifi.WifiHealthMonitor.REASON_SHORT_CONNECTION_NONLOCAL; 33 34 import android.annotation.IntDef; 35 import android.annotation.NonNull; 36 import android.annotation.Nullable; 37 import android.content.Context; 38 import android.net.MacAddress; 39 import android.net.wifi.ScanResult; 40 import android.net.wifi.SupplicantState; 41 import android.net.wifi.WifiManager; 42 import android.util.ArrayMap; 43 import android.util.Base64; 44 import android.util.LocalLog; 45 import android.util.Log; 46 import android.util.Pair; 47 import android.util.SparseLongArray; 48 49 import com.android.internal.annotations.VisibleForTesting; 50 import com.android.internal.util.Preconditions; 51 import com.android.server.wifi.WifiBlocklistMonitor.FailureReason; 52 import com.android.server.wifi.WifiHealthMonitor.FailureStats; 53 import com.android.server.wifi.proto.WifiScoreCardProto; 54 import com.android.server.wifi.proto.WifiScoreCardProto.AccessPoint; 55 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStats; 56 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAll; 57 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAllLevel; 58 import com.android.server.wifi.proto.WifiScoreCardProto.BandwidthStatsAllLink; 59 import com.android.server.wifi.proto.WifiScoreCardProto.ConnectionStats; 60 import com.android.server.wifi.proto.WifiScoreCardProto.Event; 61 import com.android.server.wifi.proto.WifiScoreCardProto.HistogramBucket; 62 import com.android.server.wifi.proto.WifiScoreCardProto.Network; 63 import com.android.server.wifi.proto.WifiScoreCardProto.NetworkList; 64 import com.android.server.wifi.proto.WifiScoreCardProto.NetworkStats; 65 import com.android.server.wifi.proto.WifiScoreCardProto.SecurityType; 66 import com.android.server.wifi.proto.WifiScoreCardProto.Signal; 67 import com.android.server.wifi.proto.WifiScoreCardProto.UnivariateStatistic; 68 import com.android.server.wifi.proto.nano.WifiMetricsProto.BandwidthEstimatorStats; 69 import com.android.server.wifi.util.IntHistogram; 70 import com.android.server.wifi.util.LruList; 71 import com.android.server.wifi.util.NativeUtil; 72 import com.android.server.wifi.util.RssiUtil; 73 74 import com.google.protobuf.ByteString; 75 import com.google.protobuf.InvalidProtocolBufferException; 76 77 import java.io.FileDescriptor; 78 import java.io.PrintWriter; 79 import java.lang.annotation.Retention; 80 import java.lang.annotation.RetentionPolicy; 81 import java.nio.ByteBuffer; 82 import java.security.MessageDigest; 83 import java.security.NoSuchAlgorithmException; 84 import java.util.ArrayList; 85 import java.util.Iterator; 86 import java.util.List; 87 import java.util.Map; 88 import java.util.Objects; 89 import java.util.concurrent.atomic.AtomicReference; 90 import java.util.stream.Collectors; 91 92 import javax.annotation.concurrent.NotThreadSafe; 93 94 /** 95 * Retains statistical information about the performance of various 96 * access points and networks, as experienced by this device. 97 * 98 * The purpose is to better inform future network selection and switching 99 * by this device and help health monitor detect network issues. 100 */ 101 @NotThreadSafe 102 public class WifiScoreCard { 103 104 public static final String DUMP_ARG = "WifiScoreCard"; 105 106 private static final String TAG = "WifiScoreCard"; 107 private boolean mVerboseLoggingEnabled = false; 108 109 @VisibleForTesting 110 boolean mPersistentHistograms = true; 111 112 private static final int TARGET_IN_MEMORY_ENTRIES = 50; 113 private static final int UNKNOWN_REASON = -1; 114 115 public static final String PER_BSSID_DATA_NAME = "scorecard.proto"; 116 public static final String PER_NETWORK_DATA_NAME = "perNetworkData"; 117 118 static final int INSUFFICIENT_RECENT_STATS = 0; 119 static final int SUFFICIENT_RECENT_STATS_ONLY = 1; 120 static final int SUFFICIENT_RECENT_PREV_STATS = 2; 121 122 private static final int MAX_FREQUENCIES_PER_SSID = 30; 123 private static final int MAX_TRAFFIC_STATS_POLL_TIME_DELTA_MS = 6_000; 124 125 private final Clock mClock; 126 private final String mL2KeySeed; 127 private MemoryStore mMemoryStore; 128 private final DeviceConfigFacade mDeviceConfigFacade; 129 private final Context mContext; 130 private final WifiGlobals mWifiGlobals; 131 private final LocalLog mLocalLog = new LocalLog(256); 132 private final long[][][] mL2ErrorAccPercent = 133 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 134 private final long[][][] mBwEstErrorAccPercent = 135 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 136 private final long[][][] mBwEstValue = 137 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 138 private final int[][][] mBwEstCount = 139 new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 140 141 @VisibleForTesting 142 static final int[] RSSI_BUCKETS = intsInRange(-100, -20); 143 intsInRange(int min, int max)144 private static int[] intsInRange(int min, int max) { 145 int[] a = new int[max - min + 1]; 146 for (int i = 0; i < a.length; i++) { 147 a[i] = min + i; 148 } 149 return a; 150 } 151 152 /** Our view of the memory store */ 153 public interface MemoryStore { 154 /** Requests a read, with asynchronous reply */ read(String key, String name, BlobListener blobListener)155 void read(String key, String name, BlobListener blobListener); 156 /** Requests a write, does not wait for completion */ write(String key, String name, byte[] value)157 void write(String key, String name, byte[] value); 158 /** Sets the cluster identifier */ setCluster(String key, String cluster)159 void setCluster(String key, String cluster); 160 /** Requests removal of all entries matching the cluster */ removeCluster(String cluster)161 void removeCluster(String cluster); 162 } 163 /** Asynchronous response to a read request */ 164 public interface BlobListener { 165 /** Provides the previously stored value, or null if none */ onBlobRetrieved(@ullable byte[] value)166 void onBlobRetrieved(@Nullable byte[] value); 167 } 168 169 /** 170 * Installs a memory store. 171 * 172 * Normally this happens just once, shortly after we start. But wifi can 173 * come up before the disk is ready, and we might not yet have a valid wall 174 * clock when we start up, so we need to be prepared to begin recording data 175 * even if the MemoryStore is not yet available. 176 * 177 * When the store is installed for the first time, we want to merge any 178 * recently recorded data together with data already in the store. But if 179 * the store restarts and has to be reinstalled, we don't want to do 180 * this merge, because that would risk double-counting the old data. 181 * 182 */ installMemoryStore(@onNull MemoryStore memoryStore)183 public void installMemoryStore(@NonNull MemoryStore memoryStore) { 184 Preconditions.checkNotNull(memoryStore); 185 if (mMemoryStore == null) { 186 mMemoryStore = memoryStore; 187 Log.i(TAG, "Installing MemoryStore"); 188 requestReadForAllChanged(); 189 } else { 190 mMemoryStore = memoryStore; 191 Log.e(TAG, "Reinstalling MemoryStore"); 192 // Our caller will call doWrites() eventually, so nothing more to do here. 193 } 194 } 195 196 /** 197 * Enable/Disable verbose logging. 198 * 199 * @param verbose true to enable and false to disable. 200 */ enableVerboseLogging(boolean verbose)201 public void enableVerboseLogging(boolean verbose) { 202 mVerboseLoggingEnabled = verbose; 203 } 204 205 @VisibleForTesting 206 static final long TS_NONE = -1; 207 208 /** Tracks the connection status per Wifi interface. */ 209 private static final class IfaceInfo { 210 /** 211 * Timestamp of the start of the most recent connection attempt. 212 * 213 * Based on mClock.getElapsedSinceBootMillis(). 214 * 215 * This is for calculating the time to connect and the duration of the connection. 216 * Any negative value means we are not currently connected. 217 */ 218 public long tsConnectionAttemptStart = TS_NONE; 219 220 /** 221 * Timestamp captured when we find out about a firmware roam 222 */ 223 public long tsRoam = TS_NONE; 224 225 /** 226 * Becomes true the first time we see a poll with a valid RSSI in a connection 227 */ 228 public boolean polled = false; 229 230 /** 231 * Records validation success for the current connection. 232 * 233 * We want to gather statistics only on the first success. 234 */ 235 public boolean validatedThisConnectionAtLeastOnce = false; 236 237 /** 238 * A note to ourself that we are attempting a network switch 239 */ 240 public boolean attemptingSwitch = false; 241 242 /** 243 * SSID of currently connected or connecting network. Used during disconnection 244 */ 245 public String ssidCurr = ""; 246 /** 247 * SSID of previously connected network. Used during disconnection when connection attempt 248 * of current network is issued before the disconnection of previous network. 249 */ 250 public String ssidPrev = ""; 251 /** 252 * A flag that notes that current disconnection is not generated by wpa_supplicant 253 * which may indicate abnormal disconnection. 254 */ 255 public boolean nonlocalDisconnection = false; 256 public int disconnectionReason; 257 258 public long firmwareAlertTimeMs = TS_NONE; 259 } 260 261 /** 262 * String key: iface name 263 * IfaceInfo value: current status of iface 264 */ 265 private final Map<String, IfaceInfo> mIfaceToInfoMap = new ArrayMap<>(); 266 267 /** Gets the IfaceInfo, or create it if it doesn't exist. */ getIfaceInfo(String ifaceName)268 private IfaceInfo getIfaceInfo(String ifaceName) { 269 return mIfaceToInfoMap.computeIfAbsent(ifaceName, k -> new IfaceInfo()); 270 } 271 272 /** 273 * @param clock is the time source 274 * @param l2KeySeed is for making our L2Keys usable only on this device 275 */ WifiScoreCard(Clock clock, String l2KeySeed, DeviceConfigFacade deviceConfigFacade, Context context, WifiGlobals wifiGlobals)276 public WifiScoreCard(Clock clock, String l2KeySeed, DeviceConfigFacade deviceConfigFacade, 277 Context context, WifiGlobals wifiGlobals) { 278 mClock = clock; 279 mContext = context; 280 mL2KeySeed = l2KeySeed; 281 mPlaceholderPerBssid = new PerBssid("", MacAddress.fromString(DEFAULT_MAC_ADDRESS)); 282 mPlaceholderPerNetwork = new PerNetwork(""); 283 mDeviceConfigFacade = deviceConfigFacade; 284 mWifiGlobals = wifiGlobals; 285 } 286 287 /** 288 * Gets the L2Key and GroupHint associated with the connection. 289 */ getL2KeyAndGroupHint(ExtendedWifiInfo wifiInfo)290 public @NonNull Pair<String, String> getL2KeyAndGroupHint(ExtendedWifiInfo wifiInfo) { 291 PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID()); 292 if (perBssid == mPlaceholderPerBssid) { 293 return new Pair<>(null, null); 294 } 295 return new Pair<>(perBssid.getL2Key(), groupHintFromSsid(perBssid.ssid)); 296 } 297 298 /** 299 * Computes the GroupHint associated with the given ssid. 300 */ groupHintFromSsid(String ssid)301 public @NonNull String groupHintFromSsid(String ssid) { 302 final long groupIdHash = computeHashLong(ssid, mPlaceholderPerBssid.bssid, mL2KeySeed); 303 return groupHintFromLong(groupIdHash); 304 } 305 306 /** Handle network disconnection. */ resetConnectionState(String ifaceName)307 public void resetConnectionState(String ifaceName) { 308 IfaceInfo ifaceInfo = getIfaceInfo(ifaceName); 309 noteDisconnectionForIface(ifaceInfo); 310 resetConnectionStateForIfaceInternal(ifaceInfo, true); 311 } 312 313 /** Handle shutdown event. */ resetAllConnectionStates()314 public void resetAllConnectionStates() { 315 for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) { 316 noteDisconnectionForIface(ifaceInfo); 317 resetConnectionStateForIfaceInternal(ifaceInfo, true); 318 } 319 } 320 noteDisconnectionForIface(IfaceInfo ifaceInfo)321 private void noteDisconnectionForIface(IfaceInfo ifaceInfo) { 322 String ssidDisconnected = ifaceInfo.attemptingSwitch 323 ? ifaceInfo.ssidPrev : ifaceInfo.ssidCurr; 324 updatePerNetwork(Event.DISCONNECTION, ssidDisconnected, INVALID_RSSI, LINK_SPEED_UNKNOWN, 325 UNKNOWN_REASON, ifaceInfo); 326 if (mVerboseLoggingEnabled && ifaceInfo.tsConnectionAttemptStart > TS_NONE 327 && !ifaceInfo.attemptingSwitch) { 328 Log.v(TAG, "handleNetworkDisconnect", new Exception()); 329 } 330 } 331 resetAllConnectionStatesInternal()332 private void resetAllConnectionStatesInternal() { 333 for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) { 334 resetConnectionStateForIfaceInternal(ifaceInfo, false); 335 } 336 } 337 338 /** 339 * @param calledFromResetConnectionState says the call is from outside the class, 340 * indicating that we need to respect the value of mAttemptingSwitch. 341 */ resetConnectionStateForIfaceInternal(IfaceInfo ifaceInfo, boolean calledFromResetConnectionState)342 private void resetConnectionStateForIfaceInternal(IfaceInfo ifaceInfo, 343 boolean calledFromResetConnectionState) { 344 if (!calledFromResetConnectionState) { 345 ifaceInfo.attemptingSwitch = false; 346 } 347 if (!ifaceInfo.attemptingSwitch) { 348 ifaceInfo.tsConnectionAttemptStart = TS_NONE; 349 } 350 ifaceInfo.tsRoam = TS_NONE; 351 ifaceInfo.polled = false; 352 ifaceInfo.validatedThisConnectionAtLeastOnce = false; 353 ifaceInfo.nonlocalDisconnection = false; 354 ifaceInfo.firmwareAlertTimeMs = TS_NONE; 355 } 356 357 /** 358 * Updates perBssid using relevant parts of WifiInfo 359 * 360 * @param wifiInfo object holding relevant values. 361 */ updatePerBssid(WifiScoreCardProto.Event event, ExtendedWifiInfo wifiInfo)362 private void updatePerBssid(WifiScoreCardProto.Event event, ExtendedWifiInfo wifiInfo) { 363 PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID()); 364 perBssid.updateEventStats(event, 365 wifiInfo.getFrequency(), 366 wifiInfo.getRssi(), 367 wifiInfo.getLinkSpeed(), 368 wifiInfo.getIfaceName()); 369 perBssid.setNetworkConfigId(wifiInfo.getNetworkId()); 370 logd("BSSID update " + event + " ID: " + perBssid.id + " " + wifiInfo); 371 } 372 373 /** 374 * Updates perNetwork with SSID, current RSSI and failureReason. failureReason is meaningful 375 * only during connection failure. 376 */ updatePerNetwork(WifiScoreCardProto.Event event, String ssid, int rssi, int txSpeed, int failureReason, IfaceInfo ifaceInfo)377 private void updatePerNetwork(WifiScoreCardProto.Event event, String ssid, int rssi, 378 int txSpeed, int failureReason, IfaceInfo ifaceInfo) { 379 PerNetwork perNetwork = lookupNetwork(ssid); 380 logd("network update " + event + ((ssid == null) ? " " : " " 381 + ssid) + " ID: " + perNetwork.id + " RSSI " + rssi + " txSpeed " + txSpeed); 382 perNetwork.updateEventStats(event, rssi, txSpeed, failureReason, ifaceInfo); 383 } 384 385 /** 386 * Updates the score card after a signal poll 387 * 388 * @param wifiInfo object holding relevant values 389 */ noteSignalPoll(@onNull ExtendedWifiInfo wifiInfo)390 public void noteSignalPoll(@NonNull ExtendedWifiInfo wifiInfo) { 391 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 392 if (!ifaceInfo.polled && wifiInfo.getRssi() != INVALID_RSSI) { 393 updatePerBssid(Event.FIRST_POLL_AFTER_CONNECTION, wifiInfo); 394 ifaceInfo.polled = true; 395 } 396 updatePerBssid(Event.SIGNAL_POLL, wifiInfo); 397 int validTxSpeed = geTxLinkSpeedWithSufficientTxRate(wifiInfo); 398 updatePerNetwork(Event.SIGNAL_POLL, wifiInfo.getSSID(), wifiInfo.getRssi(), 399 validTxSpeed, UNKNOWN_REASON, ifaceInfo); 400 if (ifaceInfo.tsRoam > TS_NONE && wifiInfo.getRssi() != INVALID_RSSI) { 401 long duration = mClock.getElapsedSinceBootMillis() - ifaceInfo.tsRoam; 402 if (duration >= SUCCESS_MILLIS_SINCE_ROAM) { 403 updatePerBssid(Event.ROAM_SUCCESS, wifiInfo); 404 ifaceInfo.tsRoam = TS_NONE; 405 doWritesBssid(); 406 } 407 } 408 } 409 geTxLinkSpeedWithSufficientTxRate(@onNull ExtendedWifiInfo wifiInfo)410 private int geTxLinkSpeedWithSufficientTxRate(@NonNull ExtendedWifiInfo wifiInfo) { 411 int txRate = (int) Math.ceil(wifiInfo.getSuccessfulTxPacketsPerSecond() 412 + wifiInfo.getLostTxPacketsPerSecond() 413 + wifiInfo.getRetriedTxPacketsPerSecond()); 414 int txSpeed = wifiInfo.getTxLinkSpeedMbps(); 415 logd("txRate: " + txRate + " txSpeed: " + txSpeed); 416 return (txRate >= HEALTH_MONITOR_MIN_TX_PACKET_PER_SEC) ? txSpeed : LINK_SPEED_UNKNOWN; 417 } 418 419 /** Wait a few seconds before considering the roam successful */ 420 private static final long SUCCESS_MILLIS_SINCE_ROAM = 4_000; 421 422 /** 423 * Updates the score card after IP configuration 424 * 425 * @param wifiInfo object holding relevant values 426 */ noteIpConfiguration(@onNull ExtendedWifiInfo wifiInfo)427 public void noteIpConfiguration(@NonNull ExtendedWifiInfo wifiInfo) { 428 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 429 updatePerBssid(Event.IP_CONFIGURATION_SUCCESS, wifiInfo); 430 updatePerNetwork(Event.IP_CONFIGURATION_SUCCESS, wifiInfo.getSSID(), wifiInfo.getRssi(), 431 wifiInfo.getTxLinkSpeedMbps(), UNKNOWN_REASON, ifaceInfo); 432 PerNetwork perNetwork = lookupNetwork(wifiInfo.getSSID()); 433 perNetwork.initBandwidthFilter(wifiInfo); 434 ifaceInfo.attemptingSwitch = false; 435 doWrites(); 436 } 437 438 /** 439 * Updates the score card after network validation success. 440 * 441 * @param wifiInfo object holding relevant values 442 */ noteValidationSuccess(@onNull ExtendedWifiInfo wifiInfo)443 public void noteValidationSuccess(@NonNull ExtendedWifiInfo wifiInfo) { 444 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 445 if (ifaceInfo.validatedThisConnectionAtLeastOnce) return; // Only once per connection 446 updatePerBssid(Event.VALIDATION_SUCCESS, wifiInfo); 447 ifaceInfo.validatedThisConnectionAtLeastOnce = true; 448 doWrites(); 449 } 450 451 /** 452 * Updates the score card after network validation failure 453 * 454 * @param wifiInfo object holding relevant values 455 */ noteValidationFailure(@onNull ExtendedWifiInfo wifiInfo)456 public void noteValidationFailure(@NonNull ExtendedWifiInfo wifiInfo) { 457 // VALIDATION_FAILURE is not currently recorded. 458 } 459 460 /** 461 * Records the start of a connection attempt 462 * 463 * @param wifiInfo may have state about an existing connection 464 * @param scanRssi is the highest RSSI of recent scan found from scanDetailCache 465 * @param ssid is the network SSID of connection attempt 466 */ noteConnectionAttempt(@onNull ExtendedWifiInfo wifiInfo, int scanRssi, String ssid)467 public void noteConnectionAttempt(@NonNull ExtendedWifiInfo wifiInfo, 468 int scanRssi, String ssid) { 469 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 470 // We may or may not be currently connected. If not, simply record the start. 471 // But if we are connected, wrap up the old one first. 472 if (ifaceInfo.tsConnectionAttemptStart > TS_NONE) { 473 if (ifaceInfo.polled) { 474 updatePerBssid(Event.LAST_POLL_BEFORE_SWITCH, wifiInfo); 475 } 476 ifaceInfo.attemptingSwitch = true; 477 } 478 ifaceInfo.tsConnectionAttemptStart = mClock.getElapsedSinceBootMillis(); 479 ifaceInfo.polled = false; 480 ifaceInfo.ssidPrev = ifaceInfo.ssidCurr; 481 ifaceInfo.ssidCurr = ssid; 482 ifaceInfo.firmwareAlertTimeMs = TS_NONE; 483 484 updatePerNetwork(Event.CONNECTION_ATTEMPT, ssid, scanRssi, LINK_SPEED_UNKNOWN, 485 UNKNOWN_REASON, ifaceInfo); 486 logd("CONNECTION_ATTEMPT" + (ifaceInfo.attemptingSwitch ? " X " : " ") + wifiInfo); 487 } 488 489 /** 490 * Records a newly assigned NetworkAgent netId. 491 */ noteNetworkAgentCreated(@onNull ExtendedWifiInfo wifiInfo, int networkAgentId)492 public void noteNetworkAgentCreated(@NonNull ExtendedWifiInfo wifiInfo, int networkAgentId) { 493 PerBssid perBssid = lookupBssid(wifiInfo.getSSID(), wifiInfo.getBSSID()); 494 logd("NETWORK_AGENT_ID: " + networkAgentId + " ID: " + perBssid.id); 495 perBssid.mNetworkAgentId = networkAgentId; 496 } 497 498 /** 499 * Record disconnection not initiated by wpa_supplicant in connected mode 500 * @param reason is detailed disconnection reason code 501 */ noteNonlocalDisconnect(String ifaceName, int reason)502 public void noteNonlocalDisconnect(String ifaceName, int reason) { 503 IfaceInfo ifaceInfo = getIfaceInfo(ifaceName); 504 505 ifaceInfo.nonlocalDisconnection = true; 506 ifaceInfo.disconnectionReason = reason; 507 logd("nonlocal disconnection with reason: " + reason); 508 } 509 510 /** 511 * Record firmware alert timestamp and error code 512 */ noteFirmwareAlert(int errorCode)513 public void noteFirmwareAlert(int errorCode) { 514 long ts = mClock.getElapsedSinceBootMillis(); 515 // Firmware alert is device-level, not per-iface. Thus, note firmware alert on all ifaces. 516 for (IfaceInfo ifaceInfo : mIfaceToInfoMap.values()) { 517 ifaceInfo.firmwareAlertTimeMs = ts; 518 } 519 logd("firmware alert with error code: " + errorCode); 520 } 521 522 /** 523 * Updates the score card after a failed connection attempt 524 * 525 * @param wifiInfo object holding relevant values. 526 * @param scanRssi is the highest RSSI of recent scan found from scanDetailCache 527 * @param ssid is the network SSID. 528 * @param failureReason is connection failure reason 529 */ noteConnectionFailure(@onNull ExtendedWifiInfo wifiInfo, int scanRssi, String ssid, @FailureReason int failureReason)530 public void noteConnectionFailure(@NonNull ExtendedWifiInfo wifiInfo, 531 int scanRssi, String ssid, @FailureReason int failureReason) { 532 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 533 // TODO: add the breakdown of level2FailureReason 534 updatePerBssid(Event.CONNECTION_FAILURE, wifiInfo); 535 updatePerNetwork(Event.CONNECTION_FAILURE, ssid, scanRssi, LINK_SPEED_UNKNOWN, 536 failureReason, ifaceInfo); 537 resetConnectionStateForIfaceInternal(ifaceInfo, false); 538 } 539 540 /** 541 * Updates the score card after network reachability failure 542 * 543 * @param wifiInfo object holding relevant values 544 */ noteIpReachabilityLost(@onNull ExtendedWifiInfo wifiInfo)545 public void noteIpReachabilityLost(@NonNull ExtendedWifiInfo wifiInfo) { 546 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 547 if (ifaceInfo.tsRoam > TS_NONE) { 548 ifaceInfo.tsConnectionAttemptStart = ifaceInfo.tsRoam; // just to update elapsed 549 updatePerBssid(Event.ROAM_FAILURE, wifiInfo); 550 } else { 551 updatePerBssid(Event.IP_REACHABILITY_LOST, wifiInfo); 552 } 553 // No need to call resetConnectionStateInternal() because 554 // resetConnectionState() will be called after WifiNative.disconnect() in ClientModeImpl 555 doWrites(); 556 } 557 558 /** 559 * Updates the score card before a roam 560 * 561 * We may have already done a firmware roam, but wifiInfo has not yet 562 * been updated, so we still have the old state. 563 * 564 * @param wifiInfo object holding relevant values 565 */ noteRoam(IfaceInfo ifaceInfo, @NonNull ExtendedWifiInfo wifiInfo)566 private void noteRoam(IfaceInfo ifaceInfo, @NonNull ExtendedWifiInfo wifiInfo) { 567 updatePerBssid(Event.LAST_POLL_BEFORE_ROAM, wifiInfo); 568 ifaceInfo.tsRoam = mClock.getElapsedSinceBootMillis(); 569 } 570 571 /** 572 * Called when the supplicant state is about to change, before wifiInfo is updated 573 * 574 * @param wifiInfo object holding old values 575 * @param state the new supplicant state 576 */ noteSupplicantStateChanging(@onNull ExtendedWifiInfo wifiInfo, SupplicantState state)577 public void noteSupplicantStateChanging(@NonNull ExtendedWifiInfo wifiInfo, 578 SupplicantState state) { 579 IfaceInfo ifaceInfo = getIfaceInfo(wifiInfo.getIfaceName()); 580 if (state == SupplicantState.COMPLETED && wifiInfo.getSupplicantState() == state) { 581 // Our signal that a firmware roam has occurred 582 noteRoam(ifaceInfo, wifiInfo); 583 } 584 logd("Changing state to " + state + " " + wifiInfo); 585 } 586 587 /** 588 * Called after the supplicant state changed 589 * 590 * @param wifiInfo object holding old values 591 */ noteSupplicantStateChanged(ExtendedWifiInfo wifiInfo)592 public void noteSupplicantStateChanged(ExtendedWifiInfo wifiInfo) { 593 logd("ifaceName=" + wifiInfo.getIfaceName() + ",wifiInfo=" + wifiInfo); 594 } 595 596 /** 597 * Updates the score card when wifi is disabled 598 * 599 * @param wifiInfo object holding relevant values 600 */ noteWifiDisabled(@onNull ExtendedWifiInfo wifiInfo)601 public void noteWifiDisabled(@NonNull ExtendedWifiInfo wifiInfo) { 602 updatePerBssid(Event.WIFI_DISABLED, wifiInfo); 603 } 604 605 /** 606 * Records the last successful L2 connection timestamp for a BSSID. 607 * @return the previous BSSID connection time. 608 */ setBssidConnectionTimestampMs(String ssid, String bssid, long timeMs)609 public long setBssidConnectionTimestampMs(String ssid, String bssid, long timeMs) { 610 PerBssid perBssid = lookupBssid(ssid, bssid); 611 long prev = perBssid.lastConnectionTimestampMs; 612 perBssid.lastConnectionTimestampMs = timeMs; 613 return prev; 614 } 615 616 /** 617 * Returns the last successful L2 connection time for this BSSID. 618 */ getBssidConnectionTimestampMs(String ssid, String bssid)619 public long getBssidConnectionTimestampMs(String ssid, String bssid) { 620 return lookupBssid(ssid, bssid).lastConnectionTimestampMs; 621 } 622 623 /** 624 * Increment the blocklist streak count for a failure reason on an AP. 625 * @return the updated count 626 */ incrementBssidBlocklistStreak(String ssid, String bssid, @WifiBlocklistMonitor.FailureReason int reason)627 public int incrementBssidBlocklistStreak(String ssid, String bssid, 628 @WifiBlocklistMonitor.FailureReason int reason) { 629 PerBssid perBssid = lookupBssid(ssid, bssid); 630 return ++perBssid.blocklistStreakCount[reason]; 631 } 632 633 /** 634 * Get the blocklist streak count for a failure reason on an AP. 635 * @return the blocklist streak count 636 */ getBssidBlocklistStreak(String ssid, String bssid, @WifiBlocklistMonitor.FailureReason int reason)637 public int getBssidBlocklistStreak(String ssid, String bssid, 638 @WifiBlocklistMonitor.FailureReason int reason) { 639 return lookupBssid(ssid, bssid).blocklistStreakCount[reason]; 640 } 641 642 /** 643 * Clear the blocklist streak count for a failure reason on an AP. 644 */ resetBssidBlocklistStreak(String ssid, String bssid, @WifiBlocklistMonitor.FailureReason int reason)645 public void resetBssidBlocklistStreak(String ssid, String bssid, 646 @WifiBlocklistMonitor.FailureReason int reason) { 647 lookupBssid(ssid, bssid).blocklistStreakCount[reason] = 0; 648 } 649 650 /** 651 * Clear the blocklist streak count for all APs that belong to this SSID. 652 */ resetBssidBlocklistStreakForSsid(@onNull String ssid)653 public void resetBssidBlocklistStreakForSsid(@NonNull String ssid) { 654 Iterator<Map.Entry<MacAddress, PerBssid>> it = mApForBssid.entrySet().iterator(); 655 while (it.hasNext()) { 656 PerBssid perBssid = it.next().getValue(); 657 if (!ssid.equals(perBssid.ssid)) { 658 continue; 659 } 660 for (int i = 0; i < perBssid.blocklistStreakCount.length; i++) { 661 perBssid.blocklistStreakCount[i] = 0; 662 } 663 } 664 } 665 666 /** 667 * Detect abnormal disconnection at high RSSI with a high rate 668 */ detectAbnormalDisconnection(String ifaceName)669 public int detectAbnormalDisconnection(String ifaceName) { 670 IfaceInfo ifaceInfo = getIfaceInfo(ifaceName); 671 String ssid = ifaceInfo.attemptingSwitch ? ifaceInfo.ssidPrev : ifaceInfo.ssidCurr; 672 PerNetwork perNetwork = lookupNetwork(ssid); 673 NetworkConnectionStats recentStats = perNetwork.getRecentStats(); 674 if (recentStats.getRecentCountCode() == CNT_SHORT_CONNECTION_NONLOCAL) { 675 return detectAbnormalFailureReason(recentStats, CNT_SHORT_CONNECTION_NONLOCAL, 676 REASON_SHORT_CONNECTION_NONLOCAL, 677 mDeviceConfigFacade.getShortConnectionNonlocalHighThrPercent(), 678 mDeviceConfigFacade.getShortConnectionNonlocalCountMin(), 679 CNT_DISCONNECTION); 680 } else if (recentStats.getRecentCountCode() == CNT_DISCONNECTION_NONLOCAL) { 681 return detectAbnormalFailureReason(recentStats, CNT_DISCONNECTION_NONLOCAL, 682 REASON_DISCONNECTION_NONLOCAL, 683 mDeviceConfigFacade.getDisconnectionNonlocalHighThrPercent(), 684 mDeviceConfigFacade.getDisconnectionNonlocalCountMin(), 685 CNT_DISCONNECTION); 686 } else { 687 return REASON_NO_FAILURE; 688 } 689 } 690 691 /** 692 * Detect abnormal connection failure at high RSSI with a high rate 693 */ detectAbnormalConnectionFailure(String ssid)694 public int detectAbnormalConnectionFailure(String ssid) { 695 PerNetwork perNetwork = lookupNetwork(ssid); 696 NetworkConnectionStats recentStats = perNetwork.getRecentStats(); 697 int recentCountCode = recentStats.getRecentCountCode(); 698 if (recentCountCode == CNT_AUTHENTICATION_FAILURE) { 699 return detectAbnormalFailureReason(recentStats, CNT_AUTHENTICATION_FAILURE, 700 REASON_AUTH_FAILURE, 701 mDeviceConfigFacade.getAuthFailureHighThrPercent(), 702 mDeviceConfigFacade.getAuthFailureCountMin(), 703 CNT_CONNECTION_ATTEMPT); 704 } else if (recentCountCode == CNT_ASSOCIATION_REJECTION) { 705 return detectAbnormalFailureReason(recentStats, CNT_ASSOCIATION_REJECTION, 706 REASON_ASSOC_REJECTION, 707 mDeviceConfigFacade.getAssocRejectionHighThrPercent(), 708 mDeviceConfigFacade.getAssocRejectionCountMin(), 709 CNT_CONNECTION_ATTEMPT); 710 } else if (recentCountCode == CNT_ASSOCIATION_TIMEOUT) { 711 return detectAbnormalFailureReason(recentStats, CNT_ASSOCIATION_TIMEOUT, 712 REASON_ASSOC_TIMEOUT, 713 mDeviceConfigFacade.getAssocTimeoutHighThrPercent(), 714 mDeviceConfigFacade.getAssocTimeoutCountMin(), 715 CNT_CONNECTION_ATTEMPT); 716 } else if (recentCountCode == CNT_DISCONNECTION_NONLOCAL_CONNECTING) { 717 return detectAbnormalFailureReason(recentStats, CNT_DISCONNECTION_NONLOCAL_CONNECTING, 718 REASON_CONNECTION_FAILURE_DISCONNECTION, 719 mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent(), 720 mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(), 721 CNT_CONNECTION_ATTEMPT); 722 } else if (recentCountCode == CNT_CONNECTION_FAILURE) { 723 return detectAbnormalFailureReason(recentStats, CNT_CONNECTION_FAILURE, 724 REASON_CONNECTION_FAILURE, 725 mDeviceConfigFacade.getConnectionFailureHighThrPercent(), 726 mDeviceConfigFacade.getConnectionFailureCountMin(), 727 CNT_CONNECTION_ATTEMPT); 728 } else { 729 return REASON_NO_FAILURE; 730 } 731 } 732 detectAbnormalFailureReason(NetworkConnectionStats stats, int countCode, int reasonCode, int highThresholdPercent, int minCount, int refCountCode)733 private int detectAbnormalFailureReason(NetworkConnectionStats stats, int countCode, 734 int reasonCode, int highThresholdPercent, int minCount, int refCountCode) { 735 // To detect abnormal failure which may trigger bugReport, 736 // increase the detection threshold by thresholdRatio 737 int thresholdRatio = 738 mDeviceConfigFacade.getBugReportThresholdExtraRatio(); 739 if (isHighPercentageAndEnoughCount(stats, countCode, reasonCode, 740 highThresholdPercent * thresholdRatio, 741 minCount * thresholdRatio, 742 refCountCode)) { 743 return reasonCode; 744 } else { 745 return REASON_NO_FAILURE; 746 } 747 } 748 isHighPercentageAndEnoughCount(NetworkConnectionStats stats, int countCode, int reasonCode, int highThresholdPercent, int minCount, int refCountCode)749 private boolean isHighPercentageAndEnoughCount(NetworkConnectionStats stats, int countCode, 750 int reasonCode, int highThresholdPercent, int minCount, int refCountCode) { 751 highThresholdPercent = Math.min(highThresholdPercent, 100); 752 // Use Laplace's rule of succession, useful especially for a small 753 // connection attempt count 754 // R = (f+1)/(n+2) with a pseudo count of 2 (one for f and one for s) 755 return ((stats.getCount(countCode) >= minCount) 756 && ((stats.getCount(countCode) + 1) * 100) 757 >= (highThresholdPercent * (stats.getCount(refCountCode) + 2))); 758 } 759 760 final class PerBssid extends MemoryStoreAccessBase { 761 public int id; 762 public final String ssid; 763 public final MacAddress bssid; 764 public final int[] blocklistStreakCount = 765 new int[WifiBlocklistMonitor.NUMBER_REASON_CODES]; 766 public long[][][] bandwidthStatsValue = 767 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 768 public int[][][] bandwidthStatsCount = 769 new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 770 // The wall clock time in milliseconds for the last successful l2 connection. 771 public long lastConnectionTimestampMs; 772 public boolean changed; 773 public boolean referenced; 774 775 private SecurityType mSecurityType = null; 776 private int mNetworkAgentId = Integer.MIN_VALUE; 777 private int mNetworkConfigId = Integer.MIN_VALUE; 778 private final Map<Pair<Event, Integer>, PerSignal> 779 mSignalForEventAndFrequency = new ArrayMap<>(); 780 PerBssid(String ssid, MacAddress bssid)781 PerBssid(String ssid, MacAddress bssid) { 782 super(computeHashLong(ssid, bssid, mL2KeySeed)); 783 this.ssid = ssid; 784 this.bssid = bssid; 785 this.id = idFromLong(); 786 this.changed = false; 787 this.referenced = false; 788 } updateEventStats(Event event, int frequency, int rssi, int linkspeed, String ifaceName)789 void updateEventStats(Event event, int frequency, int rssi, int linkspeed, 790 String ifaceName) { 791 PerSignal perSignal = lookupSignal(event, frequency); 792 if (rssi != INVALID_RSSI) { 793 perSignal.rssi.update(rssi); 794 changed = true; 795 } 796 if (linkspeed > 0) { 797 perSignal.linkspeed.update(linkspeed); 798 changed = true; 799 } 800 IfaceInfo ifaceInfo = getIfaceInfo(ifaceName); 801 if (perSignal.elapsedMs != null && ifaceInfo.tsConnectionAttemptStart > TS_NONE) { 802 long millis = 803 mClock.getElapsedSinceBootMillis() - ifaceInfo.tsConnectionAttemptStart; 804 if (millis >= 0) { 805 perSignal.elapsedMs.update(millis); 806 changed = true; 807 } 808 } 809 } lookupSignal(Event event, int frequency)810 PerSignal lookupSignal(Event event, int frequency) { 811 finishPendingRead(); 812 Pair<Event, Integer> key = new Pair<>(event, frequency); 813 PerSignal ans = mSignalForEventAndFrequency.get(key); 814 if (ans == null) { 815 ans = new PerSignal(event, frequency); 816 mSignalForEventAndFrequency.put(key, ans); 817 } 818 return ans; 819 } getSecurityType()820 SecurityType getSecurityType() { 821 finishPendingRead(); 822 return mSecurityType; 823 } setSecurityType(SecurityType securityType)824 void setSecurityType(SecurityType securityType) { 825 finishPendingRead(); 826 if (!Objects.equals(securityType, mSecurityType)) { 827 mSecurityType = securityType; 828 changed = true; 829 } 830 } setNetworkConfigId(int networkConfigId)831 void setNetworkConfigId(int networkConfigId) { 832 // Not serialized, so don't need to set changed, etc. 833 if (networkConfigId >= 0) { 834 mNetworkConfigId = networkConfigId; 835 } 836 } toAccessPoint()837 AccessPoint toAccessPoint() { 838 return toAccessPoint(false); 839 } toAccessPoint(boolean obfuscate)840 AccessPoint toAccessPoint(boolean obfuscate) { 841 finishPendingRead(); 842 AccessPoint.Builder builder = AccessPoint.newBuilder(); 843 builder.setId(id); 844 if (!obfuscate) { 845 builder.setBssid(ByteString.copyFrom(bssid.toByteArray())); 846 } 847 if (mSecurityType != null) { 848 builder.setSecurityType(mSecurityType); 849 } 850 for (PerSignal sig: mSignalForEventAndFrequency.values()) { 851 builder.addEventStats(sig.toSignal()); 852 } 853 builder.setBandwidthStatsAll(toBandwidthStatsAll( 854 bandwidthStatsValue, bandwidthStatsCount)); 855 return builder.build(); 856 } merge(AccessPoint ap)857 PerBssid merge(AccessPoint ap) { 858 if (ap.hasId() && this.id != ap.getId()) { 859 return this; 860 } 861 if (ap.hasSecurityType()) { 862 SecurityType prev = ap.getSecurityType(); 863 if (mSecurityType == null) { 864 mSecurityType = prev; 865 } else if (!mSecurityType.equals(prev)) { 866 if (mVerboseLoggingEnabled) { 867 Log.i(TAG, "ID: " + id 868 + "SecurityType changed: " + prev + " to " + mSecurityType); 869 } 870 changed = true; 871 } 872 } 873 for (Signal signal: ap.getEventStatsList()) { 874 Pair<Event, Integer> key = new Pair<>(signal.getEvent(), signal.getFrequency()); 875 PerSignal perSignal = mSignalForEventAndFrequency.get(key); 876 if (perSignal == null) { 877 mSignalForEventAndFrequency.put(key, 878 new PerSignal(key.first, key.second).merge(signal)); 879 // No need to set changed for this, since we are in sync with what's stored 880 } else { 881 perSignal.merge(signal); 882 changed = true; 883 } 884 } 885 if (ap.hasBandwidthStatsAll()) { 886 mergeBandwidthStatsAll(ap.getBandwidthStatsAll(), 887 bandwidthStatsValue, bandwidthStatsCount); 888 } 889 return this; 890 } 891 892 /** 893 * Handles (when convenient) the arrival of previously stored data. 894 * 895 * The response from IpMemoryStore arrives on a different thread, so we 896 * defer handling it until here, when we're on our favorite thread and 897 * in a good position to deal with it. We may have already collected some 898 * data before now, so we need to be prepared to merge the new and old together. 899 */ finishPendingRead()900 void finishPendingRead() { 901 final byte[] serialized = finishPendingReadBytes(); 902 if (serialized == null) return; 903 AccessPoint ap; 904 try { 905 ap = AccessPoint.parseFrom(serialized); 906 } catch (InvalidProtocolBufferException e) { 907 Log.e(TAG, "Failed to deserialize", e); 908 return; 909 } 910 merge(ap); 911 } 912 913 /** 914 * Estimates the probability of getting internet access, based on the 915 * device experience. 916 * 917 * @return a probability, expressed as a percentage in the range 0 to 100 918 */ estimatePercentInternetAvailability()919 public int estimatePercentInternetAvailability() { 920 // Initialize counts accoring to Laplace's rule of succession 921 int trials = 2; 922 int successes = 1; 923 // Aggregate over all of the frequencies 924 for (PerSignal s : mSignalForEventAndFrequency.values()) { 925 switch (s.event) { 926 case IP_CONFIGURATION_SUCCESS: 927 if (s.elapsedMs != null) { 928 trials += s.elapsedMs.count; 929 } 930 break; 931 case VALIDATION_SUCCESS: 932 if (s.elapsedMs != null) { 933 successes += s.elapsedMs.count; 934 } 935 break; 936 default: 937 break; 938 } 939 } 940 // Note that because of roaming it is possible to count successes 941 // without corresponding trials. 942 return Math.min(Math.max(Math.round(successes * 100.0f / trials), 0), 100); 943 } 944 } 945 toBandwidthStatsAll(long[][][] values, int[][][] counts)946 private BandwidthStatsAll toBandwidthStatsAll(long[][][] values, int[][][] counts) { 947 BandwidthStatsAll.Builder builder = BandwidthStatsAll.newBuilder(); 948 builder.setStats2G(toBandwidthStatsAllLink(values[0], counts[0])); 949 builder.setStatsAbove2G(toBandwidthStatsAllLink(values[1], counts[1])); 950 return builder.build(); 951 } 952 toBandwidthStatsAllLink(long[][] values, int[][] counts)953 private BandwidthStatsAllLink toBandwidthStatsAllLink(long[][] values, int[][] counts) { 954 BandwidthStatsAllLink.Builder builder = BandwidthStatsAllLink.newBuilder(); 955 builder.setTx(toBandwidthStatsAllLevel(values[LINK_TX], counts[LINK_TX])); 956 builder.setRx(toBandwidthStatsAllLevel(values[LINK_RX], counts[LINK_RX])); 957 return builder.build(); 958 } 959 toBandwidthStatsAllLevel(long[] values, int[] counts)960 private BandwidthStatsAllLevel toBandwidthStatsAllLevel(long[] values, int[] counts) { 961 BandwidthStatsAllLevel.Builder builder = BandwidthStatsAllLevel.newBuilder(); 962 for (int i = 0; i < NUM_SIGNAL_LEVEL; i++) { 963 builder.addLevel(toBandwidthStats(values[i], counts[i])); 964 } 965 return builder.build(); 966 } 967 toBandwidthStats(long value, int count)968 private BandwidthStats toBandwidthStats(long value, int count) { 969 BandwidthStats.Builder builder = BandwidthStats.newBuilder(); 970 builder.setValue(value); 971 builder.setCount(count); 972 return builder.build(); 973 } 974 mergeBandwidthStatsAll(BandwidthStatsAll source, long[][][] values, int[][][] counts)975 private void mergeBandwidthStatsAll(BandwidthStatsAll source, 976 long[][][] values, int[][][] counts) { 977 if (source.hasStats2G()) { 978 mergeBandwidthStatsAllLink(source.getStats2G(), values[0], counts[0]); 979 } 980 if (source.hasStatsAbove2G()) { 981 mergeBandwidthStatsAllLink(source.getStatsAbove2G(), values[1], counts[1]); 982 } 983 } 984 mergeBandwidthStatsAllLink(BandwidthStatsAllLink source, long[][] values, int[][] counts)985 private void mergeBandwidthStatsAllLink(BandwidthStatsAllLink source, 986 long[][] values, int[][] counts) { 987 if (source.hasTx()) { 988 mergeBandwidthStatsAllLevel(source.getTx(), values[LINK_TX], counts[LINK_TX]); 989 } 990 if (source.hasRx()) { 991 mergeBandwidthStatsAllLevel(source.getRx(), values[LINK_RX], counts[LINK_RX]); 992 } 993 } 994 mergeBandwidthStatsAllLevel(BandwidthStatsAllLevel source, long[] values, int[] counts)995 private void mergeBandwidthStatsAllLevel(BandwidthStatsAllLevel source, 996 long[] values, int[] counts) { 997 int levelCnt = source.getLevelCount(); 998 for (int i = 0; i < levelCnt; i++) { 999 BandwidthStats stats = source.getLevel(i); 1000 if (stats.hasValue()) { 1001 values[i] += stats.getValue(); 1002 } 1003 if (stats.hasCount()) { 1004 counts[i] += stats.getCount(); 1005 } 1006 } 1007 } 1008 1009 // TODO: b/178641307 move the following parameters to config.xml 1010 // Array dimension : int [NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL] 1011 static final int[][][] LINK_BANDWIDTH_INIT_KBPS = 1012 {{{500, 2500, 10000, 12000, 12000}, {500, 2500, 10000, 30000, 30000}}, 1013 {{1500, 7500, 12000, 12000, 12000}, {1500, 7500, 30000, 60000, 60000}}}; 1014 // To be used in link bandwidth estimation, each TrafficStats poll sample needs to be above 1015 // the following values. Defined per signal level. 1016 // int [NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL] 1017 // Use the low Tx threshold because xDSL UL speed could be below 1Mbps. 1018 static final int[][] LINK_BANDWIDTH_BYTE_DELTA_THR_KBYTE = 1019 {{200, 300, 300, 300, 300}, {200, 500, 1000, 2000, 2000}}; 1020 // To be used in the long term avg, each count needs to be above the following value 1021 static final int BANDWIDTH_STATS_COUNT_THR = 5; 1022 private static final int TIME_CONSTANT_SMALL_SEC = 6; 1023 // If RSSI changes by more than the below value, update BW filter with small time constant 1024 private static final int RSSI_DELTA_THR_DB = 8; 1025 private static final int FILTER_SCALE = 128; 1026 // Force weight to 0 if the elapsed time is above LARGE_TIME_DECAY_RATIO * time constant 1027 private static final int LARGE_TIME_DECAY_RATIO = 4; 1028 // Used to derive byte count threshold from avg BW 1029 private static final int LOW_BW_TO_AVG_BW_RATIO_NUM = 6; 1030 private static final int LOW_BW_TO_AVG_BW_RATIO_DEN = 8; 1031 // For some high speed connections, heavy DL traffic could falsely trigger UL BW update due to 1032 // TCP ACK and the low Tx byte count threshold. To work around the issue, skip Tx BW update if 1033 // Rx Bytes / Tx Bytes > RX_OVER_TX_BYTE_RATIO_MAX (heavy DL and light UL traffic) 1034 private static final int RX_OVER_TX_BYTE_RATIO_MAX = 5; 1035 // radio on time below the following value is ignored. 1036 static final int RADIO_ON_TIME_MIN_MS = 200; 1037 static final int RADIO_ON_ELAPSED_TIME_DELTA_MAX_MS = 200; 1038 static final int NUM_SIGNAL_LEVEL = 5; 1039 static final int LINK_TX = 0; 1040 static final int LINK_RX = 1; 1041 private static final int NUM_LINK_BAND = 2; 1042 private static final int NUM_LINK_DIRECTION = 2; 1043 private static final long BW_UPDATE_TIME_RESET_MS = TIME_CONSTANT_SMALL_SEC * 1000 * -10; 1044 private static final int MAX_ERROR_PERCENT = 100 * 100; 1045 private static final int EXTRA_SAMPLE_BW_FILTERING = 2; 1046 /** 1047 * A class collecting the connection and link bandwidth stats of one network or SSID. 1048 */ 1049 final class PerNetwork extends MemoryStoreAccessBase { 1050 public int id; 1051 public final String ssid; 1052 public boolean changed; 1053 private int mLastRssiPoll = INVALID_RSSI; 1054 private int mLastTxSpeedPoll = LINK_SPEED_UNKNOWN; 1055 private long mLastRssiPollTimeMs = TS_NONE; 1056 private long mConnectionSessionStartTimeMs = TS_NONE; 1057 private NetworkConnectionStats mRecentStats; 1058 private NetworkConnectionStats mStatsCurrBuild; 1059 private NetworkConnectionStats mStatsPrevBuild; 1060 private LruList<Integer> mFrequencyList; 1061 // In memory keep frequency with timestamp last time available, the elapsed time since boot. 1062 private SparseLongArray mFreqTimestamp; 1063 private long mLastRxBytes; 1064 private long mLastTxBytes; 1065 private boolean mLastTrafficValid = true; 1066 private String mBssid = ""; 1067 private int mSignalLevel; // initialize to zero to handle possible race condition 1068 private int mBandIdx; // initialize to zero to handle possible race condition 1069 private int[] mByteDeltaAccThr = new int[NUM_LINK_DIRECTION]; 1070 private int[] mFilterKbps = new int[NUM_LINK_DIRECTION]; 1071 private int[] mBandwidthSampleKbps = new int[NUM_LINK_DIRECTION]; 1072 private int[] mAvgUsedKbps = new int[NUM_LINK_DIRECTION]; 1073 private int mBandwidthUpdateRssiDbm = -1; 1074 private int mBandwidthUpdateBandIdx = -1; 1075 private boolean[] mBandwidthSampleValid = new boolean[NUM_LINK_DIRECTION]; 1076 private long[] mBandwidthSampleValidTimeMs = new long[]{BW_UPDATE_TIME_RESET_MS, 1077 BW_UPDATE_TIME_RESET_MS}; 1078 long[][][] mBandwidthStatsValue = 1079 new long[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 1080 int[][][] mBandwidthStatsCount = 1081 new int[NUM_LINK_BAND][NUM_LINK_DIRECTION][NUM_SIGNAL_LEVEL]; 1082 PerNetwork(String ssid)1083 PerNetwork(String ssid) { 1084 super(computeHashLong(ssid, MacAddress.fromString(DEFAULT_MAC_ADDRESS), mL2KeySeed)); 1085 this.ssid = ssid; 1086 this.id = idFromLong(); 1087 this.changed = false; 1088 mRecentStats = new NetworkConnectionStats(); 1089 mStatsCurrBuild = new NetworkConnectionStats(); 1090 mStatsPrevBuild = new NetworkConnectionStats(); 1091 mFrequencyList = new LruList<>(MAX_FREQUENCIES_PER_SSID); 1092 mFreqTimestamp = new SparseLongArray(); 1093 } 1094 updateEventStats(Event event, int rssi, int txSpeed, int failureReason, IfaceInfo ifaceInfo)1095 void updateEventStats(Event event, int rssi, int txSpeed, int failureReason, 1096 IfaceInfo ifaceInfo) { 1097 finishPendingRead(); 1098 long currTimeMs = mClock.getElapsedSinceBootMillis(); 1099 switch (event) { 1100 case SIGNAL_POLL: 1101 mLastRssiPoll = rssi; 1102 mLastRssiPollTimeMs = currTimeMs; 1103 mLastTxSpeedPoll = txSpeed; 1104 changed = true; 1105 break; 1106 case CONNECTION_ATTEMPT: 1107 logd(" scan rssi: " + rssi); 1108 if (rssi >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm()) { 1109 mRecentStats.incrementCount(CNT_CONNECTION_ATTEMPT); 1110 } 1111 mConnectionSessionStartTimeMs = currTimeMs; 1112 changed = true; 1113 break; 1114 case CONNECTION_FAILURE: 1115 mConnectionSessionStartTimeMs = TS_NONE; 1116 if (rssi >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm()) { 1117 if (failureReason != WifiBlocklistMonitor.REASON_WRONG_PASSWORD) { 1118 mRecentStats.incrementCount(CNT_CONNECTION_FAILURE); 1119 mRecentStats.incrementCount(CNT_CONSECUTIVE_CONNECTION_FAILURE); 1120 } 1121 switch (failureReason) { 1122 case WifiBlocklistMonitor.REASON_ASSOCIATION_REJECTION: 1123 mRecentStats.incrementCount(CNT_ASSOCIATION_REJECTION); 1124 break; 1125 case WifiBlocklistMonitor.REASON_ASSOCIATION_TIMEOUT: 1126 mRecentStats.incrementCount(CNT_ASSOCIATION_TIMEOUT); 1127 break; 1128 case WifiBlocklistMonitor.REASON_AUTHENTICATION_FAILURE: 1129 case WifiBlocklistMonitor.REASON_EAP_FAILURE: 1130 mRecentStats.incrementCount(CNT_AUTHENTICATION_FAILURE); 1131 break; 1132 case WifiBlocklistMonitor.REASON_NONLOCAL_DISCONNECT_CONNECTING: 1133 mRecentStats.incrementCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING); 1134 break; 1135 case WifiBlocklistMonitor.REASON_WRONG_PASSWORD: 1136 mRecentStats.incrementCount(CNT_CONSECUTIVE_WRONG_PASSWORD_FAILURE); 1137 break; 1138 case WifiBlocklistMonitor.REASON_AP_UNABLE_TO_HANDLE_NEW_STA: 1139 case WifiBlocklistMonitor.REASON_DHCP_FAILURE: 1140 default: 1141 break; 1142 } 1143 } 1144 changed = true; 1145 break; 1146 case IP_CONFIGURATION_SUCCESS: 1147 // Reset CNT_CONSECUTIVE_CONNECTION_FAILURE since L3 is also connected 1148 mRecentStats.clearCount(CNT_CONSECUTIVE_CONNECTION_FAILURE); 1149 mRecentStats.clearCount(CNT_CONSECUTIVE_WRONG_PASSWORD_FAILURE); 1150 changed = true; 1151 logd(this.toString()); 1152 break; 1153 case WIFI_DISABLED: 1154 case DISCONNECTION: 1155 if (mConnectionSessionStartTimeMs <= TS_NONE) { 1156 return; 1157 } 1158 handleDisconnectionAfterConnection(ifaceInfo); 1159 mConnectionSessionStartTimeMs = TS_NONE; 1160 mLastRssiPollTimeMs = TS_NONE; 1161 mFilterKbps[LINK_TX] = 0; 1162 mFilterKbps[LINK_RX] = 0; 1163 mBandwidthUpdateRssiDbm = -1; 1164 mBandwidthUpdateBandIdx = -1; 1165 changed = true; 1166 break; 1167 default: 1168 break; 1169 } 1170 } 1171 @Override toString()1172 public String toString() { 1173 StringBuilder sb = new StringBuilder(); 1174 sb.append("SSID: ").append(ssid).append("\n"); 1175 if (mLastRssiPollTimeMs != TS_NONE) { 1176 sb.append(" LastRssiPollTime: "); 1177 sb.append(mLastRssiPollTimeMs); 1178 } 1179 sb.append(" LastRssiPoll: " + mLastRssiPoll); 1180 sb.append(" LastTxSpeedPoll: " + mLastTxSpeedPoll); 1181 sb.append("\n"); 1182 sb.append(" StatsRecent: ").append(mRecentStats).append("\n"); 1183 sb.append(" StatsCurr: ").append(mStatsCurrBuild).append("\n"); 1184 sb.append(" StatsPrev: ").append(mStatsPrevBuild); 1185 sb.append(" BandwidthStats:\n"); 1186 for (int i = 0; i < NUM_LINK_BAND; i++) { 1187 for (int j = 0; j < NUM_LINK_DIRECTION; j++) { 1188 sb.append(" avgKbps: "); 1189 for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) { 1190 int avgKbps = mBandwidthStatsCount[i][j][k] == 0 ? 0 : (int) 1191 (mBandwidthStatsValue[i][j][k] / mBandwidthStatsCount[i][j][k]); 1192 sb.append(" " + avgKbps); 1193 } 1194 sb.append("\n count: "); 1195 for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) { 1196 sb.append(" " + mBandwidthStatsCount[i][j][k]); 1197 } 1198 sb.append("\n"); 1199 } 1200 sb.append("\n"); 1201 } 1202 return sb.toString(); 1203 } 1204 handleDisconnectionAfterConnection(IfaceInfo ifaceInfo)1205 private void handleDisconnectionAfterConnection(IfaceInfo ifaceInfo) { 1206 long currTimeMs = mClock.getElapsedSinceBootMillis(); 1207 int currSessionDurationMs = (int) (currTimeMs - mConnectionSessionStartTimeMs); 1208 int currSessionDurationSec = currSessionDurationMs / 1000; 1209 mRecentStats.accumulate(CNT_CONNECTION_DURATION_SEC, currSessionDurationSec); 1210 long timeSinceLastRssiPollMs = currTimeMs - mLastRssiPollTimeMs; 1211 boolean hasRecentRssiPoll = mLastRssiPollTimeMs > TS_NONE 1212 && timeSinceLastRssiPollMs <= mDeviceConfigFacade 1213 .getHealthMonitorRssiPollValidTimeMs(); 1214 if (hasRecentRssiPoll) { 1215 mRecentStats.incrementCount(CNT_DISCONNECTION); 1216 } 1217 int fwAlertValidTimeMs = mDeviceConfigFacade.getHealthMonitorFwAlertValidTimeMs(); 1218 long timeSinceLastFirmAlert = currTimeMs - ifaceInfo.firmwareAlertTimeMs; 1219 boolean isInvalidFwAlertTime = ifaceInfo.firmwareAlertTimeMs == TS_NONE; 1220 boolean disableFwAlertCheck = fwAlertValidTimeMs == -1; 1221 boolean passFirmwareAlertCheck = disableFwAlertCheck ? true : (isInvalidFwAlertTime 1222 ? false : timeSinceLastFirmAlert < fwAlertValidTimeMs); 1223 boolean hasHighRssiOrHighTxSpeed = 1224 mLastRssiPoll >= mDeviceConfigFacade.getHealthMonitorMinRssiThrDbm() 1225 || mLastTxSpeedPoll >= HEALTH_MONITOR_COUNT_TX_SPEED_MIN_MBPS; 1226 if (ifaceInfo.nonlocalDisconnection && hasRecentRssiPoll 1227 && isAbnormalDisconnectionReason(ifaceInfo.disconnectionReason) 1228 && passFirmwareAlertCheck 1229 && hasHighRssiOrHighTxSpeed) { 1230 mRecentStats.incrementCount(CNT_DISCONNECTION_NONLOCAL); 1231 if (currSessionDurationMs <= mDeviceConfigFacade 1232 .getHealthMonitorShortConnectionDurationThrMs()) { 1233 mRecentStats.incrementCount(CNT_SHORT_CONNECTION_NONLOCAL); 1234 } 1235 } 1236 1237 } 1238 isAbnormalDisconnectionReason(int disconnectionReason)1239 private boolean isAbnormalDisconnectionReason(int disconnectionReason) { 1240 long mask = mDeviceConfigFacade.getAbnormalDisconnectionReasonCodeMask(); 1241 return disconnectionReason >= 0 && disconnectionReason <= 63 1242 && ((mask >> disconnectionReason) & 0x1) == 0x1; 1243 } 1244 getRecentStats()1245 @NonNull NetworkConnectionStats getRecentStats() { 1246 return mRecentStats; 1247 } getStatsCurrBuild()1248 @NonNull NetworkConnectionStats getStatsCurrBuild() { 1249 return mStatsCurrBuild; 1250 } getStatsPrevBuild()1251 @NonNull NetworkConnectionStats getStatsPrevBuild() { 1252 return mStatsPrevBuild; 1253 } 1254 1255 /** 1256 * Retrieve the list of frequencies seen for this network, with the most recent first. 1257 * @param ageInMills Max age to filter the channels. 1258 * @return a list of frequencies 1259 */ getFrequencies(Long ageInMills)1260 List<Integer> getFrequencies(Long ageInMills) { 1261 List<Integer> results = new ArrayList<>(); 1262 Long nowInMills = mClock.getElapsedSinceBootMillis(); 1263 for (Integer freq : mFrequencyList.getEntries()) { 1264 if (nowInMills - mFreqTimestamp.get(freq, 0L) > ageInMills) { 1265 continue; 1266 } 1267 results.add(freq); 1268 } 1269 return results; 1270 } 1271 1272 /** 1273 * Add a frequency to the list of frequencies for this network. 1274 * Will evict the least recently added frequency if the cache is full. 1275 */ addFrequency(int frequency)1276 void addFrequency(int frequency) { 1277 mFrequencyList.add(frequency); 1278 mFreqTimestamp.put(frequency, mClock.getElapsedSinceBootMillis()); 1279 } 1280 1281 /** 1282 * Update link bandwidth estimates based on TrafficStats byte counts and radio on time 1283 */ updateLinkBandwidth(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, ExtendedWifiInfo wifiInfo, long txBytes, long rxBytes)1284 void updateLinkBandwidth(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, 1285 ExtendedWifiInfo wifiInfo, long txBytes, long rxBytes) { 1286 mBandwidthSampleValid[LINK_TX] = false; 1287 mBandwidthSampleValid[LINK_RX] = false; 1288 // Sometimes TrafficStats byte counts return invalid values 1289 // Ignore next two polls if it happens 1290 boolean trafficValid = txBytes >= mLastTxBytes && rxBytes >= mLastRxBytes; 1291 if (!mLastTrafficValid || !trafficValid) { 1292 mLastTrafficValid = trafficValid; 1293 logv("invalid traffic count tx " + txBytes + " last " + mLastTxBytes 1294 + " rx " + rxBytes + " last " + mLastRxBytes); 1295 mLastTxBytes = txBytes; 1296 mLastRxBytes = rxBytes; 1297 return; 1298 } 1299 1300 updateWifiInfo(wifiInfo); 1301 updateLinkBandwidthTxRxSample(oldStats, newStats, wifiInfo, txBytes, rxBytes); 1302 mLastTxBytes = txBytes; 1303 mLastRxBytes = rxBytes; 1304 1305 updateBandwidthWithFilterApplied(LINK_TX, wifiInfo); 1306 updateBandwidthWithFilterApplied(LINK_RX, wifiInfo); 1307 mBandwidthUpdateRssiDbm = wifiInfo.getRssi(); 1308 mBandwidthUpdateBandIdx = mBandIdx; 1309 } 1310 updateWifiInfo(ExtendedWifiInfo wifiInfo)1311 void updateWifiInfo(ExtendedWifiInfo wifiInfo) { 1312 int rssi = wifiInfo.getRssi(); 1313 mSignalLevel = RssiUtil.calculateSignalLevel(mContext, rssi); 1314 mSignalLevel = Math.min(mSignalLevel, NUM_SIGNAL_LEVEL - 1); 1315 mBandIdx = getBandIdx(wifiInfo); 1316 mBssid = wifiInfo.getBSSID(); 1317 mByteDeltaAccThr[LINK_TX] = getByteDeltaAccThr(LINK_TX); 1318 mByteDeltaAccThr[LINK_RX] = getByteDeltaAccThr(LINK_RX); 1319 } 1320 updateLinkBandwidthTxRxSample(WifiLinkLayerStats oldStats, WifiLinkLayerStats newStats, ExtendedWifiInfo wifiInfo, long txBytes, long rxBytes)1321 private void updateLinkBandwidthTxRxSample(WifiLinkLayerStats oldStats, 1322 WifiLinkLayerStats newStats, ExtendedWifiInfo wifiInfo, 1323 long txBytes, long rxBytes) { 1324 // oldStats is reset to null after screen off or disconnection 1325 if (oldStats == null || newStats == null) { 1326 return; 1327 } 1328 1329 int elapsedTimeMs = (int) (newStats.timeStampInMs - oldStats.timeStampInMs); 1330 if (elapsedTimeMs > MAX_TRAFFIC_STATS_POLL_TIME_DELTA_MS) { 1331 return; 1332 } 1333 1334 int onTimeMs = getTotalRadioOnTimeMs(newStats) - getTotalRadioOnTimeMs(oldStats); 1335 if (onTimeMs <= RADIO_ON_TIME_MIN_MS 1336 || onTimeMs > RADIO_ON_ELAPSED_TIME_DELTA_MAX_MS + elapsedTimeMs) { 1337 return; 1338 } 1339 onTimeMs = Math.min(elapsedTimeMs, onTimeMs); 1340 1341 long txBytesDelta = txBytes - mLastTxBytes; 1342 long rxBytesDelta = rxBytes - mLastRxBytes; 1343 int txLinkSpeedMbps = wifiInfo.getTxLinkSpeedMbps(); 1344 int txBandwidthCapMbps = (txLinkSpeedMbps == LINK_SPEED_UNKNOWN) 1345 ? wifiInfo.getMaxSupportedTxLinkSpeedMbps() : txLinkSpeedMbps; 1346 if (txBytesDelta * RX_OVER_TX_BYTE_RATIO_MAX >= rxBytesDelta) { 1347 updateBandwidthSample(txBytesDelta, LINK_TX, onTimeMs, txBandwidthCapMbps); 1348 } 1349 1350 int rxLinkSpeedMbps = wifiInfo.getRxLinkSpeedMbps(); 1351 // Rx link speed is not available in many devices. In these cases, fall back to Tx link 1352 // speed which is available in most devices. 1353 int rxBandwidthCapMbps = (rxLinkSpeedMbps == LINK_SPEED_UNKNOWN) 1354 ? txBandwidthCapMbps : rxLinkSpeedMbps; 1355 updateBandwidthSample(rxBytesDelta, LINK_RX, onTimeMs, rxBandwidthCapMbps); 1356 1357 if (!mBandwidthSampleValid[LINK_RX] && !mBandwidthSampleValid[LINK_TX]) { 1358 return; 1359 } 1360 StringBuilder sb = new StringBuilder(); 1361 logv(sb.append(" rssi ").append(wifiInfo.getRssi()) 1362 .append(" level ").append(mSignalLevel) 1363 .append(" bssid ").append(wifiInfo.getBSSID()) 1364 .append(" freq ").append(wifiInfo.getFrequency()) 1365 .append(" onTimeMs ").append(onTimeMs) 1366 .append(" txKB ").append(txBytesDelta / 1024) 1367 .append(" rxKB ").append(rxBytesDelta / 1024) 1368 .append(" txKBThr ").append(mByteDeltaAccThr[LINK_TX] / 1024) 1369 .append(" rxKBThr ").append(mByteDeltaAccThr[LINK_RX] / 1024) 1370 .toString()); 1371 } 1372 getTotalRadioOnTimeMs(@onNull WifiLinkLayerStats stats)1373 private int getTotalRadioOnTimeMs(@NonNull WifiLinkLayerStats stats) { 1374 if (stats.radioStats != null && stats.radioStats.length > 0) { 1375 int totalRadioOnTime = 0; 1376 for (WifiLinkLayerStats.RadioStat stat : stats.radioStats) { 1377 totalRadioOnTime += stat.on_time; 1378 } 1379 return totalRadioOnTime; 1380 } 1381 return stats.on_time; 1382 } 1383 getBandIdx(ExtendedWifiInfo wifiInfo)1384 private int getBandIdx(ExtendedWifiInfo wifiInfo) { 1385 return ScanResult.is24GHz(wifiInfo.getFrequency()) ? 0 : 1; 1386 } 1387 updateBandwidthSample(long bytesDelta, int link, int onTimeMs, int bandwidthCapMbps)1388 private void updateBandwidthSample(long bytesDelta, int link, int onTimeMs, 1389 int bandwidthCapMbps) { 1390 if (bytesDelta < mByteDeltaAccThr[link]) { 1391 return; 1392 } 1393 long speedKbps = bytesDelta / onTimeMs * 8; 1394 if (speedKbps > (bandwidthCapMbps * 1000)) { 1395 return; 1396 } 1397 int linkBandwidthKbps = (int) speedKbps; 1398 changed = true; 1399 mBandwidthSampleValid[link] = true; 1400 mBandwidthSampleKbps[link] = linkBandwidthKbps; 1401 // Update SSID level stats 1402 mBandwidthStatsValue[mBandIdx][link][mSignalLevel] += linkBandwidthKbps; 1403 mBandwidthStatsCount[mBandIdx][link][mSignalLevel]++; 1404 // Update BSSID level stats 1405 PerBssid perBssid = lookupBssid(ssid, mBssid); 1406 if (perBssid != mPlaceholderPerBssid) { 1407 perBssid.changed = true; 1408 perBssid.bandwidthStatsValue[mBandIdx][link][mSignalLevel] += linkBandwidthKbps; 1409 perBssid.bandwidthStatsCount[mBandIdx][link][mSignalLevel]++; 1410 } 1411 } 1412 getByteDeltaAccThr(int link)1413 private int getByteDeltaAccThr(int link) { 1414 int maxTimeDeltaMs = mWifiGlobals.getPollRssiIntervalMillis(); 1415 int lowBytes = calculateByteCountThreshold(getAvgUsedLinkBandwidthKbps(link), 1416 maxTimeDeltaMs); 1417 // Start with a predefined value 1418 int deltaAccThr = LINK_BANDWIDTH_BYTE_DELTA_THR_KBYTE[link][mSignalLevel] * 1024; 1419 if (lowBytes > 0) { 1420 // Raise the threshold if the avg usage BW is high 1421 deltaAccThr = Math.max(lowBytes, deltaAccThr); 1422 deltaAccThr = Math.min(deltaAccThr, mDeviceConfigFacade 1423 .getTrafficStatsThresholdMaxKbyte() * 1024); 1424 } 1425 return deltaAccThr; 1426 } 1427 initBandwidthFilter(ExtendedWifiInfo wifiInfo)1428 private void initBandwidthFilter(ExtendedWifiInfo wifiInfo) { 1429 updateWifiInfo(wifiInfo); 1430 for (int link = 0; link < NUM_LINK_DIRECTION; link++) { 1431 mFilterKbps[link] = getAvgLinkBandwidthKbps(link); 1432 } 1433 } 1434 updateBandwidthWithFilterApplied(int link, ExtendedWifiInfo wifiInfo)1435 private void updateBandwidthWithFilterApplied(int link, ExtendedWifiInfo wifiInfo) { 1436 int avgKbps = getAvgLinkBandwidthKbps(link); 1437 // Feed the filter with the long term avg if there is no valid BW sample so that filter 1438 // will gradually converge the long term avg. 1439 int filterInKbps = mBandwidthSampleValid[link] ? mBandwidthSampleKbps[link] : avgKbps; 1440 1441 long currTimeMs = mClock.getElapsedSinceBootMillis(); 1442 int timeDeltaSec = (int) ((currTimeMs - mBandwidthSampleValidTimeMs[link]) / 1000); 1443 1444 // If the operation condition changes since the last valid sample or the current sample 1445 // has higher BW, use a faster filter. Otherwise, use a slow filter 1446 int timeConstantSec; 1447 if (Math.abs(mBandwidthUpdateRssiDbm - wifiInfo.getRssi()) > RSSI_DELTA_THR_DB 1448 || (mBandwidthSampleValid[link] && mBandwidthSampleKbps[link] > avgKbps) 1449 || mBandwidthUpdateBandIdx != mBandIdx) { 1450 timeConstantSec = TIME_CONSTANT_SMALL_SEC; 1451 } else { 1452 timeConstantSec = mDeviceConfigFacade.getBandwidthEstimatorLargeTimeConstantSec(); 1453 } 1454 // Update timestamp for next iteration 1455 if (mBandwidthSampleValid[link]) { 1456 mBandwidthSampleValidTimeMs[link] = currTimeMs; 1457 } 1458 1459 if (filterInKbps == mFilterKbps[link]) { 1460 return; 1461 } 1462 int alpha = timeDeltaSec > LARGE_TIME_DECAY_RATIO * timeConstantSec ? 0 1463 : (int) (FILTER_SCALE * Math.exp(-1.0 * timeDeltaSec / timeConstantSec)); 1464 1465 if (alpha == 0) { 1466 mFilterKbps[link] = filterInKbps; 1467 return; 1468 } 1469 long filterOutKbps = (long) mFilterKbps[link] * alpha 1470 + filterInKbps * FILTER_SCALE - filterInKbps * alpha; 1471 filterOutKbps = filterOutKbps / FILTER_SCALE; 1472 mFilterKbps[link] = (int) Math.min(filterOutKbps, Integer.MAX_VALUE); 1473 StringBuilder sb = new StringBuilder(); 1474 logd(sb.append(link) 1475 .append(" lastSampleWeight=").append(alpha) 1476 .append("/").append(FILTER_SCALE) 1477 .append(" filterInKbps=").append(filterInKbps) 1478 .append(" avgKbps=").append(avgKbps) 1479 .append(" filterOutKbps=").append(mFilterKbps[link]) 1480 .toString()); 1481 } 1482 getAvgLinkBandwidthKbps(int link)1483 private int getAvgLinkBandwidthKbps(int link) { 1484 mAvgUsedKbps[link] = getAvgUsedLinkBandwidthKbps(link); 1485 if (mAvgUsedKbps[link] > 0) { 1486 return mAvgUsedKbps[link]; 1487 } 1488 1489 int avgBwAdjSignalKbps = getAvgUsedBandwidthAdjacentThreeLevelKbps(link); 1490 if (avgBwAdjSignalKbps > 0) { 1491 return avgBwAdjSignalKbps; 1492 } 1493 1494 // Fall back to a cold-start value 1495 return LINK_BANDWIDTH_INIT_KBPS[mBandIdx][link][mSignalLevel]; 1496 } 1497 getAvgUsedLinkBandwidthKbps(int link)1498 private int getAvgUsedLinkBandwidthKbps(int link) { 1499 // Check if current BSSID/signal level has enough count 1500 PerBssid perBssid = lookupBssid(ssid, mBssid); 1501 int count = perBssid.bandwidthStatsCount[mBandIdx][link][mSignalLevel]; 1502 long value = perBssid.bandwidthStatsValue[mBandIdx][link][mSignalLevel]; 1503 if (count >= BANDWIDTH_STATS_COUNT_THR) { 1504 return (int) (value / count); 1505 } 1506 1507 // Check if current SSID/band/signal level has enough count 1508 count = mBandwidthStatsCount[mBandIdx][link][mSignalLevel]; 1509 value = mBandwidthStatsValue[mBandIdx][link][mSignalLevel]; 1510 if (count >= BANDWIDTH_STATS_COUNT_THR) { 1511 return (int) (value / count); 1512 } 1513 1514 return -1; 1515 } 1516 getAvgUsedBandwidthAdjacentThreeLevelKbps(int link)1517 private int getAvgUsedBandwidthAdjacentThreeLevelKbps(int link) { 1518 int count = 0; 1519 long value = 0; 1520 for (int i = -1; i <= 1; i++) { 1521 int currLevel = mSignalLevel + i; 1522 if (currLevel < 0 || currLevel >= NUM_SIGNAL_LEVEL) { 1523 continue; 1524 } 1525 count += mBandwidthStatsCount[mBandIdx][link][currLevel]; 1526 value += mBandwidthStatsValue[mBandIdx][link][currLevel]; 1527 } 1528 if (count >= BANDWIDTH_STATS_COUNT_THR) { 1529 return (int) (value / count); 1530 } 1531 1532 return -1; 1533 } 1534 1535 // Calculate a byte count threshold for the given avg BW and observation window size calculateByteCountThreshold(int avgBwKbps, int durationMs)1536 private int calculateByteCountThreshold(int avgBwKbps, int durationMs) { 1537 long avgBytes = (long) avgBwKbps / 8 * durationMs; 1538 long result = avgBytes * LOW_BW_TO_AVG_BW_RATIO_NUM / LOW_BW_TO_AVG_BW_RATIO_DEN; 1539 return (int) Math.min(result, Integer.MAX_VALUE); 1540 } 1541 1542 /** 1543 * Get the latest TrafficStats based end-to-end Tx link bandwidth estimation in Kbps 1544 */ getTxLinkBandwidthKbps()1545 public int getTxLinkBandwidthKbps() { 1546 return (mFilterKbps[LINK_TX] > 0) ? mFilterKbps[LINK_TX] 1547 : getAvgLinkBandwidthKbps(LINK_TX); 1548 } 1549 1550 /** 1551 * Get the latest TrafficStats based end-to-end Rx link bandwidth estimation in Kbps 1552 */ getRxLinkBandwidthKbps()1553 public int getRxLinkBandwidthKbps() { 1554 return (mFilterKbps[LINK_RX] > 0) ? mFilterKbps[LINK_RX] 1555 : getAvgLinkBandwidthKbps(LINK_RX); 1556 } 1557 1558 /** 1559 * Update Bandwidth metrics with the latest reported bandwidth and L2 BW values 1560 */ updateBwMetrics(int[] reportedKbps, int[] l2Kbps)1561 public void updateBwMetrics(int[] reportedKbps, int[] l2Kbps) { 1562 for (int link = 0; link < NUM_LINK_DIRECTION; link++) { 1563 calculateError(link, reportedKbps[link], l2Kbps[link]); 1564 } 1565 } 1566 calculateError(int link, int reportedKbps, int l2Kbps)1567 private void calculateError(int link, int reportedKbps, int l2Kbps) { 1568 if (mBandwidthStatsCount[mBandIdx][link][mSignalLevel] < (BANDWIDTH_STATS_COUNT_THR 1569 + EXTRA_SAMPLE_BW_FILTERING) || !mBandwidthSampleValid[link] 1570 || mAvgUsedKbps[link] <= 0) { 1571 return; 1572 } 1573 int bwSampleKbps = mBandwidthSampleKbps[link]; 1574 int bwEstExtErrPercent = calculateErrorPercent(reportedKbps, bwSampleKbps); 1575 int bwEstIntErrPercent = calculateErrorPercent(mFilterKbps[link], bwSampleKbps); 1576 int l2ErrPercent = calculateErrorPercent(l2Kbps, bwSampleKbps); 1577 mBwEstErrorAccPercent[mBandIdx][link][mSignalLevel] += Math.abs(bwEstExtErrPercent); 1578 mL2ErrorAccPercent[mBandIdx][link][mSignalLevel] += Math.abs(l2ErrPercent); 1579 mBwEstValue[mBandIdx][link][mSignalLevel] += bwSampleKbps; 1580 mBwEstCount[mBandIdx][link][mSignalLevel]++; 1581 StringBuilder sb = new StringBuilder(); 1582 logv(sb.append(link) 1583 .append(" sampKbps ").append(bwSampleKbps) 1584 .append(" filtKbps ").append(mFilterKbps[link]) 1585 .append(" reportedKbps ").append(reportedKbps) 1586 .append(" avgUsedKbps ").append(mAvgUsedKbps[link]) 1587 .append(" l2Kbps ").append(l2Kbps) 1588 .append(" intErrPercent ").append(bwEstIntErrPercent) 1589 .append(" extErrPercent ").append(bwEstExtErrPercent) 1590 .append(" l2ErrPercent ").append(l2ErrPercent) 1591 .toString()); 1592 } 1593 calculateErrorPercent(int inKbps, int bwSampleKbps)1594 private int calculateErrorPercent(int inKbps, int bwSampleKbps) { 1595 long errorPercent = 100L * (inKbps - bwSampleKbps) / bwSampleKbps; 1596 return (int) Math.max(-MAX_ERROR_PERCENT, Math.min(errorPercent, MAX_ERROR_PERCENT)); 1597 } 1598 1599 /** 1600 /* Detect a significant failure stats change with historical data 1601 /* or high failure stats without historical data. 1602 /* @return 0 if recentStats doesn't have sufficient data 1603 * 1 if recentStats has sufficient data while statsPrevBuild doesn't 1604 * 2 if recentStats and statsPrevBuild have sufficient data 1605 */ dailyDetection(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh)1606 int dailyDetection(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh) { 1607 finishPendingRead(); 1608 dailyDetectionDisconnectionEvent(statsDec, statsInc, statsHigh); 1609 return dailyDetectionConnectionEvent(statsDec, statsInc, statsHigh); 1610 } 1611 dailyDetectionConnectionEvent(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh)1612 private int dailyDetectionConnectionEvent(FailureStats statsDec, FailureStats statsInc, 1613 FailureStats statsHigh) { 1614 // Skip daily detection if recentStats is not sufficient 1615 if (!isRecentConnectionStatsSufficient()) return INSUFFICIENT_RECENT_STATS; 1616 if (mStatsPrevBuild.getCount(CNT_CONNECTION_ATTEMPT) 1617 < mDeviceConfigFacade.getHealthMonitorMinNumConnectionAttempt()) { 1618 // don't have enough historical data, 1619 // so only detect high failure stats without relying on mStatsPrevBuild. 1620 recentStatsHighDetectionConnection(statsHigh); 1621 return SUFFICIENT_RECENT_STATS_ONLY; 1622 } else { 1623 // mStatsPrevBuild has enough updates, 1624 // detect improvement or degradation 1625 statsDeltaDetectionConnection(statsDec, statsInc); 1626 return SUFFICIENT_RECENT_PREV_STATS; 1627 } 1628 } 1629 dailyDetectionDisconnectionEvent(FailureStats statsDec, FailureStats statsInc, FailureStats statsHigh)1630 private void dailyDetectionDisconnectionEvent(FailureStats statsDec, FailureStats statsInc, 1631 FailureStats statsHigh) { 1632 // Skip daily detection if recentStats is not sufficient 1633 int minConnectAttempt = mDeviceConfigFacade.getHealthMonitorMinNumConnectionAttempt(); 1634 if (mRecentStats.getCount(CNT_CONNECTION_ATTEMPT) < minConnectAttempt) { 1635 return; 1636 } 1637 if (mStatsPrevBuild.getCount(CNT_CONNECTION_ATTEMPT) < minConnectAttempt) { 1638 recentStatsHighDetectionDisconnection(statsHigh); 1639 } else { 1640 statsDeltaDetectionDisconnection(statsDec, statsInc); 1641 } 1642 } 1643 statsDeltaDetectionConnection(FailureStats statsDec, FailureStats statsInc)1644 private void statsDeltaDetectionConnection(FailureStats statsDec, 1645 FailureStats statsInc) { 1646 statsDeltaDetection(statsDec, statsInc, CNT_CONNECTION_FAILURE, 1647 REASON_CONNECTION_FAILURE, 1648 mDeviceConfigFacade.getConnectionFailureCountMin(), 1649 CNT_CONNECTION_ATTEMPT); 1650 statsDeltaDetection(statsDec, statsInc, CNT_DISCONNECTION_NONLOCAL_CONNECTING, 1651 REASON_CONNECTION_FAILURE_DISCONNECTION, 1652 mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(), 1653 CNT_CONNECTION_ATTEMPT); 1654 statsDeltaDetection(statsDec, statsInc, CNT_AUTHENTICATION_FAILURE, 1655 REASON_AUTH_FAILURE, 1656 mDeviceConfigFacade.getAuthFailureCountMin(), 1657 CNT_CONNECTION_ATTEMPT); 1658 statsDeltaDetection(statsDec, statsInc, CNT_ASSOCIATION_REJECTION, 1659 REASON_ASSOC_REJECTION, 1660 mDeviceConfigFacade.getAssocRejectionCountMin(), 1661 CNT_CONNECTION_ATTEMPT); 1662 statsDeltaDetection(statsDec, statsInc, CNT_ASSOCIATION_TIMEOUT, 1663 REASON_ASSOC_TIMEOUT, 1664 mDeviceConfigFacade.getAssocTimeoutCountMin(), 1665 CNT_CONNECTION_ATTEMPT); 1666 } 1667 recentStatsHighDetectionConnection(FailureStats statsHigh)1668 private void recentStatsHighDetectionConnection(FailureStats statsHigh) { 1669 recentStatsHighDetection(statsHigh, CNT_CONNECTION_FAILURE, 1670 REASON_CONNECTION_FAILURE, 1671 mDeviceConfigFacade.getConnectionFailureHighThrPercent(), 1672 mDeviceConfigFacade.getConnectionFailureCountMin(), 1673 CNT_CONNECTION_ATTEMPT); 1674 recentStatsHighDetection(statsHigh, CNT_DISCONNECTION_NONLOCAL_CONNECTING, 1675 REASON_CONNECTION_FAILURE_DISCONNECTION, 1676 mDeviceConfigFacade.getConnectionFailureDisconnectionHighThrPercent(), 1677 mDeviceConfigFacade.getConnectionFailureDisconnectionCountMin(), 1678 CNT_CONNECTION_ATTEMPT); 1679 recentStatsHighDetection(statsHigh, CNT_AUTHENTICATION_FAILURE, 1680 REASON_AUTH_FAILURE, 1681 mDeviceConfigFacade.getAuthFailureHighThrPercent(), 1682 mDeviceConfigFacade.getAuthFailureCountMin(), 1683 CNT_CONNECTION_ATTEMPT); 1684 recentStatsHighDetection(statsHigh, CNT_ASSOCIATION_REJECTION, 1685 REASON_ASSOC_REJECTION, 1686 mDeviceConfigFacade.getAssocRejectionHighThrPercent(), 1687 mDeviceConfigFacade.getAssocRejectionCountMin(), 1688 CNT_CONNECTION_ATTEMPT); 1689 recentStatsHighDetection(statsHigh, CNT_ASSOCIATION_TIMEOUT, 1690 REASON_ASSOC_TIMEOUT, 1691 mDeviceConfigFacade.getAssocTimeoutHighThrPercent(), 1692 mDeviceConfigFacade.getAssocTimeoutCountMin(), 1693 CNT_CONNECTION_ATTEMPT); 1694 } 1695 statsDeltaDetectionDisconnection(FailureStats statsDec, FailureStats statsInc)1696 private void statsDeltaDetectionDisconnection(FailureStats statsDec, 1697 FailureStats statsInc) { 1698 statsDeltaDetection(statsDec, statsInc, CNT_SHORT_CONNECTION_NONLOCAL, 1699 REASON_SHORT_CONNECTION_NONLOCAL, 1700 mDeviceConfigFacade.getShortConnectionNonlocalCountMin(), 1701 CNT_CONNECTION_ATTEMPT); 1702 statsDeltaDetection(statsDec, statsInc, CNT_DISCONNECTION_NONLOCAL, 1703 REASON_DISCONNECTION_NONLOCAL, 1704 mDeviceConfigFacade.getDisconnectionNonlocalCountMin(), 1705 CNT_CONNECTION_ATTEMPT); 1706 } 1707 recentStatsHighDetectionDisconnection(FailureStats statsHigh)1708 private void recentStatsHighDetectionDisconnection(FailureStats statsHigh) { 1709 recentStatsHighDetection(statsHigh, CNT_SHORT_CONNECTION_NONLOCAL, 1710 REASON_SHORT_CONNECTION_NONLOCAL, 1711 mDeviceConfigFacade.getShortConnectionNonlocalHighThrPercent(), 1712 mDeviceConfigFacade.getShortConnectionNonlocalCountMin(), 1713 CNT_DISCONNECTION); 1714 recentStatsHighDetection(statsHigh, CNT_DISCONNECTION_NONLOCAL, 1715 REASON_DISCONNECTION_NONLOCAL, 1716 mDeviceConfigFacade.getDisconnectionNonlocalHighThrPercent(), 1717 mDeviceConfigFacade.getDisconnectionNonlocalCountMin(), 1718 CNT_DISCONNECTION); 1719 } 1720 statsDeltaDetection(FailureStats statsDec, FailureStats statsInc, int countCode, int reasonCode, int minCount, int refCountCode)1721 private boolean statsDeltaDetection(FailureStats statsDec, 1722 FailureStats statsInc, int countCode, int reasonCode, 1723 int minCount, int refCountCode) { 1724 if (isRatioAboveThreshold(mRecentStats, mStatsPrevBuild, countCode, refCountCode) 1725 && mRecentStats.getCount(countCode) >= minCount) { 1726 statsInc.incrementCount(reasonCode); 1727 return true; 1728 } 1729 1730 if (isRatioAboveThreshold(mStatsPrevBuild, mRecentStats, countCode, refCountCode) 1731 && mStatsPrevBuild.getCount(countCode) >= minCount) { 1732 statsDec.incrementCount(reasonCode); 1733 return true; 1734 } 1735 return false; 1736 } 1737 recentStatsHighDetection(FailureStats statsHigh, int countCode, int reasonCode, int highThresholdPercent, int minCount, int refCountCode)1738 private boolean recentStatsHighDetection(FailureStats statsHigh, int countCode, 1739 int reasonCode, int highThresholdPercent, int minCount, int refCountCode) { 1740 if (isHighPercentageAndEnoughCount(mRecentStats, countCode, reasonCode, 1741 highThresholdPercent, minCount, refCountCode)) { 1742 statsHigh.incrementCount(reasonCode); 1743 return true; 1744 } 1745 return false; 1746 } 1747 isRatioAboveThreshold(NetworkConnectionStats stats1, NetworkConnectionStats stats2, @ConnectionCountCode int countCode, int refCountCode)1748 private boolean isRatioAboveThreshold(NetworkConnectionStats stats1, 1749 NetworkConnectionStats stats2, 1750 @ConnectionCountCode int countCode, int refCountCode) { 1751 // Also with Laplace's rule of succession discussed above 1752 // R1 = (stats1(countCode) + 1) / (stats1(refCountCode) + 2) 1753 // R2 = (stats2(countCode) + 1) / (stats2(refCountCode) + 2) 1754 // Check R1 / R2 >= ratioThr 1755 return ((stats1.getCount(countCode) + 1) * (stats2.getCount(refCountCode) + 2) 1756 * mDeviceConfigFacade.HEALTH_MONITOR_RATIO_THR_DENOMINATOR) 1757 >= ((stats1.getCount(refCountCode) + 2) * (stats2.getCount(countCode) + 1) 1758 * mDeviceConfigFacade.getHealthMonitorRatioThrNumerator()); 1759 } 1760 isRecentConnectionStatsSufficient()1761 private boolean isRecentConnectionStatsSufficient() { 1762 return (mRecentStats.getCount(CNT_CONNECTION_ATTEMPT) 1763 >= mDeviceConfigFacade.getHealthMonitorMinNumConnectionAttempt()); 1764 } 1765 1766 // Update StatsCurrBuild with recentStats and clear recentStats updateAfterDailyDetection()1767 void updateAfterDailyDetection() { 1768 // Skip update if recentStats is not sufficient since daily detection is also skipped 1769 if (!isRecentConnectionStatsSufficient()) return; 1770 mStatsCurrBuild.accumulateAll(mRecentStats); 1771 mRecentStats.clear(); 1772 changed = true; 1773 } 1774 1775 // Refresh StatsPrevBuild with StatsCurrBuild which is cleared afterwards updateAfterSwBuildChange()1776 void updateAfterSwBuildChange() { 1777 finishPendingRead(); 1778 mStatsPrevBuild.copy(mStatsCurrBuild); 1779 mRecentStats.clear(); 1780 mStatsCurrBuild.clear(); 1781 changed = true; 1782 } 1783 toNetworkStats()1784 NetworkStats toNetworkStats() { 1785 finishPendingRead(); 1786 NetworkStats.Builder builder = NetworkStats.newBuilder(); 1787 builder.setId(id); 1788 builder.setRecentStats(toConnectionStats(mRecentStats)); 1789 builder.setStatsCurrBuild(toConnectionStats(mStatsCurrBuild)); 1790 builder.setStatsPrevBuild(toConnectionStats(mStatsPrevBuild)); 1791 if (mFrequencyList.size() > 0) { 1792 builder.addAllFrequencies(mFrequencyList.getEntries()); 1793 } 1794 builder.setBandwidthStatsAll(toBandwidthStatsAll( 1795 mBandwidthStatsValue, mBandwidthStatsCount)); 1796 return builder.build(); 1797 } 1798 toConnectionStats(NetworkConnectionStats stats)1799 private ConnectionStats toConnectionStats(NetworkConnectionStats stats) { 1800 ConnectionStats.Builder builder = ConnectionStats.newBuilder(); 1801 builder.setNumConnectionAttempt(stats.getCount(CNT_CONNECTION_ATTEMPT)); 1802 builder.setNumConnectionFailure(stats.getCount(CNT_CONNECTION_FAILURE)); 1803 builder.setConnectionDurationSec(stats.getCount(CNT_CONNECTION_DURATION_SEC)); 1804 builder.setNumDisconnectionNonlocal(stats.getCount(CNT_DISCONNECTION_NONLOCAL)); 1805 builder.setNumDisconnection(stats.getCount(CNT_DISCONNECTION)); 1806 builder.setNumShortConnectionNonlocal(stats.getCount(CNT_SHORT_CONNECTION_NONLOCAL)); 1807 builder.setNumAssociationRejection(stats.getCount(CNT_ASSOCIATION_REJECTION)); 1808 builder.setNumAssociationTimeout(stats.getCount(CNT_ASSOCIATION_TIMEOUT)); 1809 builder.setNumAuthenticationFailure(stats.getCount(CNT_AUTHENTICATION_FAILURE)); 1810 builder.setNumDisconnectionNonlocalConnecting( 1811 stats.getCount(CNT_DISCONNECTION_NONLOCAL_CONNECTING)); 1812 return builder.build(); 1813 } 1814 finishPendingRead()1815 void finishPendingRead() { 1816 final byte[] serialized = finishPendingReadBytes(); 1817 if (serialized == null) return; 1818 NetworkStats ns; 1819 try { 1820 ns = NetworkStats.parseFrom(serialized); 1821 } catch (InvalidProtocolBufferException e) { 1822 Log.e(TAG, "Failed to deserialize", e); 1823 return; 1824 } 1825 mergeNetworkStatsFromMemory(ns); 1826 changed = true; 1827 } 1828 mergeNetworkStatsFromMemory(@onNull NetworkStats ns)1829 PerNetwork mergeNetworkStatsFromMemory(@NonNull NetworkStats ns) { 1830 if (ns.hasId() && this.id != ns.getId()) { 1831 return this; 1832 } 1833 if (ns.hasRecentStats()) { 1834 ConnectionStats recentStats = ns.getRecentStats(); 1835 mergeConnectionStats(recentStats, mRecentStats); 1836 } 1837 if (ns.hasStatsCurrBuild()) { 1838 ConnectionStats statsCurr = ns.getStatsCurrBuild(); 1839 mStatsCurrBuild.clear(); 1840 mergeConnectionStats(statsCurr, mStatsCurrBuild); 1841 } 1842 if (ns.hasStatsPrevBuild()) { 1843 ConnectionStats statsPrev = ns.getStatsPrevBuild(); 1844 mStatsPrevBuild.clear(); 1845 mergeConnectionStats(statsPrev, mStatsPrevBuild); 1846 } 1847 if (ns.getFrequenciesList().size() > 0) { 1848 // This merge assumes that whatever data is in memory is more recent that what's 1849 // in store 1850 List<Integer> mergedFrequencyList = mFrequencyList.getEntries(); 1851 mergedFrequencyList.addAll(ns.getFrequenciesList()); 1852 mFrequencyList = new LruList<>(MAX_FREQUENCIES_PER_SSID); 1853 for (int i = mergedFrequencyList.size() - 1; i >= 0; i--) { 1854 mFrequencyList.add(mergedFrequencyList.get(i)); 1855 } 1856 } 1857 if (ns.hasBandwidthStatsAll()) { 1858 mergeBandwidthStatsAll(ns.getBandwidthStatsAll(), 1859 mBandwidthStatsValue, mBandwidthStatsCount); 1860 } 1861 return this; 1862 } 1863 mergeConnectionStats(ConnectionStats source, NetworkConnectionStats target)1864 private void mergeConnectionStats(ConnectionStats source, NetworkConnectionStats target) { 1865 if (source.hasNumConnectionAttempt()) { 1866 target.accumulate(CNT_CONNECTION_ATTEMPT, source.getNumConnectionAttempt()); 1867 } 1868 if (source.hasNumConnectionFailure()) { 1869 target.accumulate(CNT_CONNECTION_FAILURE, source.getNumConnectionFailure()); 1870 } 1871 if (source.hasConnectionDurationSec()) { 1872 target.accumulate(CNT_CONNECTION_DURATION_SEC, source.getConnectionDurationSec()); 1873 } 1874 if (source.hasNumDisconnectionNonlocal()) { 1875 target.accumulate(CNT_DISCONNECTION_NONLOCAL, source.getNumDisconnectionNonlocal()); 1876 } 1877 if (source.hasNumDisconnection()) { 1878 target.accumulate(CNT_DISCONNECTION, source.getNumDisconnection()); 1879 } 1880 if (source.hasNumShortConnectionNonlocal()) { 1881 target.accumulate(CNT_SHORT_CONNECTION_NONLOCAL, 1882 source.getNumShortConnectionNonlocal()); 1883 } 1884 if (source.hasNumAssociationRejection()) { 1885 target.accumulate(CNT_ASSOCIATION_REJECTION, source.getNumAssociationRejection()); 1886 } 1887 if (source.hasNumAssociationTimeout()) { 1888 target.accumulate(CNT_ASSOCIATION_TIMEOUT, source.getNumAssociationTimeout()); 1889 } 1890 if (source.hasNumAuthenticationFailure()) { 1891 target.accumulate(CNT_AUTHENTICATION_FAILURE, source.getNumAuthenticationFailure()); 1892 } 1893 if (source.hasNumDisconnectionNonlocalConnecting()) { 1894 target.accumulate(CNT_DISCONNECTION_NONLOCAL_CONNECTING, 1895 source.getNumDisconnectionNonlocalConnecting()); 1896 } 1897 } 1898 } 1899 1900 // Codes for various connection related counts 1901 public static final int CNT_INVALID = -1; 1902 public static final int CNT_CONNECTION_ATTEMPT = 0; 1903 public static final int CNT_CONNECTION_FAILURE = 1; 1904 public static final int CNT_CONNECTION_DURATION_SEC = 2; 1905 public static final int CNT_ASSOCIATION_REJECTION = 3; 1906 public static final int CNT_ASSOCIATION_TIMEOUT = 4; 1907 public static final int CNT_AUTHENTICATION_FAILURE = 5; 1908 public static final int CNT_SHORT_CONNECTION_NONLOCAL = 6; 1909 public static final int CNT_DISCONNECTION_NONLOCAL = 7; 1910 public static final int CNT_DISCONNECTION = 8; 1911 public static final int CNT_CONSECUTIVE_CONNECTION_FAILURE = 9; 1912 public static final int CNT_DISCONNECTION_NONLOCAL_CONNECTING = 10; 1913 public static final int CNT_CONSECUTIVE_WRONG_PASSWORD_FAILURE = 11; 1914 // Constant being used to keep track of how many counter there are. 1915 public static final int NUMBER_CONNECTION_CNT_CODE = 12; 1916 private static final String[] CONNECTION_CNT_NAME = { 1917 " ConnectAttempt: ", 1918 " ConnectFailure: ", 1919 " ConnectDurSec: ", 1920 " AssocRej: ", 1921 " AssocTimeout: ", 1922 " AuthFailure: ", 1923 " ShortDiscNonlocal: ", 1924 " DisconnectNonlocal: ", 1925 " Disconnect: ", 1926 " ConsecutiveConnectFailure: ", 1927 " ConnectFailureDiscon: ", 1928 " ConsecutiveWrongPassword: " 1929 }; 1930 1931 @IntDef(prefix = { "CNT_" }, value = { 1932 CNT_CONNECTION_ATTEMPT, 1933 CNT_CONNECTION_FAILURE, 1934 CNT_CONNECTION_DURATION_SEC, 1935 CNT_ASSOCIATION_REJECTION, 1936 CNT_ASSOCIATION_TIMEOUT, 1937 CNT_AUTHENTICATION_FAILURE, 1938 CNT_SHORT_CONNECTION_NONLOCAL, 1939 CNT_DISCONNECTION_NONLOCAL, 1940 CNT_DISCONNECTION, 1941 CNT_CONSECUTIVE_CONNECTION_FAILURE, 1942 CNT_DISCONNECTION_NONLOCAL_CONNECTING, 1943 CNT_CONSECUTIVE_WRONG_PASSWORD_FAILURE 1944 }) 1945 @Retention(RetentionPolicy.SOURCE) 1946 public @interface ConnectionCountCode {} 1947 1948 /** 1949 * A class maintaining the connection related statistics of a Wifi network. 1950 */ 1951 public static class NetworkConnectionStats { 1952 private final int[] mCount = new int[NUMBER_CONNECTION_CNT_CODE]; 1953 private int mRecentCountCode = CNT_INVALID; 1954 /** 1955 * Copy all values 1956 * @param src is the source of copy 1957 */ copy(NetworkConnectionStats src)1958 public void copy(NetworkConnectionStats src) { 1959 for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) { 1960 mCount[i] = src.getCount(i); 1961 } 1962 mRecentCountCode = src.mRecentCountCode; 1963 } 1964 1965 /** 1966 * Clear all counters 1967 */ clear()1968 public void clear() { 1969 for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) { 1970 mCount[i] = 0; 1971 } 1972 mRecentCountCode = CNT_INVALID; 1973 } 1974 1975 /** 1976 * Get counter value 1977 * @param countCode is the selected counter 1978 * @return the value of selected counter 1979 */ getCount(@onnectionCountCode int countCode)1980 public int getCount(@ConnectionCountCode int countCode) { 1981 return mCount[countCode]; 1982 } 1983 1984 /** 1985 * Clear counter value 1986 * @param countCode is the selected counter to be cleared 1987 */ clearCount(@onnectionCountCode int countCode)1988 public void clearCount(@ConnectionCountCode int countCode) { 1989 mCount[countCode] = 0; 1990 } 1991 1992 /** 1993 * Increment count value by 1 1994 * @param countCode is the selected counter 1995 */ incrementCount(@onnectionCountCode int countCode)1996 public void incrementCount(@ConnectionCountCode int countCode) { 1997 mCount[countCode]++; 1998 mRecentCountCode = countCode; 1999 } 2000 2001 /** 2002 * Got the recent incremented count code 2003 */ getRecentCountCode()2004 public int getRecentCountCode() { 2005 return mRecentCountCode; 2006 } 2007 2008 /** 2009 * Decrement count value by 1 2010 * @param countCode is the selected counter 2011 */ decrementCount(@onnectionCountCode int countCode)2012 public void decrementCount(@ConnectionCountCode int countCode) { 2013 mCount[countCode]--; 2014 } 2015 2016 /** 2017 * Add and accumulate the selected counter 2018 * @param countCode is the selected counter 2019 * @param cnt is the value to be added to the counter 2020 */ accumulate(@onnectionCountCode int countCode, int cnt)2021 public void accumulate(@ConnectionCountCode int countCode, int cnt) { 2022 mCount[countCode] += cnt; 2023 } 2024 2025 /** 2026 * Accumulate daily stats to historical data 2027 * @param recentStats are the raw daily counts 2028 */ accumulateAll(NetworkConnectionStats recentStats)2029 public void accumulateAll(NetworkConnectionStats recentStats) { 2030 // 32-bit counter in second can support connection duration up to 68 years. 2031 // Similarly 32-bit counter can support up to continuous connection attempt 2032 // up to 68 years with one attempt per second. 2033 for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) { 2034 mCount[i] += recentStats.getCount(i); 2035 } 2036 } 2037 2038 @Override toString()2039 public String toString() { 2040 StringBuilder sb = new StringBuilder(); 2041 for (int i = 0; i < NUMBER_CONNECTION_CNT_CODE; i++) { 2042 sb.append(CONNECTION_CNT_NAME[i]); 2043 sb.append(mCount[i]); 2044 } 2045 return sb.toString(); 2046 } 2047 } 2048 2049 /** 2050 * A base class dealing with common operations of MemoryStore. 2051 */ 2052 public static class MemoryStoreAccessBase { 2053 private final String mL2Key; 2054 private final long mHash; 2055 private static final String TAG = "WifiMemoryStoreAccessBase"; 2056 private final AtomicReference<byte[]> mPendingReadFromStore = new AtomicReference<>(); MemoryStoreAccessBase(long hash)2057 MemoryStoreAccessBase(long hash) { 2058 mHash = hash; 2059 mL2Key = l2KeyFromLong(); 2060 } getL2Key()2061 String getL2Key() { 2062 return mL2Key; 2063 } 2064 l2KeyFromLong()2065 private String l2KeyFromLong() { 2066 return "W" + Long.toHexString(mHash); 2067 } 2068 2069 /** 2070 * Callback function when MemoryStore read is done 2071 * @param serialized is the readback value 2072 */ readBackListener(byte[] serialized)2073 void readBackListener(byte[] serialized) { 2074 if (serialized == null) return; 2075 byte[] old = mPendingReadFromStore.getAndSet(serialized); 2076 if (old != null) { 2077 Log.e(TAG, "More answers than we expected!"); 2078 } 2079 } 2080 2081 /** 2082 * Handles (when convenient) the arrival of previously stored data. 2083 * 2084 * The response from IpMemoryStore arrives on a different thread, so we 2085 * defer handling it until here, when we're on our favorite thread and 2086 * in a good position to deal with it. We may have already collected some 2087 * data before now, so we need to be prepared to merge the new and old together. 2088 */ finishPendingReadBytes()2089 byte[] finishPendingReadBytes() { 2090 return mPendingReadFromStore.getAndSet(null); 2091 } 2092 idFromLong()2093 int idFromLong() { 2094 return (int) mHash & 0x7fffffff; 2095 } 2096 } 2097 logd(String string)2098 private void logd(String string) { 2099 if (mVerboseLoggingEnabled) { 2100 Log.d(TAG, string, null); 2101 } 2102 } 2103 logv(String string)2104 private void logv(String string) { 2105 if (mVerboseLoggingEnabled) { 2106 Log.v(TAG, string, null); 2107 } 2108 mLocalLog.log(string); 2109 } 2110 2111 // Returned by lookupBssid when the BSSID is not available, 2112 // for instance when we are not associated. 2113 private final PerBssid mPlaceholderPerBssid; 2114 2115 private final Map<MacAddress, PerBssid> mApForBssid = new ArrayMap<>(); 2116 private int mApForBssidTargetSize = TARGET_IN_MEMORY_ENTRIES; 2117 private int mApForBssidReferenced = 0; 2118 2119 // TODO should be private, but WifiCandidates needs it lookupBssid(String ssid, String bssid)2120 @NonNull PerBssid lookupBssid(String ssid, String bssid) { 2121 MacAddress mac; 2122 if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid) || bssid == null) { 2123 return mPlaceholderPerBssid; 2124 } 2125 try { 2126 mac = MacAddress.fromString(bssid); 2127 } catch (IllegalArgumentException e) { 2128 return mPlaceholderPerBssid; 2129 } 2130 if (mac.equals(mPlaceholderPerBssid.bssid)) { 2131 return mPlaceholderPerBssid; 2132 } 2133 PerBssid ans = mApForBssid.get(mac); 2134 if (ans == null || !ans.ssid.equals(ssid)) { 2135 ans = new PerBssid(ssid, mac); 2136 PerBssid old = mApForBssid.put(mac, ans); 2137 if (old != null) { 2138 Log.i(TAG, "Discarding stats for score card (ssid changed) ID: " + old.id); 2139 if (old.referenced) mApForBssidReferenced--; 2140 } 2141 requestReadBssid(ans); 2142 } 2143 if (!ans.referenced) { 2144 ans.referenced = true; 2145 mApForBssidReferenced++; 2146 clean(); 2147 } 2148 return ans; 2149 } 2150 requestReadBssid(final PerBssid perBssid)2151 private void requestReadBssid(final PerBssid perBssid) { 2152 if (mMemoryStore != null) { 2153 mMemoryStore.read(perBssid.getL2Key(), PER_BSSID_DATA_NAME, 2154 (value) -> perBssid.readBackListener(value)); 2155 } 2156 } 2157 requestReadForAllChanged()2158 private void requestReadForAllChanged() { 2159 for (PerBssid perBssid : mApForBssid.values()) { 2160 if (perBssid.changed) { 2161 requestReadBssid(perBssid); 2162 } 2163 } 2164 } 2165 2166 // Returned by lookupNetwork when the network is not available, 2167 // for instance when we are not associated. 2168 private final PerNetwork mPlaceholderPerNetwork; 2169 private final Map<String, PerNetwork> mApForNetwork = new ArrayMap<>(); lookupNetwork(String ssid)2170 @NonNull PerNetwork lookupNetwork(String ssid) { 2171 if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)) { 2172 return mPlaceholderPerNetwork; 2173 } 2174 2175 PerNetwork ans = mApForNetwork.get(ssid); 2176 if (ans == null) { 2177 ans = new PerNetwork(ssid); 2178 mApForNetwork.put(ssid, ans); 2179 requestReadNetwork(ans); 2180 } 2181 return ans; 2182 } 2183 2184 /** 2185 * Remove network from cache and memory store 2186 * @param ssid is the network SSID 2187 */ removeNetwork(String ssid)2188 public void removeNetwork(String ssid) { 2189 if (ssid == null || WifiManager.UNKNOWN_SSID.equals(ssid)) { 2190 return; 2191 } 2192 mApForNetwork.remove(ssid); 2193 mApForBssid.entrySet().removeIf(entry -> ssid.equals(entry.getValue().ssid)); 2194 if (mMemoryStore == null) return; 2195 mMemoryStore.removeCluster(groupHintFromSsid(ssid)); 2196 } 2197 requestReadNetwork(final PerNetwork perNetwork)2198 void requestReadNetwork(final PerNetwork perNetwork) { 2199 if (mMemoryStore != null) { 2200 mMemoryStore.read(perNetwork.getL2Key(), PER_NETWORK_DATA_NAME, 2201 (value) -> perNetwork.readBackListener(value)); 2202 } 2203 } 2204 2205 /** 2206 * Issues write requests for all changed entries. 2207 * 2208 * This should be called from time to time to save the state to persistent 2209 * storage. Since we always check internal state first, this does not need 2210 * to be called very often, but it should be called before shutdown. 2211 * 2212 * @returns number of writes issued. 2213 */ doWrites()2214 public int doWrites() { 2215 return doWritesBssid() + doWritesNetwork(); 2216 } 2217 doWritesBssid()2218 private int doWritesBssid() { 2219 if (mMemoryStore == null) return 0; 2220 int count = 0; 2221 int bytes = 0; 2222 for (PerBssid perBssid : mApForBssid.values()) { 2223 if (perBssid.changed) { 2224 perBssid.finishPendingRead(); 2225 byte[] serialized = perBssid.toAccessPoint(/* No BSSID */ true).toByteArray(); 2226 mMemoryStore.setCluster(perBssid.getL2Key(), groupHintFromSsid(perBssid.ssid)); 2227 mMemoryStore.write(perBssid.getL2Key(), PER_BSSID_DATA_NAME, serialized); 2228 2229 perBssid.changed = false; 2230 count++; 2231 bytes += serialized.length; 2232 } 2233 } 2234 if (mVerboseLoggingEnabled && count > 0) { 2235 Log.v(TAG, "Write count: " + count + ", bytes: " + bytes); 2236 } 2237 return count; 2238 } 2239 doWritesNetwork()2240 private int doWritesNetwork() { 2241 if (mMemoryStore == null) return 0; 2242 int count = 0; 2243 int bytes = 0; 2244 for (PerNetwork perNetwork : mApForNetwork.values()) { 2245 if (perNetwork.changed) { 2246 perNetwork.finishPendingRead(); 2247 byte[] serialized = perNetwork.toNetworkStats().toByteArray(); 2248 mMemoryStore.setCluster(perNetwork.getL2Key(), groupHintFromSsid(perNetwork.ssid)); 2249 mMemoryStore.write(perNetwork.getL2Key(), PER_NETWORK_DATA_NAME, serialized); 2250 perNetwork.changed = false; 2251 count++; 2252 bytes += serialized.length; 2253 } 2254 } 2255 if (mVerboseLoggingEnabled && count > 0) { 2256 Log.v(TAG, "Write count: " + count + ", bytes: " + bytes); 2257 } 2258 return count; 2259 } 2260 2261 /** 2262 * Evicts older entries from memory. 2263 * 2264 * This uses an approximate least-recently-used method. When the number of 2265 * referenced entries exceeds the target value, any items that have not been 2266 * referenced since the last round are evicted, and the remaining entries 2267 * are marked as unreferenced. The total count varies between the target 2268 * value and twice the target value. 2269 */ clean()2270 private void clean() { 2271 if (mMemoryStore == null) return; 2272 if (mApForBssidReferenced >= mApForBssidTargetSize) { 2273 doWritesBssid(); // Do not want to evict changed items 2274 // Evict the unreferenced ones, and clear all the referenced bits for the next round. 2275 Iterator<Map.Entry<MacAddress, PerBssid>> it = mApForBssid.entrySet().iterator(); 2276 while (it.hasNext()) { 2277 PerBssid perBssid = it.next().getValue(); 2278 if (perBssid.referenced) { 2279 perBssid.referenced = false; 2280 } else { 2281 it.remove(); 2282 if (mVerboseLoggingEnabled) Log.v(TAG, "Evict " + perBssid.id); 2283 } 2284 } 2285 mApForBssidReferenced = 0; 2286 } 2287 } 2288 2289 /** 2290 * Compute a hash value with the given SSID and MAC address 2291 * @param ssid is the network SSID 2292 * @param mac is the network MAC address 2293 * @param l2KeySeed is the seed for hash generation 2294 * @return 2295 */ computeHashLong(String ssid, MacAddress mac, String l2KeySeed)2296 public static long computeHashLong(String ssid, MacAddress mac, String l2KeySeed) { 2297 final ArrayList<Byte> decodedSsid; 2298 try { 2299 decodedSsid = NativeUtil.decodeSsid(ssid); 2300 } catch (IllegalArgumentException e) { 2301 Log.e(TAG, "NativeUtil.decodeSsid failed: malformed string: " + ssid); 2302 return 0; 2303 } 2304 byte[][] parts = { 2305 // Our seed keeps the L2Keys specific to this device 2306 l2KeySeed.getBytes(), 2307 // ssid is either quoted utf8 or hex-encoded bytes; turn it into plain bytes. 2308 NativeUtil.byteArrayFromArrayList(decodedSsid), 2309 // And the BSSID 2310 mac.toByteArray() 2311 }; 2312 // Assemble the parts into one, with single-byte lengths before each. 2313 int n = 0; 2314 for (int i = 0; i < parts.length; i++) { 2315 n += 1 + parts[i].length; 2316 } 2317 byte[] mashed = new byte[n]; 2318 int p = 0; 2319 for (int i = 0; i < parts.length; i++) { 2320 byte[] part = parts[i]; 2321 mashed[p++] = (byte) part.length; 2322 for (int j = 0; j < part.length; j++) { 2323 mashed[p++] = part[j]; 2324 } 2325 } 2326 // Finally, turn that into a long 2327 MessageDigest md; 2328 try { 2329 md = MessageDigest.getInstance("SHA-256"); 2330 } catch (NoSuchAlgorithmException e) { 2331 Log.e(TAG, "SHA-256 not supported."); 2332 return 0; 2333 } 2334 ByteBuffer buffer = ByteBuffer.wrap(md.digest(mashed)); 2335 return buffer.getLong(); 2336 } 2337 groupHintFromLong(long hash)2338 private static String groupHintFromLong(long hash) { 2339 return "G" + Long.toHexString(hash); 2340 } 2341 2342 @VisibleForTesting fetchByBssid(MacAddress mac)2343 PerBssid fetchByBssid(MacAddress mac) { 2344 return mApForBssid.get(mac); 2345 } 2346 2347 @VisibleForTesting fetchByNetwork(String ssid)2348 PerNetwork fetchByNetwork(String ssid) { 2349 return mApForNetwork.get(ssid); 2350 } 2351 2352 @VisibleForTesting perBssidFromAccessPoint(String ssid, AccessPoint ap)2353 PerBssid perBssidFromAccessPoint(String ssid, AccessPoint ap) { 2354 MacAddress bssid = MacAddress.fromBytes(ap.getBssid().toByteArray()); 2355 return new PerBssid(ssid, bssid).merge(ap); 2356 } 2357 2358 @VisibleForTesting perNetworkFromNetworkStats(String ssid, NetworkStats ns)2359 PerNetwork perNetworkFromNetworkStats(String ssid, NetworkStats ns) { 2360 return new PerNetwork(ssid).mergeNetworkStatsFromMemory(ns); 2361 } 2362 2363 final class PerSignal { 2364 public final Event event; 2365 public final int frequency; 2366 public final PerUnivariateStatistic rssi; 2367 public final PerUnivariateStatistic linkspeed; 2368 @Nullable public final PerUnivariateStatistic elapsedMs; PerSignal(Event event, int frequency)2369 PerSignal(Event event, int frequency) { 2370 this.event = event; 2371 this.frequency = frequency; 2372 switch (event) { 2373 case SIGNAL_POLL: 2374 case IP_CONFIGURATION_SUCCESS: 2375 case IP_REACHABILITY_LOST: 2376 this.rssi = new PerUnivariateStatistic(RSSI_BUCKETS); 2377 break; 2378 default: 2379 this.rssi = new PerUnivariateStatistic(); 2380 break; 2381 } 2382 this.linkspeed = new PerUnivariateStatistic(); 2383 switch (event) { 2384 case FIRST_POLL_AFTER_CONNECTION: 2385 case IP_CONFIGURATION_SUCCESS: 2386 case VALIDATION_SUCCESS: 2387 case CONNECTION_FAILURE: 2388 case DISCONNECTION: 2389 case WIFI_DISABLED: 2390 case ROAM_FAILURE: 2391 this.elapsedMs = new PerUnivariateStatistic(); 2392 break; 2393 default: 2394 this.elapsedMs = null; 2395 break; 2396 } 2397 } merge(Signal signal)2398 PerSignal merge(Signal signal) { 2399 Preconditions.checkArgument(event == signal.getEvent()); 2400 Preconditions.checkArgument(frequency == signal.getFrequency()); 2401 rssi.merge(signal.getRssi()); 2402 linkspeed.merge(signal.getLinkspeed()); 2403 if (elapsedMs != null && signal.hasElapsedMs()) { 2404 elapsedMs.merge(signal.getElapsedMs()); 2405 } 2406 return this; 2407 } toSignal()2408 Signal toSignal() { 2409 Signal.Builder builder = Signal.newBuilder(); 2410 builder.setEvent(event) 2411 .setFrequency(frequency) 2412 .setRssi(rssi.toUnivariateStatistic()) 2413 .setLinkspeed(linkspeed.toUnivariateStatistic()); 2414 if (elapsedMs != null) { 2415 builder.setElapsedMs(elapsedMs.toUnivariateStatistic()); 2416 } 2417 if (rssi.intHistogram != null 2418 && rssi.intHistogram.numNonEmptyBuckets() > 0) { 2419 logd("Histogram " + event + " RSSI" + rssi.intHistogram); 2420 } 2421 return builder.build(); 2422 } 2423 } 2424 2425 final class PerUnivariateStatistic { 2426 public long count = 0; 2427 public double sum = 0.0; 2428 public double sumOfSquares = 0.0; 2429 public double minValue = Double.POSITIVE_INFINITY; 2430 public double maxValue = Double.NEGATIVE_INFINITY; 2431 public double historicalMean = 0.0; 2432 public double historicalVariance = Double.POSITIVE_INFINITY; 2433 public IntHistogram intHistogram = null; PerUnivariateStatistic()2434 PerUnivariateStatistic() {} PerUnivariateStatistic(int[] bucketBoundaries)2435 PerUnivariateStatistic(int[] bucketBoundaries) { 2436 intHistogram = new IntHistogram(bucketBoundaries); 2437 } update(double value)2438 void update(double value) { 2439 count++; 2440 sum += value; 2441 sumOfSquares += value * value; 2442 minValue = Math.min(minValue, value); 2443 maxValue = Math.max(maxValue, value); 2444 if (intHistogram != null) { 2445 intHistogram.add(Math.round((float) value), 1); 2446 } 2447 } age()2448 void age() { 2449 //TODO Fold the current stats into the historical stats 2450 } merge(UnivariateStatistic stats)2451 void merge(UnivariateStatistic stats) { 2452 if (stats.hasCount()) { 2453 count += stats.getCount(); 2454 sum += stats.getSum(); 2455 sumOfSquares += stats.getSumOfSquares(); 2456 } 2457 if (stats.hasMinValue()) { 2458 minValue = Math.min(minValue, stats.getMinValue()); 2459 } 2460 if (stats.hasMaxValue()) { 2461 maxValue = Math.max(maxValue, stats.getMaxValue()); 2462 } 2463 if (stats.hasHistoricalVariance()) { 2464 if (historicalVariance < Double.POSITIVE_INFINITY) { 2465 // Combine the estimates; c.f. 2466 // Maybeck, Stochasic Models, Estimation, and Control, Vol. 1 2467 // equations (1-3) and (1-4) 2468 double numer1 = stats.getHistoricalVariance(); 2469 double numer2 = historicalVariance; 2470 double denom = numer1 + numer2; 2471 historicalMean = (numer1 * historicalMean 2472 + numer2 * stats.getHistoricalMean()) 2473 / denom; 2474 historicalVariance = numer1 * numer2 / denom; 2475 } else { 2476 historicalMean = stats.getHistoricalMean(); 2477 historicalVariance = stats.getHistoricalVariance(); 2478 } 2479 } 2480 if (intHistogram != null) { 2481 for (HistogramBucket bucket : stats.getBucketsList()) { 2482 long low = bucket.getLow(); 2483 long count = bucket.getNumber(); 2484 if (low != (int) low || count != (int) count || count < 0) { 2485 Log.e(TAG, "Found corrupted histogram! Clearing."); 2486 intHistogram.clear(); 2487 break; 2488 } 2489 intHistogram.add((int) low, (int) count); 2490 } 2491 } 2492 } toUnivariateStatistic()2493 UnivariateStatistic toUnivariateStatistic() { 2494 UnivariateStatistic.Builder builder = UnivariateStatistic.newBuilder(); 2495 if (count != 0) { 2496 builder.setCount(count) 2497 .setSum(sum) 2498 .setSumOfSquares(sumOfSquares) 2499 .setMinValue(minValue) 2500 .setMaxValue(maxValue); 2501 } 2502 if (historicalVariance < Double.POSITIVE_INFINITY) { 2503 builder.setHistoricalMean(historicalMean) 2504 .setHistoricalVariance(historicalVariance); 2505 } 2506 if (mPersistentHistograms 2507 && intHistogram != null && intHistogram.numNonEmptyBuckets() > 0) { 2508 for (IntHistogram.Bucket b : intHistogram) { 2509 if (b.count == 0) continue; 2510 builder.addBuckets( 2511 HistogramBucket.newBuilder().setLow(b.start).setNumber(b.count)); 2512 } 2513 } 2514 return builder.build(); 2515 } 2516 } 2517 2518 /** 2519 * Returns the current scorecard in the form of a protobuf com_android_server_wifi.NetworkList 2520 * 2521 * Synchronization is the caller's responsibility. 2522 * 2523 * @param obfuscate - if true, ssids and bssids are omitted (short id only) 2524 */ getNetworkListByteArray(boolean obfuscate)2525 public byte[] getNetworkListByteArray(boolean obfuscate) { 2526 // These are really grouped by ssid, ignoring the security type. 2527 Map<String, Network.Builder> networks = new ArrayMap<>(); 2528 for (PerBssid perBssid: mApForBssid.values()) { 2529 String key = perBssid.ssid; 2530 Network.Builder network = networks.get(key); 2531 if (network == null) { 2532 network = Network.newBuilder(); 2533 networks.put(key, network); 2534 if (!obfuscate) { 2535 network.setSsid(perBssid.ssid); 2536 } 2537 } 2538 if (perBssid.mNetworkAgentId >= network.getNetworkAgentId()) { 2539 network.setNetworkAgentId(perBssid.mNetworkAgentId); 2540 } 2541 if (perBssid.mNetworkConfigId >= network.getNetworkConfigId()) { 2542 network.setNetworkConfigId(perBssid.mNetworkConfigId); 2543 } 2544 network.addAccessPoints(perBssid.toAccessPoint(obfuscate)); 2545 } 2546 for (PerNetwork perNetwork: mApForNetwork.values()) { 2547 String key = perNetwork.ssid; 2548 Network.Builder network = networks.get(key); 2549 if (network != null) { 2550 network.setNetworkStats(perNetwork.toNetworkStats()); 2551 } 2552 } 2553 NetworkList.Builder builder = NetworkList.newBuilder(); 2554 for (Network.Builder network: networks.values()) { 2555 builder.addNetworks(network); 2556 } 2557 return builder.build().toByteArray(); 2558 } 2559 2560 /** 2561 * Returns the current scorecard as a base64-encoded protobuf 2562 * 2563 * Synchronization is the caller's responsibility. 2564 * 2565 * @param obfuscate - if true, bssids are omitted (short id only) 2566 */ getNetworkListBase64(boolean obfuscate)2567 public String getNetworkListBase64(boolean obfuscate) { 2568 byte[] raw = getNetworkListByteArray(obfuscate); 2569 return Base64.encodeToString(raw, Base64.DEFAULT); 2570 } 2571 2572 /** 2573 * Clears the internal state. 2574 * 2575 * This is called in response to a factoryReset call from Settings. 2576 * The memory store will be called after we are called, to wipe the stable 2577 * storage as well. Since we will have just removed all of our networks, 2578 * it is very unlikely that we're connected, or will connect immediately. 2579 * Any in-flight reads will land in the objects we are dropping here, and 2580 * the memory store should drop the in-flight writes. Ideally we would 2581 * avoid issuing reads until we were sure that the memory store had 2582 * received the factoryReset. 2583 */ clear()2584 public void clear() { 2585 mApForBssid.clear(); 2586 mApForNetwork.clear(); 2587 resetAllConnectionStatesInternal(); 2588 } 2589 2590 /** 2591 * build bandwidth estimator stats proto and then clear all related counters 2592 */ dumpBandwidthEstimatorStats()2593 public BandwidthEstimatorStats dumpBandwidthEstimatorStats() { 2594 BandwidthEstimatorStats stats = new BandwidthEstimatorStats(); 2595 stats.stats2G = dumpBandwdithStatsPerBand(0); 2596 stats.statsAbove2G = dumpBandwdithStatsPerBand(1); 2597 return stats; 2598 } 2599 dumpBandwdithStatsPerBand(int bandIdx)2600 private BandwidthEstimatorStats.PerBand dumpBandwdithStatsPerBand(int bandIdx) { 2601 BandwidthEstimatorStats.PerBand stats = new BandwidthEstimatorStats.PerBand(); 2602 stats.tx = dumpBandwidthStatsPerLink(bandIdx, LINK_TX); 2603 stats.rx = dumpBandwidthStatsPerLink(bandIdx, LINK_RX); 2604 return stats; 2605 } 2606 dumpBandwidthStatsPerLink( int bandIdx, int linkIdx)2607 private BandwidthEstimatorStats.PerLink dumpBandwidthStatsPerLink( 2608 int bandIdx, int linkIdx) { 2609 BandwidthEstimatorStats.PerLink stats = new BandwidthEstimatorStats.PerLink(); 2610 List<BandwidthEstimatorStats.PerLevel> levels = new ArrayList<>(); 2611 for (int level = 0; level < NUM_SIGNAL_LEVEL; level++) { 2612 BandwidthEstimatorStats.PerLevel currStats = 2613 dumpBandwidthStatsPerLevel(bandIdx, linkIdx, level); 2614 if (currStats != null) { 2615 levels.add(currStats); 2616 } 2617 } 2618 stats.level = levels.toArray(new BandwidthEstimatorStats.PerLevel[0]); 2619 return stats; 2620 } 2621 dumpBandwidthStatsPerLevel( int bandIdx, int linkIdx, int level)2622 private BandwidthEstimatorStats.PerLevel dumpBandwidthStatsPerLevel( 2623 int bandIdx, int linkIdx, int level) { 2624 int count = mBwEstCount[bandIdx][linkIdx][level]; 2625 if (count <= 0) { 2626 return null; 2627 } 2628 2629 BandwidthEstimatorStats.PerLevel stats = new BandwidthEstimatorStats.PerLevel(); 2630 stats.signalLevel = level; 2631 stats.count = count; 2632 stats.avgBandwidthKbps = calculateAvg(mBwEstValue[bandIdx][linkIdx][level], count); 2633 stats.l2ErrorPercent = calculateAvg( 2634 mL2ErrorAccPercent[bandIdx][linkIdx][level], count); 2635 stats.bandwidthEstErrorPercent = calculateAvg( 2636 mBwEstErrorAccPercent[bandIdx][linkIdx][level], count); 2637 2638 // reset counters for next run 2639 mBwEstCount[bandIdx][linkIdx][level] = 0; 2640 mBwEstValue[bandIdx][linkIdx][level] = 0; 2641 mL2ErrorAccPercent[bandIdx][linkIdx][level] = 0; 2642 mBwEstErrorAccPercent[bandIdx][linkIdx][level] = 0; 2643 return stats; 2644 } 2645 calculateAvg(long acc, int count)2646 private int calculateAvg(long acc, int count) { 2647 return (count > 0) ? (int) (acc / count) : 0; 2648 } 2649 2650 /** 2651 * Dump the internal state and local logs 2652 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)2653 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2654 pw.println("Dump of WifiScoreCard"); 2655 pw.println("current SSID(s):" + mIfaceToInfoMap.entrySet().stream() 2656 .map(entry -> 2657 "{iface=" + entry.getKey() + ",ssid=" + entry.getValue().ssidCurr + "}") 2658 .collect(Collectors.joining(","))); 2659 try { 2660 mLocalLog.dump(fd, pw, args); 2661 } catch (Exception e) { 2662 e.printStackTrace(); 2663 } 2664 2665 pw.println(" BW Estimation Stats"); 2666 for (int i = 0; i < 2; i++) { 2667 pw.println((i == 0 ? "2G" : "5G")); 2668 for (int j = 0; j < NUM_LINK_DIRECTION; j++) { 2669 pw.println((j == 0 ? " Tx" : " Rx")); 2670 pw.println(" Count"); 2671 printValues(mBwEstCount[i][j], pw); 2672 pw.println(" AvgKbps"); 2673 printAvgStats(mBwEstValue[i][j], mBwEstCount[i][j], pw); 2674 pw.println(" BwEst error"); 2675 printAvgStats(mBwEstErrorAccPercent[i][j], mBwEstCount[i][j], pw); 2676 pw.println(" L2 error"); 2677 printAvgStats(mL2ErrorAccPercent[i][j], mBwEstCount[i][j], pw); 2678 } 2679 } 2680 pw.println(); 2681 } 2682 printValues(int[] values, PrintWriter pw)2683 private void printValues(int[] values, PrintWriter pw) { 2684 StringBuilder sb = new StringBuilder(); 2685 for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) { 2686 sb.append(" " + values[k]); 2687 } 2688 pw.println(sb.toString()); 2689 } 2690 printAvgStats(long[] stats, int[] count, PrintWriter pw)2691 private void printAvgStats(long[] stats, int[] count, PrintWriter pw) { 2692 StringBuilder sb = new StringBuilder(); 2693 for (int k = 0; k < NUM_SIGNAL_LEVEL; k++) { 2694 sb.append(" " + calculateAvg(stats[k], count[k])); 2695 } 2696 pw.println(sb.toString()); 2697 } 2698 } 2699