WifiMetrics.java revision 4dead162c5336443e9d7b3deae5eb26b07d39254
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.wifi; 18 19import android.net.wifi.ScanResult; 20import android.net.wifi.WifiConfiguration; 21import android.os.SystemClock; 22import android.util.Base64; 23import android.util.SparseArray; 24 25import java.io.FileDescriptor; 26import java.io.PrintWriter; 27import java.util.ArrayList; 28import java.util.Calendar; 29import java.util.List; 30 31/** 32 * Provides storage for wireless connectivity metrics, as they are generated. 33 * Metrics logged by this class include: 34 * Aggregated connection stats (num of connections, num of failures, ...) 35 * Discrete connection event stats (time, duration, failure codes, ...) 36 * Router details (technology type, authentication type, ...) 37 * Scan stats 38 */ 39public class WifiMetrics { 40 private static final String TAG = "WifiMetrics"; 41 private final Object mLock = new Object(); 42 private static final int MAX_CONNECTION_EVENTS = 256; 43 /** 44 * Metrics are stored within an instance of the WifiLog proto during runtime, 45 * The ConnectionEvent, SystemStateEntries & ScanReturnEntries metrics are stored during 46 * runtime in member lists of this WifiMetrics class, with the final WifiLog proto being pieced 47 * together at dump-time 48 */ 49 private final WifiMetricsProto.WifiLog mWifiLogProto; 50 /** 51 * Session information that gets logged for every Wifi connection attempt. 52 */ 53 private final List<ConnectionEvent> mConnectionEventList; 54 /** 55 * The latest started (but un-ended) connection attempt 56 */ 57 private ConnectionEvent mCurrentConnectionEvent; 58 /** 59 * Count of number of times each scan return code, indexed by WifiLog.ScanReturnCode 60 */ 61 private final SparseArray<WifiMetricsProto.WifiLog.ScanReturnEntry> mScanReturnEntries; 62 /** 63 * Mapping of system state to the counts of scans requested in that wifi state * screenOn 64 * combination. Indexed by WifiLog.WifiState * (1 + screenOn) 65 */ 66 private final SparseArray<WifiMetricsProto.WifiLog.WifiSystemStateEntry> 67 mWifiSystemStateEntries; 68 69 class RouterFingerPrint { 70 private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto; 71 RouterFingerPrint() { 72 mRouterFingerPrintProto = new WifiMetricsProto.RouterFingerPrint(); 73 } 74 75 public String toString() { 76 StringBuilder sb = new StringBuilder(); 77 synchronized (mLock) { 78 sb.append("mConnectionEvent.roamType=" + mRouterFingerPrintProto.roamType); 79 sb.append(", mChannelInfo=" + mRouterFingerPrintProto.channelInfo); 80 sb.append(", mDtim=" + mRouterFingerPrintProto.dtim); 81 sb.append(", mAuthentication=" + mRouterFingerPrintProto.authentication); 82 sb.append(", mHidden=" + mRouterFingerPrintProto.hidden); 83 sb.append(", mRouterTechnology=" + mRouterFingerPrintProto.routerTechnology); 84 sb.append(", mSupportsIpv6=" + mRouterFingerPrintProto.supportsIpv6); 85 } 86 return sb.toString(); 87 } 88 public void updateFromWifiConfiguration(WifiConfiguration config) { 89 if (config != null) { 90 /*<TODO> 91 mRouterFingerPrintProto.roamType 92 mRouterFingerPrintProto.routerTechnology 93 mRouterFingerPrintProto.supportsIpv6 94 */ 95 if (config.allowedAuthAlgorithms != null 96 && config.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.OPEN)) { 97 mRouterFingerPrintProto.authentication = 98 WifiMetricsProto.RouterFingerPrint.AUTH_OPEN; 99 } else if (config.isEnterprise()) { 100 mRouterFingerPrintProto.authentication = 101 WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE; 102 } else { 103 mRouterFingerPrintProto.authentication = 104 WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL; 105 } 106 mRouterFingerPrintProto.hidden = config.hiddenSSID; 107 mRouterFingerPrintProto.channelInfo = config.apChannel; 108 // Config may not have a valid dtimInterval set yet, in which case dtim will be zero 109 // (These are only populated from beacon frame scan results, which are returned as 110 // scan results from the chip far less frequently than Probe-responses) 111 if (config.dtimInterval > 0) { 112 mRouterFingerPrintProto.dtim = config.dtimInterval; 113 } 114 } 115 } 116 } 117 118 /** 119 * Log event, tracking the start time, end time and result of a wireless connection attempt. 120 */ 121 class ConnectionEvent { 122 WifiMetricsProto.ConnectionEvent mConnectionEvent; 123 //<TODO> Move these constants into a wifi.proto Enum 124 // Level 2 Failure Codes 125 // Failure is unknown 126 public static final int LLF_UNKNOWN = 0; 127 // NONE 128 public static final int LLF_NONE = 1; 129 // ASSOCIATION_REJECTION_EVENT 130 public static final int LLF_ASSOCIATION_REJECTION = 2; 131 // AUTHENTICATION_FAILURE_EVENT 132 public static final int LLF_AUTHENTICATION_FAILURE = 3; 133 // SSID_TEMP_DISABLED (Also Auth failure) 134 public static final int LLF_SSID_TEMP_DISABLED = 4; 135 // reconnect() or reassociate() call to WifiNative failed 136 public static final int LLF_CONNECT_NETWORK_FAILED = 5; 137 // NETWORK_DISCONNECTION_EVENT 138 public static final int LLF_NETWORK_DISCONNECTION = 6; 139 // NEW_CONNECTION_ATTEMPT before previous finished 140 public static final int LLF_NEW_CONNECTION_ATTEMPT = 7; 141 RouterFingerPrint mRouterFingerPrint; 142 private long mRealStartTime; 143 private long mRealEndTime; 144 private String mConfigSsid; 145 private String mConfigBssid; 146 147 private ConnectionEvent() { 148 mConnectionEvent = new WifiMetricsProto.ConnectionEvent(); 149 mRealEndTime = 0; 150 mRealStartTime = 0; 151 mRouterFingerPrint = new RouterFingerPrint(); 152 mConnectionEvent.routerFingerprint = mRouterFingerPrint.mRouterFingerPrintProto; 153 mConfigSsid = "<NULL>"; 154 mConfigBssid = "<NULL>"; 155 } 156 157 public String toString() { 158 StringBuilder sb = new StringBuilder(); 159 sb.append("startTime="); 160 Calendar c = Calendar.getInstance(); 161 synchronized (mLock) { 162 c.setTimeInMillis(mConnectionEvent.startTimeMillis); 163 sb.append(mConnectionEvent.startTimeMillis == 0 ? " <null>" : 164 String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); 165 sb.append(", SSID="); 166 sb.append(mConfigSsid); 167 sb.append(", BSSID="); 168 sb.append(mConfigBssid); 169 sb.append(", durationMillis="); 170 sb.append(mConnectionEvent.durationTakenToConnectMillis); 171 sb.append(", roamType="); 172 switch(mConnectionEvent.roamType) { 173 case 1: 174 sb.append("ROAM_NONE"); 175 break; 176 case 2: 177 sb.append("ROAM_DBDC"); 178 break; 179 case 3: 180 sb.append("ROAM_ENTERPRISE"); 181 break; 182 case 4: 183 sb.append("ROAM_USER_SELECTED"); 184 break; 185 case 5: 186 sb.append("ROAM_UNRELATED"); 187 break; 188 default: 189 sb.append("ROAM_UNKNOWN"); 190 } 191 sb.append(", connectionResult="); 192 sb.append(mConnectionEvent.connectionResult); 193 sb.append(", level2FailureCode="); 194 switch(mConnectionEvent.level2FailureCode) { 195 case LLF_NONE: 196 sb.append("NONE"); 197 break; 198 case LLF_ASSOCIATION_REJECTION: 199 sb.append("ASSOCIATION_REJECTION"); 200 break; 201 case LLF_AUTHENTICATION_FAILURE: 202 sb.append("AUTHENTICATION_FAILURE"); 203 break; 204 case LLF_SSID_TEMP_DISABLED: 205 sb.append("SSID_TEMP_DISABLED"); 206 break; 207 case LLF_CONNECT_NETWORK_FAILED: 208 sb.append("CONNECT_NETWORK_FAILED"); 209 break; 210 case LLF_NETWORK_DISCONNECTION: 211 sb.append("NETWORK_DISCONNECTION"); 212 break; 213 case LLF_NEW_CONNECTION_ATTEMPT: 214 sb.append("NEW_CONNECTION_ATTEMPT"); 215 break; 216 default: 217 sb.append("UNKNOWN"); 218 break; 219 } 220 sb.append(", connectivityLevelFailureCode="); 221 switch(mConnectionEvent.connectivityLevelFailureCode) { 222 case WifiMetricsProto.ConnectionEvent.HLF_NONE: 223 sb.append("NONE"); 224 break; 225 case WifiMetricsProto.ConnectionEvent.HLF_DHCP: 226 sb.append("DHCP"); 227 break; 228 case WifiMetricsProto.ConnectionEvent.HLF_NO_INTERNET: 229 sb.append("NO_INTERNET"); 230 break; 231 case WifiMetricsProto.ConnectionEvent.HLF_UNWANTED: 232 sb.append("UNWANTED"); 233 break; 234 default: 235 sb.append("UNKNOWN"); 236 break; 237 } 238 sb.append(", signalStrength="); 239 sb.append(mConnectionEvent.signalStrength); 240 sb.append("\n "); 241 sb.append("mRouterFingerprint: "); 242 sb.append(mRouterFingerPrint.toString()); 243 } 244 return sb.toString(); 245 } 246 } 247 248 public WifiMetrics() { 249 mWifiLogProto = new WifiMetricsProto.WifiLog(); 250 mConnectionEventList = new ArrayList<>(); 251 mCurrentConnectionEvent = null; 252 mScanReturnEntries = new SparseArray<WifiMetricsProto.WifiLog.ScanReturnEntry>(); 253 mWifiSystemStateEntries = new SparseArray<WifiMetricsProto.WifiLog.WifiSystemStateEntry>(); 254 } 255 256 /** 257 * Create a new connection event. Call when wifi attempts to make a new network connection 258 * If there is a current 'un-ended' connection event, it will be ended with UNKNOWN connectivity 259 * failure code. 260 * Gathers and sets the RouterFingerPrint data as well 261 * 262 * @param config WifiConfiguration of the config used for the current connection attempt 263 * @param roamType Roam type that caused connection attempt, see WifiMetricsProto.WifiLog.ROAM_X 264 */ 265 public void startConnectionEvent(WifiConfiguration config, int roamType) { 266 if (mCurrentConnectionEvent != null) { 267 endConnectionEvent(ConnectionEvent.LLF_NEW_CONNECTION_ATTEMPT, 268 WifiMetricsProto.ConnectionEvent.HLF_NONE); 269 } 270 synchronized (mLock) { 271 //If at maximum connection events, start removing the oldest 272 while(mConnectionEventList.size() >= MAX_CONNECTION_EVENTS) { 273 mConnectionEventList.remove(0); 274 } 275 mCurrentConnectionEvent = new ConnectionEvent(); 276 mCurrentConnectionEvent.mConnectionEvent.startTimeMillis = 277 System.currentTimeMillis(); 278 mCurrentConnectionEvent.mConnectionEvent.roamType = roamType; 279 mCurrentConnectionEvent.mRouterFingerPrint.updateFromWifiConfiguration(config); 280 if (config != null) { 281 //RSSI 282 ScanResult candidate = config.getNetworkSelectionStatus().getCandidate(); 283 if (candidate != null) { 284 mCurrentConnectionEvent.mConnectionEvent.signalStrength = 285 candidate.level; 286 } 287 mCurrentConnectionEvent.mConfigSsid = config.SSID; 288 mCurrentConnectionEvent.mConfigBssid = config.BSSID; 289 } 290 mCurrentConnectionEvent.mRealStartTime = SystemClock.elapsedRealtime(); 291 mConnectionEventList.add(mCurrentConnectionEvent); 292 } 293 } 294 295 /** 296 * set the RoamType of the current ConnectionEvent (if any) 297 */ 298 public void setConnectionEventRoamType(int roamType) { 299 if (mCurrentConnectionEvent != null) { 300 mCurrentConnectionEvent.mConnectionEvent.roamType = roamType; 301 } 302 } 303 /** 304 * End a Connection event record. Call when wifi connection attempt succeeds or fails. 305 * If a Connection event has not been started and is active when .end is called, a new one is 306 * created with zero duration. 307 * 308 * @param level2FailureCode Level 2 failure code returned by supplicant 309 * @param connectivityFailureCode WifiMetricsProto.ConnectionEvent.HLF_X 310 */ 311 public void endConnectionEvent(int level2FailureCode, int connectivityFailureCode) { 312 synchronized (mLock) { 313 if (mCurrentConnectionEvent != null) { 314 boolean result = (level2FailureCode == 1) 315 && (connectivityFailureCode == WifiMetricsProto.ConnectionEvent.HLF_NONE); 316 mCurrentConnectionEvent.mConnectionEvent.connectionResult = result ? 1 : 0; 317 mCurrentConnectionEvent.mRealEndTime = SystemClock.elapsedRealtime(); 318 mCurrentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis = (int) 319 (mCurrentConnectionEvent.mRealEndTime 320 - mCurrentConnectionEvent.mRealStartTime); 321 mCurrentConnectionEvent.mConnectionEvent.level2FailureCode = level2FailureCode; 322 mCurrentConnectionEvent.mConnectionEvent.connectivityLevelFailureCode = 323 connectivityFailureCode; 324 //ConnectionEvent already added to ConnectionEvents List 325 mCurrentConnectionEvent = null; 326 } 327 } 328 } 329 330 void setNumSavedNetworks(int num) { 331 synchronized (mLock) { 332 mWifiLogProto.numSavedNetworks = num; 333 } 334 } 335 336 void setNumOpenNetworks(int num) { 337 synchronized (mLock) { 338 mWifiLogProto.numOpenNetworks = num; 339 } 340 } 341 342 void setNumPersonalNetworks(int num) { 343 synchronized (mLock) { 344 mWifiLogProto.numPersonalNetworks = num; 345 } 346 } 347 348 void setNumEnterpriseNetworks(int num) { 349 synchronized (mLock) { 350 mWifiLogProto.numEnterpriseNetworks = num; 351 } 352 } 353 354 void setNumNetworksAddedByUser(int num) { 355 synchronized (mLock) { 356 mWifiLogProto.numNetworksAddedByUser = num; 357 } 358 } 359 360 void setNumNetworksAddedByApps(int num) { 361 synchronized (mLock) { 362 mWifiLogProto.numNetworksAddedByApps = num; 363 } 364 } 365 366 void setIsLocationEnabled(boolean enabled) { 367 synchronized (mLock) { 368 mWifiLogProto.isLocationEnabled = enabled; 369 } 370 } 371 372 void setIsScanningAlwaysEnabled(boolean enabled) { 373 synchronized (mLock) { 374 mWifiLogProto.isScanningAlwaysEnabled = enabled; 375 } 376 } 377 378 /** 379 * Increment Airplane mode toggle count 380 */ 381 public void incrementAirplaneToggleCount() { 382 synchronized (mLock) { 383 mWifiLogProto.numWifiToggledViaAirplane++; 384 } 385 } 386 387 /** 388 * Increment Wifi Toggle count 389 */ 390 public void incrementWifiToggleCount() { 391 synchronized (mLock) { 392 mWifiLogProto.numWifiToggledViaSettings++; 393 } 394 } 395 396 /** 397 * Increment Non Empty Scan Results count 398 */ 399 public void incrementNonEmptyScanResultCount() { 400 synchronized (mLock) { 401 mWifiLogProto.numNonEmptyScanResults++; 402 } 403 } 404 405 /** 406 * Increment Empty Scan Results count 407 */ 408 public void incrementEmptyScanResultCount() { 409 synchronized (mLock) { 410 mWifiLogProto.numEmptyScanResults++; 411 } 412 } 413 414 /** 415 * Increment count of scan return code occurrence 416 * 417 * @param scanReturnCode Return code from scan attempt WifiMetricsProto.WifiLog.SCAN_X 418 */ 419 public void incrementScanReturnEntry(int scanReturnCode) { 420 synchronized (mLock) { 421 WifiMetricsProto.WifiLog.ScanReturnEntry entry = mScanReturnEntries.get(scanReturnCode); 422 if (entry == null) { 423 entry = new WifiMetricsProto.WifiLog.ScanReturnEntry(); 424 entry.scanReturnCode = scanReturnCode; 425 entry.scanResultsCount = 0; 426 } 427 entry.scanResultsCount++; 428 mScanReturnEntries.put(scanReturnCode, entry); 429 } 430 } 431 432 /** 433 * Increments the count of scans initiated by each wifi state, accounts for screenOn/Off 434 * 435 * @param state State of the system when scan was initiated, see WifiMetricsProto.WifiLog.WIFI_X 436 * @param screenOn Is the screen on 437 */ 438 public void incrementWifiSystemScanStateCount(int state, boolean screenOn) { 439 synchronized (mLock) { 440 int index = state * (screenOn ? 2 : 1); 441 WifiMetricsProto.WifiLog.WifiSystemStateEntry entry = 442 mWifiSystemStateEntries.get(index); 443 if (entry == null) { 444 entry = new WifiMetricsProto.WifiLog.WifiSystemStateEntry(); 445 entry.wifiState = state; 446 entry.wifiStateCount = 0; 447 entry.isScreenOn = screenOn; 448 } 449 entry.wifiStateCount++; 450 mWifiSystemStateEntries.put(state, entry); 451 } 452 } 453 454 public static final String PROTO_DUMP_ARG = "wifiMetricsProto"; 455 /** 456 * Dump all WifiMetrics. Collects some metrics from ConfigStore, Settings and WifiManager 457 * at this time 458 * 459 * @param fd unused 460 * @param pw PrintWriter for writing dump to 461 * @param args unused 462 */ 463 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 464 synchronized (mLock) { 465 pw.println("WifiMetrics:"); 466 if (args.length > 0 && PROTO_DUMP_ARG.equals(args[0])) { 467 //Dump serialized WifiLog proto 468 consolidateProto(true); 469 for (ConnectionEvent event : mConnectionEventList) { 470 if (mCurrentConnectionEvent != event) { 471 //indicate that automatic bug report has been taken for all valid 472 //connection events 473 event.mConnectionEvent.automaticBugReportTaken = true; 474 } 475 } 476 byte[] wifiMetricsProto = WifiMetricsProto.WifiLog.toByteArray(mWifiLogProto); 477 String metricsProtoDump = Base64.encodeToString(wifiMetricsProto, Base64.DEFAULT); 478 pw.println(metricsProtoDump); 479 pw.println("EndWifiMetrics"); 480 clear(); 481 } else { 482 pw.println("mConnectionEvents:"); 483 for (ConnectionEvent event : mConnectionEventList) { 484 String eventLine = event.toString(); 485 if (event == mCurrentConnectionEvent) { 486 eventLine += "CURRENTLY OPEN EVENT"; 487 } 488 pw.println(eventLine); 489 } 490 pw.println("mWifiLogProto.numSavedNetworks=" + mWifiLogProto.numSavedNetworks); 491 pw.println("mWifiLogProto.numOpenNetworks=" + mWifiLogProto.numOpenNetworks); 492 pw.println("mWifiLogProto.numPersonalNetworks=" 493 + mWifiLogProto.numPersonalNetworks); 494 pw.println("mWifiLogProto.numEnterpriseNetworks=" 495 + mWifiLogProto.numEnterpriseNetworks); 496 pw.println("mWifiLogProto.isLocationEnabled=" + mWifiLogProto.isLocationEnabled); 497 pw.println("mWifiLogProto.isScanningAlwaysEnabled=" 498 + mWifiLogProto.isScanningAlwaysEnabled); 499 pw.println("mWifiLogProto.numWifiToggledViaSettings=" 500 + mWifiLogProto.numWifiToggledViaSettings); 501 pw.println("mWifiLogProto.numWifiToggledViaAirplane=" 502 + mWifiLogProto.numWifiToggledViaAirplane); 503 pw.println("mWifiLogProto.numNetworksAddedByUser=" 504 + mWifiLogProto.numNetworksAddedByUser); 505 //TODO - Pending scanning refactor 506 pw.println("mWifiLogProto.numNetworksAddedByApps=" + "<TODO>"); 507 pw.println("mWifiLogProto.numNonEmptyScanResults=" + "<TODO>"); 508 pw.println("mWifiLogProto.numEmptyScanResults=" + "<TODO>"); 509 pw.println("mWifiLogProto.numOneshotScans=" + "<TODO>"); 510 pw.println("mWifiLogProto.numBackgroundScans=" + "<TODO>"); 511 pw.println("mScanReturnEntries:" + " <TODO>"); 512 pw.println("mSystemStateEntries:" + " <TODO>"); 513 } 514 } 515 } 516 517 /** 518 * Assign the separate ConnectionEvent, SystemStateEntry and ScanReturnCode lists to their 519 * respective lists within mWifiLogProto, and clear the original lists managed here. 520 * 521 * @param incremental Only include ConnectionEvents created since last automatic bug report 522 */ 523 private void consolidateProto(boolean incremental) { 524 List<WifiMetricsProto.ConnectionEvent> events = new ArrayList<>(); 525 synchronized (mLock) { 526 for (ConnectionEvent event : mConnectionEventList) { 527 if (!incremental || ((mCurrentConnectionEvent != event) 528 && !event.mConnectionEvent.automaticBugReportTaken)) { 529 //Get all ConnectionEvents that haven not been dumped as a proto, also exclude 530 //the current active un-ended connection event 531 events.add(event.mConnectionEvent); 532 event.mConnectionEvent.automaticBugReportTaken = true; 533 } 534 } 535 if (events.size() > 0) { 536 mWifiLogProto.connectionEvent = events.toArray(mWifiLogProto.connectionEvent); 537 } 538 //<TODO> SystemStateEntry and ScanReturnCode list consolidation 539 } 540 } 541 542 /** 543 * Serializes all of WifiMetrics to WifiLog proto, and returns the byte array. 544 * Does not count as taking an automatic bug report 545 * 546 * @return byte array of the deserialized & consolidated Proto 547 */ 548 public byte[] toByteArray() { 549 consolidateProto(false); 550 return mWifiLogProto.toByteArray(mWifiLogProto); 551 } 552 553 /** 554 * Clear all WifiMetrics, except for currentConnectionEvent. 555 */ 556 private void clear() { 557 synchronized (mLock) { 558 mConnectionEventList.clear(); 559 if (mCurrentConnectionEvent != null) { 560 mConnectionEventList.add(mCurrentConnectionEvent); 561 } 562 mScanReturnEntries.clear(); 563 mWifiSystemStateEntries.clear(); 564 mWifiLogProto.clear(); 565 } 566 } 567} 568