WifiMetrics.java revision 2532a24b254d724a9b6771d327dc410b32b18602
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.WifiConfiguration; 20import android.net.wifi.WifiInfo; 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.dtim 93 mRouterFingerPrintProto.routerTechnology 94 mRouterFingerPrintProto.supportsIpv6 95 */ 96 if (config.allowedAuthAlgorithms != null 97 && config.allowedAuthAlgorithms.get(WifiConfiguration.AuthAlgorithm.OPEN)) { 98 mRouterFingerPrintProto.authentication = 99 WifiMetricsProto.RouterFingerPrint.AUTH_OPEN; 100 } else if (config.isEnterprise()) { 101 mRouterFingerPrintProto.authentication = 102 WifiMetricsProto.RouterFingerPrint.AUTH_ENTERPRISE; 103 } else { 104 mRouterFingerPrintProto.authentication = 105 WifiMetricsProto.RouterFingerPrint.AUTH_PERSONAL; 106 } 107 mRouterFingerPrintProto.hidden = config.hiddenSSID; 108 mRouterFingerPrintProto.channelInfo = config.apChannel; 109 110 } 111 } 112 } 113 114 /** 115 * Log event, tracking the start time, end time and result of a wireless connection attempt. 116 */ 117 class ConnectionEvent { 118 WifiMetricsProto.ConnectionEvent mConnectionEvent; 119 RouterFingerPrint mRouterFingerPrint; 120 private long mRealStartTime; 121 /** 122 * Bitset tracking the capture completeness of this connection event bit 1='Event started', 123 * bit 2='Event ended' value = 3 for capture completeness 124 */ 125 private int mEventCompleteness; 126 private long mRealEndTime; 127 128 //<TODO> Move these constants into a wifi.proto Enum 129 // Level 2 Failure Codes 130 // Failure is unknown 131 public static final int LLF_UNKNOWN = 0; 132 // NONE 133 public static final int LLF_NONE = 1; 134 // ASSOCIATION_REJECTION_EVENT 135 public static final int LLF_ASSOCIATION_REJECTION = 2; 136 // AUTHENTICATION_FAILURE_EVENT 137 public static final int LLF_AUTHENTICATION_FAILURE = 3; 138 // SSID_TEMP_DISABLED (Also Auth failure) 139 public static final int LLF_SSID_TEMP_DISABLED = 4; 140 // CONNECT_NETWORK_FAILED 141 public static final int LLF_CONNECT_NETWORK_FAILED = 5; 142 // NETWORK_DISCONNECTION_EVENT 143 public static final int LLF_NETWORK_DISCONNECTION = 6; 144 145 private ConnectionEvent() { 146 mConnectionEvent = new WifiMetricsProto.ConnectionEvent(); 147 mConnectionEvent.startTimeMillis = -1; 148 mRealEndTime = -1; 149 mConnectionEvent.durationTakenToConnectMillis = -1; 150 mRouterFingerPrint = new RouterFingerPrint(); 151 mConnectionEvent.routerFingerprint = mRouterFingerPrint.mRouterFingerPrintProto; 152 mConnectionEvent.signalStrength = -1; 153 mConnectionEvent.roamType = WifiMetricsProto.ConnectionEvent.ROAM_UNKNOWN; 154 mConnectionEvent.connectionResult = -1; 155 mConnectionEvent.level2FailureCode = -1; 156 mConnectionEvent.connectivityLevelFailureCode = 157 WifiMetricsProto.ConnectionEvent.HLF_UNKNOWN; 158 mConnectionEvent.automaticBugReportTaken = false; 159 mEventCompleteness = 0; 160 } 161 162 public String toString() { 163 StringBuilder sb = new StringBuilder(); 164 sb.append("startTime="); 165 Calendar c = Calendar.getInstance(); 166 synchronized (mLock) { 167 c.setTimeInMillis(mConnectionEvent.startTimeMillis); 168 sb.append(mConnectionEvent.startTimeMillis == 0 ? " <null>" : 169 String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); 170 sb.append(", endTime="); 171 c.setTimeInMillis(mRealEndTime); 172 sb.append(mRealEndTime == 0 ? " <null>" : 173 String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); 174 sb.append(", durationTakenToConnectMillis="); 175 sb.append(mConnectionEvent.durationTakenToConnectMillis); 176 sb.append(", roamType="); 177 switch(mConnectionEvent.roamType){ 178 case 1: 179 sb.append("ROAM_NONE"); 180 break; 181 case 2: 182 sb.append("ROAM_DBDC"); 183 break; 184 case 3: 185 sb.append("ROAM_ENTERPRISE"); 186 break; 187 case 4: 188 sb.append("ROAM_USER_SELECTED"); 189 break; 190 case 5: 191 sb.append("ROAM_UNRELATED"); 192 break; 193 default: 194 sb.append("ROAM_UNKNOWN"); 195 } 196 sb.append(", level2FailureCode="); 197 sb.append(mConnectionEvent.level2FailureCode); 198 sb.append(", connectivityLevelFailureCode="); 199 sb.append(mConnectionEvent.connectivityLevelFailureCode); 200 sb.append(", mEventCompleteness="); 201 sb.append(mEventCompleteness); 202 sb.append("\n "); 203 sb.append("mRouterFingerprint: "); 204 sb.append(mRouterFingerPrint.toString()); 205 } 206 return sb.toString(); 207 } 208 } 209 210 public WifiMetrics() { 211 mWifiLogProto = new WifiMetricsProto.WifiLog(); 212 mConnectionEventList = new ArrayList<>(); 213 mCurrentConnectionEvent = null; 214 mScanReturnEntries = new SparseArray<WifiMetricsProto.WifiLog.ScanReturnEntry>(); 215 mWifiSystemStateEntries = new SparseArray<WifiMetricsProto.WifiLog.WifiSystemStateEntry>(); 216 } 217 218 /** 219 * Create a new connection event. Call when wifi attempts to make a new network connection 220 * If there is a current 'un-ended' connection event, it will be ended with UNKNOWN connectivity 221 * failure code. 222 * Gathers and sets the RouterFingerPrint data as well 223 * 224 * @param wifiInfo WifiInfo for the current connection attempt, used for connection metrics 225 * @param roamType Roam type that caused connection attempt, see WifiMetricsProto.WifiLog.ROAM_X 226 */ 227 public void startConnectionEvent(WifiInfo wifiInfo, WifiConfiguration config, int roamType) { 228 synchronized (mLock) { 229 if (mConnectionEventList.size() <= MAX_CONNECTION_EVENTS) { 230 mCurrentConnectionEvent = new ConnectionEvent(); 231 mCurrentConnectionEvent.mEventCompleteness |= 1; 232 mCurrentConnectionEvent.mConnectionEvent.startTimeMillis = 233 System.currentTimeMillis(); 234 mCurrentConnectionEvent.mConnectionEvent.roamType = roamType; 235 mCurrentConnectionEvent.mRouterFingerPrint.updateFromWifiConfiguration(config); 236 if (wifiInfo != null) { 237 mCurrentConnectionEvent.mConnectionEvent.signalStrength = wifiInfo.getRssi(); 238 } 239 mCurrentConnectionEvent.mRealStartTime = SystemClock.elapsedRealtime(); 240 mConnectionEventList.add(mCurrentConnectionEvent); 241 } 242 } 243 } 244 245 public void startConnectionEvent(WifiInfo wifiInfo) { 246 startConnectionEvent(wifiInfo, null, WifiMetricsProto.ConnectionEvent.ROAM_NONE); 247 } 248 249 /** 250 * set the RoamType of the current ConnectionEvent (if any) 251 */ 252 public void setConnectionEventRoamType(int roamType) { 253 if (mCurrentConnectionEvent != null) { 254 mCurrentConnectionEvent.mConnectionEvent.roamType = roamType; 255 } 256 } 257 /** 258 * End a Connection event record. Call when wifi connection attempt succeeds or fails. 259 * If a Connection event has not been started and is active when .end is called, a new one is 260 * created with zero duration. 261 * 262 * @param level2FailureCode Level 2 failure code returned by supplicant 263 * @param connectivityFailureCode WifiMetricsProto.ConnectionEvent.HLF_X 264 */ 265 public void endConnectionEvent(int level2FailureCode, int connectivityFailureCode) { 266 synchronized (mLock) { 267 if (mCurrentConnectionEvent != null) { 268 boolean result = (level2FailureCode == 1) 269 && (connectivityFailureCode == WifiMetricsProto.ConnectionEvent.HLF_NONE); 270 mCurrentConnectionEvent.mConnectionEvent.connectionResult = result ? 1 : 0; 271 mCurrentConnectionEvent.mEventCompleteness |= 2; 272 mCurrentConnectionEvent.mRealEndTime = SystemClock.elapsedRealtime(); 273 mCurrentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis = (int) 274 (mCurrentConnectionEvent.mRealEndTime 275 - mCurrentConnectionEvent.mRealStartTime); 276 mCurrentConnectionEvent.mConnectionEvent.level2FailureCode = level2FailureCode; 277 mCurrentConnectionEvent.mConnectionEvent.connectivityLevelFailureCode = 278 connectivityFailureCode; 279 //ConnectionEvent already added to ConnectionEvents List 280 mCurrentConnectionEvent = null; 281 } 282 } 283 } 284 285 void setNumSavedNetworks(int num) { 286 synchronized (mLock) { 287 mWifiLogProto.numSavedNetworks = num; 288 } 289 } 290 291 void setNumOpenNetworks(int num) { 292 synchronized (mLock) { 293 mWifiLogProto.numOpenNetworks = num; 294 } 295 } 296 297 void setNumPersonalNetworks(int num) { 298 synchronized (mLock) { 299 mWifiLogProto.numPersonalNetworks = num; 300 } 301 } 302 303 void setNumEnterpriseNetworks(int num) { 304 synchronized (mLock) { 305 mWifiLogProto.numEnterpriseNetworks = num; 306 } 307 } 308 309 void setNumNetworksAddedByUser(int num) { 310 synchronized (mLock) { 311 mWifiLogProto.numNetworksAddedByUser = num; 312 } 313 } 314 315 void setNumNetworksAddedByApps(int num) { 316 synchronized (mLock) { 317 mWifiLogProto.numNetworksAddedByApps = num; 318 } 319 } 320 321 void setIsLocationEnabled(boolean enabled) { 322 synchronized (mLock) { 323 mWifiLogProto.isLocationEnabled = enabled; 324 } 325 } 326 327 void setIsScanningAlwaysEnabled(boolean enabled) { 328 synchronized (mLock) { 329 mWifiLogProto.isScanningAlwaysEnabled = enabled; 330 } 331 } 332 333 /** 334 * Increment Airplane mode toggle count 335 */ 336 public void incrementAirplaneToggleCount() { 337 synchronized (mLock) { 338 mWifiLogProto.numWifiToggledViaAirplane++; 339 } 340 } 341 342 /** 343 * Increment Wifi Toggle count 344 */ 345 public void incrementWifiToggleCount() { 346 synchronized (mLock) { 347 mWifiLogProto.numWifiToggledViaSettings++; 348 } 349 } 350 351 /** 352 * Increment Non Empty Scan Results count 353 */ 354 public void incrementNonEmptyScanResultCount() { 355 synchronized (mLock) { 356 mWifiLogProto.numNonEmptyScanResults++; 357 } 358 } 359 360 /** 361 * Increment Empty Scan Results count 362 */ 363 public void incrementEmptyScanResultCount() { 364 synchronized (mLock) { 365 mWifiLogProto.numEmptyScanResults++; 366 } 367 } 368 369 /** 370 * Increment count of scan return code occurrence 371 * 372 * @param scanReturnCode Return code from scan attempt WifiMetricsProto.WifiLog.SCAN_X 373 */ 374 public void incrementScanReturnEntry(int scanReturnCode) { 375 synchronized (mLock) { 376 WifiMetricsProto.WifiLog.ScanReturnEntry entry = mScanReturnEntries.get(scanReturnCode); 377 if (entry == null) { 378 entry = new WifiMetricsProto.WifiLog.ScanReturnEntry(); 379 entry.scanReturnCode = scanReturnCode; 380 entry.scanResultsCount = 0; 381 } 382 entry.scanResultsCount++; 383 mScanReturnEntries.put(scanReturnCode, entry); 384 } 385 } 386 387 /** 388 * Increments the count of scans initiated by each wifi state, accounts for screenOn/Off 389 * 390 * @param state State of the system when scan was initiated, see WifiMetricsProto.WifiLog.WIFI_X 391 * @param screenOn Is the screen on 392 */ 393 public void incrementWifiSystemScanStateCount(int state, boolean screenOn) { 394 synchronized (mLock) { 395 int index = state * (screenOn ? 2 : 1); 396 WifiMetricsProto.WifiLog.WifiSystemStateEntry entry = 397 mWifiSystemStateEntries.get(index); 398 if (entry == null) { 399 entry = new WifiMetricsProto.WifiLog.WifiSystemStateEntry(); 400 entry.wifiState = state; 401 entry.wifiStateCount = 0; 402 entry.isScreenOn = screenOn; 403 } 404 entry.wifiStateCount++; 405 mWifiSystemStateEntries.put(state, entry); 406 } 407 } 408 409 public static final String PROTO_DUMP_ARG = "wifiMetricsProto"; 410 /** 411 * Dump all WifiMetrics. Collects some metrics from ConfigStore, Settings and WifiManager 412 * at this time 413 * 414 * @param fd unused 415 * @param pw PrintWriter for writing dump to 416 * @param args unused 417 */ 418 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 419 synchronized (mLock) { 420 pw.println("WifiMetrics:"); 421 if (args.length > 0 && PROTO_DUMP_ARG.equals(args[0])) { 422 //Dump serialized WifiLog proto 423 consolidateProto(true); 424 for (ConnectionEvent event : mConnectionEventList) { 425 if (mCurrentConnectionEvent != event) { 426 //indicate that automatic bug report has been taken for all valid 427 //connection events 428 event.mConnectionEvent.automaticBugReportTaken = true; 429 } 430 } 431 byte[] wifiMetricsProto = WifiMetricsProto.WifiLog.toByteArray(mWifiLogProto); 432 String metricsProtoDump = Base64.encodeToString(wifiMetricsProto, Base64.DEFAULT); 433 pw.println(metricsProtoDump); 434 pw.println("EndWifiMetrics"); 435 clear(); 436 } else { 437 pw.println("mConnectionEvents:"); 438 for (ConnectionEvent event : mConnectionEventList) { 439 String eventLine = event.toString(); 440 if (event == mCurrentConnectionEvent) { 441 eventLine += "CURRENTLY OPEN EVENT"; 442 } 443 pw.println(eventLine); 444 } 445 pw.println("mWifiLogProto.numSavedNetworks=" + mWifiLogProto.numSavedNetworks); 446 pw.println("mWifiLogProto.numOpenNetworks=" + mWifiLogProto.numOpenNetworks); 447 pw.println("mWifiLogProto.numPersonalNetworks=" 448 + mWifiLogProto.numPersonalNetworks); 449 pw.println("mWifiLogProto.numEnterpriseNetworks=" 450 + mWifiLogProto.numEnterpriseNetworks); 451 pw.println("mWifiLogProto.isLocationEnabled=" + mWifiLogProto.isLocationEnabled); 452 pw.println("mWifiLogProto.isScanningAlwaysEnabled=" 453 + mWifiLogProto.isScanningAlwaysEnabled); 454 pw.println("mWifiLogProto.numWifiToggledViaSettings=" 455 + mWifiLogProto.numWifiToggledViaSettings); 456 pw.println("mWifiLogProto.numWifiToggledViaAirplane=" 457 + mWifiLogProto.numWifiToggledViaAirplane); 458 pw.println("mWifiLogProto.numNetworksAddedByUser=" 459 + mWifiLogProto.numNetworksAddedByUser); 460 //TODO - Pending scanning refactor 461 pw.println("mWifiLogProto.numNetworksAddedByApps=" + "<TODO>"); 462 pw.println("mWifiLogProto.numNonEmptyScanResults=" + "<TODO>"); 463 pw.println("mWifiLogProto.numEmptyScanResults=" + "<TODO>"); 464 pw.println("mWifiLogProto.numOneshotScans=" + "<TODO>"); 465 pw.println("mWifiLogProto.numBackgroundScans=" + "<TODO>"); 466 pw.println("mScanReturnEntries:" + " <TODO>"); 467 pw.println("mSystemStateEntries:" + " <TODO>"); 468 } 469 } 470 } 471 472 /** 473 * Assign the separate ConnectionEvent, SystemStateEntry and ScanReturnCode lists to their 474 * respective lists within mWifiLogProto, and clear the original lists managed here. 475 * 476 * @param incremental Only include ConnectionEvents created since last automatic bug report 477 */ 478 private void consolidateProto(boolean incremental) { 479 List<WifiMetricsProto.ConnectionEvent> events = new ArrayList<>(); 480 synchronized (mLock) { 481 for (ConnectionEvent event : mConnectionEventList) { 482 if (!incremental || ((mCurrentConnectionEvent != event) 483 && !event.mConnectionEvent.automaticBugReportTaken)) { 484 //Get all ConnectionEvents that haven not been dumped as a proto, also exclude 485 //the current active un-ended connection event 486 events.add(event.mConnectionEvent); 487 event.mConnectionEvent.automaticBugReportTaken = true; 488 } 489 } 490 if (events.size() > 0) { 491 mWifiLogProto.connectionEvent = events.toArray(mWifiLogProto.connectionEvent); 492 } 493 //<TODO> SystemStateEntry and ScanReturnCode list consolidation 494 } 495 } 496 497 /** 498 * Serializes all of WifiMetrics to WifiLog proto, and returns the byte array. 499 * Does not count as taking an automatic bug report 500 * 501 * @return byte array of the deserialized & consolidated Proto 502 */ 503 public byte[] toByteArray() { 504 consolidateProto(false); 505 return mWifiLogProto.toByteArray(mWifiLogProto); 506 } 507 508 /** 509 * Clear all WifiMetrics, except for currentConnectionEvent. 510 */ 511 private void clear() { 512 synchronized (mLock) { 513 mConnectionEventList.clear(); 514 if (mCurrentConnectionEvent != null) { 515 mConnectionEventList.add(mCurrentConnectionEvent); 516 } 517 mScanReturnEntries.clear(); 518 mWifiSystemStateEntries.clear(); 519 mWifiLogProto.clear(); 520 } 521 } 522} 523