WifiMetrics.java revision 1b067831bbff14f8e7a99b927b69f714d1b03448
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.WifiInfo; 20import android.os.SystemClock; 21import android.util.Base64; 22import android.util.SparseArray; 23 24import java.io.FileDescriptor; 25import java.io.PrintWriter; 26import java.util.ArrayList; 27import java.util.Calendar; 28import java.util.List; 29 30/** 31 * Provides storage for wireless connectivity metrics, as they are generated. 32 * Metrics logged by this class include: 33 * Aggregated connection stats (num of connections, num of failures, ...) 34 * Discrete connection event stats (time, duration, failure codes, ...) 35 * Router details (technology type, authentication type, ...) 36 * Scan stats 37 */ 38public class WifiMetrics { 39 private static final String TAG = "WifiMetrics"; 40 private final Object mLock = new Object(); 41 /** 42 * Metrics are stored within an instance of the WifiLog proto during runtime, 43 * The ConnectionEvent, SystemStateEntries & ScanReturnEntries metrics are stored during 44 * runtime in member lists of this WifiMetrics class, with the final WifiLog proto being pieced 45 * together at dump-time 46 */ 47 private final WifiMetricsProto.WifiLog mWifiLogProto; 48 /** 49 * Session information that gets logged for every Wifi connection attempt. 50 */ 51 private final List<ConnectionEvent> mConnectionEventList; 52 /** 53 * The latest started (but un-ended) connection attempt 54 */ 55 private ConnectionEvent mCurrentConnectionEvent; 56 /** 57 * Count of number of times each scan return code, indexed by WifiLog.ScanReturnCode 58 */ 59 private final SparseArray<WifiMetricsProto.WifiLog.ScanReturnEntry> mScanReturnEntries; 60 /** 61 * Mapping of system state to the counts of scans requested in that wifi state * screenOn 62 * combination. Indexed by WifiLog.WifiState * (1 + screenOn) 63 */ 64 private final SparseArray<WifiMetricsProto.WifiLog.WifiSystemStateEntry> 65 mWifiSystemStateEntries; 66 67 class RouterFingerPrint { 68 private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto; 69 70 public RouterFingerPrint(int roamType, 71 int channelInfo, 72 int dtim, 73 int authentication, 74 boolean hidden, 75 int routerTechnology, 76 boolean supportsIpv6) { 77 mRouterFingerPrintProto = new WifiMetricsProto.RouterFingerPrint(); 78 mRouterFingerPrintProto.roamType = roamType; 79 mRouterFingerPrintProto.channelInfo = channelInfo; 80 mRouterFingerPrintProto.dtim = dtim; 81 mRouterFingerPrintProto.authentication = authentication; 82 mRouterFingerPrintProto.hidden = hidden; 83 mRouterFingerPrintProto.routerTechnology = routerTechnology; 84 mRouterFingerPrintProto.supportsIpv6 = supportsIpv6; 85 } 86 87 public String toString() { 88 StringBuilder sb = new StringBuilder(); 89 synchronized (mLock) { 90 sb.append("mConnectionEvent.roamType=" + mRouterFingerPrintProto.roamType); 91 sb.append(", mChannelInfo=" + mRouterFingerPrintProto.channelInfo); 92 sb.append(", mDtim=" + mRouterFingerPrintProto.dtim); 93 sb.append(", mAuthentication=" + mRouterFingerPrintProto.authentication); 94 sb.append(", mHidden=" + mRouterFingerPrintProto.hidden); 95 sb.append(", mRouterTechnology=" + mRouterFingerPrintProto.routerTechnology); 96 sb.append(", mSupportsIpv6=" + mRouterFingerPrintProto.supportsIpv6); 97 } 98 return sb.toString(); 99 } 100 } 101 102 /** 103 * Log event, tracking the start time, end time and result of a wireless connection attempt. 104 */ 105 class ConnectionEvent { 106 WifiMetricsProto.ConnectionEvent mConnectionEvent; 107 RouterFingerPrint mRouterFingerPrint; 108 private long mRealStartTime; 109 /** 110 * Bitset tracking the capture completeness of this connection event bit 1='Event started', 111 * bit 2='Event ended' value = 3 for capture completeness 112 */ 113 private int mEventCompleteness; 114 private long mRealEndTime; 115 116 private ConnectionEvent() { 117 mConnectionEvent = new WifiMetricsProto.ConnectionEvent(); 118 mConnectionEvent.startTimeMillis = -1; 119 mRealEndTime = -1; 120 mConnectionEvent.durationTakenToConnectMillis = -1; 121 mRouterFingerPrint = new RouterFingerPrint(0, 0, 0, 0, false, 0, false); 122 mConnectionEvent.signalStrength = -1; 123 mConnectionEvent.roamType = WifiMetricsProto.ConnectionEvent.ROAM_UNKNOWN; 124 mConnectionEvent.connectionResult = -1; 125 mConnectionEvent.level2FailureCode = -1; 126 mConnectionEvent.connectivityLevelFailureCode = 127 WifiMetricsProto.ConnectionEvent.HLF_UNKNOWN; 128 mConnectionEvent.automaticBugReportTaken = false; 129 mEventCompleteness = 0; 130 } 131 132 public String toString() { 133 StringBuilder sb = new StringBuilder(); 134 sb.append("startTime="); 135 Calendar c = Calendar.getInstance(); 136 synchronized (mLock) { 137 c.setTimeInMillis(mConnectionEvent.startTimeMillis); 138 sb.append(mConnectionEvent.startTimeMillis == 0 ? " <null>" : 139 String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); 140 sb.append(", endTime="); 141 c.setTimeInMillis(mRealEndTime); 142 sb.append(mRealEndTime == 0 ? " <null>" : 143 String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c)); 144 sb.append(", durationTakenToConnectMillis="); 145 sb.append(mConnectionEvent.durationTakenToConnectMillis); 146 sb.append(", level2FailureCode="); 147 sb.append(mConnectionEvent.level2FailureCode); 148 sb.append(", connectivityLevelFailureCode="); 149 sb.append(mConnectionEvent.connectivityLevelFailureCode); 150 sb.append(", mEventCompleteness="); 151 sb.append(mEventCompleteness); 152 sb.append("\n "); 153 sb.append("mRouterFingerprint:\n "); 154 sb.append(mRouterFingerPrint.toString()); 155 } 156 return sb.toString(); 157 } 158 } 159 160 public WifiMetrics() { 161 mWifiLogProto = new WifiMetricsProto.WifiLog(); 162 mConnectionEventList = new ArrayList<>(); 163 mCurrentConnectionEvent = null; 164 mScanReturnEntries = new SparseArray<WifiMetricsProto.WifiLog.ScanReturnEntry>(); 165 mWifiSystemStateEntries = new SparseArray<WifiMetricsProto.WifiLog.WifiSystemStateEntry>(); 166 } 167 168 /** 169 * Create a new connection event. Call when wifi attempts to make a new network connection 170 * If there is a current 'un-ended' connection event, it will be ended with UNKNOWN connectivity 171 * failure code. 172 * Gathers and sets the RouterFingerPrint data as well 173 * 174 * @param wifiInfo WifiInfo for the current connection attempt, used for connection metrics 175 * @param roamType Roam type that caused connection attempt, see WifiMetricsProto.WifiLog.ROAM_X 176 */ 177 public void startConnectionEvent(WifiInfo wifiInfo, int roamType) { 178 synchronized (mLock) { 179 mCurrentConnectionEvent = new ConnectionEvent(); 180 mCurrentConnectionEvent.mEventCompleteness |= 1; 181 mCurrentConnectionEvent.mConnectionEvent.startTimeMillis = System.currentTimeMillis(); 182 mCurrentConnectionEvent.mConnectionEvent.roamType = roamType; 183 //<TODO> Get actual routerfingerprint metrics, not these placeholders 184 mCurrentConnectionEvent.mRouterFingerPrint = new RouterFingerPrint( 185 WifiMetricsProto.RouterFingerPrint.ROAM_TYPE_UNKNOWN, //TODO 186 -1, //mChannelInfo TODO 187 -1, //Dtim TODO 188 WifiMetricsProto.RouterFingerPrint.AUTH_UNKNOWN, //TODO 189 false, //mHidden TODO 190 WifiMetricsProto.RouterFingerPrint.ROUTER_TECH_UNKNOWN, //TODO 191 false //mSupportsIpv6 TODO 192 ); 193 if (wifiInfo != null) { 194 mCurrentConnectionEvent.mConnectionEvent.signalStrength = wifiInfo.getRssi(); 195 } 196 mCurrentConnectionEvent.mRealStartTime = SystemClock.elapsedRealtime(); 197 mConnectionEventList.add(mCurrentConnectionEvent); 198 } 199 } 200 201 public void startConnectionEvent(WifiInfo wifiInfo) { 202 startConnectionEvent(wifiInfo, WifiMetricsProto.ConnectionEvent.ROAM_NONE); 203 } 204 205 /** 206 * End a Connection event record. Call when wifi connection attempt succeeds or fails. 207 * If a Connection event has not been started and is active when .end is called, a new one is 208 * created with zero duration. 209 * 210 * @param level2FailureCode Level 2 failure code returned by supplicant 211 * @param connectivityFailureCode WifiMetricsProto.ConnectionEvent.HLF_X 212 */ 213 public void endConnectionEvent(int level2FailureCode, int connectivityFailureCode) { 214 synchronized (mLock) { 215 if (mCurrentConnectionEvent == null) { 216 //No currentConnectionEvent exists, create an 'un-started' one, and End it 217 mCurrentConnectionEvent = new ConnectionEvent(); 218 mConnectionEventList.add(mCurrentConnectionEvent); 219 } 220 mCurrentConnectionEvent.mEventCompleteness |= 2; 221 mCurrentConnectionEvent.mRealEndTime = SystemClock.elapsedRealtime(); 222 mCurrentConnectionEvent.mConnectionEvent.durationTakenToConnectMillis = (int) 223 (mCurrentConnectionEvent.mRealEndTime - mCurrentConnectionEvent.mRealStartTime); 224 mCurrentConnectionEvent.mConnectionEvent.level2FailureCode = level2FailureCode; 225 mCurrentConnectionEvent.mConnectionEvent.connectivityLevelFailureCode = 226 connectivityFailureCode; 227 //ConnectionEvent already added to ConnectionEvents List 228 mCurrentConnectionEvent = null; 229 } 230 } 231 232 void setNumSavedNetworks(int num) { 233 synchronized (mLock) { 234 mWifiLogProto.numSavedNetworks = num; 235 } 236 } 237 238 void setNumOpenNetworks(int num) { 239 synchronized (mLock) { 240 mWifiLogProto.numOpenNetworks = num; 241 } 242 } 243 244 void setNumPersonalNetworks(int num) { 245 synchronized (mLock) { 246 mWifiLogProto.numPersonalNetworks = num; 247 } 248 } 249 250 void setNumEnterpriseNetworks(int num) { 251 synchronized (mLock) { 252 mWifiLogProto.numEnterpriseNetworks = num; 253 } 254 } 255 256 void setNumNetworksAddedByUser(int num) { 257 synchronized (mLock) { 258 mWifiLogProto.numNetworksAddedByUser = num; 259 } 260 } 261 262 void setNumNetworksAddedByApps(int num) { 263 synchronized (mLock) { 264 mWifiLogProto.numNetworksAddedByApps = num; 265 } 266 } 267 268 void setIsLocationEnabled(boolean enabled) { 269 synchronized (mLock) { 270 mWifiLogProto.isLocationEnabled = enabled; 271 } 272 } 273 274 void setIsScanningAlwaysEnabled(boolean enabled) { 275 synchronized (mLock) { 276 mWifiLogProto.isScanningAlwaysEnabled = enabled; 277 } 278 } 279 280 /** 281 * Increment Airplane mode toggle count 282 */ 283 public void incrementAirplaneToggleCount() { 284 synchronized (mLock) { 285 mWifiLogProto.numWifiToggledViaAirplane++; 286 } 287 } 288 289 /** 290 * Increment Wifi Toggle count 291 */ 292 public void incrementWifiToggleCount() { 293 synchronized (mLock) { 294 mWifiLogProto.numWifiToggledViaSettings++; 295 } 296 } 297 298 /** 299 * Increment Non Empty Scan Results count 300 */ 301 public void incrementNonEmptyScanResultCount() { 302 synchronized (mLock) { 303 mWifiLogProto.numNonEmptyScanResults++; 304 } 305 } 306 307 /** 308 * Increment Empty Scan Results count 309 */ 310 public void incrementEmptyScanResultCount() { 311 synchronized (mLock) { 312 mWifiLogProto.numEmptyScanResults++; 313 } 314 } 315 316 /** 317 * Increment count of scan return code occurrence 318 * 319 * @param scanReturnCode Return code from scan attempt WifiMetricsProto.WifiLog.SCAN_X 320 */ 321 public void incrementScanReturnEntry(int scanReturnCode) { 322 synchronized (mLock) { 323 WifiMetricsProto.WifiLog.ScanReturnEntry entry = mScanReturnEntries.get(scanReturnCode); 324 if (entry == null) { 325 entry = new WifiMetricsProto.WifiLog.ScanReturnEntry(); 326 entry.scanReturnCode = scanReturnCode; 327 entry.scanResultsCount = 0; 328 } 329 entry.scanResultsCount++; 330 mScanReturnEntries.put(scanReturnCode, entry); 331 } 332 } 333 334 /** 335 * Increments the count of scans initiated by each wifi state, accounts for screenOn/Off 336 * 337 * @param state State of the system when scan was initiated, see WifiMetricsProto.WifiLog.WIFI_X 338 * @param screenOn Is the screen on 339 */ 340 public void incrementWifiSystemScanStateCount(int state, boolean screenOn) { 341 synchronized (mLock) { 342 int index = state * (screenOn ? 2 : 1); 343 WifiMetricsProto.WifiLog.WifiSystemStateEntry entry = 344 mWifiSystemStateEntries.get(index); 345 if (entry == null) { 346 entry = new WifiMetricsProto.WifiLog.WifiSystemStateEntry(); 347 entry.wifiState = state; 348 entry.wifiStateCount = 0; 349 entry.isScreenOn = screenOn; 350 } 351 entry.wifiStateCount++; 352 mWifiSystemStateEntries.put(state, entry); 353 } 354 } 355 356 /** 357 * Dump all WifiMetrics. Collects some metrics from ConfigStore, Settings and WifiManager 358 * at this time 359 * 360 * @param fd unused 361 * @param pw PrintWriter for writing dump to 362 * @param args unused 363 */ 364 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 365 synchronized (mLock) { 366 pw.println("WifiMetrics:"); 367 if (args.length > 0 && "proto".equals(args[0])) { 368 //Dump serialized WifiLog proto 369 consolidateProto(true); 370 for (ConnectionEvent event : mConnectionEventList) { 371 if (mCurrentConnectionEvent != event) { 372 //indicate that automatic bug report has been taken for all valid 373 //connection events 374 event.mConnectionEvent.automaticBugReportTaken = true; 375 } 376 } 377 byte[] wifiMetricsProto = WifiMetricsProto.WifiLog.toByteArray(mWifiLogProto); 378 String metricsProtoDump = Base64.encodeToString(wifiMetricsProto, Base64.DEFAULT); 379 pw.println(metricsProtoDump); 380 pw.println("EndWifiMetrics"); 381 } else { 382 pw.println("mConnectionEvents:"); 383 for (ConnectionEvent event : mConnectionEventList) { 384 String eventLine = event.toString(); 385 if (event == mCurrentConnectionEvent) { 386 eventLine += "CURRENTLY OPEN EVENT"; 387 } 388 pw.println(eventLine); 389 } 390 pw.println("mWifiLogProto.numSavedNetworks=" + mWifiLogProto.numSavedNetworks); 391 pw.println("mWifiLogProto.numOpenNetworks=" + mWifiLogProto.numOpenNetworks); 392 pw.println("mWifiLogProto.numPersonalNetworks=" 393 + mWifiLogProto.numPersonalNetworks); 394 pw.println("mWifiLogProto.numEnterpriseNetworks=" 395 + mWifiLogProto.numEnterpriseNetworks); 396 pw.println("mWifiLogProto.isLocationEnabled=" + mWifiLogProto.isLocationEnabled); 397 pw.println("mWifiLogProto.isScanningAlwaysEnabled=" 398 + mWifiLogProto.isScanningAlwaysEnabled); 399 pw.println("mWifiLogProto.numWifiToggledViaSettings=" 400 + mWifiLogProto.numWifiToggledViaSettings); 401 pw.println("mWifiLogProto.numWifiToggledViaAirplane=" 402 + mWifiLogProto.numWifiToggledViaAirplane); 403 pw.println("mWifiLogProto.numNetworksAddedByUser=" 404 + mWifiLogProto.numNetworksAddedByUser); 405 //TODO - Pending scanning refactor 406 pw.println("mWifiLogProto.numNetworksAddedByApps=" + "<TODO>"); 407 pw.println("mWifiLogProto.numNonEmptyScanResults=" + "<TODO>"); 408 pw.println("mWifiLogProto.numEmptyScanResults=" + "<TODO>"); 409 pw.println("mWifiLogProto.numOneshotScans=" + "<TODO>"); 410 pw.println("mWifiLogProto.numBackgroundScans=" + "<TODO>"); 411 pw.println("mScanReturnEntries:" + " <TODO>"); 412 pw.println("mSystemStateEntries:" + " <TODO>"); 413 } 414 } 415 } 416 417 /** 418 * Assign the separate ConnectionEvent, SystemStateEntry and ScanReturnCode lists to their 419 * respective lists within mWifiLogProto, and clear the original lists managed here. 420 * 421 * @param incremental Only include ConnectionEvents created since last automatic bug report 422 */ 423 private void consolidateProto(boolean incremental) { 424 List<WifiMetricsProto.ConnectionEvent> events = new ArrayList<>(); 425 synchronized (mLock) { 426 for (ConnectionEvent event : mConnectionEventList) { 427 if (!incremental || ((mCurrentConnectionEvent != event) 428 && !event.mConnectionEvent.automaticBugReportTaken)) { 429 //<TODO> Revisit logic on when to mark connection events 430 // as 'automaticBugreportTaken'</TODO> 431 //Get all ConnectionEvents that haven not been dumped as a proto, also exclude 432 //the current active un-ended connection event 433 events.add(event.mConnectionEvent); 434 } 435 } 436 if (events.size() > 0) { 437 mWifiLogProto.connectionEvent = events.toArray(mWifiLogProto.connectionEvent); 438 } 439 //<TODO> SystemStateEntry and ScanReturnCode list consolidation 440 } 441 } 442 443 /** 444 * Serializes all of WifiMetrics to WifiLog proto, and returns the byte array. 445 * Does not count as taking an automatic bug report 446 * 447 * @return byte array of the deserialized & consolidated Proto 448 */ 449 public byte[] toByteArray() { 450 consolidateProto(false); 451 return mWifiLogProto.toByteArray(mWifiLogProto); 452 } 453} 454